2011-05-05 12 views
26

Mam aplikację, która musi wyświetlać wykres słupkowy dla aktywności w ciągu ostatnich 30 dni. Wykres musi pokazywać wszystkie dni, nawet jeśli nie ma aktywności na dany dzień.Serwer SQL: wybór wszystkich dni w zakresie dat, nawet jeśli dane nie istnieją przez kilka dni.

na przykład:

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

mogłem zrobić przetwarzanie po zapytaniu, aby dowiedzieć się, co brakuje daty i dodać je, ale zastanawiałem się, czy istnieje prostszy sposób to zrobić w SQL Server. Wielkie dzięki

+4

Podpowiedź: numery (aka data) Tabela –

Odpowiedz

34

Można użyć rekurencyjnej CTE zbudować swoją listę 30 dni, a następnie przystąpić że do danych

--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 
... 
+2

+1 za korzystanie z rekurencyjnej CTE uzyskania zbioru wartości. –

+2

To nie działa, jeśli liczba dni jest większa niż 100. Pojawia się błąd "maksymalna głębokość rekursji wynosi 100". Tak, odpowiedź jest odpowiedzią na pytanie, ale może prowadzić do błędów. –

+4

Twój problem nie jest taki sam jak w OP, który ma okno z całkowitą liczbą 30 pozycji, dla którego to rozwiązanie jest w porządku i nie spowoduje błędu. Jeśli chcesz zwiększyć limit rekurencji, po prostu powiedz, aby to zrobić; 'opcja (maxrecursion 100)' –

1

Zdefiniuj tabelę statyczną zawierającą daty lub utwórz w locie tabelę tymczasową \ zmienną tabeli, aby przechowywać każdą datę między (i tym) datami min. I maks. W tabeli działań, z którymi pracujesz.

Użyj sprzężenia zewnętrznego między dwiema tabelami, aby upewnić się, że każda data w tabeli dat jest odzwierciedlona w danych wyjściowych.

Jeśli używasz statycznej tabeli dat, prawdopodobnie ograniczysz zakres dat, który jest wyprowadzany tylko do zakresu wymaganego na wykresie.

0

create a numbers table i używać go jak:

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 [email protected]+Number 
    WHERE Number>=1 AND Number<=15 
    GROUP BY @StartDate+Number 

WYJŚCIE:

----------------------- ----------- 
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

Może coś takiego: Tworzenie DaysTable countaining na 30 dni. I DataTable zawierające kolumnę "dzień" i kolumnę "licznik". A następnie w lewo dołącz do nich.

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 
9

Korzystanie 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] 

to zakładając wszystko jest randka; jeśli jest to DateTime, będziesz musiał obciąć (z DATEADD(dd, 0, DATEDIFF(dd, 0, [DATE]))).

1

Bez Transact-SQL: MS SQL 2005 - Pobierz listę wszystkich dni miesiąca:

W moim przypadku "20121201" jest predefiniowaną wartością.


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 
0

Dla osób z alergią rekurencji

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

spróbować.

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

Mój scenariusz był nieco bardziej skomplikowany niż na przykładzie OP, więc pomyślałem, że podzielę się tym, aby pomóc innym, którzy mają podobne problemy. Musiałem pogrupować zamówienia sprzedaży według daty wykonania, podczas gdy zamówienia są przechowywane z datetime.

Tak więc w tabeli "dni" nie mogłem przechowywać jako datę z czasem "00: 00: 00.000" i uzyskać wszystkie dopasowania. Dlatego też zapisałem go jako ciąg znaków i próbowałem bezpośrednio dołączyć do skonwertowanej wartości.

To nie zwróciło żadnych zerowych rzędów, a rozwiązaniem było wykonanie pod-zapytania zwracającego datę już skonwertowaną na ciąg znaków.

Przykładowy kod w następujący sposób:

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 

Przykładowe Wyniki:

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
odpowiedź

@Alex K. jest całkowicie poprawne, ale to nie działa w wersjach, które nie obsługują Recursive wspólne wyrażenia tabel (takie jak wersja, z którą pracuję). W takim przypadku następujące czynności wykonają zadanie.

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; 
0

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

Powiązane problemy