AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / dba / Perguntas / 282604
Accepted
Michael Green
Michael Green
Asked: 2021-01-06 05:40:40 +0800 CST2021-01-06 05:40:40 +0800 CST 2021-01-06 05:40:40 +0800 CST

Intervalos divididos

  • 772

Eu tenho duas mesas. Cada um contém alguns atributos para uma entidade comercial e o intervalo de datas para o qual esses atributos eram válidos. Eu quero combinar essas tabelas em uma, combinando linhas na chave de negócios comum e dividindo os intervalos de tempo.

O exemplo do mundo real são duas tabelas temporais de origem que alimentam uma tabela de dimensão tipo 2 no data warehouse.

A entidade pode estar presente em nenhum, um ou ambos os sistemas de origem em qualquer momento. Uma vez que uma entidade é registrada em um sistema de origem, os intervalos são bem comportados - sem lacunas, duplicatas ou outros negócios de macaco. A associação às fontes pode terminar em datas diferentes.

As regras de negócios afirmam que queremos apenas retornar intervalos em que a entidade esteja presente em ambas as fontes simultaneamente.

Qual consulta dará esse resultado?

Isso ilustra a situação:

Month          J     F     M     A     M     J     J
Source A:  <--><----------><----------><---->
Source B:            <----><----><----------------><-->
               
Result:              <----><----><----><---->

Dados de amostra

Para simplificar, usei intervalos de datas fechados; provavelmente qualquer solução poderia ser estendida para intervalos semi-abertos com um pouco de digitação.

drop table if exists dbo.SourceA;
drop table if exists dbo.SourceB;
go

create table dbo.SourceA
(
    BusinessKey int,
    StartDate   date,
    EndDate     date,
    Attribute   char(9)
);

create table dbo.SourceB
(
    BusinessKey int,
    StartDate   date,
    EndDate     date,
    Attribute   char(9)
);
GO


insert dbo.SourceA(BusinessKey, StartDate, EndDate, Attribute)
values
    (1, '19990101', '19990113', 'black'),
    (1, '19990114', '19990313', 'red'),
    (1, '19990314', '19990513', 'blue'),
    (1, '19990514', '19990613', 'green'),
    (2, '20110714', '20110913', 'pink'),
    (2, '20110914', '20111113', 'white'),
    (2, '20111114', '20111213', 'gray');

insert dbo.SourceB(BusinessKey, StartDate, EndDate, Attribute)
values
    (1, '19990214', '19990313', 'left'),
    (1, '19990314', '19990413', 'right'),
    (1, '19990414', '19990713', 'centre'),
    (1, '19990714', '19990730', 'back'),
    (2, '20110814', '20110913', 'top'),
    (2, '20110914', '20111013', 'middle'),
    (2, '20111014', '20120113', 'bottom');

Saída desejada

BusinessKey StartDate   EndDate     a_Colour  b_Placement
----------- ----------  ----------  --------- -----------
1           1999-02-14  1999-03-13  red       left     
1           1999-03-14  1999-04-13  blue      right    
1           1999-04-14  1999-05-13  blue      centre   
1           1999-05-14  1999-06-13  green     centre   
2           2011-08-14  2011-09-13  pink      top      
2           2011-09-14  2011-10-13  white     middle   
2           2011-10-14  2011-11-13  white     bottom   
2           2011-11-14  2011-12-13  gray      bottom    
sql-server interval
  • 3 3 respostas
  • 385 Views

