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 / 43728
Accepted
WileCau
WileCau
Asked: 2013-06-04 18:49:47 +0800 CST2013-06-04 18:49:47 +0800 CST 2013-06-04 18:49:47 +0800 CST

Como mesclar conjuntos de dados sem incluir linhas redundantes?

  • 772

Acho que isso deve ser uma coisa bastante comum de se fazer, mas não consegui encontrar um exemplo. Envolve a mesclagem de dados que envolvem datas/horas (ou mais geralmente quaisquer valores sequenciais) sem incluir registros redundantes. Por redundante, quero dizer registros que podem ser implícitos em outros registros.

Por exemplo, se o preço ontem foi $ 1 e o preço hoje é $ 1, e não houve outras mudanças de preço entre ontem e hoje, então o preço hoje pode ser inferido a partir do preço ontem sem armazenar um preço para ambos os dias.

Aqui está o cenário que estou tentando resolver. Temos uma tabela de preços históricos dos itens. Novos preços são importados periodicamente para a tabela. O arquivo de importação pode conter datas de qualquer época, não sabemos se elas estão sempre "depois" dos dados na tabela de histórico.

Para evitar o inchaço dos dados, só quero inserir registros se eles fornecerem novas informações. Portanto, se um novo registro puder ser inferido de um registro antigo, não desejo inserir o novo registro e, se um registro antigo puder ser inferido de um novo registro, desejo remover o registro antigo e inserir o novo registro.

Alguns exemplos concretos podem ajudar, aqui estão os dois cenários de problemas:

  1. Um registro de entrada pode ser inferido de um registro existente, portanto, o registro de entrada é redundante. Por exemplo

    recorde antigo: 23/04/2013 1,00

    novo registro: 2013-04-24 1.00 <-- isso está implícito no registro existente, não o insira

  2. Um registro existente pode ser inferido de um novo registro, portanto, o registro existente é redundante. Por exemplo

    novo recorde: 2013-04-23 1,00

    registro antigo: 2013-04-24 1.00 <-- isso está implícito no novo registro, exclua-o

  3. Isso apenas mostra um exemplo de quando um valor não é redundante. Por exemplo

    recorde antigo: 23/04/2013 1,00

    recorde antigo: 2013-04-24 1.20

    novo recorde: 2013-04-25 1,00 <-- não redundante, o preço mudou desde o último 1,00

Há um exemplo mais detalhado aqui http://sqlfiddle.com/#!3/2ef87/2

Atualmente, estou inclinado para uma abordagem de várias etapas de:

  1. Excluir da entrada onde houver um registro existente que tenha o mesmo preço com uma data anterior (cenário 1 acima).
  2. Excluir de existente onde houver um registro de entrada que tenha o mesmo preço com uma data anterior (cenário 2 acima).
  3. Insira os registros de entrada restantes.

Deve haver uma maneira melhor, talvez usando MERGE, mas está me dando trabalho tentando descobrir como fazê-lo.

Como faço para "mesclar" com eficiência os registros existentes e recebidos? Obrigado

sql-server sql-server-2008
  • 3 3 respostas
  • 2742 Views

