Group by day and conditional count issue - sql

Using SQLServer 2008r2 - I have a table which has records inserted every hour. The relevant columns for my query are currentScore (int) and obsDate (smallDateTime). I wish to get five records grouped by day. Today, the two days prior to today (starting at mid-night) and two days in the future. SO if its Jun 20 I want June 18, 19, 20, 21 and 22. I am successfully doing this like so:
select dateadd(DAY,0, datediff(day,0, obsDate)) as theDate,
count(currentScore) as numOfScores
from diseaseScores
where siteID=8315 and obsDate > dateAdd(day, -2, (SELECT CONVERT(DATETIME,
CONVERT(DATE, CURRENT_TIMESTAMP)) + '00:00'))
group by dateadd(DAY,0, datediff(day,0, obsDate))
order by dateadd(DAY,0, datediff(day,0, obsDate))
My record set looks like so:
theDate numOfScores
2017-06-18 00:00:00.000 23
2017-06-19 00:00:00.000 22
2017-06-20 00:00:00.000 24
2017-06-21 00:00:00.000 24
2017-06-22 00:00:00.000 9
I wish to add three more columns which will count the number of currentScore in a certain range. Something like this
CASE
WHEN currentScore < 8 THEN COUNT(where currentScore < 8) as Low
WHEN currentScore > 8 and < 17 THEN COUNT(where currentScore > 8 and < 17) as Med
WHEN currentScore > 17 THEN COUNT(where currentScore > 17 ) as High
Can I do this with a select case? What is the best way to achieve this?
Thanks in advance
Here is the result I wish to achieve:
theDAte numOfScores low med high
2017-06-18 23 23 0 0
2017-06-19 22 22 0 0
2017-06-20 24 5 19 0
2017-06-21 24 0 24 0
2017-06-22 9 0 9 0

First, use cast(. . as date). Much clearer! Then you can do what you want using conditional aggregation:
select cast(obsDate as date) as theDate,
count(currentScore) as numOfScores ,
sum(case when currentScore < 8 then 1 else 0 end) as currentscore_low,
sum(case when currentScore >= 8 and currentScore < 17 then 1 else 0 end) as currentscore_medium,
sum(case when currentScore >= 17 then 1 else 0 end) as currentscore_high
from diseaseScores
where siteID = 8315 and
obsDate >= cast(getdate() - 2 as date)
group by cast(obsDate as date)
order by cast(obsDate as date);
Note: Your original where clause has only half the date condition. I didn't add the other half, but it should be pretty obvious how to get no more than two days in the future.

Related

T-SQL pivot inventory aging by day range

Writing a T-SQL statement to display items in inventory broken out by day range (pivot).
For example from this inventory table:
ItemName
DateCreated
PO_ID
A
2020-10-07
0
B
2020-10-07
1
A
2020-10-22
2
A
2020-10-22
2
A
2020-10-22
2
B
2020-10-29
3
Would like to generate the bellow results (typically a pivot table), showing the number of pieces per ItemName per day range. The date used to calculate the # of days since DateCreated would be the day the report was ran or passed in as a parameter - in the example shown here, the date used is from '2020-11-07':
ItemName
0-10 days
11-20 days
21-30 days
>30 days
A
0
3
0
1
B
1
0
0
1
Not sure what would be the best way to write the statement to generate the above results?
I would use conditional aggregation:
select itemName,
sum(case when datediff(day, dateCreated, getdate()) <= 10 then 1 else 0 end) as days_0_10,
sum(case when datediff(day, dateCreated, getdate()) > 10 and
datediff(day, dateCreated, getdate()) <= 20
then 1 else 0 end) as days_11_20,
sum(case when datediff(day, dateCreated, getdate()) > 20 and
datediff(day, dateCreated, getdate()) <= 30
then 1 else 0 end) as days_21_30,
sum(case when datediff(day, dateCreated, getdate()) > 30 then 1 else 0 end) as days_31
from t
group by itemName
I would use something similar to the following query SQL Server:
SELECT *
FROM
(
SELECT [ItemName],[PO_ID],
CASE
WHEN DATEDIFF(day, DateCreated, getdate()) BETWEEN 0 AND 10 THEN '0-10 days'
WHEN DATEDIFF(day, DateCreated, getdate()) BETWEEN 11 AND 20 THEN '11-20 days'
WHEN DATEDIFF(day, DateCreated, getdate()) BETWEEN 21 AND 30 THEN '21-30 days'
ELSE '>30 days'
END AS PeriodCreated
FROM [TableName])
) src
pivot
(
COUNT(PO_ID)
FOR PeriodCreated in ([0-10 days], [11-20 days], [>30 days])
) piv

