web-dev-qa-db-fra.com

SQL Server: Comment sélectionner tous les jours dans une plage de dates même s'il n'y a pas de données pour certains jours

J'ai une application qui doit afficher un graphique à barres pour l'activité au cours des 30 derniers jours. Le graphique doit afficher tous les jours même s'il n'y a pas d'activité pour la journée. 

par exemple:

DATE       COUNT
==================
1/1/2011   5 
1/2/2011   3 
1/3/2011   0
1/4/2011   4
1/5/2011   0
etc....

Je pouvais faire du post-traitement après la requête pour déterminer les dates manquantes et les ajouter, mais je me demandais s'il existe un moyen plus simple de le faire dans SQL Server. Merci beaucoup

27
Alex

Vous pouvez utiliser un CTE récursif pour construire votre liste de 30 jours, puis l'associer à vos données.

--test
select cast('05 jan 2011' as datetime) as DT, 1 as val into #t
union all select CAST('05 jan 2011' as datetime), 1 
union all select CAST('29 jan 2011' as datetime), 1 

declare @start datetime = '01 jan 2011'
declare @end   datetime = dateadd(day, 29, @start)

;with amonth(day) as
(
    select @start as day
        union all
    select day + 1
        from amonth
        where day < @end
)
select amonth.day, count(val)
    from amonth 
    left join #t on #t.DT = amonth.day
group by amonth.day


>>

2011-01-04 00:00:00.000 0
2011-01-05 00:00:00.000 2
2011-01-06 00:00:00.000 0
2011-01-07 00:00:00.000 0
2011-01-08 00:00:00.000 0
2011-01-09 00:00:00.000 0
...
35
Alex K.

Utilisation de CTE:

WITH DateTable
AS
(
    SELECT CAST('20110101' AS Date) AS [DATE]
    UNION ALL
    SELECT DATEADD(dd, 1, [DATE])
    FROM DateTable
    WHERE DATEADD(dd, 1, [DATE]) < cast('20110201' as Date)
)
SELECT dt.[DATE], ISNULL(md.[COUNT], 0) as [COUNT]
FROM [DateTable] dt
LEFT JOIN [MyData] md
ON md.[DATE] = dt.[DATE]

Ceci suppose que tout est une date; s'il s'agit de DateTime, vous devrez tronquer (avec DATEADD(dd, 0, DATEDIFF(dd, 0, [DATE]))).

9
Ian Pugsley

La réponse de @Alex K. est tout à fait correcte, mais cela ne fonctionne pas pour les versions qui ne prennent pas en charge les expressions de table communes récursives (comme la version avec laquelle je travaille). Dans ce cas, ce qui suit ferait le travail.

DECLARE @StartDate datetime = '2015-01-01'
DECLARE @EndDate datetime = SYSDATETIME()

;WITH days AS
(
  SELECT DATEADD(DAY, n, DATEADD(DAY, DATEDIFF(DAY, 0, @StartDate), 0)) as d
    FROM ( SELECT TOP (DATEDIFF(DAY, @StartDate, @EndDate) + 1)
            n = ROW_NUMBER() OVER (ORDER BY [object_id]) - 1
           FROM sys.all_objects ORDER BY [object_id] ) AS n
)
select days.d, count(t.val)
    FROM days LEFT OUTER JOIN yourTable as t
    ON t.dateColumn >= days.d AND t.dateColumn < DATEADD(DAY, 1, days.d)
GROUP BY days.d
ORDER BY days.d;
3
pedram bashiri

Mon scénario était un peu plus complexe que celui du PO, c’est pourquoi j’ai pensé partager avec d’autres personnes qui ont des problèmes similaires. J'avais besoin de regrouper les commandes par date de prise, alors que les commandes sont stockées avec date/heure.

Donc, dans la table de recherche "jours", je ne pouvais pas vraiment enregistrer de date avec l'heure "00: 00: 00.000" et obtenir des correspondances. Par conséquent, j'ai stocké sous forme de chaîne et j'ai essayé de rejoindre directement la valeur convertie. 

Cela ne renvoyait aucune ligne à zéro et la solution consistait à effectuer une sous-requête renvoyant la date déjà convertie en chaîne.

Exemple de code comme suit:

declare @startDate datetime = convert(datetime,'09/02/2016')
declare @curDate datetime = @startDate
declare @endDate datetime = convert(datetime,'09/09/2016')
declare @dtFormat int = 102;
DECLARE @null_Date varchar(24) = '1970-01-01 00:00:00.000'

/* Initialize #days table */
select CONVERT(VARCHAR(24),@curDate, @dtFormat) as [Period] into #days

/* Populate dates into #days table */
while (@curDate < @endDate )
begin
    set @curDate = dateadd(d, 1, @curDate)
    insert into #days values (CONVERT(VARCHAR(24),@curDate, @dtFormat))
end

/* Outer aggregation query to group by order numbers */
select [Period], count(c)-case when sum(c)=0 then 1 else 0 end as [Orders],
sum(c) as [Lines] from
(
    /* Inner aggregation query to sum by order lines */ 
    select
        [Period], sol.t_orno, count(*)-1 as c   
        from (
            /* Inner query against source table with date converted */
            select convert(varchar(24),t_dldt, @dtFormat) as [shipdt], t_orno
                from salesorderlines where t_dldt > @startDate
        ) sol
        right join #days on shipdt = #days.[Period]     
        group by [Period], sol.t_orno
) as t
group by Period
order by Period desc

drop table #days

Exemples de résultats:

Period      Orders  Lines
2016.09.09  388     422
2016.09.08  169     229
2016.09.07  1       1
2016.09.06  0       0
2016.09.05  0       0
2016.09.04  165     241
2016.09.03  0       0
2016.09.02  0       0
1
Shane