3 respostas

  • Voted
  1. Best Answer
    Paul White
    2013-08-01T01:53:55+08:002013-08-01T01:53:55+08:00

    A questão dos registros que chegam atrasados ​​torna a remoção de duplicatas mais complexa, mas não é impossível. Usar uma exibição (conforme proposto em sua outra pergunta ) para remover duplicatas dinamicamente é viável, mas as consultas nessa exibição podem produzir planos de consulta complexos e/ou ineficientes.

    Um design alternativo é manter os registros duplicados em uma tabela separada, caso sejam necessários para processar adequadamente um futuro registro atrasado. Isso adiciona um pouco de complexidade ao processo de importação de dados, mas cada etapa não é muito difícil e o resultado é uma bela tabela de histórico limpa e livre de duplicatas:

    Tabelas

    -- Original table
    CREATE TABLE dbo.History
    (
        Effective   datetime NOT NULL,      -- When the value became current
        Product     integer NOT NULL,       -- The product
        Kind        tinyint NOT NULL,       -- The price kind (RRP, special, etc)
        Price       smallmoney NOT NULL,    -- The new price
    
        CONSTRAINT [PK dbo.History Effective, Product, Kind]
            PRIMARY KEY CLUSTERED
                (Effective, Product, Kind)
    );
    
    -- Holding area for duplicates that may be needed in future
    CREATE TABLE dbo.HistoryDuplicates
    (
        Effective   datetime NOT NULL,
        Product     integer NOT NULL,
        Kind        tinyint NOT NULL,
        Price       smallmoney NOT NULL,
    
        CONSTRAINT [PK dbo.HistoryDuplicates Product, Kind, Effective]
            PRIMARY KEY CLUSTERED
                (Product, Kind, Effective)
    );
    

    Dados iniciais

    CREATE TABLE #NewRows
    (
        Effective   datetime NOT NULL,
        Product     integer NOT NULL,
        Kind        tinyint NOT NULL,
        Price       smallmoney NOT NULL,
        [Action]    char(1) NOT NULL DEFAULT 'X',
    );
    
    CREATE UNIQUE CLUSTERED INDEX cuq
    ON #NewRows (Product, Kind, Effective);
    
    INSERT #NewRows
        (Effective, Product, Kind, Price)
    VALUES
        ('2013-04-23T00:23:00', 1234, 1, 1.00),
        ('2013-04-24T00:24:00', 1234, 1, 1.00),
        ('2013-04-25T00:25:00', 1234, 1, 1.50),
        ('2013-04-25T00:25:00', 1234, 2, 2.00),
        ('2013-04-26T00:26:00', 1234, 1, 2.00),
        ('2013-04-27T00:27:00', 1234, 1, 2.00),
        ('2013-04-28T00:28:00', 1234, 1, 1.00);
    

    O primeiro passo é remover quaisquer redundâncias nos dados de entrada, armazenando os dados removidos na nova tabela de retenção:

    -- Remove redundancies in the input set
    -- and save in the duplicates table
    DELETE NR
    OUTPUT
        DELETED.Effective,
        DELETED.Product,
        DELETED.Kind,
        DELETED.Price
    INTO dbo.HistoryDuplicates
        (Effective, Product, Kind, Price)
    FROM #NewRows AS NR
    OUTER APPLY
    (
        SELECT TOP (1)
            NR2.Price
        FROM #NewRows AS NR2
        WHERE
            NR2.Product = NR.Product
            AND NR2.Kind = NR.Kind
            AND NR2.Effective < NR.Effective
        ORDER BY
            NR2.Effective DESC
    ) AS X
    WHERE
        EXISTS (SELECT X.Price INTERSECT SELECT NR.Price);
    

    Plano de execução de desduplicação

    Classificando linhas de entrada

    A próxima etapa é decidir se cada linha na tabela de entrada é redundante (gravar a tabela Histórico) ou não. A consulta a seguir define a Actioncoluna dos dados do conjunto de entrada apropriadamente:

    -- Decide what to do with each row
    UPDATE NewRows
    SET [Action] = 
        CASE
            WHEN NOT EXISTS (SELECT ExistingRow.Price INTERSECT SELECT NewRows.Price)
                THEN 'I' -- Insert
            WHEN ExistingRow.Price = NewRows.Price
                THEN 'D' -- Duplicate
            ELSE 'X'
        END
    FROM #NewRows AS NewRows
    OUTER APPLY
    (
        SELECT TOP (1)
            H.Price,
            H.Effective
        FROM dbo.History AS H
        WHERE
            H.Product = NewRows.Product
            AND H.Kind = NewRows.Kind
            AND H.Effective <= NewRows.Effective
        ORDER BY
            H.Effective DESC
    ) AS ExistingRow;
    
    CREATE UNIQUE CLUSTERED INDEX cuq
    ON #NewRows 
        ([Action], Product, Kind, Effective) 
    WITH (DROP_EXISTING = ON);
    

    Plano de execução de ação

    Armazenar linhas redundantes

    Agora armazenamos as linhas identificadas como redundantes na tabela de retenção:

    -- Store duplicates
    WITH Duplicates AS
    (
        SELECT NR.*
        FROM #NewRows AS NR
        WHERE NR.[Action] = 'D'
    )
    MERGE dbo.HistoryDuplicates AS HD
    USING Duplicates AS NR
        ON NR.Product = HD.Product
        AND NR.Kind = HD.Kind
        AND NR.Effective = HD.Effective
    WHEN NOT MATCHED BY TARGET THEN 
        INSERT (Product, Kind, Effective, Price) 
        VALUES (Product, Kind, Effective, Price);
    

    Plano de registros redundantes

    Novas linhas do histórico

    As linhas não redundantes são adicionadas à tabela Histórico:

    -- Inserts
    WITH Inserts AS
    (
        SELECT NR.*
        FROM #NewRows AS NR
        WHERE NR.[Action] = 'I'
    )
    MERGE dbo.History AS H
    USING Inserts AS NR
        ON NR.Product = H.Product
        AND NR.Kind = H.Kind
        AND NR.Effective = H.Effective
        AND NR.Price = H.Price
    WHEN NOT MATCHED BY TARGET THEN 
        INSERT (Effective, Product, Kind, Price)
        VALUES (Effective, Product, Kind, Price);
    

    Novo plano de linhas

    Restaurando registros redundantes

    Adicionar novos registros pode resultar em linhas redundantes que precisam ser restabelecidas. A consulta a seguir identifica as linhas redundantes qualificadas e as move para a tabela Histórico:

    DELETE NextDuplicate
    OUTPUT DELETED.Product,
           DELETED.Kind,
           DELETED.Price,
           DELETED.Effective
    INTO dbo.History
        (Product, Kind, Price, Effective)
    FROM #NewRows AS NR
    CROSS APPLY
    (
        SELECT TOP (1)
            H.Effective,
            H.Price
        FROM dbo.History AS H
        WHERE
            H.Product = NR.Product
            AND H.Kind = NR.Kind
            AND H.Effective > NR.Effective
        ORDER BY
            H.Effective ASC
    ) AS NextHistory
    CROSS APPLY
    (
        SELECT TOP (1)
            HD.Effective,
            HD.Product,
            HD.Kind,
            HD.Price
        FROM dbo.HistoryDuplicates AS HD
        WHERE
            HD.Product = NR.Product
            AND HD.Kind = NR.Kind
            AND HD.Effective > NR.Effective
            AND HD.Effective < NextHistory.Effective
        ORDER BY
            HD.Effective ASC
    ) AS NextDuplicate
    WHERE
        NR.[Action] = 'I'
        AND NOT EXISTS (SELECT NextDuplicate.Price INTERSECT SELECT NR.Price);
    

    Realocar plano de linhas

    Resultados

    tabela de histórico

    ╔═════════════════════════╦═════════╦══════╦═══════╗
    ║        Effective        ║ Product ║ Kind ║ Price ║
    ╠═════════════════════════╬═════════╬══════╬═══════╣
    ║ 2013-04-23 00:23:00.000 ║    1234 ║    1 ║ 1.00  ║
    ║ 2013-04-25 00:25:00.000 ║    1234 ║    1 ║ 1.50  ║
    ║ 2013-04-25 00:25:00.000 ║    1234 ║    2 ║ 2.00  ║
    ║ 2013-04-26 00:26:00.000 ║    1234 ║    1 ║ 2.00  ║
    ║ 2013-04-28 00:28:00.000 ║    1234 ║    1 ║ 1.00  ║
    ╚═════════════════════════╩═════════╩══════╩═══════╝
    

    Tabela de Duplicatas do Histórico

    ╔═════════════════════════╦═════════╦══════╦═══════╗
    ║        Effective        ║ Product ║ Kind ║ Price ║
    ╠═════════════════════════╬═════════╬══════╬═══════╣
    ║ 2013-04-24 00:24:00.000 ║    1234 ║    1 ║ 1.00  ║
    ║ 2013-04-27 00:27:00.000 ║    1234 ║    1 ║ 2.00  ║
    ╚═════════════════════════╩═════════╩══════╩═══════╝
    

    Processando novos dados

    As etapas anteriores são bastante gerais. Podemos processar um novo lote de linhas usando exatamente o mesmo código. O próximo script carrega a tabela de entrada com duas linhas de amostra, uma das quais é duplicada e a outra é um exemplo da necessidade de restabelecer uma linha redundante anteriormente:

    -- If needed
    DROP TABLE #NewRows;
    
    CREATE TABLE #NewRows
    (
        Effective   datetime NOT NULL,
        Product     integer NOT NULL,
        Kind        tinyint NOT NULL,
        Price       smallmoney NOT NULL,
        [Action]    char(1) NOT NULL DEFAULT 'X',
    );
    
    CREATE UNIQUE CLUSTERED INDEX cuq
    ON #NewRows (Product, Kind, Effective);
    
    INSERT #NewRows
        (Effective, Product, Kind, Price)
    VALUES
        ('2013-04-24 00:24:01.000', 1234, 1, 1.00), -- New duplicate
        ('2013-04-26 00:26:30.000', 1234, 1, 5.00); -- Complex new row
    

    A execução do restante do script geral produz este estado final:

    História:

    ╔═════════════════════════╦═════════╦══════╦═══════╗
    ║        Effective        ║ Product ║ Kind ║ Price ║
    ╠═════════════════════════╬═════════╬══════╬═══════╣
    ║ 2013-04-23 00:23:00.000 ║    1234 ║    1 ║ 1.00  ║
    ║ 2013-04-25 00:25:00.000 ║    1234 ║    1 ║ 1.50  ║
    ║ 2013-04-25 00:25:00.000 ║    1234 ║    2 ║ 2.00  ║
    ║ 2013-04-26 00:26:00.000 ║    1234 ║    1 ║ 2.00  ║
    ║ 2013-04-26 00:26:30.000 ║    1234 ║    1 ║ 5.00  ║ <- New
    ║ 2013-04-27 00:27:00.000 ║    1234 ║    1 ║ 2.00  ║ <- Recovered
    ║ 2013-04-28 00:28:00.000 ║    1234 ║    1 ║ 1.00  ║
    ╚═════════════════════════╩═════════╩══════╩═══════╝
    

    Histórico Duplicado:

    ╔═════════════════════════╦═════════╦══════╦═══════╗
    ║        Effective        ║ Product ║ Kind ║ Price ║
    ╠═════════════════════════╬═════════╬══════╬═══════╣
    ║ 2013-04-24 00:24:00.000 ║    1234 ║    1 ║ 1.00  ║
    ║ 2013-04-24 00:24:01.000 ║    1234 ║    1 ║ 1.00  ║ <- New duplicate
    ╚═════════════════════════╩═════════╩══════╩═══════╝
    
    • 4
  2. David Spillett
    2013-06-05T00:50:08+08:002013-06-05T00:50:08+08:00

    (isso é mais uma consulta ou um "tem certeza que realmente quer fazer isso?" do que uma resposta, desculpe, mas é muito longo para um comentário!)

    Se os dados anteriores puderem chegar mais tarde, você realmente deseja excluir alguma coisa? Um novo ponto de dados pode aparecer, o que significa que você precisava manter algo que removeu, por exemplo:

    2013-04-01 1.00
    2013-04-04 2.00
    

    então

    2013-04-02 2.00
    

    entra e você desduplica

    2013-04-01 1.00
    2013-04-02 2.00
    2013-04-04 2.00
    

    para

    2013-04-01 1.00
    2013-04-02 2.00
    

    mas então

    2013-04-03 3.00
    

    chega, e você gostaria

    2013-04-01 1.00
    2013-04-02 2.00
    2013-04-03 3.00
    2013-04-04 2.00
    

    não

    2013-04-01 1.00
    2013-04-02 2.00
    2013-04-03 3.00
    

    mas você excluiu o valor para o 4º.

    Como os dados do passado podem chegar agora, você pode precisar manter tudo e usar uma exibição para filtrar duplicatas quando quiser apenas considerar as alterações de valor em vez de cada leitura.

    • 2
  3. listik
    2013-06-05T07:07:32+08:002013-06-05T07:07:32+08:00

    Se eu entendi corretamente:

    1. Se um registro de entrada tiver uma Data&Hora anterior à existente para o mesmo grupo de PKs (Site e PriceType), você deseja manter a Data&Hora de entrada (o preço seria o mesmo, e o Estado e o PriceType também)
    2. Se um registro de entrada tiver uma data e hora posterior à existente, você deseja descartar a entrada (mantendo a data e hora existente)
    3. Se houver uma nova combinação de PK, adicione-a à existente

    Isso pode ser feito usando uma cláusula MERGE como esta:

    MERGE History h
    USING incoming  i
    ON h.product = i.product
       and h.kind = i.kind
       and h.price = i.price
       and h.effective >= i.effective
    WHEN matched then
      update set h.effective = i.effective
    when not matched by target then 
    insert (Effective, Product, Kind, Price)
    values (i.effective
           ,i.product
           ,i.kind
           ,i.price);
    

    Verifique neste violino para ver se este é o resultado que você deseja.

    • 0

relate perguntas

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

  • Quanto "Padding" coloco em meus índices?

  • Existe um processo do tipo "práticas recomendadas" para os desenvolvedores seguirem para alterações no banco de dados?

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

  • Downgrade do SQL Server 2008 para 2005

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • 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

    Conceder acesso a todas as tabelas para um usuário

    • 5 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
    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
    pedrosanta Listar os privilégios do banco de dados usando o psql 2011-08-04 11:01:21 +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