Sum Based on Date

I currently have this code that I want to sum every quantity based on the year. I have written a code that I thought would sum all the charges in 2016 and 2017, but it isn't running correctly.
I added the two different types of partition by statements to test and see if either would work and they don't. When I take them out, the Annual column just shows me the quantity for that specific receipt.
Here is my current code:
SELECT
ReceiptNumber
,Quantity
,Date
,sum(CASE WHEN (Date >= '2016-01-01' and Date < '2017-01-01') THEN
Quantity
ELSE 0 END)
OVER (PARTITION BY Date)
as Annual2016
,sum(CASE WHEN (Date >= '2017-01-01' and Date < '2018-01-01') THEN
Quantity
ELSE 0 END)
OVER (PARTITION BY ReceiptNumber)
as Annual2017
FROM Table1
GROUP BY ReceiptNumber, Quantity, Date
I would like my data to look like this
ReceiptNumber Quantity Date Annual2016 Annual2017
1 5 2016-01-05 17 13
2 11 2017-04-03 17 13
3 12 2016-11-11 17 13
4 2 2017-09-09 17 13
Here is a sample of some of the data I am pulling from:
ReceiptNumber Quantity Date
1 5 2016-01-05
2 11 2017-04-03
3 12 2016-11-11
4 2 2017-09-09
5 96 2015-07-08
6 15 2016-12-12
7 24 2016-04-19
8 31 2017-01-02
9 10 2017-0404
10 18 2015-10-10
11 56 2017-06-02
Try something like this
Select
..
sum(CASE WHEN (Date >= '2016-01-01' and Date < '2017-01-01') THEN
Quantity
ELSE 0 END)
OVER () as Annual2016
sum(CASE WHEN (Date >= '2017-01-01' and Date < '2018-01-01') THEN
Quantity
ELSE 0 END)
OVER ()as Annual2017
..
Where Date >= '2016-01-01' and Date < '2018-01-01'
If you want it printed only once at the top then you should run it in a separate query like:
SELECT YEAR(Date) y, sum(Quantity) s FROM Table1 GROUP BY YEAR(Date)
and then do the main query like this:
SELECT * FROM table1
Easy, peasey ... ;-)
Your original question could also be answered with:
SELECT *,
(SELECT SUM(Quantity) FROM Table1 WHERE YEAR(Date)=2016 ) Annual2016,
(SELECT SUM(Quantity) FROM Table1 WHERE YEAR(Date)=2017 ) Annual2017
FROM table1
You need some conditional aggreation over a Window Aggregate. Simply remove both PARTITION BY as you're already filtering the year in the CASE:
SELECT
ReceiptNumber
,Quantity
,Date
,sum(CASE WHEN (Date >= '2016-01-01' and Date < '2017-01-01') THEN
Quantity
ELSE 0 END)
OVER () as Annual2016
,sum(CASE WHEN (Date >= '2017-01-01' and Date < '2018-01-01') THEN
Quantity
ELSE 0 END)
OVER () as Annual2017
FROM Table1
You probably don't need the final GROUP BY ReceiptNumber, Quantity, Date

Group by day from a certain hour of the day

