DAY shift calculations - sql-server

Input table
Start time End Time
8/12/14 17:00 8/14/14 12:00
I need to show the output as below
Date 11:00 to 23:00 23:00 to 11:00
8/12/14 6 1
8/13/14 12 12
8/14/14 1 11
I know you all will say "What did you try ?"
But the answer "I Can't think of where to start"

Using recursive CTE:
SQL Fiddle
MS SQL Server 2012 Schema Setup:
Query 1:
DECLARE #StartDate DateTime = '2014-08-12 17:00'
DECLARE #EndDate DateTime = '2014-08-14 12:00'
;WITH CTE
AS
(
SELECT #StartDate AS [Date],
CAST(#StartDate As time) AS [Time]
UNION ALL
SELECT DATEADD(HH, 1, [Date]) As [Date],
CAST(DATEADD(HH, 1, [Date]) AS Time) AS [Time]
FROM CTE
WHERE DATEADD(HH, 1, [Date]) < #EndDate
)
SELECT CAST([Date] AS Date) [Date],
SUM(CASE WHEN [Time] BETWEEN '11:00' AND '22:59'
THEN 1 ELSE 0 END) as [11:00 to 23:00],
SUM(CASE WHEN [Time] BETWEEN '00:00' AND '10:59'
THEN 1
WHEN [Time] Between '23:00' and '23:59' THEN 1
ELSE 0 END) as [23:00 to 11:00]
FROM CTE
GROUP BY CAST([Date] AS Date)
ORDER BY [Date]
Results:
| DATE | 11:00 TO 23:00 | 23:00 TO 11:00 |
|------------|----------------|----------------|
| 2014-08-12 | 6 | 1 |
| 2014-08-13 | 12 | 12 |
| 2014-08-14 | 1 | 11 |

The following shows one way to do with using a Tally tables (aka "table of numbers" table.)
Key assumptions I made:
Start and End times are both known
Start and End times are for precise hours (e.g. 1:00, 2:00, and not 13:23 or 00:00.00.007). (This is why I used the smalldatetime datatype)
End time is always greater than Start time
I went further and assumed that the data is stored in a table for multiple entities--that is, you'd ultimately want to process multiple items in one query. If you only ever want to do this for one item at a time, (a) the query below can be readily chopped down, and (b) it'd probably be easier to do in C# or whatever the calling language is.
Setting up testing data:
-- DROP TABLE Testing
CREATE TABLE Testing
(
EntryId int not null
,StartTime smalldatetime not null
,EndTime smalldatetime not null
)
INSERT Testing values
(1, 'Aug 12, 2014 17:00', 'Aug 14, 2014 12:00') -- Original problem
,(2, 'Aug 11, 2014 00:00', 'Aug 11, 2014 23:00') -- 23 hours
,(3, 'Aug 11, 2014 00:00', 'Aug 12, 2014 00:00') -- 24 hour shift
,(4, 'Aug 11, 2014 12:00', 'Aug 12, 2014 12:00') -- Noon to Noon
,(11, 'Aug 22, 2014 4:00', 'Aug 22, 2014 5:00') -- One-hour problem cases
,(12, 'Aug 22, 2014 10:00', 'Aug 22, 2014 11:00') -- One-hour problem cases
,(13, 'Aug 22, 2014 11:00', 'Aug 22, 2014 12:00') -- One-hour problem cases
,(14, 'Aug 22, 2014 12:00', 'Aug 22, 2014 13:00') -- One-hour problem cases
,(21, 'Aug 23, 2014 18:00', 'Aug 23, 2014 19:00') -- One-hour problem cases
,(22, 'Aug 23, 2014 22:00', 'Aug 23, 2014 23:00') -- One-hour problem cases
,(23, 'Aug 23, 2014 23:00', 'Aug 24, 2014 00:00') -- One-hour problem cases
,(24, 'Aug 24, 2014 00:00', 'Aug 24, 2014 1:00') -- One-hour problem cases
My routine:
DECLARE
#Earliest smalldatetime
,#Latest smalldatetime
-- This could be thrown in as a first CTE, but doing so would make the overall query that much less comprehensible.
SELECT
#Earliest = min(StartTime)
,#Latest = max(EndTime)
from Testing
--where <filtering criteria, if you're not parsing the whole table)
;WITH
Pass0 as (select 1 as C union all select 1), --2 rows
Pass1 as (select 1 as C from Pass0 as A, Pass0 as B),--4 rows
Pass2 as (select 1 as C from Pass1 as A, Pass1 as B),--16 rows
Pass3 as (select 1 as C from Pass2 as A, Pass2 as B),--256 rows
Pass4 as (select 1 as C from Pass3 as A, Pass3 as B),--65536 rows
Tally as (select row_number() over(order by C) as Number from Pass4),
DateRange as (select
dateadd(hh, ta.Number, #Earliest) ShiftHour
from Tally ta
where dateadd(hh, ta.Number, #Earliest) <= #Latest)
SELECT
te.EntryId
,cast(dateadd(hh, -1, dr.Shifthour) as date) [Date]
,sum(case when datepart(hh, dateadd(hh, -1, dr.Shifthour)) between 11 and 22 then 1 else 0 end) [11:00 to 23:00]
,sum(case when datepart(hh, dateadd(hh, -1, dr.Shifthour)) between 11 and 22 then 0 else 1 end) [23:00 to 11:00]
from Testing te
inner join DateRange dr
on dr.ShiftHour > te.StartTime
and dr.ShiftHour <= te.Endtime
group by
te.EntryId
,cast(dateadd(hh, -1, dr.Shifthour) as date)
order by
te.EntryId
,cast(dateadd(hh, -1, dr.Shifthour) as date)
Plug this in to show the results without the grouping, invaluable in figuring out what's going on in there:
;WITH
Pass0 as (select 1 as C union all select 1), --2 rows
Pass1 as (select 1 as C from Pass0 as A, Pass0 as B),--4 rows
Pass2 as (select 1 as C from Pass1 as A, Pass1 as B),--16 rows
Pass3 as (select 1 as C from Pass2 as A, Pass2 as B),--256 rows
Pass4 as (select 1 as C from Pass3 as A, Pass3 as B),--65536 rows
Tally as (select row_number() over(order by C) as Number from Pass4),
DateRange as (select
dateadd(hh, ta.Number, #Earliest) ShiftHour
from Tally ta
where dateadd(hh, ta.Number, #Earliest) <= #Latest)
SELECT
te.EntryId
,dateadd(hh, -1, dr.Shifthour)
,cast(dateadd(hh, -1, dr.Shifthour) as date) [Date]
,datepart(hh, dateadd(hh, -1, dr.Shifthour))
from Testing te
inner join DateRange dr
on dr.ShiftHour > te.StartTime
and dr.ShiftHour <= te.Endtime
order by
te.EntryId
,cast(dateadd(hh, -1, dr.Shifthour) as date)
,dateadd(hh, -1, dr.Shifthour)
The hard parts were:
Getting the 24th hour (Aug 23, 00:00) into the previous day (Aug 24). That's why I shift everything back one day [edit] one HOUR
< and > logic when dealing with multiple entries
Reconciling with the fact that the results for a "One day Noon to Noon" sample just looks goofy.
Yeah, this may be over-engineering, but some times you just have to give it a try. I couldn't say how well it would perform on large datasets, and if your first and last dates are more than four years apart you'll need to add "Pass5" when building the tally table.

DECLARE #StartTime DATETIME='08/01/2013 00:00:00'
,#EndTime DATETIME='08/03/2013 23:00:00'
DECLARE #StartDate DATE,
#Starthour INT,
#Startmin INT,
#EndDate DATE,
#Endhour INT,
#Endmin INT
SELECT #StartDate=CONVERT(DATE,#StartTime,101),#Starthour=DATEPART(HOUR,#StartTime),#Startmin=DATEPART(MINUTE,#StartTime)
SELECT #EndDate=CONVERT(DATE,#EndTime,101),#Endhour=DATEPART(HOUR,#EndTime),#Endmin=DATEPART(MINUTE,#EndTime)
DECLARE #T TABLE (StartTime DATETIME, EndTime DATETIME)
INSERT #T VALUES (DATEADD(MINUTE,-#Startmin,#StartTime), DATEADD(MINUTE,-#Endmin,#EndTime))
DECLARE #Final1 TABLE (ShiftDate DATETIME,[11:00 to 23:00] INT,[23:00 to 11:00] INT)
DECLARE #Final2 TABLE (ShiftDate DATETIME,[11:00 to 23:00] INT,[23:00 to 11:00] INT)
DECLARE #Final3 TABLE (ShiftDate DATETIME,[11:00 to 23:00] INT,[23:00 to 11:00] INT)
;WITH CTE AS
(
SELECT StartTime AS dt, EndTime,StartTime
FROM #T
UNION ALL
SELECT DATEADD(MINUTE, 60, dt) AS dt, EndTime,DATEADD(MINUTE, 60,StartTime) AS StartTime
FROM CTE
WHERE dt < DATEADD(HOUR, -1, EndTime)
)
INSERT INTO #Final1([11:00 to 23:00],[23:00 to 11:00],ShiftDate)
SELECT
CASE
WHEN CAST(dt AS TIME) >= '11:00' AND CAST(dt AS TIME) < '23:00' THEN 1
ELSE 0
END AS '11:00 to 23:00',
CASE
WHEN CAST(dt AS TIME) >= '11:00' AND CAST(dt AS TIME) < '23:00' THEN 0
ELSE 1
END AS '23:00 to 11:00'
,StartTime
FROM CTE
INSERT INTO #Final2([11:00 to 23:00],[23:00 to 11:00],ShiftDate)
SELECT SUM([11:00 to 23:00])*60 AS [11:00 to 23:00]
,SUM([23:00 to 11:00])*60 AS [23:00 to 11:00]
,CONVERT(DATE,DATEADD(MINUTE,-660,ShiftDate)) AS ShiftDate
FROM #Final1
GROUP BY CONVERT(DATE,DATEADD(MINUTE,-660,ShiftDate))
INSERT INTO #Final3([11:00 to 23:00],[23:00 to 11:00],ShiftDate)
SELECT CASE WHEN ShiftDate=#StartDate AND #Starthour BETWEEN 11 AND 22 THEN [11:00 to 23:00]-#Startmin ELSE [11:00 to 23:00] END AS [11:00 to 23:00],
CASE WHEN ShiftDate=#StartDate AND #Starthour BETWEEN 23 AND 10 THEN [23:00 to 11:00]-#Startmin ELSE [23:00 to 11:00] END AS [23:00 to 11:00],
ShiftDate
FROM #Final2
SELECT ShiftDate,
CONVERT(VARCHAR(10),
(CASE WHEN ShiftDate=#EndDate AND #Endhour BETWEEN 11 AND 22
THEN [11:00 to 23:00]+#Endmin
ELSE [11:00 to 23:00] END)) AS [11:00 to 23:00],
CONVERT(VARCHAR(10),
(CASE WHEN ShiftDate=#EndDate AND #Endhour BETWEEN 23 AND 10
THEN [23:00 to 11:00]+#Endmin
ELSE [23:00 to 11:00] END)) AS [23:00 to 11:00]
FROM #Final3

Related

How to count the number of days in a given month and in a given year in MSSQL

EmployeeID RecordID DateRecord
1 1 2/19/2013 12:00:00 AM
1 2 2/21/2013 12:00:00 AM
1 3 2/23/2013 12:00:00 AM
1 4 2/27/2013 12:00:00 AM
1 5 3/3/2013 12:00:00 AM
2 11 3/10/2013 12:00:00 AM
2 12 3/14/2013 12:00:00 AM
1 14 3/16/2013 12:00:00 AM
How can I count the number of days?
Example in February 2013 which has "19, 21, 23, 27" that should be count to "4" days .. ??
I found this method ..
SELECT DATEPART(yy, Daterecord),
DATEPART(mm, Daterecord),
DATEPART(dd, Daterecord),
COUNT(*)
FROM Records
GROUP BY DATEPART(yy, Daterecord),
DATEPART(mm, Daterecord),
DATEPART(dd, Daterecord)
and resulted to ..
2013 2 19 1
2013 2 21 1
2013 2 23 1
2013 2 27 1
2013 3 3 1
2013 3 10 1
2013 3 14 1
2013 3 16 1
it just get the specific dates but didm't count the total number of days in each month .. help me .. pls
I have change few names hopr you won't mind
WITH Emp_CTE AS (
SELECT EmployeeID ,DATEPART(yy, Daterecord) AS years,
DATEPART(mm, Daterecord) AS months
-- DATEPART(dd, Daterecord) AS days
FROM testTrial
)
SELECT COUNT(months) AS noOfMonths ,* FROM Emp_CTE GROUP BY months,EmployeeID,years
SqlFiddle
Let you try this:-
1: Find the number of days in whatever month we're currently in
DECLARE #dt datetime
SET #dt = getdate()
SELECT #dt AS [DateTime],
DAY(DATEADD(mm, DATEDIFF(mm, -1, #dt), -1)) AS [Days in Month]Solution
2: Find the number of days in a given month-year combo
DECLARE #y int, #m int
SET #y = 2012
SET #m = 2
SELECT #y AS [Year],
#m AS [Month],
DATEDIFF(DAY,
DATEADD(DAY, 0, DATEADD(m, ((#y - 1900) * 12) + #m - 1, 0)),
DATEADD(DAY, 0, DATEADD(m, ((#y - 1900) * 12) + #m, 0))
) AS [Days in Month]
If your table is called Employee then this will do the trick:
select convert(varchar, DateRecord, 112)/ 100, count(*)
from Employee
group by convert(varchar, DateRecord, 112)/ 100
Your initial query was almost right, just needed to remove the DATEPART(dd, Daterecord) from the grouping and it would work. Add in a HAVING clause to find the records from the month of February:
SELECT
DATEPART(yy, Daterecord),
DATEPART(mm, Daterecord),
COUNT(1)
FROM
Records
GROUP BY
DATEPART(yy, Daterecord),
DATEPART(mm, Daterecord)
HAVING
DATEPART(yy, eCreationTime) = 2013
AND DATEPART(mm, Daterecord) = 2
there is no 'yearmonth' in the suggested code ??
try this perhaps
select
datename(month,daterecord) as [Month]
, year(DateRecord) as [Year]
, count(distinct DateRecord ) as day_count
, count(distinct dateadd(day, datediff(day,0, DateRecord ), 0)) as daytime_count
from your_table
where ( DateRecord >= '20130201' and DateRecord < '20130301' )
group by
datename(month,daterecord)
, year(DateRecord)
note the column [daytime_count] is only required if the field [DateRecord] has times othe than 12:00 AM (i.e. it "trims off" times so you deal with dates at 12:AM)
Regarding date range selections: many people will feel that using 'between' is the solution however that isn't true and the safest most reliable method is as I shown above. Note that the higher date is 1st March, but we are asking for information that is less than the 1st March, so we don't need to worry about leap years and we don't have to worry about hours and minutes either.
see: What do BETWEEN and the devil have in common?
try this...
declare #date2 nvarchar(max)
set #date2 = (select getdate())
select DateDiff(Day,#date2,DateAdd(month,1,#date2))

SQL Server: How to select all days in a date range even if no data exists for some days

I have an app that needs to show a bar graph for activity over the last 30 days. The graph needs to show all days even if there is no activity for the day.
for example:
DATE COUNT
==================
1/1/2011 5
1/2/2011 3
1/3/2011 0
1/4/2011 4
1/5/2011 0
etc....
I could do post processing after the query to figure out what dates are missing and add them but was wondering if there is an easier way to do it in SQL Server. Thanks much
You can use a recursive CTE to build your list of 30 days, then join that to your data
--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
...
Using 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]
This is assuming everything's a Date; if it's DateTime, you'll have to truncate (with DATEADD(dd, 0, DATEDIFF(dd, 0, [DATE]))).
#Alex K.'s answer is completely correct, but it doesn't work for versions that do not support Recursive common table expressions (like the version I'm working with). In this case the following would do the job.
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;
My scenario was a bit more complex than the OP example, so thought I'd share to help others who have similar issues. I needed to group sales orders by date taken, whereas the orders are stored with datetime.
So in the "days" lookup table I could not really store as a date time with the time being '00:00:00.000' and get any matches. Therefore I stored as a string and I tried to join on the converted value directly.
That did not return any zero rows, and the solution was to do a sub-query returning the date already converted to a string.
Sample code as follows:
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
Sample Results:
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
Either define a static table containing dates or create a temp table \ table variable on the fly to store each date between (and including) the min and max dates in the activity table you're working with.
Use an outer join between the two tables to make sure that each date in your dates table is reflected in the output.
If you use a static dates table you will likely want to limit the date range that is output to only the range needed in the graph.
Without Transact-SQL: MS SQL 2005 - Get a list of all days of a Month:
In my case '20121201' is a predefined value.
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
Recursive CTE works for max 80 years which is good enough:
DECLARE #dStart DATE,
#dEnd DATE
SET #dStart = GETDATE ()
SET #dEnd = DATEADD (YEAR, 80, #dStart)
;WITH CTE AS
(
SELECT #dStart AS dDay
UNION ALL
SELECT DATEADD (DAY, 1, dDay)
FROM CTE
WHERE dDay < #dEnd
)
SELECT * FROM CTE
OPTION (MaxRecursion 32767)
create a numbers table and use it like:
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 DateColumn=#StartDate+Number
WHERE Number>=1 AND Number<=15
GROUP BY #StartDate+Number
OUTPUT:
----------------------- -----------
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)
Maybe something like this:
Create DaysTable countaining the 30 days.
And DataTable containing "day" column and "count" column.
And then left join them.
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
For those with a recursion allergy
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
Try it.
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
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

SQL - 2 Counts in one query

I have 2 queries which return counts of different information in a table:
SELECT Date, COUNT(*) AS Total
FROM Table
WHERE Type = 7 AND Date >= '2010-01-01'
GROUP BY Date
HAVING COUNT(*) > 5000
ORDER BY Date
which returns the totals for all of the 'busy' dates:
Date Total
---------- -----------
2010-01-05 9466
2010-02-02 8747
2010-03-02 9010
2010-04-06 7916
2010-05-05 9342
2010-06-02 8723
2010-07-02 7829
2010-08-03 8411
2010-09-02 7687
2010-10-04 7706
2010-11-02 8567
2010-12-02 7645
and
SELECT Date, COUNT(*) AS Failures
FROM Table
WHERE Type = 7 AND ErrorCode = -2 AND Date >= '2010-01-01'
GROUP BY Date
ORDER BY Date
which returns the total failures (all of which happened on busy dates):
Date Failures
---------- -----------
2010-09-02 29
2010-10-04 16
2010-11-02 8
Is it possible to combine these into a single query to return one result?
E.g.:
Date Total Failures
---------- ----------- -----------
2010-01-05 9466
2010-02-02 8747
2010-03-02 9010
2010-04-06 7916
2010-05-05 9342
2010-06-02 8723
2010-07-02 7829
2010-08-03 8411
2010-09-02 7687 29
2010-10-04 7706 16
2010-11-02 8567 8
2010-12-02 7645
;With baseData As
(
SELECT
Date,
COUNT(*) AS Total,
COUNT(CASE WHEN ErrorCode = -2 THEN 1 END) AS Failures
FROM Table
WHERE Type = 7 AND Date >= '2010-01-01'
GROUP BY Date
)
SELECT
Date,
Total,
Failures,
CAST(Failures AS float)/Total AS Ratio
FROM baseData
WHERE Total > 5000 OR Failures > 0
ORDER BY Date
If you can refactor to the same where clause, this should be possible.
I haven't taken your HAVING(Count()) into consideration
SELECT [Date], COUNT(*) AS Total, SUM(CASE WHEN ErrorCode = -2 THEN 1 ELSE 0 END) AS Failures
FROM [Table]
WHERE [Type] = 7 AND [Date] >= '2010-01-01'
GROUP BY [Date]
ORDER BY [Date]
Edit : Here is some test data
create table [Table]
(
[ErrorCode] int,
[Type] int,
[Date] datetime
)
insert into [table]([Date], [Type], [ErrorCode] )values ('1 Jan 2010', 7, 0)
insert into [table]([Date], [Type], [ErrorCode] )values ('1 Jan 2010', 7, -2)
insert into [table]([Date], [Type], [ErrorCode] )values ('2 Jan 2010', 7, -2)
insert into [table]([Date], [Type], [ErrorCode] )values ('2 Jan 2010', 8, -2)
insert into [table]([Date], [Type], [ErrorCode] )values ('2 Jan 2010', 7, 1)
yes you should be able to do a UNION ALL between the 2 tables

Select Statement to show missing records (Easy Question)

I need some T-SQL that will show missing records.
Here is some sample data:
Emp 1
01/01/2010
02/01/2010
04/01/2010
06/01/2010
Emp 2
02/01/2010
04/01/2010
05/01/2010
etc...
I need to know
Emp 1 is missing
03/01/2010
05/01/2010
Emp 2 is missing
01/01/2010
03/01/2010
06/01/2010
The range to check will start with todays date and go back 6 months.
In this example, lets say today's date is 06/12/2010 so the range is going to be 01/01/2010 thru 06/01/2010.
The day is always going to be the 1st in the data.
Thanks a bunch. :)
Gerhard Weiss
Secretary of Great Lakes Area .NET Users Group
GANG Upcoming Meetings | GANG LinkedIn Group
Try This:
DECLARE #Employees table (DateOf datetime, EmployeeID int)
INSERT #Employees VALUES ('01/01/2010',1)
INSERT #Employees VALUES ('02/01/2010',1)
INSERT #Employees VALUES ('04/01/2010',1)
INSERT #Employees VALUES ('06/01/2010',1)
INSERT #Employees VALUES ('02/01/2010',2)
INSERT #Employees VALUES ('04/01/2010',2)
INSERT #Employees VALUES ('05/01/2010',2)
--I was unsure of the data in the question
--this gives first day of each month for last six months
DECLARE #StartDate datetime
,#EndDate datetime
SELECT #StartDate=DATEADD(month,-6,DATEADD(month,DATEDIFF(month,0,GETDATE()),0) )
,#EndDate=GETDATE()
;with AllDates AS
(
SELECT #StartDate AS DateOf
UNION ALL
SELECT DateAdd(month,1,DateOf)
FROM AllDates
WHERE DateOf<#EndDate
)
SELECT
dt.DateOf,dt.EmployeeID
FROM (SELECT DISTINCT
a.DateOf,e.EmployeeID
FROM AllDates a
CROSS JOIN (SELECT DISTINCT EmployeeID FROM #Employees) e
) dt
LEFT OUTER JOIN #Employees ee ON dt.EmployeeID=ee.EmployeeID AND dt.DateOf=ee.DateOf
WHERE ee.EmployeeID IS NULL
ORDER BY dt.EmployeeID,dt.DateOf
OUTPUT:
DateOf EmployeeID
----------------------- -----------
2009-10-01 00:00:00.000 1
2009-11-01 00:00:00.000 1
2009-12-01 00:00:00.000 1
2010-03-01 00:00:00.000 1
2010-05-01 00:00:00.000 1
2009-10-01 00:00:00.000 2
2009-11-01 00:00:00.000 2
2009-12-01 00:00:00.000 2
2010-01-01 00:00:00.000 2
2010-03-01 00:00:00.000 2
(10 row(s) affected)
this will do every day for last six months, just incorporate this in the above if that is what you want:
DECLARE #StartDate datetime
,#EndDate datetime
SELECT #StartDate=DATEADD(month,-6,GETDATE())
,#EndDate=GETDATE()
;with AllDates AS
(
SELECT #StartDate AS DateOf
UNION ALL
SELECT DateOf+1
FROM AllDates
WHERE DateOf<#EndDate
)
SELECT * FROM AllDates
--OPTION (MAXRECURSION 500) --uncomment and increase if the date range needs more rows
fill a temp table with the date ranges and outer join the temp table to your Emp* table and only return records from your temp table that have null in the corresponding row of the Emp* table
If you're only going back a fixed # of months, you can precalc those "first of month" dates and left join to your employee data:
SELECT d.DT, CASE WHEN e.DT IS NULL THEN 1 ELSE 0 END AS IsMissing
FROM (
SELECT DATEADD(m, DATEDIFF(m, 0, CURRENT_TIMESTAMP), 0) AS DT
UNION
SELECT DATEADD(m, DATEDIFF(m, 0, CURRENT_TIMESTAMP) - 1, 0)
UNION
SELECT DATEADD(m, DATEDIFF(m, 0, CURRENT_TIMESTAMP) - 2, 0)
UNION
SELECT DATEADD(m, DATEDIFF(m, 0, CURRENT_TIMESTAMP) - 3, 0)
UNION
SELECT DATEADD(m, DATEDIFF(m, 0, CURRENT_TIMESTAMP) - 4, 0)
UNION
SELECT DATEADD(m, DATEDIFF(m, 0, CURRENT_TIMESTAMP) - 5, 0)
) AS d
LEFT JOIN EmployeeDates e ON d.DT = e.DT AND e.EmpID = 1

TSQL: How to Split dates and count months in between

I need to split these dates by FY and get the number of months between the dates. I have the FY split answered previously at How to duplicate Rows with new entries link.
Having the following dates:-
ID Start dt End dt
2550 10/1/2010 9/30/2011
2551 8/1/2014 7/31/2015
2552 6/1/2013 5/31/2015
2553 5/10/2012 6/11/2014
I would like the following result set:
ID FY # of Months Start Dt End Dt
2550 2011 12 10/1/2010 9/30/2011
2551 2014 2 8/1/2014 9/30/2014
2551 2015 10 10/1/2014 7/31/2015
2552 2013 4 6/1/2013 9/30/2013
2552 2014 12 10/1/2013 9/30/2014
2552 2015 8 10/1/2014 5/31/2015
2553 2012 5 5/10/2012 9/30/2012
2553 2013 12 10/1/2012 9/30/2013
2553 2014 9 10/1/2013 6/11/2014
An FY is considered from Oct to Sept of the next year.
Thanks in advance!
It was a fun query to build:
declare #t table(id int, start_dt datetime, end_dt datetime)
Insert into #t(id, start_dt, end_dt) Values
(2550, '2010/10/1', '2011/9/30')
, (2551, '2014/8/1', '2015/7/31')
, (2552, '2013/6/1', '2015/5/31')
, (2553, '2012/5/10', '2014/6/11')
, (2554, '2012/5/10', '2012/11/1')
, (2555, '2012/5/10', '2012/8/11')
; with data as(
Select id, start_dt, end_dt, next_y = DATEADD(month, 9, DATEADD(year, DATEDIFF(year, 0, DATEADD(month, 3, start_dt)), 0))
From #t
), split as (
Select id, start_dt
, end_dt = case when next_y > end_dt then end_dt else DATEADD(day, -1, next_y) end
, next_y = DATEADD(year, 1, next_y)
From data as d
Union All
Select s.id
, DATEADD(day, 1, s.end_dt)
, case when DATEADD(day, -1, next_y) < t.end_dt then DATEADD(day, -1, next_y) else t.end_dt end
, next_y = DATEADD(year, 1, s.next_y)
From split as s
Inner Join #t as t on t.id = s.id
Where s.end_dt < t.end_dt
)
Select ID, FY = year(end_dt), '# of Months' = DATEDIFF(month, start_dt, end_dt)+1, 'Start Dt' = start_dt, 'End Dt' = end_dt
From split
Order by id, start_dt, end_dt

Resources