Hourly Report in SQL - sql

I'm building hourly report(last 8 hour production count) from SQL Table CONFIRMATION via SQL Query. Query absolutely runs fine and gives proper results as follow:
SELECT
(DATENAME(hour, C.DT_CONFIRMED) + ' - ' + DATENAME(hour, DATEADD(hour, 1, C.DT_CONFIRMED))) as PERIOD,
SUM(C.QT_CONFIRMED) as QT_CONFIRMED
FROM
CONFIRMATION C
WHERE C.DT_CONFIRMED >= DATEADD(hh, -8 , '2015-12-03T11:00:00')
GROUP BY (DATENAME(hour, C.DT_CONFIRMED) + ' - ' +
DATENAME(hour, DATEADD(hour, 1, C.DT_CONFIRMED)))
ORDER BY PERIOD
I get following result:
Period QT_CONFIRMED
8 - 9 4
10 - 11 8
But instead of that, i want result in following forma:
Period QT_CONFIRMED
2 - 3 0
3 - 4 0
4 - 5 0
5 - 6 0
7 - 8 0
8 - 9 4
9- 10 0
10 - 11 8
Basically for all those hours where QT_CONFIRMED is zero, I want to show that in the report.
How can I achieve that?
CONFIRMATION Table looks like following:
DT_CONFIRMED QT_CONFIRMED ID_CONFIRMATION
2015-12-03T10:40:43 5 1
2015-12-03T10:48:33 3 2
2015-12-03T11:03:03 12 3
Thanks

The below should work.
WITH Periods AS (
SELECT 8 AS num, (DATENAME(hour, DATEADD(hour, -9, getDate())) + ' - ' + DATENAME(hour, DATEADD(hour, -8, getDate()))) as PERIOD
UNION ALL
SELECT num - 1, (DATENAME(hour, DATEADD(hour, -num, getDate())) + ' - ' + DATENAME(hour, DATEADD(hour, -num + 1, getDate()))) as PERIOD
FROM Periods WHERE num> 0
) ,
Confrim as (SELECT
(DATENAME(hour, C.DT_CONFIRMED) + ' - ' + DATENAME(hour, DATEADD(hour, 1, C.DT_CONFIRMED))) as PERIOD,
SUM(C.QT_CONFIRMED) as QT_CONFIRMED
FROM
CONFIRMATION C
WHERE C.DT_CONFIRMED >= DATEADD(hh, -8 , '2015-12-03T11:00:00')
GROUP BY (DATENAME(hour, C.DT_CONFIRMED) + ' - ' +
DATENAME(hour, DATEADD(hour, 1, C.DT_CONFIRMED))) )
select P.PERIOD, isnull(C.QT_CONFIRMED, 0) from Periods P
left join Confrim C
on P.PERIOD = C.PERIOD
ORDER BY PERIOD
It simply generates periods and later it left joins periods to your query.
You can find example here:
SQL Fiddle sample
Additionally you can change order by to ORDER BY num desc. After that your periods will be sorted correctly.

You could use a recursive CTE:
https://technet.microsoft.com/en-us/library/ms186243(v=sql.105).aspx
https://dba.stackexchange.com/questions/14661/do-you-know-an-easy-way-to-generate-one-record-for-each-hour-of-the-past-12-hour

Related

Count last 10 data for each minute