SQLServer 2008r2. I have a table which is populated with a record at 10 mins past every hour of every day. Every hour the job is run it also enters 48 records which represent a forecast of what is likely to happen for the next 48 hours. Note - Just before it enters the 48 hour forecast it deletes the forecast which was entered last time. So although it enters a 48 hour forecast every hour there is only ever one forecast in the system. The relevant fields in the table look like this:
currentScore obsDate
9 2017-06-22 08:10:00
9 2017-06-22 07:10:00
9 2017-06-22 06:10:00
10 2017-06-22 05:10:00
... ...
How can I query this table and group by day from a certain time of day? I would like the day to start at 6am the day before and finish at 6am on the day. I only need five records from the table, the day, two before and two in the future. So if its Jun 20 I want June 18, 19, 20, 21 and 22. Here is the query which gets the correct results by calendar day.
SELECT cast(obsDate AS DATE) AS theDate
,sum(CASE
WHEN currentScore < 8
THEN 1
ELSE 0
END) AS currentscore_low
,sum(CASE
WHEN currentScore >= 8
AND currentScore < 17
THEN 1
ELSE 0
END) AS currentscore_medium
,sum(CASE
WHEN currentScore >= 17
THEN 1
ELSE 0
END) AS currentscore_high
FROM diseaseScores
WHERE siteID = 8315
AND obsDate >= cast(getdate() - 2 AS DATE)
GROUP BY cast(obsDate AS DATE)
ORDER BY cast(obsDate AS DATE);
which returns this result:
theDAte low med high
2017-06-18 23 0 0
2017-06-19 22 0 0
2017-06-20 5 19 0
2017-06-21 0 24 0
2017-06-22 0 9 0
There is a new requirement to get the same result but the group by and the subsequent counts need to be from 6am to 6am. e.g
the first rec should be from 2017-06-17 06:00am to 2017-06-18 06:00am
the second rec should be from 2017-06-18 06:00am to 2017-06-19 06:00am
....etc
How can I do this? Thanks in advance
UPDATE, I have done two things:
1..introduce Tims idea
2..I also add an extra field 'numOfScores' to show how many hours worth of data
each line represent
select
cast(dateadd(hour, -6, obsDate) as date) as theDate, count(currentScore) as numOfScores,
sum(case when currentScore < 8 then 1 else 0 end) as currentscore_low,
sum(case when currentScore >= 8 and currentScore < 17
then 1 else 0 end) as currentscore_medium,
sum(case when currentScore >= 17 then 1 else 0 end) as currentscore_high
from diseaseScores
where siteID = 8315 and
obsDate >= cast(getdate() - 2 as date)
group by cast(dateadd(hour, -6, obsDate) as date)
order by cast(dateadd(hour, -6, obsDate) as date);
I now get this result:
2017-06-18 5 5 0 0
2017-06-19 24 23 1 0
2017-06-20 24 1 23 0
2017-06-21 24 8 16 0
2017-06-22 24 1 23 0
2017-06-23 9 0 9 0
This tells me that that there is only 5 hours worth of scores on the 2017-06-18. I want this first line to be 24 hours worth. From 6am on the 17th until 6am on the 18th. This makes me think I am not getting the result I wish
The 23rd only having 9 hours is ok because this is the most recent forecast
UPDATED:
I dont think its easily done in one query (if even possible) so I will just use five queries and specifically state the dates&times to get my outcome. e.g here are the first two:
select
sum(case when currentScore < 9 then 1 else 0 end) as numOfLOWRecs,
sum(case when currentScore > 8 and currentScore < 17 then 1 else 0 end) as currentscore_medium,
sum(case when currentScore >= 17 then 1 else 0 end) as currentscore_high
from diseaseScores where siteID = 9999
and obsDate >= '2017-06-18 06:00' and obsDate < '2017-06-19 06:00'
select
sum(case when currentScore < 9 then 1 else 0 end) as numOfLOWRecs,
sum(case when currentScore > 8 and currentScore < 17 then 1 else 0 end) as currentscore_medium,
sum(case when currentScore >= 17 then 1 else 0 end) as currentscore_high
from diseaseScores where siteID = 9999
and obsDate >= '2017-06-19 06:00' and obsDate < '2017-06-20 06:00'
One trick which might work here would be to simply shift each observation backwards by 6 hours. This would shift 2017-06-17 06:00:00 to 2017-06-17 00:00:00, i.e. now 6am becomes the start of that actual day.
select
cast(dateadd(hour, -6, obsDate) as date) as theDate,
sum(case when currentScore < 8 then 1 else 0 end) as currentscore_low,
sum(case when currentScore >= 8 and currentScore < 17
then 1 else 0 end) as currentscore_medium,
sum(case when currentScore >= 17 then 1 else 0 end) as currentscore_high
from diseaseScores
where siteID = 8315 and
obsDate >= cast(getdate() - 2 as date)
group by cast(dateadd(hour, -6, obsDate) as date)
order by cast(dateadd(hour, -6, obsDate) as date);

SQL query count occurrences then group by day but also fill missing days

