Please start any new threads on our new site at https://forums.sqlteam.com. We've got lots of great SQL Server experts to answer whatever question you can come up with.

 All Forums
 General SQL Server Forums
 New to SQL Server Programming
 multiple or in stored procedure

Author  Topic 

andrewcw
Posting Yak Master

133 Posts

Posted - 2012-01-22 : 01:29:27
I wish to move some adhoc queries into stored procedures.

The stored procedure I made has several inner joins - but in the where section I would need to replace what I have now -
AND ( tableName.field1 = @item1 ) similar to the adhoc SQL statement as AND ( tableName.field1 = @item1 or tableName.field1 = @item2)
Question ( 2 parts )How do I
1. express an list/array of items in the stored procedure
2. take the array an unwind it inside the stored procedure to create the multiple OR ?

A simple example would be such a help

Thank you !

andrewcw

andrewcw
Posting Yak Master

133 Posts

Posted - 2012-01-22 : 05:02:56
OK I have searched like questions on the forum :
I can build a comma delimited string and set as @items
and change my stored procedure
to be .... AND (tableName.field1 IN (@items))
Will be back if it does not work :)

andrewcw
Go to Top of Page

andrewcw
Posting Yak Master

133 Posts

Posted - 2012-01-22 : 05:33:41
Well - that looked right in the adhoc SQL pane
But when I modified the stored procedure & tried it, BUT for more than one item I get no data

The sp modified like this:
AND (dbo.TFPHistory.ItemName in (@ItemName))

EXEC @return_value = [dbo].[usp_GetFPData]
@ItemName = N'LFT,RGT'

SELECT 'Return Value' = @return_value

What is the stored procedure really doing ?
I have
PRINT @ItemName and its LFT,RGT

Is there some kind of difference between this and adhoc - it should work ?

Thanks !!!!

andrewcw
Go to Top of Page

sunitabeck
Master Smack Fu Yak Hacker

5155 Posts

Posted - 2012-01-22 : 06:43:46
SQL Server interprets your input parameter as a literal string - i.e., it is looking for any row where field1 is 'LFT,RGT'.

Alternatives are:
1. Make the entire select into a dynamic sql string. A very bad idea because of SQL injection risk.

2. Use a like clause as in:
AND ( ','+@items+',' LIKE '%,'+tableName.field1+',%' )
This has approach has two issues that you need to be aware of. First is that if there is an index on field1 this query won't be able to take advantage of that index. So performance may not be stellar. The second is that you have to make sure that your token separator (in this case comma) does not exist anywhere in the data in column field1.

3. Another widely used approach is to split the input string into a (virtual or temporary) table and join with that table. There are functions available online for doing the splitting - one I particularly like is in this blog: http://www.sqlservercentral.com/articles/Tally+Table/72993/ Copy and install the function in Figure 21 if you want to use it. While #2 is quick and easy, if you run into performance problems, this may be a better alternative.

4. Create an XML fragment that has all the items in your client code, and send that via the parameter in the stored proc. In the stored proc, shred the XML and join with the shredded XML.

I would start with #2 above and if that does not meet your needs, try 3 or 4.
Go to Top of Page

Transact Charlie
Master Smack Fu Yak Hacker

3451 Posts

Posted - 2012-01-22 : 08:20:43
I don't think your scenario is quite like the other forum posts.

You said this:
quote:

( tableName.field1 = @item1 or tableName.field2 = @item2)


indicating that: it isn't just a list of variables for a static column you want to compare against.
But a list of column/value predicates.

What I mean is that it doesn't sound like you have a problem where the answer can be expressed as

WHERE
[Column1] IN (@var1, @var2, @var3)

which is answerable by the string splitting function

It sounds like you have something more like this you need to build up

WHERE
([column1] = @val1 OR @val1 IS NULL)
AND/OR ([column2] = @val2 OR @val2 IS NULL)
AND/OR ([column3] = @val3 OR @val3 IS NULL)
....
....

Which is often referred to as a 'catch all query'

If that is the case then, in my opinion, the best way to go is to write a parameterised dynamic sql query using sp_executeSql to pass the parameters in (only include the relevant logic if the parameter in question is not null)

You may find this interesting reading:
http://sqlinthewild.co.za/index.php/2009/03/19/catch-all-queries/

Charlie
===============================================================
Msg 3903, Level 16, State 1, Line 1736
The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION
Go to Top of Page

andrewcw
Posting Yak Master

133 Posts

Posted - 2012-01-22 : 11:41:04
Hmmm I wrote my 'apparent solution' during the night ( had to get up due to some family illness ), but closer inspection of the adhoc query compares:
[ adhoc ] IN ('LFT','RGT')
[ Sp ] IN ('LFT,RGT') - not delineated

The the comma in the string did not force the delimiters single apostraphe to be around each item. I will read your posts carefully to see whats the cleanest alternatives. By design of system the max is 20 items & typically just 1 and occasionally several others.

Thanks

andrewcw
Go to Top of Page

Transact Charlie
Master Smack Fu Yak Hacker

3451 Posts

Posted - 2012-01-22 : 15:19:28
in that case, the string splitting function and then pass that to IN is probably the cleanest least impactful.

Read this for probably the 'best' way. (if you can make used of table valued parameters)
http://www.sommarskog.se/arrays-in-sql.html

Charlie
===============================================================
Msg 3903, Level 16, State 1, Line 1736
The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION
Go to Top of Page

andrewcw
Posting Yak Master

133 Posts

Posted - 2012-01-22 : 15:37:37
quote:
Originally posted by Transact Charlie

in that case, the string splitting function and then pass that to IN is probably the cleanest least impactful.

