Procedure to count days between two dates - tsql

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')

Related

How to ignore a record in query to avoid a conversion error in JOIN?

I have a table T1 with alphanumeric codes (varchar column) where always the three first digits will be numeric like this:
001ABCD
100EFGH
541XYZZ
OTHER
NOTE: Please notice that I have ONE exception record which is all alpha (OTHER).
Also I have a table T2 with 3-digit numbers (int column) like this:
001
200
300
So when I run the following query:
SELECT * from T1
LEFT JOIN T2
ON SUBSTRING(T1.code1,1,3) = T2.code2
WHERE T1.code1 <> 'OTHER'
It is causing me the error:
Conversion failed when converting the varchar value 'OTH' to data type int.
I know the issue but not how to fix it (it's trying to compare 'OTH' with the T2.code2 INT column).
I tried to use WHERE but it didn't work at all.
I cannot get rid of the 'OTHER' record and convert the T2.code2 column from int to varchar is not an option. Any idea?
Here are 3 different ways you can solve this. I would recommended the persisted computed column since it only has to be calculated on insert and update, not every time you run the read query.
DROP TABLE IF EXISTS #T2;
DROP TABLE IF EXISTS #T1;
CREATE TABLE #T1
(
Code1 VARCHAR(10)
,Code2Computed AS TRY_CONVERT(INT,SUBSTRING(Code1,1,3)) PERSISTED
)
;
CREATE TABLE #T2
(
Code2 INT
)
;
INSERT INTO #T1
(Code1)
VALUES
('001ABCD')
,('100EFGH')
,('541XYZZ')
,('OTHER')
;
INSERT INTO #T2
(Code2)
VALUES
(001)
,(100)
,(200)
,(300)
,(541)
;
--Convert INT to 3 digit code
SELECT *
FROM #T1
LEFT JOIN #T2
ON SUBSTRING(#T1.Code1,1,3) = RIGHT(CONCAT('000',#T2.Code2),3)
;
--Convert 3 digit code to INT
SELECT *
FROM #T1
LEFT JOIN #T2
ON TRY_CONVERT(INT,SUBSTRING(#T1.Code1,1,3)) = #T2.Code2
;
--Use computed column
SELECT *
FROM #T1
LEFT JOIN #T2
ON #T1.Code2Computed = #T2.Code2
;

T-sql Concatenate variables for use in an Insert Into while loop

I'm trying to create a SQL statement that allows me to save time when creating a series of CTEs or temp tables that are all the same and only increment in certain places. I could do this with VBA, but I can't figure out if this is possible with SQL
I'm trying to run the following code
DECLARE #N as INT
DECLARE #POINTS as TABLE(ID int Not Null,n varchar(3) Not Null)
DECLARE #TABLENAME varchar(6)
Set #INC = 1
Set #N = 5
--WHILE #INC <= #N
--BEGIN
Declare #N1 INT
Declare #N2 INT
Declare #N3 INT
Declare #N4 INT
Declare #N5 INT
SET #N1=25
SET #N2=50
SET #N3=100
SET #N4=250
SET #N5=500
--END
WHILE #INC <= #N
BEGIN
INSERT INTO #POINTS(ID, n)
VALUES (#INC, CONCAT('#N',#INC))
SET #INC = #INC + 1
END
Select * from #POINTS
These are the results of the code after it runs:
ID n
1 #N1
2 #N2
3 #N3
4 #N4
5 #N5
Is it possible to have the values I set for each #Nx variable be inserted to the n column instead of the concatenated character values? If so, how? I haven't been able to find any results on someone trying to do this.
I can get around this problem by using outside tables, but I want to know if this is possible.
If you only have a small number of values values, you might as well write a single insert statement that will insert all 5 rows to the table:
INSERT INTO #POINTS(ID, n) VALUES
(1, '5'),
(2, '25'),
(3, '50'),
(4, '100'),
(5, '250'),
(6, '500');
If you have too many values to write like this, you can use a cte to generate an inline tally table, and use insert...select from that tally table:
DECLARE #Points as TABLE
(
ID int Not Null,
n varchar(3) Not Null
);
WITH E10(N) AS
(
SELECT 1
FROM (VALUES(0), (1), (2), (3), (4), (5), (6), (7), (8), (9))V(N)
), Tally(N) AS
(
-- Since all the numbers are multiples of 5,
-- there's no point of populating the tally
-- with numbers that aren't multiples of 5....
SELECT ROW_NUMBER() OVER(ORDER BY ##SPID) * 5
FROM E10 As Ten
CROSS JOIN E10 As Hundred
--Need more? Add more cross joins
-- each will multiply the number of rows by 10:
--CROSS JOIN E10 As Thousand
--CROSS JOIN E10 As [Ten Thousand] -- and so on
)
INSERT INTO #Points(ID, n)
SELECT ROW_NUMBER() OVER(ORDER BY N), CAST(N as varchar(3))
FROM Tally
WHERE N IN(5, 25, 50, 100, 250, 500);
SELECT *
FROM #Points;
Results:
ID n
1 5
2 25
3 50
4 100
5 250
6 500
Of course, if you already have a tally table, you don't need the cte...

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.

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

sql missing rows when grouped by DAY, MONTH, YEAR

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