Pass date values (dynamically changing) to input parameters of stored procedure - sql

If you see the below stored procedure it has 2019-01-01,2019-12-31 as input.
What I would like to do is, I need to pass 2019-01-01,2019-01-31 and then 2019-02-01,2019-02-28 and same till December.
Can anyone please help in how to pass date time by month dynamically.
Exec storedprcedurename '2019-01-01','2019-12-31'

You can make use of while loop and date add to execute it. Also, you may need to set a max date so that it does not go into infinite loop.
Declare #datemin date = '2019-01-01', #maxdate date = '2019-12-31'
While #datemin <= #maxdate
Begin
Declare #dateend date = dateadd(day, -1, dateadd(month,1,#datemin ))
Exec storedprcedurename #datemin , #dateend
set #datemin = dateadd(month, 1, #datemin)
end

I would probably do it with dynamic SQL. Normally, I wouldn't recommend dynamic SQL without using the #params parameter to sp_executesql, but in this case, you're controlling the input values yourself.
So first we need to define a dataset with the dates to use to pass to the stored procedure. It's enough to get the first day of each month in the result set - we can use EOMONTH-function to get the last day of each month.
I'm doing it using a CTE below.
--Define startdate
DECLARE #startdate date='2019-01-01';
--Number of months
DECLARE #numMonths int=12;
--variable to store some dynamic SQL in
DECLARE #dynSQL nvarchar(max)=N'';
--CTE to get the dates
WITH tens AS(
SELECT n FROM(VALUES(1),(1),(1),(1),(1),(1),(1),(1),(1),(1))t(n)
), thousands AS(
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL))-1 as n
FROM tens t0
CROSS JOIN tens t1
CROSS JOIN tens t2
CROSS JOIN tens t3
), dates AS(
SELECT TOP(#numMonths) DATEADD(month,n,#startDate) as startDate
FROM thousands
ORDER BY n
)
--And now we construct the dynamic SQL statement to call the proc based on the CTE dates
SELECT
#dynSQL = #dynSQL +
N'exec storedprocedurename ''' +
cast(startDate as nvarchar(50)) +
N''',''' +
cast(EOMONTH(startDate) as nvarchar(50)) +
N''';
'
FROM dates ORDER BY startDate;
--Make sure the dynamic SQL string looks correct Before executing
PRINT #dynSQL;
--When you are sure you got it right, comment the PRINT statement and uncomment below
--exec sp_executesql #dynSQL;
You could do it with a cursor, and the code would probably be a bit easier to read. But I'm brought up to shy away from cursors...

Related

SQL performance based on the WHERE clause

I am trying to reuse a query, instead of creating several scenarios. Basically I am getting a COUNT from a big table.
The question is: am I going to lose performance on the long run if I am using
SELECT COUNT(id) FROM [Documents] WHERE UploadDate > '1900/1/1' AND UploadDate < GETDATE()
Instead of a simple
SELECT COUNT(id) FROM [Documents]
Basically the 2 queries would return the same thing, because I want to return ALL the records in this case. But under some other circumstances, the date pair will use different parameters.
If you want the count based on some parameters, I do not think there will a significant difference, if you have an index defined on UploadDate. If you are using a stored procedure, it might look like the following:
CREATE PROCEDURE dbo.GetCount
(
#FromDate DATETIME2 = '1900-01-01',
#ToDate DATETIME2 = '9999-12-31',
-- other filters may come here
)
AS
BEGIN
SELECT COUNT(id)
FROM [Documents]
WHERE UploadDate > #FromDate AND #ToDate < #ToDate
END
GO
-- get all
EXEC dbo.GetCount
-- get from date
EXEC dbo.GetCount #FromDate = '2015-03-01'
-- to date
EXEC dbo.GetCount #ToDate = '2016-03-01'
-- both
EXEC dbo.GetCount #FromDate = '2015-03-01', #ToDate = '2016-03-01'
You can also try to obtain the minimum SQL to run, by creating a dynamic SQL:
CREATE PROCEDURE dbo.GetCount
(
#FromDate DATETIME2 = NULL,
#ToDate DATETIME2 = NULL
-- other filters may come here
)
AS
BEGIN
DECLARE #SQL NVARCHAR(4000) = N'
SELECT COUNT(id) FROM [Documents]
WHERE 1 = 1
'
IF (#FromDate IS NOT NULL) #SQL = #SQL + ' UploadDate > #FromDate'
IF (#ToDate IS NOT NULL) #SQL = #SQL + ' UploadDate > #ToDate'
EXECUTE sp_executesql #SQL, '#FromDate DATETIME2, #ToDate DATETIME2', #FromDate, #ToDate
END
This offers the flexibility of open interval to the left and/or right.
If you are dynamically generating queries from the application layer (e.g. LINQ2SQL), the best option is generate it based on your parameters. This will lead to the shortest and fastest queries:
var query = DbContext.Documents;
if (fromDate.HasValue) query = query.Where(UploadDate > fromDate.Value);
if (toDate.HasValue) query = query.Where(UploadDate < toDate.Value);
int count = query.Count();
The last two solutions also allow to easily add new filters.
If you want to count everything and then things that meet a condition:
SELECT COUNT(id) as total_cnt,
SUM(CASE WHEN UploadDate > '1900-01-01' AND UploadDate < GETDATE()
THEN 1 ELSE 0
END) as specific_cnt
FROM [Documents] d;
If you're worried about varying performance based on the range values you specify in your query, because of parameter sniffing, then you can add OPTION (RECOMPILE) at the end of your query.
SELECT COUNT(id)
FROM [Documents]
WHERE UploadDate > '1900/1/1' AND UploadDate < GETDATE()
OPTION (RECOMIPLE)
This will make sure that your newly generated plan is the best possible that the Optimizer could come up with, at that moment, on its own, for that date range, if you decide to check starting from '2011/1/1' for example.

Generate random records for datetime columns by stored procedure in SQL

I want to generate 5 random records from a field which is a datetime column and contains several records of (OrderDate) for a given date range using stored procedure for the table named Orders
CREATE PROCEDURE test
#StartDate DATETIME = NULL,
#EndDate DATETIME = NULL,
AS
BEGIN
SELECT OrderDate = DATEADD(......)
FROM Orders
END
May I get some help!
A while loop works ok for this purpose, especially if you're concerned with limiting your randomness to a bounded date range.
The downside is that potentially many insert queries get executed vs. a single insert for a recursive CTE as in the other answer.
create procedure dbo.spGenDates2
#MinDate datetime,
#MaxDate datetime,
#RecordCount int = 5
as
SET NOCOUNT ON;
DECLARE #Range int, #DayOffset int, #Cnt int
SET #Range = DATEDIFF(dd, #MinDate, #MaxDate)
SET #Cnt = 1
WHILE #Cnt <= #RecordCount
BEGIN
SET #DayOffset = RAND() * (#Range + 1)
INSERT INTO _test (Dt) VALUES(DATEADD(dd, #DayOffset, #MinDate))
SET #Cnt = #Cnt + 1
END
Based on your syntax I'm assuming you're using SQL Server...
Note that you cannot reliably use the sql random number generator function RAND() within the context of a single query because it does not get reseeded per row so you end up receiving the same, single random number for each row result. Instead, an approach using NEWID() converted into a numeric does the trick when generating random values within the execution of a single query.
Here's a procedure that will give you n number of sample dates in the near past.
create procedure dbo.spGenDates
#MaxDate datetime,
#RecordCount int = 5
as
WITH dates as (
SELECT DATEADD(MILLISECOND, ABS(CHECKSUM(NEWID())) * -1, #MaxDate) D,
1 as Cnt
UNION ALL
SELECT DATEADD(MILLISECOND, ABS(CHECKSUM(NEWID())) * -1, #MaxDate) D,
x.Cnt + 1 as Cnt
FROM dates x
WHERE x.Cnt < #RecordCount
)
INSERT INTO _test (Dt)
SELECT D
FROM dates
The wording of the question has been clarified (see comments on another answer) to be a desire to SELECT 5 random sample dates within a bounded range from a table.
A query like this will yield the desired result.
SELECT TOP (5) OrderDate
FROM Orders
WHERE OrderDate >= #StartDate
AND OrderDate < #EndDate
ORDER BY NEWID()

dynamic columns for dates

Columns [today], [30days ago], [60days ago], [90 days ago]
I have a table the will provide me with the sales number fro today and 30days ago, 60 days ago and 90 days ago,
But the issue I have is to figure out what the actual date was 60 days ago.
I wanted to update my script not show me 60days ago but to show me the actual date 60days ago. I want to make my columns dynamic so i get the actual date 90days ago.
Can anyone help me here?
Please remember this is a long scripts and 10 columns and I want to change each column to show me the actual date and not the 90 days ago.
You can use GETDATE() and DATEADD() to get these values:
SELECT CONVERT(DATE, GETDATE()) AS Today,
CONVERT(DATE, DATEADD(DAY, -30, GETDATE())) Minus30
Converting to DATE simply takes off the time portion.
Produces:
Today Minus30
2015-06-08 2015-05-09
To use these values you can assign a few variables and set the values to be used later in your code:
DECLARE #today DATE, #Minus30 DATE
SELECT #today = CONVERT(DATE, GETDATE()),
#Minus30 = CONVERT(DATE, DATEADD(DAY, -30, GETDATE()))
PRINT #today
PRINT #Minus30
To use the values as column names, you'll need to use some dynamic SQL:
DECLARE #today NVARCHAR(15), #Minus30 NVARCHAR(15)
SELECT #today = CONVERT(NVARCHAR(15), CONVERT(DATE, GETDATE())),
#Minus30 = CONVERT(NVARCHAR(15), CONVERT(DATE, DATEADD(DAY, -30, GETDATE())))
EXEC ('SELECT ''30'' AS ''' + #today + ''', ''123'' AS ''' + #Minus30 + '''')
Produces:
2015-06-08 2015-05-09
30 123
This is best handled in the client code that retrieves the sql results. Using c# as an example:
string sql = String.Format(
"SELECT [today] as [{0}], [30days ago] as [{1}], [60days ago] as [{2}], [90 days ago] as [{3}] FROM [MyTable]",
DateTime.Today.ToShortDateString(),
DateTime.Today.AddDays(-30).ToShortDateString(),
DateTime.Today.AddDays(-60).ToShortDateString(),
DateTime.Today.AddDays(-90).ToShortDateString());
If you really need to, you could put the same string logic into procedural sql, and save that to a stored procedure. But don't do that.
I haven't actually tried doing this, but you can try to use an sp_rename statement using the variable names that #Tanner suggested:
DECLARE #Minus30 DATE
SELECT #Minus30 = CONVERT(DATE, DATEADD(DAY, -30, GETDATE()))
EXEC sp_rename 'Table.[30days ago]', #Minus30, 'COLUMN'
If you are trying to build a list of dynamic date columns, you could do this using dynamic pivot method something like this:
Creating a Test Table
create table pvtTbl(DateColumns varchar(50),sales money,employeeid int);
insert into pvtTbl
select cast(getdate() as date) datecol,1249.00 as sales,123 employeeid
UNION
select cast(dateadd(day,-30,getdate()) as date) datecol,15615.00 as sales,456 employeeid
UNION
select cast(dateadd(day,-60,getdate()) as date) datecol,125583.00 sales,356 employeeid
UNION
select cast(dateadd(day,-90,getdate()) as date) datecol,25571.00 sales,859 employeeid
This part, I am just creating as an example to build the pivot table. But, you could actually create this step as an insert statement to a temp table in your stored proc, so that it will be updated each time to dynamically build your date columns using this query:
Query to build a list of needed date values
select cast(getdate() as date) datecol
UNION
select cast(dateadd(day,-30,getdate()) as date) datecol
UNION
select cast(dateadd(day,-60,getdate()) as date) datecol
UNION
select cast(dateadd(day,-90,getdate()) as date) datecol
After that, all you will need to do is build a dynamic pivot using STUFF function and XML path to first build a comma separated list of date values and then pivot it dynamically to output it as your columns like this:
Dynamic Pivot to build date columns
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(c.DateColumns)
FROM pvtTbl c
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT employeeid,' + #cols + ' from
(
select sales,employeeid,DateColumns
from pvtTbl
) x
pivot
(
sum(sales)
for DateColumns in (' + #cols + ')
) p '
execute(#query)
Anyway, this would be a good idea if you build this as a stored procedure. Let me know if you need any more help.
Demo

Converting YYYYMM format to YYYY-MM-DD in SQL Server

I need to perform a query on a large table that has a datetime column that is indexed.
We need to query the data for a range from a month (at a minimum) to multiple months.
This query would be executed from Cognos TM1 and the input would be a period like YYYYMM. My question is - how to convert the YYYYMM input to a format that can be used to query that table (with the index being used).
Let's say if the input is
From Date: '201312'
To Date: '201312'
then, we need convert the same to 'between 01-12-2013 and 31-12-2013' in the query
Since we need this to be hooked up in Cognos TM1, so would not be able to write a procedure or declare variables (TM1 somehow does not like it).
Thanks in advance for your reply.
I would do something like this:
create procedure dbo.getDataForMonth
#yyyymm char(6) = null
as
--
-- use the current year/month if the year or month is invalid was omitted
--
set #yyyymm = case coalesce(#yyyymm,'')
when '' then convert(char(6),current_timestamp,112)
else #yyyymm
end
--
-- this should throw an exception if the date is invalid
--
declare #dtFrom date = convert(date,#yyyymm+'01') -- 1st of specified month
declare #dtThru date = dateadd(month,1,#dtFrom) -- 1st of next month
--
-- your Big Ugly Query Here
--
select *
from dbo.some_table t
where t.date_of_record >= #dtFrom
and t.date_of_record < #dtThru
--
-- That's about all there is to it.
--
return 0
go
Suppose you are getting this value of YYYYMM in a varchar variable #datefrom .
You can do something like
DECLARE #DateFrom VARCHAR(6) = '201201';
-- Append '01' to any passed string and it will get all
-- records starting from that month in that year
DECLARE #Date VARCHAR(8) = #DateFrom + '01'
-- in your query do something like
SELECT * FROM TableName WHERE DateTimeColumn >= #Date
Passing Datetime in a ansi-standard format i.e YYYYMMDD is a sargable expression and allows sql server to take advantage of indexes defined on that datetime column.
here is an article written by Rob Farley about SARGable functions in SQL Server.
Try this...
declare #startdate date,#endate date
select #startdate =convert(date,left('201312',4)+'-'+right('201312',2)+'-01')
select #endate= DATEADD(d, -1, DATEADD(m, DATEDIFF(m, 0, #startdate) + 1, 0))
select convert(date,#startdate,102) startdate,convert(date,#endate,102) endate
In the datasource of your TM1 Turbo Integrator process, you can use parameters in the SQL query. E.g. you could take this SQL query:
SELECT Col1, Col2
FROM Table
WHERE Col1 = 'Green'
AND Col2 < 30
In TM1, to parameterise this, you would create two parameters e.g. P1 and P2 and put them in the query:
SELECT Col1, Col2
FROM Table
WHERE Col1 = '?P1?'
AND Col2 < ?P2?

Generate a range of date and compare with another range of date and pivot it

I have a situation where I have to generate a range of date (say start date and end date).
Now I want to fill the records of specific dates on those generated dates.
;WITH DateRange AS
(
SELECT #start_date DateValue
UNION ALL
SELECT DateValue + 1
FROM DateRange
WHERE DateValue + 1 <= #end_date
)
If #start_date = '2013-01-01' and #end_date= '2013-01-05'
The dates generated will be '01/01/2013,01/02/2013,01/03/2013,01/04/2013,01/05/2013'
Now I want to pivot it to fill the specific data for specific date.
How can I achieve this?
Edit:
I have 3 columns namely Duration, StartDate and EndDate. Now I want to generate these dates dynamically and fill the values of Duration accourdingly.
For eg: if Duration = 6 and #start_date = '2013-01-01' and #end_date= '2013-01-05'
I want to generate a list of dates with duration equal to 6.
Update 2:
If you haven't understand my question here's a full detail for it.
I have to pass #startDate and #EndDate as parameters
I have fields Id, StartDate and EndDate, duration and other fields in my table
Now when I pass the parameters the query should generate the range of dates from #startDate and #EndDate
As the dates are generated it must check the dates StartDate and EndDate in the table and set the value of Duration to those fields from StartDate to EndDate.
Required output:
If #startDate='2013-01-01' and #endDate='2013-01-07' then the output must be like this:
**
Note: the Id for both the rows in first image is same.
**
Update 3:
[updated 2013-12-18 11:22 UTC]
[updated 2013-12-18 14:19 UTC]
[updated 2013-12-19 11:11 UTC]
as i did not know the name of the table you are using, i created one named yeahyeah. you should replace that with the name of your own table obviously. i have inserted the values you mentioned (fromdate todate duration as shown in your question).
the procedure should look like this. i called it pivotit.
create procedure pivotit (#start_date date, #end_date date)
as
/*
step 1:
identify all the data you need to fill the pivoted table.
this is achieved by using your daterange generator and joining
it with your table. the result is kept in a dynamic table
*/
declare #acols table (i int, d date, l int);
;WITH DateRange AS
(
SELECT #start_date DateValue
UNION ALL
SELECT dateadd(dd,1,DateValue)
FROM DateRange
WHERE dateadd(dd,1,DateValue) <= #end_date
)
insert into #acols (i, d, l)
select id, DateValue, Duration from DateRange
join yeahyeah on ( DateRange.DateValue >= yeahyeah.FromDate
and DateRange.DateValue <= yeahyeah.ToDate);
/*
step 2:
for pivot you need all the columns that will be adressed. so
we create a string with all the distinct dates from the dynamic
table. these will then be put into a format like [1], [2], [3], ...
to create a dynamic select.
*/
declare #p varchar(max) = '';
declare #s varchar(max);
select #p = #p + ', [' + CONVERT(varchar,d) + ']' from (select distinct d from #acols) a;
set #p = SUBSTRING(#p,3,len(#p)-2);
/*
step 3:
create the dynamic select.
alas neither the dynamic table nor the parameters are available from
inside the dynamic sql. i might try to use bind variables, but was
not 100% sure if that would work here. so put in the declares for start_
and end_date from the procedure parameters and build up the dynamic table
once more.
then i use #p for the pivoted select. this is done by selecting the column
for the rows (id) and all the values from the pivot as columns (#p).
details on the whole pivot thing are here:
http://technet.microsoft.com/en-us/library/ms177410(v=sql.105).aspx
basically you tell sql-server (explicitly) what you want as the columns (#p),
what rows you want (id) and how to aggregate the values in the intersections
(sum(l))
[update 2013-12-19]
i added a routine that makes a cartesian product that has all the combination
of dates and ids in #acols, finds the ones that are missing in #acols and inserts
them with duration 0. then the pivoted cells are complete and display the zero
instead of NULL. you cannot use isnull or coalesce here since not the value
of the cell is NULL, the intersection simply did not exist.
*/
set #s = '
declare #start_date date = convert(date,'''+CONVERT(varchar,#start_date,112)+''',112);
declare #end_date date = convert(date,'''+CONVERT(varchar,#end_date,112)+''',112);
declare #acols table (i int, d date, l int);
;WITH DateRange AS
(
SELECT #start_date DateValue
UNION ALL
SELECT dateadd(dd,1,DateValue)
FROM DateRange
WHERE dateadd(dd,1,DateValue) <= #end_date
)
insert into #acols (i, d, l)
select id, DateValue, Duration from DateRange
join yeahyeah on ( DateRange.DateValue >= yeahyeah.FromDate
and DateRange.DateValue <= yeahyeah.ToDate);
with cart as
(
select distinct
a.i
, b.d
from #acols a
join #acols b
on 1=1
)
insert into #acols (i, d, l)
select cart.i
, cart.d
, 0
from cart
left outer join
#acols a
on cart.i = a.i
and cart.d = a.d
where a.i is null;
select id, '+#p+'
from
( select convert(varchar,d) as d
, l
, i as id
from #acols ) as sourcetable
pivot (
sum(l)
for d in ('+#p+')
) as pivottable';
execute(#s);
after you created the procedure you can do this:
exec pivotit #start_date = '2013-01-01', #end_date = '2013-01-31'
which will then yield:
You can write a query as below:
declare #start_date datetime,#end_date datetime ;
set #start_date ='2013-01-01' ;
set #end_date = '2013-01-05' ;
DECLARE #columns NVARCHAR(MAX),#sql NVARCHAR(MAX);
SET #columns = N'';
WITH DateRange AS
(
SELECT #start_date DateValue
UNION ALL
SELECT DateValue + 1
FROM DateRange
WHERE DateValue + 1 <= #end_date
)
--Get column names for entire pivoting
SELECT #columns += N', ' + QUOTENAME(SpreadCol)
FROM (select distinct convert(varchar(10),DateValue,101) as SpreadCol
from DateRange
) AS T;
PRINT #columns;
and then use #columns as spreading columns in dynamic Pivot query.
Use the SQL Server Reporting Services wizard.
In the wizard, set the query to the Left Join of the date range table with the data table.
As report type choose a matrix.
Assign the dates to the columns area, the row ids of your data table to the row area, the value column of your data table to the value area.
Choose a style
Generate the report.
If the report needs to be generated regularly, you can publish it on the reporting server. Users can export to a preferred format. It'll look nice too ;)
You can graph the results too if you like.
See http://technet.microsoft.com/en-us/library/ms160326(v=sql.105).aspx for more info on the report wizard.
See SQL query to select dates between two dates for alternative ways to generate the date range table.