Is there a way to shorten this script? - sql

Can anyone please help me figure out a more convenient and efficient (shortest) way of writing the below script? The reason I'm doing this is because I will end up using the script on SSRS.
On SSRS, there will be two parameters and both are set to take blank values.
If the user running SSRS indicated only the Starting date parameter, then my report should give any date >= the starting date.
If the user put dates on both #startingdate and #endingdate, my script will search only those that are between #startingdate and #endingdate.
Do you guys kinda get what I'm trying to accomplish? I have an SSRS report similar to this scenario but it's very cumbersome to update because if I update one of the statement, I would have to do it on the other ones. Also the script is big and repetitive because of this.
If you could help me figure out a better way to do shorten or make it less cumbersome to code, please let me know, thanks!
Below is a sample script that I use:
DECLARE #STARTINGDATE
DECLARE #ENDINGDATE
SET #STARTINGDATE = '10/01/2013'
SET #ENDINGDATE = '10/05/2013'
CASE
WHEN #STARTINGDATE <> '' AND #ENDINGDATE <> ''
SELECT * FROM SALESTABLE
WHERE SALESDATE BETWEEN #STARTINGDATE AND #ENDINGDATE
END
WHEN #STARTINGDATE = '' AND #ENDINGDATE = ''
SELECT * FROM SALESTABLE
END
WHEN #STARTING <> '' AND #ENDINGDATE = ''
SELECT * FROM SALESABLE
WHERE SALESDATE >= #STARTINGDATE
END
WHEN #ENDINGDATE <> '' AND #STARTINGDATE = ''
SELECT * FROM SALESABLE
WHERE SALESDATE <= #ENDINGDATE
END

