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.
| 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 procedure2. take the array an unwind it inside the stored procedure to create the multiple OR ?A simple example would be such a helpThank 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 @itemsand change my stored procedure to be .... AND (tableName.field1 IN (@items))Will be back if it does not work :)andrewcw |
 |
|
|
andrewcw
Posting Yak Master
133 Posts |
Posted - 2012-01-22 : 05:33:41
|
| Well - that looked right in the adhoc SQL paneBut 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_valueWhat is the stored procedure really doing ?I havePRINT @ItemName and its LFT,RGTIs there some kind of difference between this and adhoc - it should work ?Thanks !!!!andrewcw |
 |
|
|
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. |
 |
|
|
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 asWHERE [Column1] IN (@var1, @var2, @var3) which is answerable by the string splitting functionIt sounds like you have something more like this you need to build upWHERE ([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 1736The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION |
 |
|
|
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 delineatedThe 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.Thanksandrewcw |
 |
|
|
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.htmlCharlie===============================================================Msg 3903, Level 16, State 1, Line 1736The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION |
 |
|
|
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.htmlCharlie===============================================================Msg 3903, Level 16, State 1, Line 1736The 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 |
 |
|
|
Kristen
Test
22859 Posts |
Posted - 2012-01-22 : 16:34:52
|
In essence:instead ofSELECT ...FROM dbo.TFPHistoryWHERE ... AND (dbo.TFPHistory.ItemName in (@ItemName)) you needSELECT ...FROM dbo.TFPHistory JOIN dbo.MySplitFunction(@ItemName) AS S ON dbo.TFPHistory.ItemName = S.MySplitValueWHERE ... Choose suitable code/name for "MySplitFunction" from references provided earlier. |
 |
|
|
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 |
 |
|
|
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. |
 |
|
|
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 |
 |
|
|
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 upUSE tempdbGO-- Splitting function (not the fastest)IF OBJECT_ID('udf_split') IS NOT NULL DROP FUNCTION udf_splitGO CREATE FUNCTION udf_split(@s VARCHAR(8000), @sep VARCHAR(2))RETURNS tableASRETURN ( 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 )GODECLARE @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 @myTableDECLARE @searchString VARCHAR(10) = 'a,b,g'-- USING IN WITH A CALL TO THE TABLE VALUED FUNCTIONSELECT 'IN FILTER' AS [msg], * FROM @myTableWHERE [Value] IN ( SELECT [s] FROM tc.udf_split(@searchString, ',') ) -- JOINING TO THE TABLE VALUED FUNCTIONSELECT '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 1736The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION |
 |
|
|
|
|
|
|
|