Sans Transact-SQL: MS SQL 2005 - Obtenez une liste de tous les jours du mois:

Dans mon cas, «20121201» est une valeur prédéfinie.


 SELECT TOp (Select Day(DateAdd(day, -Day(DateAdd(month, 1,
 '20121201')), 
                          DateAdd(month, 1, '20121201')))) DayDate FROM ( SELECT DATEADD(DAY,ROW_NUMBER() OVER (ORDER BY (SELECT
 NULL))-1,'20121201') as DayDate FROM sys.objects s1 CROSS JOIN
 sys.objects s2 ) q
1
Stefan Brendle

Définissez une table statique contenant des dates ou créez une variable\table temporaire à la volée pour stocker chaque date entre (et y compris) les dates minimale et maximale de la table d'activités avec laquelle vous travaillez.

Utilisez une jointure externe entre les deux tables pour vous assurer que chaque date de votre table de dates est reflétée dans la sortie.

Si vous utilisez une table de dates statique, vous souhaiterez probablement limiter la plage de dates générée à la plage requise dans le graphique.

1
Tim Lentine

crée un tableau de nombres et l'utilise comme:

declare @DataTable table (DateColumn datetime)
insert @DataTable values ('2011-01-09')
insert @DataTable values ('2011-01-10')
insert @DataTable values ('2011-01-10')
insert @DataTable values ('2011-01-11')
insert @DataTable values ('2011-01-11')
insert @DataTable values ('2011-01-11')

declare @StartDate  datetime
SET @StartDate='1/1/2011'

select
    @StartDate+Number,SUM(CASE WHEN DateColumn IS NULL THEN 0 ELSE 1 END)
    FROM Numbers
        LEFT OUTER JOIN @DataTable ON DateColumn=@StartDate+Number
    WHERE Number>=1 AND Number<=15
    GROUP BY @StartDate+Number

SORTIE:

----------------------- -----------
2011-01-02 00:00:00.000 0
2011-01-03 00:00:00.000 0
2011-01-04 00:00:00.000 0
2011-01-05 00:00:00.000 0
2011-01-06 00:00:00.000 0
2011-01-07 00:00:00.000 0
2011-01-08 00:00:00.000 0
2011-01-09 00:00:00.000 1
2011-01-10 00:00:00.000 2
2011-01-11 00:00:00.000 3
2011-01-12 00:00:00.000 0
2011-01-13 00:00:00.000 0
2011-01-14 00:00:00.000 0
2011-01-15 00:00:00.000 0
2011-01-16 00:00:00.000 0

(15 row(s) affected)
0
KM.

Peut-être que quelque chose comme ceci: Create DaysTable qui contient les 30 jours . Et DataTable contenant les colonnes "day" et "count" ... .. et les rejoint ensuite.

WITH    DaysTable (name) AS (
        SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 -- .. And so on to 30
    ),
    DataTable (name, value) AS (        
        SELECT  DATEPART(DAY, [Date]), [Count]
        FROM    YourExampleTable
        WHERE   [Date] < DATEADD (day , -30 , getdate())
    )
SELECT  DaysTable.name, DataTable.value
FROM    DaysTable LEFT JOIN
        DataTable ON DaysTable.name = DataTable.name
ORDER BY DaysTable.name
0
Oleg Grishko

Pour les personnes allergiques à la récursivité 

select SubQ.TheDate
from 
(
    select DATEADD(day, a.a + (10 * b.a) + (100 * c.a), DATEADD(day, DATEDIFF(day, 0, GETDATE()), 0) - 30) AS TheDate
    from 
    (
        (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as a
        cross join (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as b
        cross join (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as c
    ) 
    WHERE a.a + (10 * b.a) + (100 * c.a) < 30
) AS SubQ
ORDER BY TheDate
0
sav

Essayez-le.

DECLARE @currentDate DATETIME = CONVERT(DATE, GetDate())
DECLARE @startDate   DATETIME = DATEADD(DAY, -DAY(@currentDate)+1, @currentDate)

;WITH fnDateNow(DayOfDate) AS
(
    SELECT @startDate AS DayOfDate
        UNION ALL
    SELECT DayOfDate + 1 FROM fnDateNow WHERE DayOfDate < @currentDate
) SELECT fnDateNow.DayOfDate FROM fnDateNow
0
Tony Hung

DECLARE @StartDate DATE = '20110101', @NumberOfYears INT = 1;

DECLARE @CutoffDate DATE = DATEADD(YEAR, @NumberOfYears, @StartDate);


CREATE TABLE Calender
(
  [date]       DATE
);


INSERT Calender([date]) 
SELECT d
FROM
(
  SELECT d = DATEADD(DAY, rn - 1, @StartDate)
  FROM 
  (
    SELECT TOP (DATEDIFF(DAY, '2011-01-01', '2011-12-31')) 
      rn = ROW_NUMBER() OVER (ORDER BY s1.[object_id])
    FROM sys.all_objects AS s1
    CROSS JOIN sys.all_objects AS s2
    ORDER BY s1.[object_id]
  ) AS x
) AS y;


create table test(a date)

insert into test values('1/1/2011')
insert into test values('1/1/2011')
insert into test values('1/1/2011')
insert into test values('1/1/2011')
insert into test values('1/1/2011')

insert into test values('1/2/2011')
insert into test values('1/2/2011')
insert into test values('1/2/2011')

insert into test values('1/4/2011')
insert into test values('1/4/2011')
insert into test values('1/4/2011')
insert into test values('1/4/2011')

select c.date as DATE,count(t.a) as COUNT from calender c left join test t on c.date = t.a group by c.date

0
Partha