what if u use IF ELSE Statement?
IF #STARTINGDATE IS NOT NULL AND #ENDINGDATE IS NULL
SELECT * FROM SALESTABLE WHERE SALESDATE >= CONVERT(DATE,#STARTINGDATE,103)
ELSE IF #STARTINGDATE IS NOT NULL AND #ENDINGDATE IS NOT NULL
SELECT * FROM SALESTABLE WHERE SALESDATE >= CONVERT(DATE,#STARTINGDATE,103)
AND SALESDATE<DATEADD(DAY,1,CONVERT(DATE,#ENDINGDATE,103))
ELSE
SELECT * FROM SALESTABLE
References http://sqlfiddle.com/#!3/783c03/12

You can try this if StartingDate and EnddingDate dates are string. Using StartingDate and EnddingDate as text you will loose the date picker functionality in SSRS. Anyway here is a method to shorten the above script
Select * FROM SalesTable
Where SalesDate between
CASE WHEN #StartingDate = '' THEN SalesDate ELSE #StartingDate END
AND CASE WHEN #EndingDate = '' THEN SalesDate ELSE #EndingDate END
Method 2
If StartingDate and Endding date are dateTime. Use this method:
FYI: SSRS doesn't allow Blank values for DateTime parameters. It only allows NULL.
First create two parameters StartingDate and EndingDate and check the Allow NULL value.
Your Dataset query will be something like this
SELECT * FROM SalesTable
Where (SalesDate IS NULL OR #StartingDate IS NULL)
OR (SalesDate between ISNULL(#StartingDate, SalesDate)
AND ISNULL(#EndingDate, SalesDate))
Assign you dataset parameters to report parameters
Now your are good to go.

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.

Case Statement With Between Clause In Sql Server

I am used to get parameters to my Stored Procedure with a default value ''. I want to check whether if the Date is selected and , if so I want to Execute My Between Clause. In Normal Scenario I am gonna do like this
SELECT tbl1.Column1,tbl1.Column2 FROM table1 tbl1
WHERE tbl1.Column1 = CASE WHEN #Column1Val = '' THEN tbl1.Column1 ELSE #Column1Val END
But I can't Do this with Between Clause. I can't figure a way to do this other than dynamic query. Is there a way other than Dynamic Query?
This is what I am Trying to do
SELECT tbl1.Column1,tbl1.Column2 FROM table1 tbl1
WHERE tbl1.txnDate = CASE WHEN #DateTo = '1900-01-01' AND #DateFrom = '1900-01-01'
THEN CAST(tbl1.txnDate AS DATE)
ELSE CAST(tbl1.txnDate AS DATE) BETWEEN #DateTo AND #DateFrom
END
Try this:
SELECT tbl1.Column1,tbl1.Column2
FROM table1 tbl1
WHERE
(#DateTo = '1900-01-01' AND #DateFrom = '1900-01-01')
OR
(
NOT (#DateTo = '1900-01-01' AND #DateFrom = '1900-01-01')
AND (CAST(tbl1.txnDate AS DATE) BETWEEN #DateFrom AND #DateTo)
)
From what I understand, '1900-01-01' is your default value for start and end dates, so you only need a filter if the user has selected some non-default values for start and end dates. Please let me know if this is what you need.
Demo

CASE / IF Statement to declare variable based on parameter value

Been researching 'CASE' syntax and I think I'm close to getting what I need here but something is off. SSMS didn't like the way I wrote my query with CASE and I ended up confusing myself so I've written the sample below as if it were an 'IF' statement just so I can express what I'm trying to achieve. Any hints or tips would certainly be appreciated.
The goal is to have a parameter list of date ranges ("Last Week", "Month to Date", etc) that when selected will pass two values (upper and lower date limits) to the WHERE statement.
DECLARE #Param1 VARCHAR(20)
-- Param1 will be set when the user selects a string value, e.g. "Last Month"
DECLARE #Date1 DATE, #Date2 DATE
-- Date1 & Date2 will be the respective lower and upper date boundaries used in the WHERE statement.
IF #Param1 = 'Last Month'
THEN
SET #Date1 = CONVERT(DATE,DATEADD(dd,-DAY(GETDATE())+1,DATEADD(mm,-1,GETDATE())))
SET #Date2 = CONVERT(DATE,DATEADD(dd,-DAY(GETDATE())+1,GETDATE()))
ELSE IF #Param1 = 'Current Month'
THEN
SET #Date1 = CONVERT(DATE,DATEADD(dd,-DAY(GETDATE())+1,GETDATE()))
SET #Date2 = CONVERT(DATE,GETDATE())
ELSE
-- Default to yesterday if nothing selected by user
SET #Date1 = CONVERT(DATE,GETDATE()-1)
SET #Date2 = CONVERT(DATE,GETDATE())
END IF
You would do this with two case statements:
select #Date1 = (case when #Param1 = 'Last Month'
then CONVERT(DATE,DATEADD(dd,-DAY(GETDATE())+1,DATEADD(mm,-1,GETDATE())))
when #Param1 = 'Current Month'
then CONVERT(DATE,DATEADD(dd,-DAY(GETDATE())+1,GETDATE()))
else CONVERT(DATE,GETDATE()-1)
end);
select #Date2 = (case when #Param1 = 'Last Month'
then CONVERT(DATE,DATEADD(dd,-DAY(GETDATE())+1,GETDATE()))
when #Param1 = 'Current Month'
then CONVERT(DATE,GETDATE())
else CONVERT(DATE,GETDATE())
end);

Creating a variable stored procedure to be used in SSRS

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.

How to fill date gaps in MySQL?

How i can fill date gaps in MySQL? Here is my query:
SELECT DATE(posted_at) AS date,
COUNT(*) AS total,
SUM(attitude = 'positive') AS positive,
SUM(attitude = 'neutral') AS neutral,
SUM(attitude = 'negative') AS negative
FROM `messages`
WHERE (`messages`.brand_id = 1)
AND (`messages`.`spam` = 0
AND `messages`.`duplicate` = 0
AND `messages`.`ignore` = 0)
GROUP BY date ORDER BY date
It returns proper result set - but i want to fill gaps between dates start and end by zeros. How i can do this?
You'll need to create a helper table and fill it with all dates from start to end, then just LEFT JOIN with that table:
SELECT d.dt AS date,
COUNT(*) AS total,
SUM(attitude = 'positive') AS positive,
SUM(attitude = 'neutral') AS neutral,
SUM(attitude = 'negative') AS negative
FROM dates d
LEFT JOIN
messages m
ON m.posted_at >= d.dt
AND m.posted_at < d.dt + INTERVAL 1 DAYS
AND spam = 0
AND duplicate = 0
AND ignore = 0
GROUP BY
d.dt
ORDER BY
d.dt
Basically, what you need here is a dummy rowsource.
MySQL is the only major system which lacks a way to generate it.
PostgreSQL implements a special function generate_series to do that, while Oracle and SQL Server can use recursion (CONNECT BY and recursive CTEs, accordingly).
I don't know whether MySQL will support the following/similar syntax; but if not, then you could just create and drop a temporary table.
--Inputs
declare #FromDate datetime, /*Inclusive*/
#ToDate datetime /*Inclusive*/
set #FromDate = '20091101'
set #ToDate = '20091130'
--Query
declare #Dates table (
DateValue datetime NOT NULL
)
set NOCOUNT ON
while #FromDate <= #ToDate /*Inclusive*/
begin
insert into #Dates(DateValue) values(#FromDate)
set #FromDate = #FromDate + 1
end
set NOCOUNT OFF
select dates.DateValue,
Col1...
from #Dates dates
left outer join SourceTableOrView data on
data.DateValue >= dates.DateValue
and data.DateValue < dates.DateValue + 1 /*NB: Exclusive*/
where ...?