SQL Group by different time periods with day - sql

I need to query a sales table and have the resulting table show the breakdown for different time periods within the given day.
For example, the table has the following fields:
Id (int), EntryDate (datetime), SaleReference (varchar)
I'd like to produce a table that looks like this:
Date Sales 9am-12pm Sales 12pm-3pm Sales 6pm-9pm
---------- -------------- -------------- -------------
01-01-2010 10 20 6
02-01-2010 12 16 3
03-01-2010 43 11 2
Any help on this would be greatly appreciated.
Thanks

Assuming SQL Server below. If not a similar logic will probably apply with your RDBMS but likely a different function to get the hour part from the datetime and the behaviour of BETWEEN may be different too (In SQL Server it is an inclusive range).
SELECT CAST([Date] AS Date) AS [Date],
COUNT(CASE WHEN DATEPART(hour, [Date]) BETWEEN 9 AND 11 THEN 1 ELSE NULL END)
AS [Sales 9am-12pm],
COUNT(CASE WHEN DATEPART(hour, [Date]) BETWEEN 12 AND 14 THEN 1 ELSE NULL END)
AS [Sales 12pm-3pm],
COUNT(CASE WHEN DATEPART(hour, [Date]) BETWEEN 18 AND 20 THEN 1 ELSE NULL END)
AS [Sales 6pm-9pm]
FROM Table
GROUP BY CAST([Date] AS Date) /*For SQL2008*/
NB: Previous versions of SQL Server require a few more hoops to get just the date part out of a datetime. e.g. CAST(FLOOR( CAST( GETDATE() AS FLOAT ) ) AS DATETIME (From here)

Assuming your database supports this kind of date math, you can say:
CASE WHEN EntryDate - date(EntryDate) >= INTERVAL '9 hours'
AND EntryDate - date(EntryDate) < INTERVAL '12 hours'
THEN ...
(That's the PostgreSQL interval syntax, btw... might be nonstandard.) But there are probably more elegant ways of doing it.

Related

Date filtering in SQL

Table below consists of 2 columns: a unique identifier and date. I am trying to build a new column of episodes, where a new episode would be triggered when >= 3 months between dates. This process should occur for each unique EMID. In the table attached, EMID ending in 98 would only have 1 episode, there are no intervals >2 months between each row in the date column. However, EMID ending in 03 would have 2 episodes, as there is almost a 3 year gap between rows 12 and 13. I have tried the following code, which doesn't work.
Table:
SELECT TOP (1000) [EMID],[Date]
CASE
WHEN DATEDIFF(month, Date, LEAD Date) <3
THEN "1"
ELSE IF DATEDIFF(month, Date, LEAD Date) BETWEEN 3 AND 5
THEN "2"
ELSE "3"
END episode
FROM [res_treatment_escalation].[dbo].[cspine42920a]
EDIT: Using Microsoft SQL Server Management Studio.
EDIT 2: I have made some progress but the output is not exactly what I am looking for. Here is the query I used:
SELECT TOP (1000) [EMID],[visit_date_01],
CASE
WHEN DATEDIFF(DAY, visit_date_01, LAG(visit_date_01,1,getdate()) OVER (partition by EMID order by EMID)) <= 90 THEN '1'
WHEN DATEDIFF(DAY, visit_date_01, LAG(visit_date_01,1,getdate()) OVER (PARTITION BY EMID ORDER BY EMID)) BETWEEN 90 AND 179 THEN '2'
WHEN DATEDIFF(DAY, visit_date_01, LAG(visit_date_01,1,getdate()) OVER (PARTITION BY EMID order by EMID)) > 180 THEN '3'
END AS EPISODE
FROM [res_treatment_escalation].[dbo].['c-spine_full_dataset_4#29#20_wi$']
table2Here is the actual vs expected output
The partition by EMID does not seem to be working correctly. Every time there is a new EMID a new episode is triggered. I am using day instead of month as the filter in DATEDIFF- this does not seem to recognize new episodes within the same EMID
Hmmm: Use LAG() to get the previous date. Use a date comparison to assign a flag and then a cumulative sum:
select c.*,
sum(case when prev_date > dateadd(month, -3, date) then 0 else 1 end) over
(partition by emid order by date) as episode_number
from (select c.*, lag(date) over (partition by emid order by date) as prev_date
from res_treatment_escalation.dbo.cspine42920a c
) c;

SQL Report - query multiple tables

I have been working on a Stats page in APEX and currently have the following report query:
select to_char(DATELOGGED,'Month - YYYY') as Month,
COUNT(*) as "Total Calls",
SUM(case when CLOSED is null then 1 else null end) as "Open",
COUNT(case CLOSED when 'Y' then 1 else null end) as "Closed",
SUM(case when EXTREF is null then 0 else 1 end) as "Referred",
round((COUNT(case SLA_MET when 'Y' then 1 else null end)/COUNT(case CLOSED when 'Y' then 1 else null end)*100),2) as "SLA Met %"
from IT_SUPPORT_CALLS
GROUP BY to_char(DATELOGGED,'Month - YYYY')
order by MIN (DATELOGGED) desc
I wish to add the sum of DURATION from a different table:
select
"START_TIME",
DECODE(DURATION,null,'Open',((select extract( minute from DURATION )
+ extract( hour from DURATION ) * 60
+ extract( day from DURATION ) * 60 * 24
from dual)||' minutes')) DURATION
from "IT_DOWNTIME"
The IT_DOWNTIME table uses START_TIME (varchar2) as the date identifier, the IT_SUPPORT_CALLS uses DATELOGGED (DATE) as date identifier.
The current output for IT_DOWNTIME is for example:
08-FEB-2019 - 30 Minutes
20-FEB-2019 - 15 Minutes
I would like the report SUM and group IT_DOWNTIME and add this into the existing report.
Hope this makes sense.
Please let me know if I missed any information that would help to resolve this.
Many thanks
Thanks for that, much appreciated. Unfortunately it doesn't return any data from IT_DOWNTIME.
I'm guessing the different date formats doesn't help, hope this clears things up a bit:
These are the columns in IT_DOWNTIME that are of interest:
START_TIME ( VARCHAR2(30) )
DURATION ( INTERVAL DAY(2) TO SECOND(6) )
Example of current IT_DOWNTIME output without formatting:
START_TIME
06-JUL-2016 11:05
DURATION
+00 00:35:00.000000
Example of current IT_SUPPORT_CALLS output without formatting:
DATELOGGED
06/07/2016
Something like this will probably do it, but there has been some guesswork as to your column names etc:
SELECT *
FROM
(
SELECT
to_char(DATELOGGED,'MON-YYYY') as Month,
COUNT(*) as Total_Calls,
SUM(case when CLOSED is null then 1 else null end) as case_Open,
COUNT(case CLOSED when 'Y' then 1 else null end) as case_Closed,
SUM(case when EXTREF is null then 0 else 1 end) as case_Referred,
round((COUNT(case SLA_MET when 'Y' then 1 else null end)/COUNT(case CLOSED when 'Y' then 1 else null end)*100),2) as percent_SLA_met
FROM IT_SUPPORT_CALLS
GROUP BY to_char(DATELOGGED,'MON-YYYY')
) calls
LEFT JOIN
(
SELECT
SUBSTR(START_TIME, 4) as down_month,
SUM(extract(minute from DURATION) +
extract(hour from DURATION) * 60 +
extract(day from DURATION) * 60 * 24
) || 'minutes' as total_down_mins
FROM IT_DOWNTIME
WHERE duration is not null
GROUP BY SUBSTR(START_TIME, 4)
) downs
ON calls.month = downs.down_month
Changed your date formatting of the first query to be MON-YYYY to make it align with what you claim is the formatting of the varchar2 date of the second query (dd-mon-yyy), and substringed the date to remove the day, leaving just the month
Edit:
Ok, so since you've posted some different example data from IT_DOWNTIME I see the problem: there's a time on the date also. Your first sample data didn't contain this time, it was just a date (as a string) so I was doing...
SUBSTR('01-JAN-1970', 4)
...to reduce the day date to a month date ('JAN-1970') and this was intended to align with the stuff going on in the other table ( to_date() with a format of 'non-yyyy' )
Now we know that there is a time in there too, of course it won't align because...
SUBSTR('01-JAN-1970 12:34', 4)
...produces 'JAN-1970 12:34' and this will then not match to anything from the other table (which will be just 'JAN-1970' without the time), so the left join means that nulls will be output
The solution is to change the SUBSTR call so it cuts 8 characters, starting at position 4:
SUBSTR(start_time, 4, 8)
This will remove the day and the time, leaving just the month-year that we need. You'll need to make the change in two places in the query above..
Apologies for the delay on replying to this. However, that is working perfectly Caius, thanks very much! So to be complete, had to change your above code to:
SUBSTR(START_TIME, 4, 8) as down_month,
and
GROUP BY SUBSTR(START_TIME, 4, 8)

Pending Monthly SQL Counts

The below query returns accurate info, I just haven't had any luck trying to make this:
1) More dynamic so I'm not repeating the same line of code every month
2) Formatted differently, so just 2 columns of month + year are needed to view pending counts by field1 + field2
Example code (basically, sum when (OPEN date is before/on the last day of the month) and (CLOSE date comes after the month OR it's still opened)
SELECT
SUM(CAST(case when OPENDATE <= '2014-11-30 23:59:59'
and ((CLOSED >= '2014-12-01')
or (CLOSED is null)) then '1' else '0' end as int)) Nov14
,SUM(CAST(case when OPENDATE <= '2014-12-31 23:59:59'
and ((CLOSED >= '2015-01-01')
or (CLOSED is null)) then '1' else '0' end as int)) Dec14
,SUM(CAST(case when OPENDATE <= '2015-01-30 23:59:59'
and ((CLOSED >= '2015-02-01')
or (CLOSED is null)) then '1' else '0' end as int)) Jan15
,FIELD1,FIELD2
FROM T
GROUP BY FIELD1,FIELD2
Results:
FIELD1 FIELD2 NOV14 DEC14 JAN15
A A 2 5 7
A B 6 8 4
C A 5 6 5
…
Instead of:
COUNT FIELD1 FIELD2 MO YR
14 A A 12 2014
18 A B 12 2014
16 C A 1 2015
...
Is there a way to get this in one shot? Sorry if this is a repeat topic, I've looked at some boards and they've helped me get closing counts.. but using a range between two date fields, I haven't had any luck.
Thanks in advance
One way to do it is to use a table of numbers or calendar table.
In the code below the table Numbers has a column Number, which contains integer numbers starting from 1. There are many ways to generate such table.
You can do it on the fly, or have the actual table. I personally have such table in the database with 100,000 rows.
The first CROSS APPLY effectively creates a column CurrentMonth, so that I don't have to repeat the call to DATEADD many times later.
Second CROSS APPLY is your query that you want to run for each month. It can be as complicated as needed, it can return more than one row if needed.
-- Start and end dates should be the first day of the month
DECLARE #StartDate date = '20141201';
DECLARE #EndDate date = '20150201';
SELECT
CurrentMonth
,FIELD1
,FIELD2
,Counts
FROM
Numbers
CROSS APPLY
(
SELECT DATEADD(month, Numbers.Number-1, #StartDate) AS CurrentMonth
) AS CA_Month
CROSS APPLY
(
SELECT
FIELD1
,FIELD2
,COUNT(*) AS Counts
FROM T
WHERE
OPENDATE < CurrentMonth
AND (CLOSED >= CurrentMonth OR CLOSED IS NULL)
GROUP BY
FIELD1
,FIELD2
) AS CA
WHERE
Numbers.Number < DATEDIFF(month, #StartDate, #EndDate) + 1
;
If you provide a table with sample data and expected output, I could verify that the query produces correct results.
The solution is written in SQL Server 2008.
Like this:
SELECT
FIELD1,FIELD2,datepart(month, OPENDATE), datepart(year, OPENDATE), sum(1)
FROM T
GROUP BY FIELD1,FIELD2, datepart(month, OPENDATE), datepart(year, OPENDATE)
But this of course is just based on OPENDATE, if you need to have the same thing calculated into several months, that's going to be more difficult, and you'll probably need a calendar "table" that you'll have to cross apply with this data.

How can i get weekly (from day to day) of one month in sql server

I have data as below:
Docdate Qty Amount
-----------------------------------
2014/08/01(Friday) 5 100
2014/08/03(Sunday) 5 100
2014/08/04(Monday) 8 100
2014/08/10(Sunday) 8 100
2014/08/11(Monday) 8 100
2014/08/17(Sunday) 8 100
2014/08/18(Monday) 8 100
2014/08/24(Sunday) 8 100
2014/08/25(Monday) 8 100
2014/08/31(Sunday) 8 100
I want to general a stored procedure with below template:
Item QtyWeek1(1-3) QtyWeek2(4-10) QtyWeek3(11-17) QtyWeek4(18-24) QtyWeek5(25-31)
A 10 16 16 16 16
B - - - - -
Is it possible to do that in stored procedure?
Is it possible to do that in Crystal Report? If yes, what stored procedure columns should be displayed?
Thanks
Try this solution, if you column Docdate DATETIME type
SELECT item, SUM(CASE WHEN DATEPART(wk, Docdate) BETWEEN 1 AND 3 THEN Qty ELSE 0 END) AS qtyWeek1,
SUM(CASE WHEN DATEPART(wk, Docdate) BETWEEN 4 AND 10 THEN Qty ELSE 0 END) AS qtyWeek2,
SUM(CASE WHEN DATEPART(wk, Docdate) BETWEEN 11 AND 17 THEN Qty ELSE 0 END) AS qtyWeek3,
SUM(CASE WHEN DATEPART(wk, Docdate) BETWEEN 18 AND 24 THEN Qty ELSE 0 END) AS qtyWeek4,
SUM(CASE WHEN DATEPART(wk, Docdate) BETWEEN 25 AND 31 THEN Qty ELSE 0 END) AS qtyWeek5
FROM YouTable
GROUP BY item
It is not easy at all. I suppose you have one more column name item and you would like to have always five weeks, and so I had to consider an extra column YYYYMM to contains the month of the weeks belong.
Then the query would be:
select ITEM, MONTH_YEAR, [1] WEEK1, [2] WEEK2, [3] WEEK3, [4] WEEK4, [5] WEEK5
from (
select
ITEM,
YEAR(DATE1)*100+MONTH(DATE1) MONTH_YEAR,
1 + WEEK - WEEK0 WEEK,
sum(Qty) Qty
from Table1 t1
cross apply (SELECT cast(Replace(LEFT(DocDate,10),'/','-') as date)) N(DATE1)
cross apply (SELECT DATEPART(WEEK, DATEADD(DAY, -1, DATE1))) T(WEEK)
cross apply (SELECT DATEPART(WEEK, DateAdd(day, -1, DateAdd(month, MONTH(DATE1) - 1, DateAdd(Year, YEAR(DATE1)-1900, 0))))) U(WEEK0)
group by WEEK, YEAR(DATE1)*100+MONTH(DATE1), WEEK0, ITEM
) A
pivot (sum(QTY) FOR WEEK in ([1],[2],[3],[4],[5])) B
Glossary:
DATE1 represents the conversion of DocDate column in date type
WEEK represents the number of the week
WEEK0 represents the number of the first week of the month
SQL Fiddle
Since you have crystal reports DO NOT do the crosstab in the database. Do it in crystal report.
If you look at anyones regular advice on this it is: do the pivot in the reporting tool
General solution is:
Add a single column to your existing recordset called, say weeknumber,
Map your existing DocDate to the week number (you'll need to explain the rules, i.e. when does a week start, what is week 1 etc., then I can suggest some code)
In crystal reports create a crosstab report which pivots on the new column weeknumber.
That way if you have 2 or 20 columns, crystal will take care of it, rather than hard coding in a database object.

Calculate items, loop by month, adding month each time through

I have a table of tickets. I am trying to calculate how many tickets were "open" at each month end over the course of the current year. As well, I am pushing this to a bar chart and I am needing out put this into an array through LINQ.
My SQL query to get my calculation is:
SELECT
(SELECT COUNT(*) FROM tblMaintenanceTicket t WHERE (CreateDate < DATEADD(MM, 1, '01/01/2012')))
-
(SELECT COUNT(*) FROM tblMaintenanceTicket t WHERE (CloseDate < DATEADD(MM, 1, '01/01/2012'))) AS 'Open #Month End'
My logic is the following: Count all tickets open between first and end of the month. Subtract that count from the tickets closed before the end of the month.
UPDATED:
I have updated my query with the comments below and it is not working with errors in the GROUP, but I am not truly understanding the logic I guess, my lack of skill in SQL is to blame.
I have added a SQL Fiddle example to show you my query: http://sqlfiddle.com/#!3/c9b638/1
Desired output:
-----------
| Jan | 3 |
-----------
| Feb | 4 |
-----------
| Mar | 0 |
-----------
Your SQL has several erros . . . are grouping by CreateDate but you don't have it as a column from the subqueries. And, you don't have a column alias on the count(*).
I think this is what you are trying to do:
select DATENAME(MONTH,CreateDate), DATEPART(YEAR,CreateDate),
(sum(case when CreateDate < DATEADD(MM, 1, '01/01/2012') then 1 else 0 end) -
sum(case when CloseDate < DATEADD(MM, 1, '01/01/2012') then 1 else 0 end)
)
from tblMaintenanceTicket
group by DATENAME(MONTH,CreateDate), DATEPART(YEAR,CreateDate)
Your comment seems to elucidate what you want clearer than your question (the explanation in the question is a bit buried). What you need is a driver table of months and then join this to your table. Something like:
select mons.yr, mons.mon, count(*) as OpenTickets
from (select month(CreateDate) as mon, year(CreateDate) as yr,
cast(min(CreateDate) as date) as MonthStart,
cast(max(CreateDate) as date) as monthEnd
from tblMaintenanceTicket
group by month(CreateDate), year(CreateDate)
) mons left outer join
tblMaintenanceTicket mt
on mt.CreateDate <= mons.MonthEnd and
(mt.CloseDate > mons.MonthEnd or mt.CloseDate is null)
group by mons.yr, mons.mon
I am assuming records are created on every day. This is a convenience so I don't have to think about getting the first and last day of each month using other SQL functions.
If your query is returning what you need, then simply use DATENAME(MONTH, yourDate) to retrieve the month and group by Month,Year:
SELECT SUM(*), DATENAME(MONTH,yourDate), DATEPART(YEAR,yourDate)
FROM
(
your actual query here
)
GROUP BY DATENAME(MONTH,yourDate), DATEPART(YEAR,yourDate)