3 respostas

  • Voted
  1. Best Answer
    Lennart - Slava Ukraini
    2021-01-06T06:36:52+08:002021-01-06T06:36:52+08:00

    Posso ter entendido mal sua pergunta, mas os resultados parecem estar de acordo com sua pergunta:

    select a.businesskey
         -- greatest(a.startdate, b.startdate)
         , case when a.startdate > b.startdate 
                then a.startdate 
                else b.startdate 
           end as startdate
         -- least(a.enddate, b.enddate)
         , case when a.enddate < b.enddate 
                then a.enddate 
                else b.enddate 
           end as enddate
         , a.attribute as a_color
         , b.attribute as b_placement
    from dbo.SourceA a 
    join dbo.SourceB b 
            on a.businesskey = b.businesskey
           and (a.startdate between b.startdate and b.enddate 
              or b.startdate between a.startdate and a.enddate)
    order by 1,2
    

    Como os intervalos precisam se sobrepor, a maior parte do trabalho pode ser feita com uma junção com isso como predicado. Depois é só escolher a intersecção dos intervalos.

    LEAST e GREATEST parecem estar faltando como funções, então usei uma expressão case.

    violino

    • 2
  2. Michael Green
    2021-01-06T05:40:40+08:002021-01-06T05:40:40+08:00

    Esta solução desconstrói os intervalos de origem para apenas suas datas de início. Ao combinar essas duas listas, obtém-se um conjunto de datas de início do intervalo de saída. A partir destes, as datas finais de saída correspondentes são calculadas por uma função de janela. Como o intervalo de saída final deve terminar quando um dos dois intervalos de entrada terminar, há um processamento especial para determinar esse valor.

    ;with Dates as
    (
        select BusinessKey, StartDate
        from dbo.SourceA
    
        union
    
        select BusinessKey, StartDate
        from dbo.SourceB
    
        union
    
        select x.BusinessKey, DATEADD(DAY, 1, MIN(x.EndDate))
        from
        (
            select BusinessKey, EndDate = MAX(EndDate) 
            from dbo.SourceA
            group by BusinessKey
    
            union all
    
            select BusinessKey, EndDate = MAX(EndDate) 
            from dbo.SourceB
            group by BusinessKey
        ) as x
        group by x.BusinessKey
    ),
    Intervals as
    (
        select
            dt.BusinessKey,
            dt.StartDate,
            EndDate = lead (DATEADD(DAY, -1, dt.StartDate), 1)
                      over (partition by dt.BusinessKey order by dt.StartDate)
        from Dates as dt
    )
    select
        i.BusinessKey,
        i.StartDate,
        i.EndDate, 
        a_Colour = a.Attribute,
        b_Placement = b.Attribute
    from Intervals as i
    inner join dbo.SourceA as a
        on i.BusinessKey = a.BusinessKey
        and i.StartDate between a.StartDate and a.EndDate
    inner join dbo.SourceB as b
        on i.BusinessKey = b.BusinessKey
        and i.StartDate between b.StartDate and b.EndDate
    where i.EndDate is not NULL
    order by
        i.BusinessKey,
        i.StartDate;
    

    O CTE "Datas" usa UNION em vez de UNION ALL para eliminar duplicatas. Se ambas as fontes mudarem na mesma data, queremos apenas uma linha de saída correspondente.

    Como queremos fechar a saída quando qualquer fonte fecha a terceira consulta em "Datas", adiciona a data de término mais antiga, ou seja, o MIN do MAX de EndDates. Como é um EndDate disfarçado de StartDate, deve ter outro dia adicionado a ele. Sua finalidade é permitir que a função de janela calcule o final do intervalo anterior. Será eliminado no predicado final.

    O uso de associações internas para a consulta final elimina os intervalos de origem para os quais não há valor correspondente na outra origem.

    • 0
  3. Michael Green
    2022-05-19T19:37:30+08:002022-05-19T19:37:30+08:00

    Existem muitas soluções interessantes para este problema (declaradas em termos diferentes) aqui e nas páginas anteriores. Lá, ele é apresentado como oferta e demanda correspondentes em um leilão. As unidades fornecidas/demandas são diretamente análogas aos dias em um intervalo desta questão, então a solução se traduz. Eu deixei nos termos usados ​​no site vinculado, no entanto.

    Dados de amostra.

    DROP TABLE IF EXISTS dbo.Auctions;
     
    CREATE TABLE dbo.Auctions
    (
      ID INT NOT NULL IDENTITY(1, 1)
        CONSTRAINT pk_Auctions PRIMARY KEY CLUSTERED,
      Code CHAR(1) NOT NULL
        CONSTRAINT ck_Auctions_Code CHECK (Code = 'D' OR Code = 'S'),
      Quantity DECIMAL(19, 6) NOT NULL
        CONSTRAINT ck_Auctions_Quantity CHECK (Quantity > 0)
    );
     
    SET NOCOUNT ON;
     
    DELETE FROM dbo.Auctions;
     
    SET IDENTITY_INSERT dbo.Auctions ON;
     
    INSERT INTO dbo.Auctions(ID, Code, Quantity) VALUES
      (1, 'D', 5.0),
      (2, 'D', 3.0),
      (3, 'D', 8.0),
      (5, 'D', 2.0),
      (6, 'D', 8.0),
      (7, 'D', 4.0),
      (8, 'D', 2.0),
      (1000, 'S', 8.0),
      (2000, 'S', 6.0),
      (3000, 'S', 2.0),
      (4000, 'S', 2.0),
      (5000, 'S', 4.0),
      (6000, 'S', 3.0),
      (7000, 'S', 2.0);
    

    As soluções expostas reduzem o tempo decorrido para seus dados de amostra de 400k linhas de ingênuos 11 segundos para 0,4s. O mais rápido é de Paul White (desta paróquia), mostrado aqui.

    DROP TABLE IF EXISTS #MyPairings;
     
    CREATE TABLE #MyPairings
    (
      DemandID integer NOT NULL,
      SupplyID integer NOT NULL,
      TradeQuantity decimal(19, 6) NOT NULL
    );
    GO
     
    INSERT #MyPairings 
        WITH (TABLOCK)
    (
        DemandID,
        SupplyID,
        TradeQuantity
    )
    SELECT 
        Q3.DemandID,
        Q3.SupplyID,
        Q3.TradeQuantity
    FROM 
    (
        SELECT
            Q2.DemandID,
            Q2.SupplyID,
            TradeQuantity =
                -- Interval overlap
                CASE
                    WHEN Q2.Code = 'S' THEN
                        CASE
                            WHEN Q2.CumDemand >= Q2.IntEnd THEN Q2.IntLength
                            WHEN Q2.CumDemand > Q2.IntStart THEN Q2.CumDemand - Q2.IntStart
                            ELSE 0.0
                        END
                    WHEN Q2.Code = 'D' THEN
                        CASE
                            WHEN Q2.CumSupply >= Q2.IntEnd THEN Q2.IntLength
                            WHEN Q2.CumSupply > Q2.IntStart THEN Q2.CumSupply - Q2.IntStart
                            ELSE 0.0
                        END
                END
        FROM
        (
            SELECT 
                Q1.Code, 
                Q1.IntStart, 
                Q1.IntEnd, 
                Q1.IntLength, 
                DemandID = MAX(IIF(Q1.Code = 'D', Q1.ID, 0)) OVER (
                        ORDER BY Q1.IntStart, Q1.ID 
                        ROWS UNBOUNDED PRECEDING),
                SupplyID = MAX(IIF(Q1.Code = 'S', Q1.ID, 0)) OVER (
                        ORDER BY Q1.IntStart, Q1.ID 
                        ROWS UNBOUNDED PRECEDING),
                CumSupply = SUM(IIF(Q1.Code = 'S', Q1.IntLength, 0)) OVER (
                        ORDER BY Q1.IntStart, Q1.ID 
                        ROWS UNBOUNDED PRECEDING),
                CumDemand = SUM(IIF(Q1.Code = 'D', Q1.IntLength, 0)) OVER (
                        ORDER BY Q1.IntStart, Q1.ID 
                        ROWS UNBOUNDED PRECEDING)
            FROM 
            (
                -- Demand intervals
                SELECT 
                    A.ID, 
                    A.Code, 
                    IntStart = SUM(A.Quantity) OVER (
                        ORDER BY A.ID 
                        ROWS UNBOUNDED PRECEDING) - A.Quantity,
                    IntEnd = SUM(A.Quantity) OVER (
                        ORDER BY A.ID 
                        ROWS UNBOUNDED PRECEDING),
                    IntLength = A.Quantity
                FROM dbo.Auctions AS A
                WHERE 
                    A.Code = 'D'
     
                UNION ALL 
     
                -- Supply intervals
                SELECT 
                    A.ID, 
                    A.Code, 
                    IntStart = SUM(A.Quantity) OVER (
                        ORDER BY A.ID 
                        ROWS UNBOUNDED PRECEDING) - A.Quantity,
                    IntEnd = SUM(A.Quantity) OVER (
                        ORDER BY A.ID 
                        ROWS UNBOUNDED PRECEDING),
                    IntLength = A.Quantity
                FROM dbo.Auctions AS A
                WHERE 
                    A.Code = 'S'
            ) AS Q1
        ) AS Q2
    ) AS Q3
    WHERE
        Q3.TradeQuantity > 0;
    
    • 0

