SQL - SHOW ALL DATES between two dates - sql

I have following data saved as dates in my [OccuredAtUtc] that look like this:
-- Spoiler ALERT: "2017-04-26" and "2017-04-29" are missing.
Original dates in [OccuredAtUtc]:
2017-04-24 12:16:58.5080000
2017-04-24 18:11:53.3090000
2017-04-25 18:34:18.3090000
2017-04-27 20:42:28.8570000
2017-04-28 21:10:36.7070000
2016-04-28 10:37:57.5970000
2016-04-30 10:38:55.7010000
2016-04-30 10:48:19.0390000
2016-04-31 10:48:19.2990000
.
.
.
And I have this code that returns correctly data from two intervals (previous week).
SELECT
[MessageType].[Name] AS [Channel],
CONVERT(VARCHAR(11), [OccuredAtUtc], 106) AS [Time],
COUNT(*) AS [Count]
FROM #table1
INNER JOIN #table2 ON ... = ...
WHERE ( [OccuredAtUtc] > '2017-04-24'
AND [OccuredAtUtc] < '2017-04-30' )
GROUP BY (CONVERT(VARCHAR(11), [OccuredAtUtc], 106)),
[MessageType].[Name]
ORDER BY [Time] ASC
But the output won't show a row of the "26 Apr 2017" and "29 Apr 2017" because there are not records on these days in my DB.
OLD OUTPUT : with missing 26th & 29th Apr.
[Channel] [Time] [Count]
------------------------------------
FTP 24 Apr 2017 7
HTTP 24 Apr 2017 9
FTP 25 Apr 2017 6
HTTP 25 Apr 2017 2
------MISSING 26 Apr--------
FTP 27 Apr 2017 56
HTTP 27 Apr 2017 12
FTP 28 Apr 2017 5
------MISSING 29 Apr--------
HTTP 28 Apr 2017 17
FTP 30 Apr 2017 156
HTTP 30 Apr 2017 19
I would like to show rows WITH THE MISSING DATE even if there was not an incident saved on this day...
So the new OUTPUT should look like this.
WANTED OUTPUT :
[Channel] [Time] [Count]
------------------------------------
FTP 24 Apr 2017 7
HTTP 24 Apr 2017 9
FTP 25 Apr 2017 6
HTTP 25 Apr 2017 2
0 26 Apr 2017 0 -- here we go
FTP 27 Apr 2017 56
HTTP 27 Apr 2017 12
FTP 28 Apr 2017 5
HTTP 28 Apr 2017 17
0 29 Apr 2017 0 -- here we go
FTP 30 Apr 2017 156
HTTP 30 Apr 2017 19
I know there are answered question like mine and I was trying to remake my code but I failed.
( SHOW ALL Dates data between two dates; if no row exists for particular date then show zero in all columns )
( How to generate all dates between two dates )

Similar to #DhruvJoshi's answer but using a recursive CTE to generate the dates instead:
DECLARE #MinDate DATE = '20170424',
#MaxDate DATE = '20170430';
WITH allDates AS
(
SELECT #MinDate AS dates
UNION ALL
SELECT DATEADD(DAY, 1, ad.[dates] )
FROM allDates AS ad
WHERE ad.[dates] < #MaxDate
)
SELECT
ISNULL([MessageType].[Name],0) AS [Channel],
dates AS [Time],
COUNT([MessageType].[Name]) AS [Count]
FROM
(
SELECT dates
FROM allDates
) AS T
LEFT JOIN
#table1 ON T.dates=CONVERT(VARCHAR(11), #table1.[OccuredAtUtc], 106)
LEFT JOIN #table2 ON ... = ...
GROUP BY dates,
[MessageType].[Name]
ORDER BY [Time] ASC

You can use something like a Tally table to generate all dates between certain time interval.
SELECT
ISNULL([MessageType].[Name],0) AS [Channel],
dates AS [Time],
COUNT([MessageType].[Name]) AS [Count]
FROM
(
SELECT
TOP (DATEDIFF(d,'2017-04-24','2017-04-30')+1)
DATEADD(d,ROW_NUMBER() OVER( ORDER BY (SELECT 1))-1,'2017-04-24') dates
FROM sys.objects a CROSS JOIN sys.objects b
)T
LEFT JOIN
#table1 ON T.dates=CONVERT(VARCHAR(11), #table1.[OccuredAtUtc], 106)
LEFT JOIN #table2 ON ... = ...
AND ( [OccuredAtUtc] > '2017-04-24'
AND [OccuredAtUtc] < '2017-04-30' )
GROUP BY dates,
[MessageType].[Name]
ORDER BY [Time] ASC
For more explanation on Tally tables please read this article

