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 / 86435
Accepted
James
James
Asked: 2014-12-18 09:09:53 +0800 CST2014-12-18 09:09:53 +0800 CST 2014-12-18 09:09:53 +0800 CST

Preenchendo lacunas de data em dados SQL agrupados por data

  • 772

O problema real envolve muito mais dados e junções, mas criei uma pequena amostra para demonstrar o problema:

-- create example table
DROP TABLE dbo.EventRecords
GO
CREATE TABLE dbo.EventRecords
    (
    EventDate datetime NOT NULL,
    EventCount int NOT NULL
    )  ON [PRIMARY]
GO
ALTER TABLE dbo.EventRecords ADD CONSTRAINT
    PK_EventRecords PRIMARY KEY CLUSTERED 
    (
    EventDate,
    EventCount
    ) WITH( STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]

GO
-- put in some random data for example
DECLARE @Counter INT=0
WHILE (@Counter<1000)
BEGIN
    DECLARE @SemiRandomCount1 INT=@Counter*589043%23
    DECLARE @SemiRandomCount2 INT=@Counter*85907%7
    IF @SemiRandomCount1>0 AND @Counter%7<>0    -- leave some dates empty
    BEGIN
        INSERT INTO dbo.EventRecords(EventDate,EventCount)
        VALUES (DATEADD(day,@Counter,'2013-01-01'),@SemiRandomCount1)
        PRINT CAST(@SemiRandomCount2 AS VARCHAR(MAX))
        IF @SemiRandomCount2>0 AND @Counter%2=0 -- some dates have multiple entries
            INSERT INTO dbo.EventRecords(EventDate,EventCount)
            VALUES (DATEADD(day,@Counter,'2013-01-01'),@SemiRandomCount2)
    END
    SET @Counter=@Counter+1
END
--SELECT * FROM dbo.EventRecords

Portanto, algumas datas têm várias entradas, outras não. Preciso obter resultados para relatórios que contenham todas as datas em um intervalo especificado, com as contagens totais para essa data (zero se não houver contagens para essa data). Depois de muito pesquisar no Google e experimentar, descobri uma maneira muito inteligente de gerar sequências em tempo real e construí essa função a partir dela. Essas sequências podem ser usadas para criar uma tabela de sequência de datas em tempo real, que pode ser usada para ingressar na tabela EventRecords e agrupar por data sem lacunas:

IF NOT EXISTS (SELECT * FROM dbo.sysobjects WHERE name='GetSequence')
   EXECUTE sp_executesql N'CREATE FUNCTION GetSequence() RETURNS @Table TABLE (Value SMALLINT NOT NULL) AS BEGIN     RETURN END'
GO

ALTER FUNCTION [dbo].[GetSequence](@StartInclusive INT, @EndExclusive INT)
RETURNS @Sequence TABLE
(
    Value BIGINT NOT NULL
)
AS
BEGIN
    INSERT @Sequence
    SELECT Value=@StartInclusive+n-1
    FROM (SELECT ROW_NUMBER() OVER (ORDER BY o1.n) 
        FROM (SELECT n=ROW_NUMBER() OVER (ORDER BY object_id) FROM sys.objects WITH (NOLOCK)) o1
        CROSS JOIN (SELECT n=ROW_NUMBER() OVER (ORDER BY object_id) FROM sys.objects WITH (NOLOCK)) o2
        CROSS JOIN (SELECT n=ROW_NUMBER() OVER (ORDER BY object_id) FROM sys.objects WITH (NOLOCK)) o3
        CROSS JOIN (SELECT n=ROW_NUMBER() OVER (ORDER BY object_id) FROM sys.objects WITH (NOLOCK)) o4
        CROSS JOIN (SELECT n=ROW_NUMBER() OVER (ORDER BY object_id) FROM sys.objects WITH (NOLOCK)) o5
        CROSS JOIN (SELECT n=ROW_NUMBER() OVER (ORDER BY object_id) FROM sys.objects WITH (NOLOCK)) o6
        ) D (n)
    WHERE n<=@EndExclusive-@StartInclusive
    RETURN
END
GO

Aqui estão exemplos de consultas:

DECLARE @StartDate DATE='2013-01-01'
DECLARE @EndDate DATE='2015-01-01'
-- query with holes: not what I need
SELECT [EventDate], [TotalEventCount]=ISNULL(SUM(EventCount),0) 
FROM dbo.EventRecords 
GROUP BY [EventDate]
-- query with date holes filled in: this is what I need
SELECT 
    [EventDate]=DATEADD(day,s.Value,@StartDate), 
    [TotalEventCount]=ISNULL(SUM(EventCount),0)
FROM [dbo].[GetSequence](0,DATEDIFF(day,@StartDate,@EndDate)) s
LEFT JOIN dbo.EventRecords c
    ON DATEDIFF(day,@StartDate, EventDate)=s.Value