relate perguntas

  • SQL Server - Como as páginas de dados são armazenadas ao usar um índice clusterizado

  • Preciso de índices separados para cada tipo de consulta ou um índice de várias colunas funcionará?

  • Quando devo usar uma restrição exclusiva em vez de um índice exclusivo?

  • Quais são as principais causas de deadlocks e podem ser evitadas?

  • Como determinar se um Índice é necessário ou necessário

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    conectar ao servidor PostgreSQL: FATAL: nenhuma entrada pg_hba.conf para o host

    • 12 respostas
  • Marko Smith

    Como fazer a saída do sqlplus aparecer em uma linha?

    • 3 respostas
  • Marko Smith

    Selecione qual tem data máxima ou data mais recente

    • 3 respostas
  • Marko Smith

    Como faço para listar todos os esquemas no PostgreSQL?

    • 4 respostas
  • Marko Smith

    Listar todas as colunas de uma tabela especificada

    • 5 respostas
  • Marko Smith

    Como usar o sqlplus para se conectar a um banco de dados Oracle localizado em outro host sem modificar meu próprio tnsnames.ora

    • 4 respostas
  • Marko Smith

    Como você mysqldump tabela (s) específica (s)?

    • 4 respostas
  • Marko Smith

    Listar os privilégios do banco de dados usando o psql

    • 10 respostas
  • Marko Smith

    Como inserir valores em uma tabela de uma consulta de seleção no PostgreSQL?

    • 4 respostas
  • Marko Smith

    Como faço para listar todos os bancos de dados e tabelas usando o psql?

    • 7 respostas
  • Martin Hope
    Jin conectar ao servidor PostgreSQL: FATAL: nenhuma entrada pg_hba.conf para o host 2014-12-02 02:54:58 +0800 CST
  • Martin Hope
    Stéphane Como faço para listar todos os esquemas no PostgreSQL? 2013-04-16 11:19:16 +0800 CST
  • Martin Hope
    Mike Walsh Por que o log de transações continua crescendo ou fica sem espaço? 2012-12-05 18:11:22 +0800 CST
  • Martin Hope
    Stephane Rolland Listar todas as colunas de uma tabela especificada 2012-08-14 04:44:44 +0800 CST
  • Martin Hope
    haxney O MySQL pode realizar consultas razoavelmente em bilhões de linhas? 2012-07-03 11:36:13 +0800 CST
  • Martin Hope
    qazwsx Como posso monitorar o andamento de uma importação de um arquivo .sql grande? 2012-05-03 08:54:41 +0800 CST
  • Martin Hope
    markdorison Como você mysqldump tabela (s) específica (s)? 2011-12-17 12:39:37 +0800 CST
  • Martin Hope
    Jonas Como posso cronometrar consultas SQL usando psql? 2011-06-04 02:22:54 +0800 CST
  • Martin Hope
    Jonas Como inserir valores em uma tabela de uma consulta de seleção no PostgreSQL? 2011-05-28 00:33:05 +0800 CST
  • Martin Hope
    Jonas Como faço para listar todos os bancos de dados e tabelas usando o psql? 2011-02-18 00:45:49 +0800 CST

Hot tag

sql-server mysql postgresql sql-server-2014 sql-server-2016 oracle sql-server-2008 database-design query-performance sql-server-2017

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve