Creating a variable stored procedure to be used in SSRS - variables

I am trying to create a stored procedure that references variable input from SSRS. Here is the code for the complex query. I am using CTE's to make the code more readable.
/****** Object: StoredProcedure [dbo].[adm_AuditHospMonth] Script Date: 11/25/2013 9:39:10 AM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
/*
-- =============================================
-- Author: Scott Schmeling
-- Create date: 11/25/2013
-- Description: Determines the products in which the price was lowered and revenue lost during a set time period.
-- =============================================
*/
Create Procedure dbo.PriceErosion
#StartDate as Date
,#EndDate as Date
,#CurDate as Date
,#Hospital as Int
,#Division as Int
as
/*
Test Data
Declare #StartDate as Date
Declare #EndDate as Date
Declare #Hospital as Int
Declare #Division as Int
DECLARE #curDate Date
SET #curDate = GETDATE()
Set #StartDate = CASE WHEN #StartDate IS NULL THEN DATEADD(dd, -31, Dateadd(dd, -1, #curdate) ) ELSE #StartDate END
Set #EndDate = CASE WHEN #EndDate IS NULL THEN Dateadd(dd, -1, #curdate) ELSE #EndDate END
Set #Hospital = 3;
*/
Begin
-- Sets the Baseline Price Date in the PriceChangeHistory Table.
With PC1
as
(Select
HospitalMasterID
,TxnCode
,UserInfoMasterID
,Active
,min(TxnDateTime) as StartingDate
From
PriceChangeHistory
Where
TxnDateTime Between #StartDate and #EndDate
Group By
HospitalMasterID, TxnCode, UserInfoMasterID, Active)
-- Gets the Baseline Price for the period from the PriceChangeHistory Table
,PC
as
(Select
PC1.HospitalMasterID
,PC1.TxnCode
,PC1.UserInfoMasterID
,PC1.Active
,Cast (PC1.StartingDate as Date) as StartingDate
,PC2.OldPrice as StartingPrice
,PC2.NewPrice
,PC2.TxnSubType
From
PC1
Inner Join
PriceChangeHistory as PC2
On
PC1.HospitalMasterID = PC2.HospitalMasterID
and
PC1.TxnCode = PC2.TxnCode
and
PC1.StartingDate = PC2.TxnDateTime
Where
PC2.OldPrice > PC2.NewPrice)
--MedicalHistory Information
,MH
as
(Select
HospitalMasterID
,PatientID
,TxnDate
,TxnCode
,Description
,ListAmount
,ExtendedAmount
,TxnType
,Quantity
,(Case
When Quantity <> '1' Then (ListAmount/Quantity)
Else ListAmount
End) as UnitPrice
From
MedicalHistory
Where
TxnDate Between #StartDate and #EndDate
and
_IsServOrITem = 1)
-- Determines the Revenue lost per each sale, also reduces the results to only those items where the Price was lowered not raised.
,RL
as
(Select
PC.HospitalMasterID
,MH.PatientID
,PC.TxnCode
,PC.TxnSubType
,MH.Description
,PC.UserInfoMasterID as ChangedByUserID
,MH.TxnDate
,PC.StartingPrice
,Cast (MH.UnitPrice as Money) as UnitPrice
,Cast ((StartingPrice - UnitPrice) as Money) as RevenueLost
From
PC
Left OUter Join
MH
on
PC.HospitalMasterID = MH.HospitalMasterID
and
PC.TxnCode = MH.TxnCode
Where
PC.StartingPrice > MH.UnitPrice)
--- Determine the name of the tech changing the prices.
,UI
as
(Select
HospitalMasterID
,UserInfoMasterID
,Name
From
UserInfo)
--- Get the Division and Hospital Name for each Hospital.
,HODI
as
(Select
DI.DivisionID
,DI.DivisionName
,HO.HospMastID
,HO.HospCode
,HO.HospName
From
ref_Hospital as HO
inner Join
ref_Division as DI
on
HO.DivisionID = DI.DivisionID)
,HI
as
(Select
HODI.DivisionID
,HODI.DivisionName
,RL.HospitalMasterID
,HODI.HospCode
,HODI.HospName
,RL.PatientID
,RL.TxnCode
,RL.TxnSubType
,RL.Description
,RL.ChangedByUserID
,RL.TxnDate
,RL.StartingPrice
,RL.UnitPrice
,RL.RevenueLost
From
RL
Left Outer Join
HODI
ON
RL.HospitalMasterID = HODI.HospMastID
Where
RL.HospitalMasterID = #Hospital
and
RL.DivisionID = #Division
and
TXNDate Between #StartDate and #EndDate)
Select
*
From
HI
End
Every time I try to run this stored procedure through SSRS, I get an error stating that the variables are not defined. I am sure there is something I am doing incorrectly in the SP mode because the query works fine with the Test Data and by itself.
Any suggestions would be greatly appreciated.
Thanks,
Scott

SSRS should be able to detect what parameters your sproc needs and add them in automatically. It unfortunatly isn't clever enough to work out datatypes, so you will have to manually select these.
Select a new dataset and choose sproc. Make. sure that you select the fully qualified name. Then click the refresh fields button.
If you check the Parameters tab of the dataset you should see that your parameters have been added, if not then you can manually add them. Remember that parameter names are case-sensitive.
Finally, you'll have to go into each parameter properties and manually select the correct datatype as SSRS will default it to text. Just double click on the parameter in the report data on the left of the screen.
NB. You don't seem to use CurDate anywhere in your sproc so you may as well remove it.

Related

What I'm trying to do is get a running stock level for every day for the past year for each item [duplicate]

This question already has answers here:
Calculate a Running Total in SQL Server
(15 answers)
Closed 1 year ago.
I have the below SQL query that works but it's very slow. it takes about 1 min to run the query. This would be made into a stored procedure. which is not the problem. but that store procedure would be called for every item of which there are about 600 items. The estimated run time probably would end up taking about 10 hours. Does anyone have any suggestions of a better way of doing it?
What I'm trying to do is get a running stock level for every day for the past year for each item.
If you need any more information. Please let me know.
DECLARE #StartDate AS DATETIME
DECLARE #EndDate AS DATETIME
DECLARE #CurrentDate AS DATETIME
DECLARE #ItemName As Varchar(450)
DECLARE #QOH DECIMAL(19,4)
SET #QOH = 0
SET #ItemName = 'TUR001-02'
SET #StartDate = '2020-04-01'
SET #EndDate = GETDATE()
SET #CurrentDate = #StartDate
CREATE TABLE #TempTable
(
Date datetime,
ItemName char(450),
QOH DECIMAL(19,4)
);
WHILE (#CurrentDate < #EndDate)
BEGIN
DECLARE #daySales DECIMAL(19,4)
SELECT #daySales = SUM(Quantity)
FROM qbInvoiceLineDetail
WHERE TxnDate = #CurrentDate AND FullName = #ItemName;
SET #QOH = #QOH - #daySales
INSERT INTO #TempTable (Date, ItemName, QOH)
SELECT #CurrentDate, #ItemName, #QOH;
SET #CurrentDate = DATEADD(DAY, 1, #CurrentDate);
END
SELECT * FROM #TempTable
DROP TABLE #TempTable
You can use a tally table to generate your dates between start and end, then insert all your data in one hit into your table.
Caveat - this is untested as I have nothing to check it against, assumes the dates are dates only, if they include time then will need to use convert - hopefully will be what you are looking for:
/*first, create a tally table - this should be a permanent feature */
select top 1000 N=Identity(int, 0, 1)
into dbo.Digits
from master.dbo.syscolumns a cross join master.dbo.syscolumns
declare #StartDate datetime='20200401', #EndDate datetime=GetDate()
select DateAdd(day,N,#startDate) currentDate, FullName ItemName, Sum(Quantity) over(order by d.N) QOH
from Digits d
left join qbInvoiceLineDetail q on q.TxnDate=DateAdd(day,N,#startDate)
where DateAdd(day,N,#startDate)<=#EndDate
group by TxnDate, ItemName

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.

Selecting all dates from a table within a date range and including 1 row per empty date

I am trying to refactor some code in an ASP.Net website and having a problem with a stored procedure I am writing.
What I want to do is get a date range, then select all data within that range from a table BUT if a date is not present I need to still select a row.
My idea for this as you can see in the code below is to create a temporary table, and populate it with all the dates within my date range, then join this onto the table I am selecting from however this does not work. Am I doing something wrong here? The tempDate column is always null in this join however I have checked the table and it deffinately has data in it.
-- Parameters
DECLARE #DutyDate datetime='2012-01-01 00:00:00'
DECLARE #InstructorID nvarchar(2) = N'29'
DECLARE #datesTBL TABLE (tempDate DATETIME)
-- Variables
DECLARE #StartDate DATETIME
DECLARE #EndDate DATETIME
SELECT
#StartDate =StartDate,
#EndDate = EndDate
FROM
DutyPeriodTbl
WHERE
(StartDate <= #DutyDate)
AND
(EndDate >= #DutyDate)
DECLARE #d DATETIME = #StartDate
WHILE #d<=#EndDate
BEGIN
INSERT INTO #datesTBL VALUES (CONVERT(DATETIME, #d, 102))
SET #d=DATEADD(day,1,#d)
END
SELECT
dt.tempDate ,
InstructorID, EventStart,
EventEnd, cancelled,
cancelledInstructor,
EventType, DevName,
Room, SimLocation,
ClassLocation, Event,
Duration, TrainingDesc,
Crew, Notes,
LastAmended, InstLastAmended,
ChangeAcknowledged, Type,
OtherType, OtherTypeDesc,
CourseType
FROM
OpsInstructorEventsView iv
LEFT OUTER JOIN
#datesTBL dt
ON
CONVERT(DATETIME, iv.EventStart, 102) = CONVERT(DATETIME, dt.tempDate, 102)
WHERE
InstructorID = #InstructorID
AND
EventStart BETWEEN CONVERT(DATETIME, #StartDate, 102) AND CONVERT(DATETIME, #EndDate, 102)
ORDER BY
EventStart
There are several ways of dealing with missing rows, but all are about having another set of data to combine with your current results.
That could be derived from your results, created by a CTE or other process (such as your example), or (my preference) by using a permanent template to join against.
The template in your case could just be a table of dates, like your #datesTBL. The difference being that it's created in advance with, for example, 100 years worth of dates.
Your query may then be similar to your example, but I would try the following...
SELECT
dt.tempDate ,
InstructorID, EventStart,
EventEnd, cancelled,
cancelledInstructor,
EventType, DevName,
Room, SimLocation,
ClassLocation, Event,
Duration, TrainingDesc,
Crew, Notes,
LastAmended, InstLastAmended,
ChangeAcknowledged, Type,
OtherType, OtherTypeDesc,
CourseType
FROM
#datesTBL dt
LEFT OUTER JOIN
OpsInstructorEventsView iv
ON iv.EventStart >= dt.tempDate
AND iv.EventStart < dt.tempDate + 1
AND iv.InstructorID = #InstructorID
WHERE
dt.tempDate >= #StartDate
AND dt.tempDate <= #EndDate
ORDER BY
dt.tempDate,
iv.EventStart
This puts the calendar template on the LEFT, and so makes many queries easier as you know the calendar's date field is always populated, is always a date only (no time part) value, is in order, is simple to GROUP BY, etc.
Well, idea is the same, but i would write function, that returns table with all dates in period. Look at this:
Create Function [dbo].[Interval]
(
#DateFrom Date,
#DateTo Date
)
Returns #tab Table
(
MyDate DateTime
)
As
Begin
Declare #Days int
Declare #i int
Set #Days = DateDiff(Day, #DateFrom, #DateTo)
Set #i = 0;
While (#Days > #i)
Begin
Insert Into #tab(MyDate)
Values (DateAdd(Day, #i, #DateTo))
Set #i = #i + 1
End
return
End
And reuse the function whenever you need it..
Select *
From [dbo].[Interval]('2011-01-01', GETDATE())

sql query to get daily payments for a month even if no payments on a given day

I am using SQL Server 2005 and trying to write a query where I want to retrieve payments for a given month. I currently have:
select sum(p1.paymentamount) as subtotal,
CONVERT(char(10), p1.paymentdate, 103) as paymentdate
from tblpayment p1
where 1=1
and p1.paymentdate >= #fromdate
and p1.paymentdate <= #todate
group by p1.paymentdate
order by p1.paymentdate
Schema:
CREATE TABLE [dbo].[tblPayment]
(
[paymentid] [int] IDENTITY(1,1) NOT NULL,
[userid] [int] NULL ,
[paymentdate] [datetime] NOT NULL,
[paymentamount] [int] NULL,
[paymenttype] [varchar](50) NULL,
[paymentnotes] [varchar](200) NULL,
[paymentcurrency] [nchar](10) NULL
)
This query gives me what I want but it doesnt give me the dates where no payments were made. What I want is a query that gives me all days even if there were no payments made on that day and jut shows the subtotal as 0 for that day.
There is another catch. The currency of payments is different. So how can I have another column in the query that gives me eurototal and sterlingtotal based on #currency parameter passed in ? Assuming there is a column in the table for "paymentcurrency"
You have to work backwards. In order to get rows for dates that don't exist, you need to outer join them to rows that do have those dates. In order to outer join, you need to have a sequence to join to. Since you don't have a sequence, you need to create one.
To create that sequence, you have two options:
Create a static date sequence and store it in a permanent table (Larry's answer); or
Use an existing numeric sequence (such as spt_values) to create one on the fly.
Let's assume you want the flexibility of the second approach. Here's a common snippet I use for things like that:
SELECT DATEADD(DAY, v.number, #fromdate)
FROM master.dbo.spt_values v
WHERE v.type = 'P'
AND v.number <= DATEDIFF(DAY, #fromdate, #todate)
Now just toss that in a CTE and join it:
WITH Dates_CTE (dt) AS
(
-- // Paste the snippet above in here
)
SELECT d.dt AS paymentdate, ISNULL(SUM(p.paymentamount), 0) AS subtotal
FROM Dates_CTE d
LEFT JOIN tblpayment p
ON p.paymentdate = d.dt
GROUP BY d.dt
ORDER BY d.dt
(Update: I left out the WHERE clause in the main query because it's technically handled by the the join, but in some instances you might get better performance by leaving it in)
As for the currency conversion, look up the syntax for PIVOT.
Update on PIVOT: You should be able to just enclose that entire query in parentheses, then go:
SELECT paymentdate, [Euro] AS euroamount, [Pound] as poundamount
FROM
(
-- // Insert the full query from above in here
) p
PIVOT
(
SUM(subtotal)
FOR paymentcurrency IN ([Euro], [Pound])
) AS pvt
Hard to verify without knowing exactly what kind of data is in there, but try that as a starting point.
If there are no dummy records in tblPayment for the dates without any payment, those dates will not appear in a query that selects only from tblPayment.
I handle this by creating a separate table with nothing but dates in it (one row per date), checking to make sure that I have all the dates to cover my query, and then LEFT JOINing my main table (in this case tblPayment) on the date table:
SELECT * FROM tblPayment LEFT OUTER JOIN tblDates
ON tblPayment.PaymentDate = tblDates.PossibleDate
This basic idea can be enhanced with GROUP BY to get the summary figures you want.
Here is one approach
Create the following function:
CREATE FUNCTION [dbo].[DateTable] (#StartDate DATETIME, #endDate DATETIME)
RETURNS #Itms TABLE
(
TheDate DATETIME
)
AS
BEGIN
DECLARE #theDate DATETIME
SET #TheDate = #StartDate
WHILE #TheDate <= #endDate
BEGIN
INSERT #Itms VALUES (#theDate)
SET #TheDate =dateAdd(d,1,#theDate)
END
RETURN
END;
Then here is a query that should do what you want
select sum(p1.paymentamount) as subtotal,
CONVERT(char(10), p1.paymentdate, 103) as paymentdate
from
(select * from tblpayment p1
where 1=1
and p1.paymentdate >= #fromDate
and p1.paymentdate <= #toDate
union
select theDate as paymentDate,0 as paymentAmount
from dbo.dateTable (#fromDate,#toDate)
) p1
group by p1.paymentdate
try something like this perhaps?
select sum(p1.paymentamount) as subtotal,
CASE WHEN (CONVERT(char(10), p1.paymentdate, 103) = 0) THEN 'No Sale'
ELSE
CONVERT(char(10), p1.paymentdate, 103)
END as paymentdate
FROM tblpayment
where paymentdate BETWEEN #fromdate and #todate
As mentioned before you have to use a separate table (temp or permanent). The currency conversion can be done using a CASE statement. Check out the below (I made up the conversion factors ;)
declare #dates table (dateitem datetime)
declare #lower datetime
declare #upper datetime
set #lower = '12/1/9'
set #upper = '12/31/9'
while #lower <= #upper
begin
insert into #dates values (#lower)
set #lower = dateadd(day, 1, #lower)
end
select dateitem, paymentcurrency,
paymentindollars = case paymentcurrency when 'dollars' then total when 'euro' then total * 1.7 else 0 end,
paymentineuros = case paymentcurrency when 'dollars' then total * 0.73 when 'euro' then total else 0 end
from
(select dateitem, paymentcurrency, sum(paymentamount) as total
from #dates DT left join tblpayment on DT.dateitem = tblpayment.paymentdate group by dateitem, paymentcurrency
) IQ order by dateitem
Caveats to watch out for:
your payementdate might have times in
it that you will have to remove
(through casting) for the join/grouping to work properly
for the conversions to work right you have to separate the differnt currency types, you could always wrap them in another sql to get a grand total for the day
currency conversion is usually only good for the day so applying a general conversion against a period of time is not going to give you good financial results, only decent ballpark figures (ie don't try and file it on your taxes ;)
Hope that helps a bit.

Calculating variances on dates in T-SQL

Guys, I am trying to write a stored procedure in T-SQL (SQL Server) that will select records based on a date field, keeping a variance of minutes in mind. Something like this:
CREATE PROCEDURE spGetCustomers(#DateRange DATETIME, #Variance int) AS
-- The next line is where I need help
-- I'm trying to subtract X amount of minutes from the date
-- So if #Variance = 4 AND #DateRange = '6/10/2009 1:15pm'
-- Then #StartDate should equal '6/10/2009 1:11pm'
DECLARE #StartDate = #DateRange - #Variance
-- I also need an #EndDate, which will be X amount of minutes
-- in the future. So if #Variance = 4 AND #DateRange = '6/10/2009 1:15pm'
-- Then #EndDate should equal '6/10/2009 1:19pm'
DECLARE #EndDate = #DateRange + #Variance
SELECT * FROM Customers WHERE Created BETWEEN #StartDate AND #EndDate
Hopefully this makes sense and someone can help me out! Thanks in advance
Check this out:
http://msdn.microsoft.com/en-us/library/ms186819(SQL.90).aspx
The DATEADD function allows you to add virtually any part of a date to another date object, it should be everything you need.
So basically do:
SELECT DATEADD(second, #Variance, #DateRange)
The following script provides an example that should get you started.
create table tmp_Customers
(
ID int identity(1,1),
CreatedDate datetime default getDate() not null,
Description varchar(15)
);
go
insert into tmp_Customers(Description) values('SomeData');
insert into tmp_Customers(Description) values('SomeData2');
insert into tmp_Customers(Description) values('SomeData3');
go
create procedure usp_GetCustomers
#iVarianceMinutes int,
#iDateRange datetime
as
set nocount on
declare #startDate datetime
declare #endDate datetime
--Define the date ranges for the select query
set #startDate = dateAdd(minute,-#iVarianceMinutes,#iDateRange)
set #endDate = dateAdd(minute,#iVarianceMinutes,#iDateRange)
--Get the Customers that were created within this time range.
SELECT *
FROM tmp_Customers
WHERE CreatedDate >= #startDate and CreatedDate < #endDate
return(0);
go
--Execute the procedure
declare #testDate datetime;
set #testDate = getDate();
exec usp_GetCustomers 5,#testDate
--drop procedure usp_GetCustomers
--drop table tmp_Customers