GROUP BY s.Value

Então, minha pergunta é: existe uma maneira melhor (mais simples ou mais rápida) de obter sequências ou uma maneira melhor de resolver esse problema no SQL?

sql-server sql-server-2008-r2
  • 2 2 respostas
  • 7672 Views

2 respostas

  • Voted
  1. Best Answer
    SqlZim
    2014-12-18T09:30:31+08:002014-12-18T09:30:31+08:00

    Eu recomendo usar uma tabela de calendário ou dimensão de data (qualquer nome que você preferir). Aqui está uma resposta com o uso de um CTE rápido.

    /* Date Range CTE */
    -- Updated based on @AaronBertrand's articles linked in the comments 
    -- Basically ends up being the same query as the last half of @AaronBertrand's post
    declare @FromDate date;
    declare @ThruDate date;
    set @FromDate='2013-01-01';
    set @ThruDate='2015-01-01';
    
    with cal as (
      select top (1+datediff(day, @FromDate, @ThruDate))
          DateValue = dateadd(day, v.number, @FromDate)
        from master.dbo.spt_values v
        where v.type = 'P'
          and v.number >= 0
        order by v.number
      )
    select EventDate = cal.DateValue
         , TotalEventCount = isnull(sum(EventCount), 0)
      from cal
        left join dbo.EventRecords e on cal.DateValue=e.EventDate
      group by cal.DateValue
      order by cal.DateValue
    

    Alguns links sobre tabelas de calendário:

    • Tabelas de calendário Sql 2005 - Por que você precisa de uma - David Stein
    • Sql 2008+ Criando uma tabela/dimensão de data no SQL 2008 - David Stein
    • Tabelas de calendário em T-SQL
    • 3
  2. Aaron Bertrand
    2014-12-18T09:24:19+08:002014-12-18T09:24:19+08:00

    Bem, você deve ter uma tabela de números ou uma tabela de calendário. Vou começar com uma tabela do Numbers:

    CREATE TABLE dbo.Numbers(Number INT PRIMARY KEY CLUSTERED);
    
    INSERT dbo.Numbers WITH (TABLOCKX) (Number) 
    SELECT TOP (1000000) Number = ROW_NUMBER() OVER (ORDER BY s1.[object_id])
      FROM sys.all_objects AS s1
      CROSS JOIN sys.all_objects AS s2;      
    

    Então:

    DECLARE @StartDate DATE='2013-01-01'
    DECLARE @EndDate DATE='2015-01-01'
    
    ;WITH x(d) AS
    (
      SELECT TOP (DATEDIFF(DAY, @StartDate, @EndDate)+1)
        DATEADD(DAY, Number-1, @StartDate) 
        FROM dbo.Numbers ORDER BY Number
    )
    SELECT 
      EventDate = x.d,
      TotalEventCount = COALESCE(SUM(er.EventCount),0)
    FROM x
    LEFT OUTER JOIN dbo.EventRecords AS er
    ON x.d = er.EventDate
    GROUP BY x.d
    ORDER BY x.d;
    

    Uma tabela de calendário, conforme descrito na outra resposta , é ainda melhor, mas deve ter um desempenho muito melhor do que sua função.

    Se você não tem uma tabela do Numbers ou do Calendar e também não deseja criar (leia os links na parte inferior desta resposta e deste artigo primeiro), então você pode usar coisas internas, como spt_valuesse o seu máximo intervalo de datas é inferior a cerca de 2.000 dias:

    DECLARE @StartDate DATE='2013-01-01'
    DECLARE @EndDate DATE='2015-01-01'
    
    ;WITH x(d) AS
    (
      SELECT TOP (DATEDIFF(DAY, @StartDate, @EndDate)+1)
        DATEADD(DAY, Number, @StartDate) 
        FROM master.dbo.spt_values
        WHERE type = 'P' AND Number >= 0 ORDER BY Number
    )
    SELECT 
      EventDate = x.d,
      TotalEventCount = COALESCE(SUM(er.EventCount),0)
    FROM x
    LEFT OUTER JOIN dbo.EventRecords AS er
    ON x.d = er.EventDate
    GROUP BY x.d
    ORDER BY x.d;
    

    Se você precisar de mais de 2.000 dias (quem vai consumir um relatório que abrange dois anos de dias individuais, afinal?), você pode usar uma única ROW_NUMBER() OVER (ORDER BY [object_id]) FROM sys.all_columns ou uma junção cruzada de diferentes exibições de catálogo para atender ao seu intervalo máximo, em vez de um junção cruzada desleixada de muitas consultas individuais. Mas, na verdade, a tabela Calendário é sua melhor aposta.

    • 1

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