declare #t table ( i int identity , b bit, d as dateadd (dd, i - 1, 0 ))
insert into #t (b)
VALUES (0),(0),(0),(0),(0),(0),(0),(0),(0)
insert into #t (b)
select 0
from #t t1
cross apply ( select b from #t) as t2
cross apply ( select b from #t) as t3
cross apply ( select b from #t) as t4
cross apply ( select b from #t) as t5
select t.d, isnull(y.channel,0), count(y.[date])
from #t t
left join yourtable y on y.[date] = t.d
where d between getdate() - 30 and getdate()
group by t.d, isnull(y.channel,0)

Related

Create a year/quarter table in SQL without a loop

I have a start year and an end year, say 2017 and 2019 for example.
I'd like to create a table with columns year and quarter (eg, 1, 2, 3, 4) between my stated startYear and endYear, and have quarter for the final, endYear, to stop at 2 (it's always forward looking).
Sample desired output below.
year quarter
2017 1
2017 2
2017 3
2017 4
2018 1
2018 2
2018 3
2018 4
2019 1
2019 2
Seems like it should be simple, nothing occurs to me except somewhat clunky methods relying on a loop or UNION or simply inserting values manually into the table.
Just another option... an ad-hoc tally table in concert with a Cross Join
Example
Declare #Y1 int = 2017
Declare #Y2 int = 2019
Select *
From ( Select Top (#Y2-#Y1+1) Year=#Y1-1+Row_Number() Over (Order By (Select NULL)) From master..spt_values n1 ) A
Cross Join (values (1),(2),(3),(4)) B([Quarter])
Returns
Year Quarter
2017 1
2017 2
2017 3
2017 4
2018 1
2018 2
2018 3
2018 4
2019 1
2019 2
2019 3
2019 4
Use a recursive CTE:
with yq as (
select 2017 as yyyy, 1 as qq
union all
select (case when qq = 4 then yyyy + 1 else yyyy end),
(case when qq = 4 then 1 else qq + 1 end)
from yq
where yyyy < 2019 or yyyy = 2019 and qq < 2
)
select *
from yq;
If the table will have more than 100 rows, you will also need option (maxrecursion 0).
Here is a db<>fiddle.
This solution is very similar to the one by John, but it doesn't depend on a system table.
Declare #Y1 int = 2017;
Declare #Y2 int = 2019;
WITH
E(n) AS(
SELECT n FROM (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0))E(n)
),
E2(n) AS(
SELECT a.n FROM E a, E b
),
E4(n) AS(
SELECT a.n FROM E2 a, E2 b
),
cteYears([Year]) AS(
SELECT TOP (#Y2-#Y1+1)
ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) + #Y1 - 1 AS [Year]
FROM E4
)
SELECT [Year], [Quarter]
FROM cteYears
CROSS JOIN (VALUES (1),(2),(3),(4)) Q([Quarter]);
Let me to propose a recursve query for you:
WITH prepare AS
(
SELECT tbl.year
FROM (VALUES (2017) ) AS tbl(year) -- for example, start year is 2k17
UNION ALL
SELECT year + 1
FROM prepare
WHERE year < 2030 -- and last year is 2030
)
SELECT
year, quarter
FROM prepare
CROSS JOIN ( VALUES (1), (2), (3), (4) ) AS tbl (quarter)

Incremental and decremental count based on a date

Name Start_date end_date
aaa 01/02/2017 05/03/2017
bbb 03/05/2017 07/07/2017
ccc 02/01/2017 10/09/2017
I want to write a query that calculates the number of people who exist in the DB in a certain month/year.
Answer:
Jan 2017 1
Feb 2017 2
Mar 2017 3
Apr 2017 3
May 2017 2 (one person - aaa ,ended in May 2017)
Jun 2017 2
Jul 2017 1 (bbb ended in July 2017)
How do I write a PSQL query to get the desired output?
Thanks.
First, get the max and min dates in order to declare the dates range.
Second, with etc select all the month in the range.
Third, sum the number of the records in each dates.
Like:
declare #date date
declare #toDate date
select #date = min(Start_date),
#toDate = max(end_date)
from table_name
;With dt As
(
Select #date As [TheDate]
Union All
Select DateAdd(month, 1, TheDate) From dt Where [TheDate] < #toDate
)
select month(dt.TheDate),
year(dt.TheDate),
sum(case when table_name.Name is not null then 1 else 0 end)
from dt
left join table_name
on table_name.Start_date >= dt.TheDate
and table_name.end_date < dateadd(day,-1,dateAdd(month,1,dt.TheDate))

How to write where clause in pivot statement in SQL Server

I need to get records based on project and year
SELECT
*
FROM
(SELECT
Source,
val,
month
FROM
tbl_OrganicResult
CROSS APPLY
(VALUES ('visitors', visitors),
('UniqueVisitors', UniqueVisitors),
('ReturnVisitors', ReturnVisitors)) cs (Source, val)) A
PIVOT (Max(val)
FOR month IN ([jun], [jul)) pv
I need the records with where condition like
select *
from tbl_OrganicResult
where project = 'Homeocare'
Sample data
ProjectName Month Year visitors UniqueVisitors ReturnVisitors
Homeocare Jun 2015 400 33 22
Homeocare Jul 2015 100 10 8
debug test. Aug 2015 15222 122 120
debug test. Jun. 2015 1500 150 15
debug test. Jul 2015 1400 140 14
I'm getting records like this which is not correct, I mean I'm not getting project wise.
How to add where condition to get like where project - 'homeocare' ?
Getting output like this
Source jun jul
ReturnVisitors 8 120
UniqueVisitors 10 122
Visitors 100 15222
I need records like this
Source jun jul
ReturnVisitors. 22 8
UniqueVisitors. 33 10
Visitors 400 100
Sorry for my mistake of giving wrong data (earlier). Here always getting last 2 records and not used where condition to get records project and year wise.
The where clause should go after the cross apply like this:
SELECT
*
FROM
(
SELECT
Source,
val,
Year,
month
FROM
tbl_OrganicResult
CROSS APPLY
(VALUES ('visitors', visitors),
('UniqueVisitors', UniqueVisitors),
('ReturnVisitors', ReturnVisitors)) cs (Source, val)
WHERE ProjectName = 'Homeocare'
) A
PIVOT ( Max(val) FOR Month IN ([jun], [jul]) ) pv
Unless you only have data for one year in your table this query will get you the max value for a month from any year where you have data recorded for that month, for example if you had 100 visitors in June 15 and 200 visitors in June 14 then the value for June 14 would be selected. This might not be what you want.
I would consider doing something like this instead:
SELECT
Source, [Jun-2015], [Jul-2015]
FROM
(
SELECT
Source,
Val,
MonthYear = CONCAT(Month,'-',Year)
FROM
tbl_OrganicResult
CROSS APPLY
(VALUES ('Visitors', visitors),
('UniqueVisitors', UniqueVisitors),
('ReturnVisitors', ReturnVisitors)) cs (Source, Val)
WHERE ProjectName = 'Homeocare'
) A
PIVOT ( MAX(Val) FOR MonthYear IN ([Jun-2015], [Jul-2015]) ) pv ;
in your case you have three months but you are looking for july in place of june and August in place of july just add alias names for those columns
declare #table table (ProjectName varchar(20), Month varchar(20), Year varchar(20), visitors INT, UniqueVisitors INT, ReturnVisitors INT)
insert into #table (ProjectName,Month,Year,visitors,UniqueVisitors,ReturnVisitors)values
('Homeocare' , 'Jun' , 2015 , 400 , 33 , 22),
('Homeocare' , 'Jul' , 2015 , 100 , 10 , 8),
('debug test', 'Aug' , 2015 , 15222 , 122 , 120 )
SELECT source,JUN as Jul,jul as aug FROM (SELECT Source,
val,
month
FROM #table
CROSS apply (VALUES ('visitors',visitors),
('UniqueVisitors',UniqueVisitors),
('ReturnVisitors',ReturnVisitors)) cs (Source, val)) A
PIVOT (max(val)
FOR month IN ([jun],[Jul],
[Aug])) pv

How do I build ISO Week Number table programatically in T-SQL query?

Anyone knows how to built temp table of week using T-SQL query?
I heard there has a lot of type of calculations for that, Gregorian or etc... My needs are ISO Week No and bind to temp table depends on week no.
The temp table has 2 columns : ISOWeekNo and WeekName
ISOWeekNo WeekName
1 01 Jan 2013 To 07 Jan 2013
2 08 Jan 2013 To 14 Jan 2013
How do I build programmatically in T-SQL Query based on ISO Week No?
Updated : I want to pass the parameter year only. e.g : 2013
EDIT: Added WHERE clause to terminate for sought year only.
This seems to match the Wikipedia description and I am sure there is room for optimisation.
Mikael, I copied your formatting code for the friendly column, thank you.
This code will work on SQL Server 2008 onwards because of the use of the ISOWEEK datepart.
use tempdb
go
DECLARE #Year SMALLINT = 2013
,#FirstISOWKDay DATETIME
;WITH FindISOWEEKFirstDay AS
(
SELECT DT = DATEADD(DAY, -7, DATEFROMPARTS(#Year, 1, 1))
UNION ALL
SELECT DATEADD(DAY, 1, DT)
FROM FindISOWEEKFirstDay
WHERE DATEADD(DAY, 1, DT) < DATEADD(DAY, 14, DATEFROMPARTS(#Year, 1, 1))
)
SELECT TOP 1 #FirstISOWKDay = DT
FROM FindISOWEEKFirstDay
WHERE DATEPART(ISO_WEEK, DT) = 1
ORDER BY DT ASC -- Eliminate probability of arb sorting (Thanks Mikael)
;WITH Base10 (n) AS
(
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1
)
,Base1000 (n) AS
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL))-1
FROM Base10 T1, Base10 T2, Base10 T3
)
SELECT Start = DATEADD(DAY, n*7, #FirstISOWKDay)
,[End] = DATEADD(DAY, n*7 + 6, #FirstISOWKDay)
,Friendly = CONVERT(VARCHAR(101), DATEADD(DAY, n*7, #FirstISOWKDay), 106)+' To '+CONVERT(VARCHAR(101), DATEADD(DAY, n*7 + 6, #FirstISOWKDay), 106)
,ISOWEEK = DATEPART(ISO_WEEK, DATEADD(DAY, n*7, #FirstISOWKDay))
FROM Base1000
-- Filter to terminate, resulting only in sought year's calendar
WHERE DATEPART(YEAR, DATEADD(DAY, n*7 + 6, #FirstISOWKDay)) = #Year
declare #Year int;
set #Year = 2016;
with C as
(
select datefromparts(#Year, 1, 1) as D
union all
select dateadd(day, 1, C.D)
from C
where C.D < datefromparts(#Year, 12, 31)
)
select datepart(iso_week, C.D) as ISOWeekNo,
convert(varchar(101), min(C.D), 106)+' To '+convert(varchar(101), max(C.D), 106) as WeekName
from C
group by datepart(iso_week, C.D),
case when datepart(month, C.D) = 12 and
datepart(iso_week, C.D) > 50
then 1
else 0
end
order by min(C.D)
option (maxrecursion 0);
Result:
ISOWeekNo WeekName
----------- --------------------------
53 01 Jan 2016 To 03 Jan 2016
1 04 Jan 2016 To 10 Jan 2016
2 11 Jan 2016 To 17 Jan 2016
3 18 Jan 2016 To 24 Jan 2016
4 25 Jan 2016 To 31 Jan 2016
5 01 Feb 2016 To 07 Feb 2016
6 08 Feb 2016 To 14 Feb 2016
7 15 Feb 2016 To 21 Feb 2016
.
.
.
47 21 Nov 2016 To 27 Nov 2016
48 28 Nov 2016 To 04 Dec 2016
49 05 Dec 2016 To 11 Dec 2016
50 12 Dec 2016 To 18 Dec 2016
51 19 Dec 2016 To 25 Dec 2016
52 26 Dec 2016 To 31 Dec 2016
This may help:
Select date '2012-12-31' + level*7 WK_STARTS_DT
, to_char(date '2012-12-31' + level*7, 'IW') ISO_WEEK
, to_char(date '2012-12-31' + level*7, 'WW') WEEK
From dual
Connect By Level <=
(
Select Round( (ADD_MONTHS(TRUNC(SYSDATE,'Y'),12)-TRUNC(SYSDATE,'Y') )/7, 0) From dual
) --365/7
/
WK_STARTS_DT ISO_WEEK WEEK
------------------------------------
1/7/2013 02 01
1/14/2013 03 02
1/21/2013 04 03
......
2/4/2013 06 05
2/11/2013 07 06
......
3/4/2013 10 09
To confirm week numbers:
http://www.epochconverter.com/date-and-time/weeknumbers-by-year.php?year=2013

How do I link the subquery to the main query?

I am having an issue getting blast_seconds to show up as the correct value. In my select subquery I need to get the blast_seconds to show up within the same time as in my main Select statement. When the query is executed Year, Month, ScheduledSeconds, TotalDays and Totaltrucks match up, but blast_seconds column shows the same value through out all the months.
Here is my query:
SELECT DATEPART(YEAR, [Time]) AS [Year]
, DATEPART(MONTH, [Time]) AS [Month]
, SUM(Total) AS ScheduledSeconds
, COUNT(DISTINCT([Time])) AS TotalDays
, COUNT(DISTINCT(Equipment)) AS TotalTrucks
, (
SELECT SUM(CASE
WHEN dbo.reasons.status_id = '298'
THEN (dbo.by_operator_reasons.seconds)
ELSE 0
END)
FROM dbo.reasons
INNER JOIN by_operator_reasons
ON dbo.reasons.id = by_operator_reasons.reason_id
INNER JOIN equipment
ON by_operator_reasons.equipment_id = equipment.id
WHERE reasons.descrip LIKE 'Blast'
AND [Time] BETWEEN '2011-01-01' AND '2012-05-15'
AND by_operator_reasons.deleted_at IS NULL
AND equipment.type = 'Truck'
) AS Blast_Seconds
FROM by_equipment_times
WHERE [Time] BETWEEN '2011-01-01' and '2012-05-15'
AND equipment_type = 'Truck'
GROUP BY DATEPART(YEAR, [Time])
, DATEPART(MONTH, [Time])
ORDER BY DATEPART(YEAR, [Time]) ASC
, DATEPART(MONTH, [Time]) ASC
Here is my current output:
Year Month SchedSec Days TotalTrucks Blast_Seconds
---- ----- -------- ---- ----------- -------------
2011 1 51340448 31 20 4931156
2011 2 51979509 28 22 4931156
2011 3 58845600 31 22 4931156
2011 4 59121967 30 24 4931156
2011 5 66857271 31 25 4931156
2011 6 67306766 30 28 4931156
2011 7 76976358 31 30 4931156
2011 8 80393145 31 30 4931156
2011 9 75556005 30 30 4931156
2011 10 77741205 31 29 4931156
2011 11 75272400 30 29 4931156
2011 12 77691044 31 29 4931156
2012 1 77683752 31 29 4931156
2012 2 72662400 29 29 4931156
2012 3 77574538 31 29 4931156
2012 4 75172177 30 29 4931156
2012 5 37584000 15 29 4931156
The subquery is not corelated to main query, that is it does not depend on it. You need to connect it, and I believe that you want to do it using Time column.
SELECT DATEPART(YEAR, [Time]) AS [Year]
, DATEPART(MONTH, [Time]) AS [Month]
, SUM(Total) AS ScheduledSeconds
, COUNT(DISTINCT([Time])) AS TotalDays
, COUNT(DISTINCT(Equipment)) AS TotalTrucks
, (
SELECT SUM(CASE
WHEN dbo.reasons.status_id = '298'
THEN (dbo.by_operator_reasons.seconds)
ELSE 0
END)
FROM dbo.reasons
INNER JOIN by_operator_reasons
ON dbo.reasons.id = by_operator_reasons.reason_id
INNER JOIN equipment
ON by_operator_reasons.equipment_id = equipment.id
WHERE reasons.descrip LIKE 'Blast'
AND DATEPART(YEAR, [Time]) = DATEPART(YEAR, by_equipment_times.[Time])
AND DATEPART(MONTH, [Time]) = DATEPART(MONTH, by_equipment_times.[Time])
AND by_operator_reasons.deleted_at IS NULL
AND equipment.type = 'Truck'
) AS Blast_Seconds
FROM by_equipment_times
WHERE [Time] BETWEEN '2011-01-01' and '2012-05-15'
AND equipment_type = 'Truck'
GROUP BY DATEPART(YEAR, [Time])
, DATEPART(MONTH, [Time])
ORDER BY DATEPART(YEAR, [Time]) ASC
, DATEPART(MONTH, [Time]) ASC
If you regularly take more than few months it would probably pay off to convert subquery to derived table, grouping by year/month and outer-joining to main query.
This is most likely because you do not tell the
Select SUM(CASE WHEN dbo.reasons.status_id = '298'
then (dbo.by_operator_reasons.seconds) ELSE 0 END....
sub query to get values for respective rows - it just sums everything it finds.
You have to bind sub query to main query - something like
[...]
AND equipment.equipemnt_id_or_something = T1.equipment_id_or_something
) AS Blast_Seconds
FROM by_equipment_times as T1
[...]
Or so I think .... :)
PS.
Names of fields are imaginary.