sql missing rows when grouped by DAY, MONTH, YEAR - tsql

If I select from a table group by the month, day, year,
it only returns rows with records and leaves out combinations without any records, making it appear at a glance that every day or month has activity, you have to look at the date column actively for gaps. How can I get a row for every day/month/year, even when no data is present, in T-SQL?

Create a calendar table and outer join on that table

My developer got back to me with this code, underscores converted to dashes because StackOverflow was mangling underscores -- no numbers table required. Our example is complicated a bit by a join to another table, but maybe the code example will help someone someday.
declare #career-fair-id int
select #career-fair-id = 125
create table #data ([date] datetime null, [cumulative] int null)
declare #event-date datetime, #current-process-date datetime, #day-count int
select #event-date = (select careerfairdate from tbl-career-fair where careerfairid = #career-fair-id)
select #current-process-date = dateadd(day, -90, #event-date)
while #event-date <> #current-process-date
begin
select #current-process-date = dateadd(day, 1, #current-process-date)
select #day-count = (select count(*) from tbl-career-fair-junction where attendanceregister <= #current-process-date and careerfairid = #career-fair-id)
if #current-process-date <= getdate()
insert into #data ([date], [cumulative]) values(#current-process-date, #day-count)
end
select * from #data
drop table #data

Look into using a numbers table. While it can be hackish, it's the best method I've come by to quickly query missing data, or show all dates, or anything where you want to examine values within a range, regardless of whether all values in that range are used.

Building on what SQLMenace said, you can use a CROSS JOIN to quickly populate the table or efficiently create it in memory.
http://www.sitepoint.com/forums/showthread.php?t=562806