Read this for probably the 'best' way. (if you can make used of table valued parameters)
http://www.sommarskog.se/arrays-in-sql.html

Charlie
===============================================================
Msg 3903, Level 16, State 1, Line 1736
The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION




Thanks - I went back and edited my original entry as it had a mistake. The query using 'in' in the adhoc query is all from the same column. I am using SQL client so I can use Table Value Parameters. I am trying to understand the article by Sommarskog... still not following what I am to do.

andrewcw
Go to Top of Page

Kristen
Test

22859 Posts

Posted - 2012-01-22 : 16:34:52
In essence:

instead of

SELECT ...
FROM dbo.TFPHistory
WHERE ...
AND (dbo.TFPHistory.ItemName in (@ItemName))

you need


SELECT ...
FROM dbo.TFPHistory
JOIN dbo.MySplitFunction(@ItemName) AS S
ON dbo.TFPHistory.ItemName = S.MySplitValue

WHERE ...

Choose suitable code/name for "MySplitFunction" from references provided earlier.
Go to Top of Page

andrewcw
Posting Yak Master

133 Posts

Posted - 2012-01-22 : 19:02:39
quote:
Originally posted by sunitabeck

SQL Server interprets your input parameter as a literal string - i.e., it is looking for any row where field1 is 'LFT,RGT'.

Alternatives are:
1. Make the entire select into a dynamic sql string. A very bad idea because of SQL injection risk.

2. Use a like clause as in:
AND ( ','+@items+',' LIKE '%,'+tableName.field1+',%' )
This has approach has two issues that you need to be aware of. First is that if there is an index on field1 this query won't be able to take advantage of that index. So performance may not be stellar. The second is that you have to make sure that your token separator (in this case comma) does not exist anywhere in the data in column field1.

3. Another widely used approach is to split the input string into a (virtual or temporary) table and join with that table. There are functions available online for doing the splitting - one I particularly like is in this blog: http://www.sqlservercentral.com/articles/Tally+Table/72993/ Copy and install the function in Figure 21 if you want to use it. While #2 is quick and easy, if you run into performance problems, this may be a better alternative.

4. Create an XML fragment that has all the items in your client code, and send that via the parameter in the stored proc. In the stored proc, shred the XML and join with the shredded XML.

I would start with #2 above and if that does not meet your needs, try 3 or 4.



I looked at your syntax ( :
AND ( ','+@items+',' LIKE '%,'+tableName.field1+',%' )
-
I had no idea what it meant /how to read it. But to my surprise it actually worked !

Thank you very much !

andrewcw
Go to Top of Page

sunitabeck
Master Smack Fu Yak Hacker

5155 Posts

Posted - 2012-01-22 : 19:27:36
You are welcome!

Be mindful of the performance issues and data requirements. If there is a large amount of data or if it is a mission-critical system, consider using the split function approach that Kristen showed in his example.
Go to Top of Page

andrewcw
Posting Yak Master

133 Posts

Posted - 2012-01-23 : 00:21:27
quote:
Originally posted by sunitabeck

You are welcome!

Be mindful of the performance issues and data requirements. If there is a large amount of data or if it is a mission-critical system, consider using the split function approach that Kristen showed in his example.



Thanks for the tips!!! My sets are small & dont expect it to be performance problem. But I would like to ask more about Kristen's method !

andrewcw
Go to Top of Page

Transact Charlie
Master Smack Fu Yak Hacker

3451 Posts

Posted - 2012-01-23 : 04:24:04
We are suggesting something like this (THis should be safe as it runs in tempdb):

-- USe the tempdb so we don't screw anything up
USE tempdb
GO

-- Splitting function (not the fastest)
IF OBJECT_ID('udf_split') IS NOT NULL DROP FUNCTION udf_split
GO

CREATE FUNCTION udf_split(@s VARCHAR(8000), @sep VARCHAR(2))
RETURNS table
AS
RETURN (
WITH Pieces([pn], [start], [stop]) AS (
SELECT 1, 1, CHARINDEX(@sep, @s)
UNION ALL
SELECT [pn] + 1, [stop] + 1, CHARINDEX(@sep, @s, [stop] + 1)
FROM Pieces
WHERE [stop] > 0
)
SELECT [pn],
SUBSTRING(@s, [start], CASE WHEN [stop] > 0 THEN [stop]-[start] ELSE 512 END) AS s
FROM Pieces
)
GO


DECLARE @myTable TABLE (
[ID] INT IDENTITY(1,1) PRIMARY KEY
, [Value] CHAR(1)
)

INSERT @myTable ([Value])
VALUES ('a'),('b'),('c'),('d'),('e'),('f'),('g')

SELECT 'NO FILTER' AS [msg], * FROM @myTable

DECLARE @searchString VARCHAR(10) = 'a,b,g'

-- USING IN WITH A CALL TO THE TABLE VALUED FUNCTION
SELECT 'IN FILTER' AS [msg], * FROM @myTable
WHERE
[Value] IN ( SELECT [s] FROM tc.udf_split(@searchString, ',') )

-- JOINING TO THE TABLE VALUED FUNCTION
SELECT
'JOIN FILTER' AS [msg]
, m.[ID]
, m.[Value]
FROM
@myTable AS m
JOIN tc.udf_split(@searchString, ',') AS sp ON sp.[s] = m.[Value]

The string splitting function is simply the first one I could find that didn't rely on other objects. I prefer a number tabled approach myself but you should be able to see what we mean.

Charlie
===============================================================
Msg 3903, Level 16, State 1, Line 1736
The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION
Go to Top of Page
   

- Advertisement -