I have a table called diseaseScores which has calculation results. Each hour a running score is calculated (currentScore). The objective of this query is to group the hourly scores by the day (obsDate), then count the numberOfhours where the running score is at a level considered high. High is greater than 16 (currentScore > 16).
My query so far is:
SELECT
DATEADD(DAY, 0, DATEDIFF(day, 0, obsDate)) AS obsDate,
(CASE
WHEN count(id) > 12 THEN count(id)
ELSE 0
END) numOfHoursAtHigh
FROM
diseaseScores
WHERE
diseaseID = 2
AND siteID = 72160
AND numOfRotationYears = 3
AND currentScore > 16
AND month(obsDate) IN (6)
GROUP BY
DATEADD(DAY, 0, DATEDIFF(day, 0, obsDate))
ORDER BY
DATEADD(DAY, 0, DATEDIFF(day, 0, obsDate));
The query returns results for 13 days of the month. I wish to fill the gaps so I a have a record for each of the day of the month. The gaps need to have a numOfHoursAtHigh result of 0.
How can I do this? This is for SQL Server 2008 +
The result set being returned is:
2016-06-04 00:00:00.000 0
2016-06-05 00:00:00.000 23
2016-06-06 00:00:00.000 23
2016-06-07 00:00:00.000 23
2016-06-08 00:00:00.000 3
2016-06-09 00:00:00.000 23
2016-06-10 00:00:00.000 0
2016-06-17 00:00:00.000 13
2016-06-18 00:00:00.000 23
2016-06-19 00:00:00.000 0
2016-06-20 00:00:00.000 14
2016-06-21 00:00:00.000 23
2016-06-22 00:00:00.000 16
UPDATE : So using a modified version of knobcreekmans approach (was doubling up certain days) I now have this which does fill my gaps and works great for one month. As soon as I ask for two months worth by changing month(obsDate) IN (6) to month(obsDate) IN (6,7) it skips days if they happen to clash in month 6 and 7. Grrrrrr, am so close!
SELECT CAST(obsDate AS DATE) as obsDate,
(CASE
WHEN COUNT(id) > 12 THEN COUNT(id)
ELSE 0
END) numOfHoursAtHigh
FROM diseaseScores
WHERE diseaseID=2
AND siteID=72160
AND numOfRotationYears=3
AND currentScore > 16
AND month(obsDate) IN (6)
GROUP BY CAST(obsDate AS DATE)
UNION
SELECT CAST(obsDate AS DATE) AS obsDate,
0 AS numOfHoursAtHigh
FROM diseaseScores
WHERE diseaseID=2
AND siteID=72160
AND numOfRotationYears=3
AND currentScore <= 17
AND month(obsDate) IN (6)
and day(obsDate) NOT IN --<-- added from here
(
SELECT distinct day(obsDate)
FROM diseaseScores
WHERE diseaseID=2
AND siteID=72160
AND numOfRotationYears=3
AND currentScore > 16
AND month(obsDate) IN (6)
) --<-- to here to omit the duplicates
GROUP BY CAST(obsDate AS DATE)
ORDER BY CAST(obsDate AS DATE)
To answer to the question around the expected result. Its one record for each day of the month (or months) giving to columns. A date and then an integer for numOfHoursAtHigh e.g
2016-06-01 0
2016-06-02 0
2016-06-03 0
2016-06-04 0
2016-06-05 23
2016-06-06 23
2016-06-07 23
2016-06-08 23
2016-06-09 23
2016-06-10 0
2016-06-11 0
2016-06-12 0
2016-06-13 0
2016-06-14 0
2016-06-15 0
2016-06-16 0
2016-06-17 13
2016-06-18 23
2016-06-19 0
2016-06-20 14
2016-06-21 23
2016-06-22 16
2016-06-23 0
2016-06-24 0
2016-06-25 0
2016-06-26 0
2016-06-27 0
2016-06-28 0
2016-06-29 0
2016-06-30 0
You could create another SELECT that is the same as the original, modify the part of your WHERE clause that is filtering out the results you want (currentScore > 16), and then UNION them together.
SELECT CAST(obsDate AS DATE) as obsDate,
(CASE
WHEN COUNT(id) > 12 THEN COUNT(id)
ELSE 0
END) numOfHoursAtHigh
FROM diseaseScores
WHERE diseaseID=2
AND siteID=72160
AND numOfRotationYears=3
AND currentScore > 16
AND month(obsDate) IN (6)
GROUP BY CAST(obsDate AS DATE)
UNION
SELECT CAST(obsDate AS DATE) AS obsDate,
0 AS numOfHoursAtHigh
FROM diseaseScores
WHERE diseaseID=2
AND siteID=72160
AND numOfRotationYears=3
AND currentScore < 17 --<-- note the change
AND month(obsDate) IN (6)
GROUP BY CAST(obsDate AS DATE)
ORDER BY CAST(obsDate AS DATE)
Hi, You can have the below query,
SELECT DS.dateadd(DAY,0, datediff(day,0, obsDate)) as obsDate,
CASE
WHEN DS1.COUNT(id) > 12 THEN COUNT(id)
ELSE 0
END AS numOfHoursAtHigh
FROM diseaseScores DS
INNER JOIN (
SELECT dateadd(DAY,0, datediff(day,0, obsDate)) AS date, COUNT(id)
FROM diseaseScores GROUP BY date
) DS1
ON DS.date = DS1.obsDate
AND DS.diseaseID=2
AND DS.siteID=721DS.60
AND DS.numOfRotationYears=3
AND DS.currentScore > 16
AND DS.month(obsDate) IN (6)
ORDER BY DS.obsDate;

