This question already has an answer here:
Passing a list to a TSQL 2008 Stored Procedure
(1 answer)
Closed 7 years ago.
I am trying to pass a string of ICD9 codes to a "WHERE IN TSQL clause" but it does not work. Can the below code be used with a few changes? Also passing a table name does not appear to work either. Any suggestions are appreciated.
DECLARE #StartDate DateTime
DECLARE #EndDate DateTime
DECLARE #ICD9 VARCHAR(MAX)
DECLARE #TABLE
SET #StartDate = '2014-01-01 00:00:00'
SET #EndDate = '2015-12-31 23:59:59'
SET #TABLE = dbo.IPOP
SET #ICD9 = '284.1', '284.2', '280.0', '280.1'
SELECT X.*
INTO #TABLE
FROM
(SELECT c.ID, ip.AdmitDateTime, ip.ICD9Code, ip.ICD9Description
FROM dbo.Inpatient ip
INNER JOIN dbo.COHORT c ON (c.ID = ip.ID)
WHERE ip.ICD9Code IN (#ICD9)
AND ip.AdmitDateTime between #StartDate and #EndDate
UNION ALL
SELECT c.ID, op.AdmitDateTime, op.ICD9Code, op.ICD9Description
FROM dbo.Outpatient op
INNER JOIN dbo.COHORT c ON (c.ID = op.ID)
WHERE op.ICD9Code IN (#ICD9)
AND op.AdmitDateTime between #StartDate and #EndDate
) X
ORDER BY ID, AdmitDateTime
GO
The short answer is that you cannot pass a string delimited list to your IN clause.
Please refer to Erland Sommarskog's series of articles on the topic for alternatives:
Arrays and Lists in SQL Server
Related
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.
I would like to use a variable for the number of rows used in an 'OVER clause' statement. Up to now I only get it working by creation of the sql statement in a string and then execute it.
While the final purpose is to also use it in SSIS this does not work while that does not recognizes the fields in the dynamic query.
What works is:
select
[GUID_Fund], [Date], [Close],
avg([Close]) over (order by [GUID_Fund], [Date] rows 7 preceding) as MA_Low
from fundrates
group by [GUID_Fund], [Date], [Close]
order by [GUID_Fund] asc, [Date] desc;
The number 7 needs to be a variable so I was trying to do something like this:
declare #var_MA_Low as int;
select distinct #var_MA_Low = [Value1]
from Variables
where [Name]='MA_Low';
select
[GUID_Fund], [Date], [Close],
avg([Close]) over (order by [GUID_Fund], [Date] rows #var_MA_Low preceding) as MA_Low
from fundrates
group by [GUID_Fund], [Date], [Close]
order by [GUID_Fund] asc, [Date] desc;
This results in a syntax error at #var_MA_Low just after 'rows'.
What works is the same statement as above, but than I cannot use it as source in SSIS:
declare #MA as nvarchar(max);
declare #var_MA_Low as nvarchar(max);
select distinct #var_MA_Low = [Value1] from Variables where [Name]='MA_Low';
set #MA = N'select [GUID_Fund], [Date], [Close], avg([Close])
over (order by [GUID_Fund], [Date] rows '+#var_MA_Low+' preceding) as MA_Low
from fundrates
group by [GUID_Fund], [Date], [Close] order by [GUID_Fund] asc, [Date] desc;'
execute sp_executesql #MA;
Has anybody an idea how to pass the number of rows as a variable into the second option?
what if you create a stored procedure with working query and use that SP as source?
I might try to improve this answer, but if you take your solution that works using the dynamic SQL and combine it with a temp table and the "insert into ... exec ... " syntax, https://stackoverflow.com/a/24073229/3591870 , and then return back to SSIS just the "select * from #holdertable", SSIS should be able to determine the columns being returned and generate your source. I don't really like the fact of you being required to use dynamic SQL to solve this however.
According to the docs, http://msdn.microsoft.com/en-us/library/ms189461(v=sql.120).aspx , it really does specify "unsigned integer literal", so I think dynamic SQL is going to be the only way.
I am trying to write a TSQL script for an SSRS report that uses a CTE to select records based on the parameters chosen. I'm looking for the most efficient way to do this, either all in TSQL and/or SSRS. I have 4 parameters which can be set to NULL (All values) or one specific value. Then in my CTE, I have the following line:
ROW_NUMBER() over(partition by G.[program_providing_service],G.people_id
order by G.[actual_date] desc) as rowID
This above CTE is for the case when Program is NULL and People is not null. My 4 parameters are:
Program, Facility, Staff, and People.
So I only want to partition values when they are NULL. Currently I implement this by one CTE depending on the parameter values. For example, if they choose NULL for all parameters except People, then this CTE would look like:
ROW_NUMBER() over(partition by G.people_id
order by G.[actual_date] desc) as rowID
Or if all 5 parameters are null:
ROW_NUMBER() over(partition by G.[program_providing_service], G.[site_providing_service], G.staff_id, G.people_id
order by G.[actual_date] desc) as rowID
If they do not choose NULL for any of the 4 parameters, then I probably do not need to partition by any field since I just want the top 1 record ordered by actual_date descending. This is what my CTE looks like:
;with cte as
(
Select distinct
G.[actual_date],
G.[site_providing_service],
p.[program_name],
G.[staff_id],
G.program_providing_service,
ROW_NUMBER() over(partition by G.[program_providing_service],G.people_id
order by G.[actual_date] desc) as rowID
From
event_log_rv G With (NoLock)
WHERE
...
AND (#ClientID Is Null OR [people_id]=#ClientID)
AND (#StaffID Is Null OR [staff_id] = #StaffID)
AND (#FacilityID Is Null OR [site_providing_service] = #FacilityID)
AND (#ProgramID Is Null OR [program_providing_service] = #ProgramID)
and (#SupervisorID is NULL OR staff_id in (select staff_id from #supervisors))
)
SELECT
[actual_date],
[site_providing_service],
[program_name],
[staff_id],
program_providing_service,
people_id,
rowID
FROM cte WHERE rowid = 1
ORDER BY [Client_FullName]
where the ROW_NUMBER line varies depending on the parameters chosen. Currently I have 5 IF statements in this TSQL script that look like:
IF #ProgramID IS NOT NULL AND #ClientID IS NULL
BEGIN
...
END
with one CTE in each of these IF statements:
IF #FacilityID IS NOT NULL AND #ClientID IS NULL
BEGIN
...
END
IF #ProgramID IS NOT NULL AND #ClientID IS NULL
BEGIN
...
END
IF #StaffID IS NOT NULL AND #ClientID IS NULL
BEGIN
...
END
IF #ClientID IS NOT NULL
BEGIN
...
END
How can I code for all possible options, whether they choose NULL or else specific values?
OMG.... it took me long time to try to understand what you want to do. There is some contradiction in your description. Pleas revist your description. Like you said you only want to partition values when they are NULL; then you also said, when they choose NULL for all parameter except for people, then you partition on people....
No matter what way you want to achieve, partition on 'null' or 'not null', you can construct dynamic sql to achieve this, instead of adding a lot of [if...else]
Following code is pseudo, definitely not tested. Just give you a hint. The following code has one assumption, which is your parameters have priority in partition order, for example, if Program is not null (or null), Program is in the first location.
declare #sql varchar(max)
set #sql = '
;with cte as
(
Select distinct
G.[actual_date],
G.[site_providing_service],
p.[program_name],
G.[staff_id],
G.program_providing_service,
ROW_NUMBER() over(partition by
'
if(#progarm is null)
set #sql = #sql + 'G.[program_providing_service],'
if(#facility is null)
set #sql = #sql + 'G.[site_providing_service],'
if(#staff is null )
set #sql = #sql + 'G.staff_id,'
if(#people is null)
set #sql = #sql + 'G.people_id'
set #sql = #sql + '
order by G.[actual_date] desc) as rowID
From
event_log_rv G With (NoLock)
WHERE
...
AND (#ClientID Is Null OR [people_id]=#ClientID)
AND (#StaffID Is Null OR [staff_id] = #StaffID)
AND (#FacilityID Is Null OR [site_providing_service] = #FacilityID)
AND (#ProgramID Is Null OR [program_providing_service] = #ProgramID)
and (#SupervisorID is NULL OR staff_id in (select staff_id from #supervisors))
)
SELECT
[actual_date],
[site_providing_service],
[program_name],
[staff_id],
program_providing_service,
people_id,
rowID
FROM cte WHERE rowid = 1
ORDER BY [Client_FullName]
'
exec(#sql)
I'm playing around with two very simple queries. There is a non-clustered index with StartDate and EndDate, as well as Id as an included column.
DECLARE #startDate DATETIME, #endDate DATETIME
SELECT #startDate = '4/1/2011', #endDate = '5/1/2011'
-- Does Index Scan (slow)
SELECT Id
FROM dbo.Table
WHERE
(#startDate IS NULL OR StartDate >= #startDate) AND
(#endDate IS NULL OR EndDate < #endDate)
-- Does Index Seek (fast)
SELECT Id
FROM dbo.Table
WHERE
(StartDate >= #startDate) AND
(EndDate < #endDate)
Is there any way to rearrange, pre-calculate, or otherwise change the query to have an index seek occur in the first example?
Edit: I know this is a very basic indexing problem, but I haven't found a good solution yet. Note that I am declaring the variables, but those would be parameters in an sproc.
What about the following?
DECLARE #startDate DATETIME, #endDate DATETIME
SELECT #startDate = '4/1/2011', #endDate = '5/1/2011'
SELECT Id
FROM dbo.Table
WHERE
StartDate >= ISNULL(#startDate, '1/1/1753')
AND
EndDate < ISNULL(#endDate, '12/31/9999')
This code is probably broken if you have an end date of 12/31/9999 in your table that you actually want returned from your result set, but how often does that happen?
Update List
set Date = "2009-07-21T19:00:40"
sql server doesn't recognize this format. is there a conversion function
You can use CAST and CONVERT. But maybe you must replace the 'T' with a space, before you can convert it. (There are also string manipulation functions available.)
Worked just fine for me (SQL Express 2005) except for the double quotes (which SQL Server assumed was a column delimiter); is that what's throwing the error? Thanks for the sample code, but can you produce the actual error?
In other words,
DECLARE #List TABLE ( [date] DATETIME
)
INSERT INTO #List
SELECT GETUTCDATE()
UPDATE #List SET Date =
"2009-07-21T19:00:40"
produces
Msg 207, Level 16, State 1, Line 7
Invalid column name
'2009-07-21T19:00:40'.
Whereas
DECLARE #List TABLE ( [date] DATETIME
)
INSERT INTO #List
SELECT GETUTCDATE()
UPDATE #List SET Date =
'2009-07-21T19:00:40'
runs successfully.
Try this:
UPDATE List SET Date = '2009/07/21 19:00:40'
Even worked for me too(2005 & 2008)..
DECLARE #tbl TABLE ( [date] DATETIME )
INSERT INTO #tbl SELECT getdate()
select * from #tbl
UPDATE #tbl SET Date = '2009-07-21T19:00:40'
select * from #tbl
However, just give a shot with DATEFORMAT COMMAND
Something like this
**SET DATEFORMAT dmy**
DECLARE #tbl TABLE ( [date] DATETIME )
INSERT INTO #tbl SELECT getdate()
select * from #tbl
UPDATE #tbl SET Date = '2009-07-21T19:00:40'
select * from #tbl