I'm trying to write a SQL Query that has to return how many transactions were made in the last 10 minutes for each minute. Like:
1 minute ago : 2 transactions
2 minutes ago : 1 transaction
...
9 minutes ago : 3 transactions
10 minutes ago : 4 transactions
I was trying to do this query:
DECLARE #N int = 10;
DECLARE #NOW DATETIME = GETDATE();
WITH numbers( num ) AS (
SELECT 1 UNION ALL
SELECT 1 + num FROM numbers WHERE num < #N )
SELECT num AS minute,
(
SELECT COUNT(*) AS RESULTS
FROM [ApiTransactions]
WHERE [DateUtc] > DATEADD(year, -1, #NOW)
GROUP BY DATEPART(minute, DATEADD(minute, -num, #NOW))
)
FROM numbers;
I still don't know if the logic is right. What I know is that I'm receiving the error:
Each GROUP BY expression must contain at least one column that is not an outer reference.
Why I'm having this error? Is there a better way to do the query?
You don't need a numbers table for this, unless you need to fill in times with no transactions. I would start with this:
SELECT DATEADD(minute, DATEDIFF(minute, 0, DateUtc), 0) as the_minute, COUNT(*)
FROM ApiTransactions
WHERE DateUtc > DATEADD(minute, -10, DateUtc)
GROUP BY DATEADD(minute, DATEDIFF(minute, 0, DateUtc), 0)
ORDER BY the_minute;
You can use this one:
SELECT DATEDIFF(minute,[DateUtc],GETDATE()) as minutes_ago,
COUNT(*) tran_count
FROM ApiTransactions
WHERE DATEDIFF(second,[DateUtc],GETDATE()) <= 600 -- 10 minutes ago
GROUP BY DATEDIFF(minute,[DateUtc],GETDATE())
Output:
minutes_ago tran_count
0 2
1 3
2 10
3 15
4 4
5 9
6 12
7 6
8 13
9 4
10 2
I managed to solve my problem in two different ways:
DECLARE #N int = 10;
DECLARE #NOW DATETIME = GETDATE();
WITH numbers( num ) AS (
SELECT 1 UNION ALL
SELECT 1 + num FROM numbers WHERE num < #N )
SELECT num-1 AS minute,
(
SELECT COUNT(*) AS RESULTS
FROM [ApiTransactions]
WHERE [DateUtc] > DATEADD(minute, -num, #NOW) AND [DateUtc] < DATEADD(minute, -num+1, #NOW)
)
FROM numbers;
that has as output:
minutes_ago amount
0 4 (most recents. still being updated)
1 0
2 2
3 3
4 1
5 2
6 1
7 2
8 1
9 3
and with:
DECLARE #NOW DATETIME = GETDATE();
SELECT CONVERT(int, DATEDIFF(second, [DateUtc], #NOW)/60) as TimePassed, COUNT([TypeCode]) as Amount
FROM [ApiTransactions]
WHERE [DateUtc] >= DATEADD (minute, -10 , #NOW)
GROUP BY CONVERT(int, DATEDIFF(second, [DateUtc], #NOW)/60)
ORDER BY CONVERT(int, DATEDIFF(second, [DateUtc], #NOW)/60)
that has as output:
minutes_ago amount
0 4 (most recents. still being updated)
2 2
3 3
4 1
5 2
6 1
7 2
8 1
9 3

T-SQL Select minimum values from different records of the table

Let's say I have a query in which I count the number of events per day:
**Date** **NumberOfEvents**
2017-11-1 7
2017-11-2 11
2017-11-3 3
...
2017-11-8 24
2017-11-9 6
2017-11-10 10
2017-11-11 9
...
2017-11-22 22
2017-11-23 11
2017-11-24 14
2017-11-25 17
...
2017-11-28 16
2017-11-29 21
2017-11-30 6
...
Then let's say I would define a variable #StartingDay ='2017-11-3'
I would like to get a query with the minimum value for the same week day +-1 day for the next 4 weeks after #StartingDay, p.ex:
**Period** **DateWithMin** **MinNumberOfEvents**
2017-11-09 To 2017-11-11 2017-11-9 6
2017-11-16 To 2017-11-18 2017-11-17 8
2017-11-23 To 2017-11-25 2017-11-23 11
2017-11-30 To 2017-12-02 2017-11-30 6
I believe I would have to cycle through the different periods to search for the min, but I can't find a way to cycle.
Another way to do this is to generate the From and To dates with a recursive CTE, apply a Row_Number() to the results to find the Min per grouping, and select only those results:
Declare #StartingDay Date = '2017-11-03',
#NumWeeks Int = 4
;With Dates As
(
Select DateFrom = DateAdd(Day, -1, DateAdd(Week, 1, #StartingDay)),
DateTo = DateAdd(Day, 1, DateAdd(Week, 1, #StartingDay))
Union All
Select DateFrom = DateAdd(Week, 1, DateFrom),
DateTo = DateAdd(Week, 1, DateTo)
From Dates
Where DateTo < DateAdd(Day, 1, DateAdd(Week, #NumWeeks, #StartingDay))
), Results As
(
Select PeriodFrom = D.DateFrom,
PeriodTo = D.DateTo,
NumberOfEvents = Y.NumberOfEvents,
RN = Row_Number() Over (Partition By D.DateFrom, D.DateTo
Order By Y.NumberOfEvents),
Date = Y.Date
From YourTable Y
Join Dates D On Y.Date Between D.DateFrom And D.DateTo
)
Select PeriodFrom,
PeriodTo,
DateWithMin = Date,
MinNumberOfEvents = NumberOfEvents
From Results
Where RN = 1
You can use modulo and date arithmetic to get the time periods and groups:
select min(date), max(date), min(NumberOfEvents)
from t
where (datediff(day, #startingday, date) % 7) in (0, 1, 6) and
date > dateadd(day, 1, #startingday) and
date <= dateadd(day, 4 * 7 + 1, #startingday)
group by (datediff(day, #startingday, date) + 1) / 7;
Getting the date of the minimum event is more troublesome. Here is one method:
select min(date), max(date), min(NumberOfEvents),
max(case when seqnum = 1 then date end) as date_at_min
from (select t.*, v.grp,
row_number() over (partition by grp order by numberofevents) as seqnum
from t cross apply
(values ((datediff(day, #startingday, date) + 1) / 7)) v(grp)
) t
where (datediff(day, #startingday, date) % 7) in (0, 1, 6) and
date > dateadd(day, 1, #startingday) and
date <= dateadd(day, 4 * 7 + 1, #startingday)
group by grp;

SQL Server null table join

I have two tables one of it is LEAGUE, another is MATCH , in MATCH table there is a column as refLeague now I want to get total matches of week
For example
Id TotalMatches
-- ------------
1 12
2 0
3 6
If there is no match, I want to write 0 as table
SELECT l.Id ,COUNT(m.Id) as TotalMatches
FROM LEAGUE l
LEFT JOIN MATCH m ON l.Id = m.refLeauge
WHERE
m.MatchDate >= DATEADD(dd, DATEDIFF(dd, 1, GETDATE()) / 7 * 7 + 1, 0)
AND m.MatchDate < DATEADD(dd, DATEDIFF(dd, -6, GETDATE())/7 * 7 + 1, 0)
AND l.refSport = 1
GROUP BY l.Id
I wrote this query but it is not giving any result due to no rows in Match table, but it must be written 0
Example
Id TotalMatches
-- ------------
1 0
2 0
3 0
Where is my mistake?
Move the right table filters to ON condition
Non matching records will have NULL values in m.MatchDate which will be filtered by the condition in Where clause . Implicitly it will be converted to INNER JOIN. So the condition should be moved to ON clause which tells what are the records to be joined with LEAGUE instead of filtering the result
SELECT l.id,
Count(m.id) AS TotalMatches
FROM league l
LEFT JOIN match m
ON l.id = m.refleauge
AND m.matchdate >= Dateadd(dd, Datediff(dd, 1, Getdate()) / 7 * 7 + 1, 0)
AND m.matchdate < Dateadd(dd, Datediff(dd, -6, Getdate()) / 7 * 7 + 1, 0)
WHERE l.refsport = 1
GROUP BY l.id
The where is breaking the left join
SELECT l.Id, COUNT(m.Id) as TotalMatches
FROM LEAGUE l
LEFT JOIN MATCH m
ON l.Id = m.refLeauge
and m.MatchDate >= dateadd(dd, datediff(dd, 1, getdate()) / 7 * 7 + 1,0)
AND m.MatchDate < dateadd(dd, datediff(dd,-6, getdate()) / 7 * 7 + 1,0)
where l.refSport=1
GROUP BY l.Id
/ 7 * 7 = 1
When I started this answer the other answer was not yet posted

DATEDIFF excluding summer months

We are running reports for a seasonal business, with expected lulls during the summer months. For some metrics, we'd essentially like to pretend that those months don't even exist.
Thus consider the default behavior of:
SELECT DATEDIFF(MONTH, '2015-05-01', '2015-06-01') -- answer = 1
SELECT DATEDIFF(MONTH, '2015-05-01', '2015-07-01') -- 2
SELECT DATEDIFF(MONTH, '2015-05-01', '2015-08-01') -- 3
SELECT DATEDIFF(MONTH, '2015-05-01', '2015-09-01') -- 4
We want to ignore June and July, so we would like those answers to look like this:
SELECT DATEDIFF(MONTH, '2015-05-01', '2015-06-01') -- answer = 1
SELECT DATEDIFF(MONTH, '2015-05-01', '2015-07-01') -- 1
SELECT DATEDIFF(MONTH, '2015-05-01', '2015-08-01') -- 1
SELECT DATEDIFF(MONTH, '2015-05-01', '2015-09-01') -- 2
What is the easiest way to accomplish this? I'd like a pure SQL solution, rather than something using TSQL, but writing a custom function such as NOSUMMER_DATEDIFF could also work.
Also, keep in mind the reports will span multiple years, so the solution should be able to handle that.
If you are only interested month differences, then I would suggest a trick here. Count the number of months since some date 0, but ignore the summer months. For example:
'2015-05-01' --> 2015 * 10 + 5 = 20155
'2015-06-01' --> 2015 * 10 + 6 = 20156
'2015-07-01' --> 2015 * 10 + 6 = 20156
'2015-08-01' --> 2015 * 10 + 6 = 20156
'2015-09-01' --> 2015 * 10 + 7 = 20157
This is a fairly easy calculation:
select (case when month(date2) <= 6 then year(date2) * 10 + month(date2)
when month(date2) in (7, 8) then year(date2) * 10 + 6
else year(date2) * 10 + (month(date2) - 2)
end)
For the difference:
select ((case when month(date2) <= 6 then year(date2) * 10 + month(date2)
when month(date2) in (7, 8) then year(date2) * 10 + 6
else year(date2) * 10 + (month(date2) - 2)
end) -
(case when month(date1) <= 6 then year(date1) * 10 + month(date1)
when month(date1) in (7, 8) then year(date1) * 10 + 6
else year(date1) * 10 + (month(date1) - 2)
end)
)
To able to achieve that, you have to "split" dates ranges to an "array" of dates for every single range of dates. CTE might be helpful in this case.
See:
--your table which holds dates ranges
DECLARE #dates TABLE(id INT IDENTITY(1,1), dFrom DATE, dTo DATE)
INSERT INTO #dates (dFrom, dTo)
VALUES('2015-05-01', '2015-06-01'),
('2015-05-01', '2015-07-01'),
('2015-05-01', '2015-08-01'),
('2015-05-01', '2015-09-01')
--summer month table
DECLARE #summermonths TABLE(summMonth INT)
INSERT INTO #summermonths(summMonth)
VALUES(6), (7)
--here Common Table Expressions is in action to "split" dates ranges to an array of dates for every single date range
;WITH CTE AS
(
SELECT id, DATEADD(MM, 0, dFrom) AS ndFrom, dTo, CASE WHEN MONTH(DATEADD(MM, 0, dFrom)) = 6 OR MONTH(DATEADD(MM, 0, dFrom)) = 7 THEN 0 ELSE 1 END AS COfMonth
FROM #dates
WHERE DATEADD(MM, 1, dFrom)<=dTo
UNION ALL
SELECT id, DATEADD(MM, 1, ndFrom) AS ndFrom, dTo, CASE WHEN MONTH(DATEADD(MM, 1, ndFrom)) = 6 OR MONTH(DATEADD(MM, 1, ndFrom)) = 7 THEN 0 ELSE 1 END AS COfMonth
FROM CTE
WHERE DATEADD(MM, 1, ndFrom)<=dTo
)
SELECT t1.id, t2.dFrom, t2.dTo, SUM(t1.COfMonth) AS MyDateDiff
FROM CTE AS t1 INNER JOIN #dates AS t2 ON t1.id = t2.id
GROUP BY t1.id, t2.dFrom , t2.dTo
Result:
id dFrom dTo MyDateDiff
1 2015-05-01 2015-06-01 1
2 2015-05-01 2015-07-01 1
3 2015-05-01 2015-08-01 2
4 2015-05-01 2015-09-01 3 --not 2, because of 5, 8, 9
Got it?
Note: a solution might be differ in case of dFrom and dTo is not the first date of month.

loop dates and return weeks tsql

I am very new in SQL (started just 3 days back) and I got an issue and would hope someone is able to assist me.
I have a query that creates date loops to return me the week number based on the passed in dates. The idea here is to pass loop through the days, months and years and return me a week value. But because of these, I have been getting multiple entries of the same result.
For eg. I am getting:
period_wk_key period_yr_key period_week period_week_day period_week_full_desc
--------------- -------------- ------------ -------------- -----------------------
200001 2000 1 6 2000 WEEK 1
200001 2000 1 6 2000 WEEK 1
200001 2000 1 6 2000 WEEK 1
. . . . .
200002 2000 2 6 2000 WEEK 2
200002 2000 2 6 2000 WEEK 2
200002 2000 2 6 2000 WEEK 2
. . . . .
200003 2000 3 6 2000 WEEK 3
each period_wk_key is returning me 7 similar rows which is not what I wanted.
The ideal situation should be:
period_wk_key period_yr_key period_week period_week_day period_week_full_desc
--------------- -------------- ------------ -------------- -----------------------
200001 2000 1 6 2000 WEEK 1
200002 2000 2 6 2000 WEEK 2
200003 2000 3 6 2000 WEEK 3
200004 2000 4 6 2000 WEEK 4
I need to know a way to limit the number of output from a loop so that I can have distinct records instead of multiple similar rows. I have attached a code below. It maybe crap to many of you but this is just my 3rd day beginning scripting. Much thanks for any help rendered.
DECLARE #iStartYear INT
SET #iStartYear = 2000
DECLARE #iEndYear INT
SET #iEndYear = 2030
DECLARE #iMth INT
SET #iMth = 1
DECLARE #iDay INT
SET #iDay = 1
DECLARE #iWeek INT
`DECLARE #StartDate DATETIME
WHILE (#iStartYear <= #iEndYear)
BEGIN
WHILE (#iMth <= 12)
BEGIN
WHILE (#iDay <= DATEDIFF(DAY,DATEADD(DAY, 0, DATEADD(m, ((#iStartYear - 1900) * 12) + #iMth - 1, 0)),DATEADD(DAY, 0, DATEADD(m, ((#iStartYear - 1900) * 12) + #iMth, 0)))))
BEGIN `
SET #iWeek = (DATEPART(dy, CONVERT(DATETIME, CONVERT(VARCHAR(4), #iStartYear) + '/' + CONVERT(VARCHAR(2), #iMth) + '/' + CONVERT(VARCHAR(2), #iDay))) - 1) / 7 + 1;
INSERT INTO dim_period_week (period_wk_key, period_yr_key, period_week, period_week_full_desc,start_date, end_date, period_week_day)
VALUES (
(SELECT CASE WHEN #iWeek < 10 THEN
CAST((#iStartYear) AS VARCHAR) + '0' + CAST((#iWeek) AS VARCHAR)
ELSE
CAST((#iStartYear) AS VARCHAR)+ CAST((#iWeek) AS VARCHAR)
END),
#iStartYear,
(SELECT (DATEPART(dy, CONVERT(DATETIME, CONVERT(VARCHAR(4), #iStartYear) + '/' + CONVERT(VARCHAR(2), #iMth) + '/' + CONVERT(VARCHAR(2), #iDay))) - 1) / 7 + 1),
(SELECT CAST((#iStartYear) AS VARCHAR) + ' ' + 'WEEK' + ' ' + CAST((#iWeek) AS VARCHAR)),
(SELECT DATEADD(wk,#iWeek-1, DATEADD(yy,#iStartYear-1900,0))), --START DATE
(SELECT DATEADD(wk,#iWeek, DATEADD(yy,#iStartYear-1900,0)) -1), --END DATE
(SELECT DATEDIFF(DAY, DATEADD(wk,#iWeek-1, DATEADD(yy,#iStartYear-1900,0)), DATEADD(wk,#iWeek, DATEADD(yy,#iStartYear-1900,0)) -1))
)
`SET #iDay = #iDay + 1
END
SET #iDay = 1
SET #iMth = #iMth + 1
END
SET #iMth = 1
SET #iStartYear = #iStartYear + 1
END
`
You are looping every day, but never storing at the day level, so you are doing 7 times as many INSERTs as needed. Add the following just above your INSERT statement
IF DATEPART(weekday, CAST(CAST(#iStartYear * 10000 + #iMth * 100 + #iDay AS VARCHAR(8)) AS datetime)) = 1
INSERT INTO dim_period_week ...
Below is a set based approach using a numbers table.
IF OBJECT_ID('tempdb.dbo.#Numbers') IS NOT NULL DROP TABLE #Numbers
GO
DECLARE #startDate DATETIME = '20000101'
SELECT
(a.Number * 256) + b.Number AS Number
INTO #Numbers
FROM
(SELECT number FROM master..spt_values WHERE type = 'P' AND number <= 255) a (Number),
(SELECT number FROM master..spt_values WHERE type = 'P' AND number <= 255) b (Number)
WHERE
(a.Number * 256) + b.Number BETWEEN 0 AND 1617
SELECT
WeekKey = CAST(YEAR(DATEADD(day, N.Number * 7, #startDate)) * 100 + DATEPART(week, DATEADD(day, N.Number * 7, #startDate)) AS VARCHAR(10)),
TheYear = YEAR(DATEADD(day, N.Number * 7, #startDate)),
TheWeek = DATEPART(week, DATEADD(day, N.Number * 7, #startDate)),
TheWeekDay= DATEPART(weekday, DATEADD(day, N.Number * 7, #startDate)),
WeekDetail= DATENAME(year, DATEADD(day, N.Number * 7, #startDate)) + ' WEEK ' + DATENAME(week, DATEADD(day, N.Number * 7, #startDate)),
StartDate = DATEADD(day, N.Number * 7, #startDate),
EndDate = DATEADD(day, 6, DATEADD(day, N.Number * 7, #startDate))
FROM #Numbers N
ORDER BY 1