The task calls for a complete set of dates to be left-joined onto your data, such as
DECLARE #StartInt int
DECLARE #Increment int
DECLARE #Iterations int
SET #StartInt = 0
SET #Increment = 1
SET #Iterations = 365
SELECT
tCompleteDateSet.[Date]
,AggregatedMeasure = SUM(ISNULL(t.Data, 0))
FROM
(
SELECT
[Date] = dateadd(dd,GeneratedInt, #StartDate)
FROM
[dbo].[tvfUtilGenerateIntegerList] (
#StartInt,
,#Increment,
,#Iterations
)
) tCompleteDateSet
LEFT JOIN tblData t
ON (t.[Date] = tCompleteDateSet.[Date])
GROUP BY
tCompleteDateSet.[Date]
where the table-valued function tvfUtilGenerateIntegerList is defined as
-- Example Inputs
-- DECLARE #StartInt int
-- DECLARE #Increment int
-- DECLARE #Iterations int
-- SET #StartInt = 56200
-- SET #Increment = 1
-- SET #Iterations = 400
-- DECLARE #tblResults TABLE
-- (
-- IterationId int identity(1,1),
-- GeneratedInt int
-- )
-- =============================================
-- Author: 6eorge Jetson
-- Create date: 11/22/3333
-- Description: Generates and returns the desired list of integers as a table
-- =============================================
CREATE FUNCTION [dbo].[tvfUtilGenerateIntegerList]
(
#StartInt int,
#Increment int,
#Iterations int
)
RETURNS
#tblResults TABLE
(
IterationId int identity(1,1),
GeneratedInt int
)
AS
BEGIN
DECLARE #counter int
SET #counter= 0
WHILE (#counter < #Iterations)
BEGIN
INSERT #tblResults(GeneratedInt) VALUES(#StartInt + #counter*#Increment)
SET #counter = #counter + 1
END
RETURN
END
--Debug
--SELECT * FROM #tblResults

Related

Adding Parameter causing issues-No data displayed

Hi all I have a stored procedure with two parameters #Startdate and #Enddate. When i execute the procedure i get data.
Now i added a parameter and it has list of values. So i added a split function and added in the WHERE clause. Now after making the changes when i execute my SP i do not get any data. I tried commenting out the 3rd Parameter from the WHERE clause and now i see the data again. Not sure what is happening. Any advice is greatly appreciated.
I have tried different split functions and Charindex(','+cast(tableid as varchar(8000))+',', #Ids) > 0 and nothing has worked.
Thanks
NOTE: The concatenation and splitting of parameter values is a poor design for performance reasons and, most importantly, very susceptible to SQL injection attacks. Please research some alternatives. If you must proceed down this path...
There are a great many split functions out there, but I used this one here to illustrate a possible solution.
CREATE FUNCTION [dbo].[fnSplitString]
(
#string NVARCHAR(MAX),
#delimiter CHAR(1)
)
RETURNS #output TABLE(splitdata NVARCHAR(MAX)
)
BEGIN
DECLARE #start INT, #end INT
SELECT #start = 1, #end = CHARINDEX(#delimiter, #string)
WHILE #start < LEN(#string) + 1 BEGIN
IF #end = 0
SET #end = LEN(#string) + 1
INSERT INTO #output (splitdata)
VALUES(SUBSTRING(#string, #start, #end - #start))
SET #start = #end + 1
SET #end = CHARINDEX(#delimiter, #string, #start)
END
RETURN
END
GO
It's unclear, from your question, if you need to filter your results based on an int, varchar or various other data types available, but here are two options (and probably the most common).
DECLARE #TableOfData TABLE
(
ID_INT INT,
ID_VAR VARCHAR(100),
START_DATE DATETIME,
END_DATE DATETIME
)
DECLARE #StartDate DATETIME
DECLARE #EndDate DATETIME
DECLARE #Ids VARCHAR(1000)
DECLARE #Delimiter VARCHAR(1)
SET #Delimiter = ','
SET #StartDate = GETDATE()
SET #EndDate = DATEADD(HH, 1, GETDATE())
SET #Ids = '1,2,4'
--Create some test data
INSERT INTO #TableOfData
SELECT 1, '1', GETDATE(), DATEADD(MI, 1, GETDATE()) --In our window of expected results (date + id)
UNION SELECT 2, '2', GETDATE(), DATEADD(D, 1, GETDATE()) --NOT in our window of expected results b/c of date
UNION SELECT 3, '3', GETDATE(), DATEADD(MI,2, GETDATE()) --NOT in our expected results (id)
UNION SELECT 4, '4', GETDATE(), DATEADD(MI,4, GETDATE()) --In our window of expected results (date + id)
--If querying by string, expect 2 results
SELECT TD.*
FROM #TableOfData TD
INNER JOIN dbo.fnSplitString(#Ids, #Delimiter) SS
ON TD.ID_VAR = SS.splitdata
WHERE START_DATE >= #StartDate
AND END_DATE <= #EndDate
--If querying by int, expect 2 results
SELECT TD.*
FROM #TableOfData TD
INNER JOIN dbo.fnSplitString(#Ids, #Delimiter) SS
ON TD.ID_INT = CONVERT(int, SS.splitdata)
WHERE START_DATE >= #StartDate
AND END_DATE <= #EndDate
You cannot use a parameter with a list directly in your query filter. Try storing that separated data into a table variable or temp table and call that in your query or use dynamic SQL to write your query if you don't want to use table variable or temp tables.

How to fill column basing on two other columns

I have table LessonHour with empty Number column.
TABLE [dbo].[LessonHour]
(
[Id] [uniqueidentifier] NOT NULL,
[StartTime] [time](7) NOT NULL,
[EndTime] [time](7) NOT NULL,
[SchoolId] [uniqueidentifier] NOT NULL,
[Number] [int] NULL
)
How can I fill up the table with Number for each LessonHour so it would be the number of lesson hour in order?
The LessonHours cannot cross each other. Every school has defined its own lesson hour schema.
Example set of data
http://pastebin.com/efWCtUbv
What'd I do:
Order by SchoolId and StartTime
Use Cursor to insert into row next number, starting from 1 every time the SchoolId changes.
Edit:
Solution with cursor
select -- top 20
LH.[Id],
[StartTime],
[EndTime],
[SchoolId]
into #LH
from
LessonHour as LH
join RowStatus as RS on LH.RowStatusId = RS.Id
where
RS.IsActive = 1
select * from #LH order by SchoolId, StartTime
declare #id uniqueidentifier, #st time(7), #et time(7), #sid uniqueidentifier
declare #prev_sid uniqueidentifier = NEWID()
declare #i int = 1
declare cur scroll cursor for
select * from #LH order by SchoolId, StartTime
open cur;
fetch next from cur into #id, #st, #et, #sid
while ##FETCH_STATUS = 0
begin
--print #prev_sid
if #sid <> #prev_sid
begin
set #i = 1
end
update LessonHour set Number = #i where Id = #id
print #i
set #i = #i + 1
set #prev_sid = #sid
fetch next from cur into #id, #st, #et, #sid
end;
close cur;
deallocate cur;
drop table #LH
This is the result I was after http://pastebin.com/iZ8cnA6w
Merging the information from the StackOverflow questions SQL Update with row_number() and
How do I use ROW_NUMBER()?:
with cte as (
select number, ROW_NUMBER() OVER(partition by schoolid order by starttime asc) as r from lessonhour
)
update cte
set number = r
Would this work
CREATE TABLE [dbo].[LessonHour]
(
[Id] [uniqueidentifier] NOT NULL,
[StartTime] [time](7) NOT NULL,
[EndTime] [time](7) NOT NULL,
[SchoolId] [uniqueidentifier] NOT NULL,
[Number] AS DATEDIFF(hour,[StartTime],[EndTime])
)
So if I understand the question correctly you require a calculated column which takes in the values of [StartTime] and [EndTime] and returns the number of hours for that lesson as an int. The above table definition should do the trick.

Procedure to count days between two dates

Seems redundant, but I am trying to count rows in a temporary created table inside a procedure.
(Note: table is most likely coded wrong) The #rows variable is needed to be counted and should output the exact amount of days in a year from the #StartDate variable. (this is including leap years).
The table created should also INSERT date VALUES for each day for exactly a year after the #StartDate variable.
TEMPORARY TABLE required in this assignment. Sorry people, but that is what the teacher is asking for.
FYI: I'm a novice, and help would be greatly appreciated :)
CREATE PROCEDURE usp_DateLookup
#StartDate DATE,
#Rows INT OUTPUT
AS
DECLARE #countIndex INT
DECLARE #yearDate DATE
DECLARE #todaysDate DATE
SET #yearDate = DATEADD(YEAR, 1,#StartDate)
SET #todaysDate = GETDATE()
BEGIN
CREATE TABLE #DateLookup
(
DateID INT IDENTITY (1,1),
DateDescription DATE
)
WHILE (SELECT DATEDIFF(DD,#StartDate, #yearDate) FROM #DateLookup) <= 366
BEGIN
SET #countIndex = #countIndex +1
INSERT #DateLookup (DateDescription)
VALUES (DAY(#todaysDate)+ #countIndex)
END
SET #Rows = (SELECT COUNT(DateDescription) FROM #DateLookup)
END
GO
DECLARE #StartDate DATE
DECLARE #Rows INT
SET #StartDate = '2012-05-06'
EXEC usp_DateLookup #StartDate, #Rows OUTPUT
PRINT CONVERT(NVARCHAR,#Rows)
I am not sure why you need #DateLookup table? You can get the number of days in a year as follows (Better to use as a function)
CREATE PROCEDURE usp_DateLookup
#StartDate DATE,
#Rows INT OUTPUT
AS
--DECLARE #StartDate DATE = '2012-05-06'
select #Rows = datediff(day,#StartDate,dateadd(year,1,#StartDate))
END
GO;
As per your comment ( You need the temp table for the assignment), you could do it without a loop as below; SQL-DEMO
CREATE PROCEDURE usp_DateLookup
#StartDate DATE,
#Rows INT OUTPUT
AS
--DECLARE #StartDate DATE = '2012-05-06'
DECLARE #Rows INT = DATEDIFF(day,#StartDate,dateadd(year,1,#StartDate))
CREATE TABLE #DateLookup
(
DateID INT IDENTITY (1,1),
DateDescription DATE
)
;with Digits as (
select Digit
from (
values (0), (1), (2), (3), (4), (5),
(6), (7), (8), (9)) as t(Digit)),
Numbers as (
select u.Digit + t.Digit*10 +h.Digit*100 as Number
from Digits u
cross join Digits t
cross join Digits h
)
insert into #DateLookup
select dateadd(day,Number+1,#StartDate) from Numbers where Number < #Rows
order by Number
END
GO;
Also you need a LOOP? Your INSERT fails since you trying to enter a number (DAY(#todaysDate)+ #countIndex) into DATE field
INSERT #DateLookup (DateDescription)
VALUES ('this-should-be-a-valid-date')

SQL query to count number of records within a given timeframe, filling in the gaps

I am trying to count the number of records that fall between a given time period, generally 15 minutes, but I'd like to have this interval a variable. My table has a datetime column and I need to get a count of the number of records for every 15 minute interval between two dates and when there aren't any records for the 15-minute window, I need that time period with a zero. I've tried using CTE in different combinations and couldn't get it to work. I can generate the date series using a CTE, but haven't been able to get the actual data in the result. I have a working solution using a stored procedure with a WHILE loop. I was hoping to avoid this if possible in favor or a more elegant solution.
Here is my working loop using a temporary table:
declare #record_count int = 0
declare #end_date_per_query datetime
create table #output (
SessionIdTime datetime,
CallCount int)
while #date_from < #date_to
begin
set #end_date_per_query = DATEADD(minute, #interval, #date_from)
select #record_count = COUNT(*) from tbl WHERE SessionIdTime between #date_from and #end_date_per_query
insert into #output values (#date_from, #record_count)
set #date_from = #end_date_per_query
end
select * from #output order by sessionIdTime
drop table #output
Hopefully someone can help with a more elegant solution. Any help is appreciated.
A CTE works just fine:
-- Parameters.
declare #Start as DateTime = '20120901'
declare #End as DateTime = '20120902'
declare #Interval as Time = '01:00:00.00' -- One hour. Change to 15 minutes.
select #Start as [Start], #End as [End], #Interval as [Interval]
-- Sample data.
declare #Sessions as Table ( SessionId Int Identity, SessionStart DateTime )
insert into #Sessions ( SessionStart ) values
( '20120831 12:15:07' ), ( '20120831 21:51:18' ),
( '20120901 12:15:07' ), ( '20120901 21:51:18' ),
( '20120902 12:15:07' ), ( '20120902 21:51:18' )
select * from #Sessions
-- Summary.
; with SampleWindows as (
select #Start as WindowStart, #Start + #Interval as WindowEnd
union all
select SW.WindowStart + #Interval, SW.WindowEnd + #Interval
from SampleWindows as SW
where SW.WindowEnd < #End
)
select SW.WindowStart, Count( S.SessionStart ) as [Sessions]
from SampleWindows as SW left outer join
#Sessions as S on SW.WindowStart <= S.SessionStart and S.SessionStart < SW.WindowEnd
group by SW.WindowStart
Is this what you're looking for?
-- setup
DECLARE #interval INT, #start DATETIME
SELECT #interval = 15, #start = '1/1/2000 0:00:00'
DECLARE #t TABLE (id INT NOT NULL IDENTITY(1,1) PRIMARY KEY, d DATETIME)
INSERT INTO #t (d) VALUES
(DATEADD(mi, #interval * 0.00, #start)) -- in
,(DATEADD(mi, #interval * 0.75, #start)) -- in
,(DATEADD(mi, #interval * 1.50, #start)) -- out
-- query
DECLARE #result INT
SELECT #result = COUNT(*) FROM #t
WHERE d BETWEEN #start AND DATEADD(mi, #interval, #start)
-- result
PRINT #result

TSQL Hack needed for getting a filter for data

A UI (before the report shows) shows a look up (Combo) that has
(ID = 0).All Organization Units
(ID =4).HR
(ID = 5).DEV
I need to:
Be able to show data of (4) + (5) if
(0) is selected.
Only (4) OR (5) if either HR or DEV is selected.
Lookup combo code (Selected Feeds the parameter in the below query.)
Select 0 AS ID,'All Org' AS Name from DP_ORG_OrganizationUnit
where DP_ORG_OrganizationUnit.Code IN {AccessData}
Union
SELECT
DP_ORG_OrganizationUnit.ID,
DP_ORG_OrganizationUnit.Name
FROM DP_ORG_OrganizationUnit where DP_ORG_OrganizationUnit.Code IN ('HR','DEV')
Report data row query
SET CONCAT_NULL_YIELDS_NULL OFF
DECLARE #EmpID as int;
DECLARE #OrganizationUnit as int;
DECLARE #StartDate as datetime;
DECLARE #EndDate as datetime;
SET #EmpID = ?;
SET #StartDate = ?;
SET #EndDate = ?;
SET #OrganizationUnit = ?;
SELECT
Employee.Code,
Employee.Name1+' '+Employee.Name2+' '+Employee.Name3+' '+Employee.Name4+' '+Employee.Name5 AS FullName,
Employee.OrganizationUnit,
ContractType.Name,
EmployeeContract.StartDate,
EmployeeContract.EndDate
FROM Employee INNER JOIN (ContractType INNER JOIN EmployeeContract
ON ContractType.ID = EmployeeContract.ContractType)
ON Employee.ID = EmployeeContract.Employee
WHERE (Employee.ID = #EmpID OR #EmpID=0)
AND
(Employee.OrganizationUnit = #OrganizationUnit OR #OrganizationUnit=0)
AND NOT((EndDate < #StartDate or StartDate > #EndDate));
Any way I can achieve it from the looks of it? 0=0 would show all the data from other
departments too..
Anybody :-o?
First off, your lookup combo code could be tightened up a bit:
-- the FROM clause was superfluous
SELECT 0 AS ID,'All Org' AS Name
UNION ALL
-- the two-part identifiers were superfluous (only one table)
SELECT ID, Name
FROM DP_ORG_OrganizationUnit
WHERE Code IN ('HR','DEV')
For the report query, the simplest form would be:
WHERE
((#OrganizationUnit > 0 AND Employee.OrganizationUnit = #OrganizationUnit) OR
(#OrganizationUnit = 0 AND Employee.OrganizationUnit IN (4,5)))
something like this should work
Where (Employee.OrganizationUnit = case when #OrganizationUnit=0 then 4 else #OrganizationUnit end OR case when #OrganizationUnit=0 then 5 else #OrganizationUnit end)
Try this, which should use indexes on your query...
DECALRE #FilterValues (FilterValue int not null primary key)
IF #Param=0
BEGIN
INSERT INTO #FilterValues VALUES (4)
INSERT INTO #FilterValues VALUES (5)
END
ELSE ID #PAram IS NOT NULL
BEGIN
INSERT INTO #FilterValues VALUES (#Param)
END
SELECT
....
FROM YourTable y
INNER JOIN #FilterValues f ON y.Value=f.Value
WHERE .....
KM's version will work, but this query does not need a temp table...
SELECT *
FROM Employee
WHERE (
#OrganizationUnit = 0
OR
(
#OrganizationUnit <> 0
AND
Employee.OrganizationUnit = #OrganizationUnit
)
)
How about
WHERE (Employee.ID = #EmpID OR #EmpID=0)
AND
(Employee.OrganizationUnit BETWEEN ISNULL(NULLIF(#OrganizationUnit,0),0) AND ISNULL(NULLIF(#OrganizationUnit,0),99))
AND NOT((EndDate < #StartDate or StartDate > #EndDate));