SQL - How to count records for each status in one line per day?

I have a table Sales
Sales
--------
id
FormUpdated
TrackingStatus
There are several status e.g. Complete, Incomplete, SaveforLater, ViewRates etc.
I want to have my results in this form for the last 8 days(including today).
Expected Result:
Date Part of FormUpdated, Day of Week, Counts of ViewRates, Counts of Sales(complete), Counts of SaveForLater
--------------------------------------
2015-05-19 Tuesday 3 1 21
2015-05-18 Monday 12 5 10
2015-05-17 Sunday 6 1 8
2015-05-16 Saturday 5 3 7
2015-05-15 Friday 67 5 32
2015-05-14 Thursday 17 0 5
2015-05-13 Wednesday 22 0 9
2015-05-12 Tuesday 19 2 6
Here is my sql query:
select datename(dw, FormUpdated), count(ID), TrackingStatus
from Sales
where FormUpdated <= GETDATE()
AND FormUpdated >= GetDate() - 8
group by datename(dw, FormUpdated), TrackingStatus
order by datename(dw, FormUpdated) desc
I do not know how to make the next step.
Update
I forgot to mention, I only need the Date part of the FormUpdated, not all parts.
You can use SUM(CASE WHEN TrackingStatus = 'SomeTrackingStatus' THEN 1 ELSE 0 END)) to get the status count for each tracking status in individual column. Something like this. SQL Fiddle
select
CONVERT(DATE,FormUpdated) FormUpdated,
DATENAME(dw, CONVERT(DATE,FormUpdated)),
SUM(CASE WHEN TrackingStatus = 'ViewRates' THEN 1 ELSE 0 END) c_ViewRates,
SUM(CASE WHEN TrackingStatus = 'Complete' THEN 1 ELSE 0 END) c_Complete,
SUM(CASE WHEN TrackingStatus = 'SaveforLater' THEN 1 ELSE 0 END) c_SaveforLater
from Sales
where FormUpdated <= GETDATE()
AND FormUpdated >= DATEADD(D,-8,GetDate())
group by CONVERT(DATE,FormUpdated)
order by CONVERT(DATE,FormUpdated) desc
You can also use a PIVOT to achieve this result - you'll just need to complete the list of TrackingStatus names in both the SELECT and the FOR, and no GROUP BY required:
WITH DatesOnly AS
(
SELECT Id, CAST(FormUpdated AS DATE) AS DateOnly, DATENAME(dw, FormUpdated) AS DayOfWeek, TrackingStatus
FROM Sales
)
SELECT DateOnly, DayOfWeek,
-- List of Pivoted Columns
[Complete],[Incomplete], [ViewRates], [SaveforLater]
FROM DatesOnly
PIVOT
(
COUNT(Id)
-- List of Pivoted columns
FOR TrackingStatus IN([Complete],[Incomplete], [ViewRates], [SaveforLater])
) pvt
WHERE DateOnly <= GETDATE() AND DateOnly >= GetDate() - 8
ORDER BY DateOnly DESC
SqlFiddle
Also, I think your ORDER BY is wrong - it should just be the Date, not day of week.