Using uniqueidentifier with IN Clause in SQL server - sql

I have a stored procedure that takes as an input a string of GUIDs and selects from table where table GUID IN (#Param).
#Param = 'b2e16cdc-1f1b-40e2-a979-f87a6a2457af,
c275dd13-bb54-4b8c-aa12-220b5980cabd,
af3552ec-37b1-4a76-81ad-1bd6b8c4cd6c,
3a7fda02-558b-49a9-a870-30350254d8c0,'
SELECT * FROM dbo.Table1 WHERE
TableGUID IN (#Param)
However, I noticed that the query return values, only if the first GUID matches, otherwise it will not return anything. which means that it only compares with the first GUID in the string.
anyone knows how solve the problem?

declare #sql varchar(max)
set #sql='SELECT * FROM dbo.Table1 WHERE
TableGUID IN ('+#Param+') '
exec (#sql)

We can't do it, because SQL has no concept of Lists, or array or other useful data structures - it only knows about tables (and table based information) so it converts the string list into a table structure when it compiles the command - and it can't compile a variable string, so it complains and you get annoyed. Or at least, I do.
What we have to do is convert the comma separated values into a table first. My initial version was inline, and rather messy, so I re-worked it to a user function and made it a bit more general purpose.
USE [Testing] GO
CREATE FUNCTION [dbo].[VarcharToTable] (#InStr NVARCHAR(MAX))
RETURNS #TempTab TABLE
(id UNIQUEIDENTIFIER NOT NULL)
AS
BEGIN
;-- Ensure input ends with comma
SET #InStr = REPLACE(#InStr + ',', ',,', ',')
DECLARE #SP INT
DECLARE #VALUE NVARCHAR(MAX)
WHILE PATINDEX('%,%', #INSTR ) <> 0
BEGIN
SELECT #SP = PATINDEX('%,%',#INSTR)
SELECT #VALUE = LEFT(#INSTR , #SP - 1)
SELECT #INSTR = STUFF(#INSTR, 1, #SP, '')
INSERT INTO #TempTab(id) VALUES (#VALUE)
END
RETURN
END
GO
This creates a user function that takes a comma separated value string and converts it into a table that SQL does understand - just pass it the sting, and it works it all out. It's pretty obvious how it works, the only complexity is the REPLACE part which ensures the string is terminated with a single comma by appending one, and removing all double commas from the string. Without this, while loop becomes harder to process, as the final number might or might not have a terminating comma and that would have to be dealt with separately.
DECLARE #LIST NVARCHAR(MAX)
SET #LIST = '973150D4-0D5E-4AD0-87E1-037B9D4FC03B,973150d4-0d5e-4ad0-87e1-037b9d4fc03c'
SELECT Id, Descr FROM TableA WHERE Id IN (SELECT * FROM dbo.VarcharToTable(#LIST))

In addition to MikkaRin's answer: a GUID has to be unclosed in apostrophes, so the value in the parameter should look like
'b2e16cdc-1f1b-40e2-a979-f87a6a2457af',
'c275dd13-bb54-4b8c-aa12-220b5980cabd',
'af3552ec-37b1-4a76-81ad-1bd6b8c4cd6c',
'3a7fda02-558b-49a9-a870-30350254d8c0'
In the end, you have to pass something like:
#Param = '''b2e16cdc-1f1b-40e2-a979-f87a6a2457af'',
''c275dd13-bb54-4b8c-aa12-220b5980cabd'',
''af3552ec-37b1-4a76-81ad-1bd6b8c4cd6c'',
''3a7fda02-558b-49a9-a870-30350254d8c0'''
Pay attention to the last comma of the list. It should be removed.

Related

Using single quote in replace?

I am passing the following string into my Stored Procedure
'1,2,3,4,5,6'
I am trying to replace the , with ',' so that my string is translated to:
'1','2','3','4','5','6'
Is this possible? Here is my attempt:
Declare #Var2 varchar(250)
SET #Var2 = Replace(#Var1, ',', "','")
If you want to pass a list of values as a parameter use a table valued parameter.
The first step would be to create your type (I tend to go for a generic name so they can be reused):
CREATE TYPE dbo.ListOfInt AS TABLE (Value INT);
Then you can pass as a parameter fairly easily:
DECLARE #T dbo.ListOfInt;
INSERT #T (Value) VALUES (1), (2), (3);
DECLARE #SQL NVARCHAR(MAX) = 'SELECT * FROM dbo.T WHERE ID IN (SELECT Value FROM #T)';
EXECUTE sp_executesql #sql, N'#T dbo.ListOfInt READONLY', #T;
Or create a procedure that takes the parameter:
CREATE PROCEDURE dbo.SomeProcName #IDs dbo.ListOfInt READONLY
AS
BEGIN
-- DO SOMETHING
END
If this isn't an option, then pretty much everything you ever need to know about splitting strings in SQL Server is covered in the article Split strings the right way – or the next best way by Aaron Bertrand. He has done lots of testing so you can pick the best approach for your needs.
It should be like below, you need to escape extra '
select replace(#Var1,',',''',''')
But doing that will result in something which you can't use in INLIST. So try like
select ''''+ replace('1,2,3,4,5,6',',',''',''') + ''''
Which will result in '1','2','3','4','5','6'

Split/explode comma delimited string with Sybase SQL Anywhere

UPDATE:
Someone marked this question as duplicate of
How do I split a string so I can access item x.
But it's different, my question is about Sybase SQL Anywhere, the other is about MS SQL Server. These are two different SQL engines, even if they have the same origin, they have different syntax. So it's not duplicate. I wrote in the first place in description and tags that it's all about Sybase SQL Anywhere.
I have field id_list='1234,23,56,576,1231,567,122,87876,57553,1216'
and I want to use it to search IN this field:
SELECT *
FROM table1
WHERE id IN (id_list)
id is integer
id_list is varchar/text
But in this way this doesn't work, so I need in some way to split id_list into select query.
What solution should I use here? I'm using the T-SQL Sybase ASA 9 database (SQL Anywhere).
Way I see this, is to create own function with while loop through,
and each element extract based on split by delimiter position search,
then insert elements into temp table which function will return as result.
This can be done without using dynamic SQL but you will need to create a couple of supporting objects. The fist object is a table valued function that will parse your string and return a table of integers. The second object is a stored procedure that will have a parameter where you can pass the string (id_list), parse it to a table, and then finally join it to your query.
First, create the function to parse the string:
CREATE FUNCTION [dbo].[String_To_Int_Table]
(
#list NVARCHAR(1024)
, #delimiter NCHAR(1) = ',' --Defaults to CSV
)
RETURNS
#tableList TABLE(
value INT
)
AS
BEGIN
DECLARE #value NVARCHAR(11)
DECLARE #position INT
SET #list = LTRIM(RTRIM(#list))+ ','
SET #position = CHARINDEX(#delimiter, #list, 1)
IF REPLACE(#list, #delimiter, '') <> ''
BEGIN
WHILE #position > 0
BEGIN
SET #value = LTRIM(RTRIM(LEFT(#list, #position - 1)));
INSERT INTO #tableList (value)
VALUES (cast(#value as int));
SET #list = RIGHT(#list, LEN(#list) - #position);
SET #position = CHARINDEX(#delimiter, #list, 1);
END
END
RETURN
END
Now create your stored procedure:
CREATE PROCEDURE ParseListExample
#id_list as nvarchar(1024)
AS
BEGIN
SET NOCOUNT ON;
--create a temp table to hold the list of ids
CREATE TABLE #idTable (ID INT);
-- use the table valued function to parse the ids into a table.
INSERT INTO #idTable(ID)
SELECT Value FROM dbo.String_to_int_table(#id_list, ',');
-- join the temp table of ids to the table you want to query...
SELECT T1.*
FROM table1 T1
JOIN #idTable T2
on T1.ID = T2.ID
Execution Example:
exec ParseListExample #id_list='1234,23,56,576,1231,567,122,87876,57553,1216'
I hope this helps...
Like Mikael Eriksson said, there is answer at dba.stackexchange.com with two very good solutions, first with use of sa_split_list system procedure, and second slower with CAST statement.
For the Sybase SQL Anywhere 9 sa_split_list system procedure not exist, so I have made sa_split_list system procedure replacement (I used parts of the code from bsivel answer):
CREATE PROCEDURE str_split_list
(in str long varchar, in delim char(10) default ',')
RESULT(
line_num integer,
row_value long varchar)
BEGIN
DECLARE str2 long varchar;
DECLARE position integer;
CREATE TABLE #str_split_list (
line_num integer DEFAULT AUTOINCREMENT,
row_value long varchar null,
primary key(line_num));
SET str = TRIM(str) || delim;
SET position = CHARINDEX(delim, str);
separaterows:
WHILE position > 0 loop
SET str2 = TRIM(LEFT(str, position - 1));
INSERT INTO #str_split_list (row_value)
VALUES (str2);
SET str = RIGHT(str, LENGTH(str) - position);
SET position = CHARINDEX(delim, str);
end loop separaterows;
select * from #str_split_list order by line_num asc;
END
Execute the same way as sa_split_list with default delimiter ,:
select * from str_split_list('1234,23,56,576,1231,567,122,87876,57553,1216')
or with specified delimiter which can be changed:
select * from str_split_list('1234,23,56,576,1231,567,122,87876,57553,1216', ',')
You use text in your query and this is not going to work.
Use dynamic query.
Good contribution from bsivel answer, but to generalise it (for other separators than a comma), then the line
SET #list = LTRIM(RTRIM(#list))+ ','
must become
SET #list = LTRIM(RTRIM(#list))+ #delimiter
The first version will only work for comma-separated lists.
The dynamic query approach would look like this:
create procedure ShowData #IdList VarChar(255)
as
exec ('use yourDatabase; select * from MyTable where Id in ('+#IdList+')')

How to perform a WHERE similar to "NOT IN" with a list of value coming from a HTTP FORM?

I have web application developed in C#, where a page is showing the output of a SQL query; the query is a linq call to a store procedure and I am using SQL Server 2008.
One of the column display the tags associated with the result line; on the same page a list of check-boxes is displayed, each check-box correspond to a tag, and if the user turn on or off one or multiple check-box I want to filter the query.
My simplest SQL solution would be to us "NOT IN" like the following:
select * from [mytable] where [tags] not in ('tag1','tag2, etc...)
Given I convert the FORM POST to a string with comma separated values.
Example:
string tags = ParseFormAndConvertCheckBoxToCSV(Page.Request.Form);
string sqlcmd = "select * from [mytable] where [tags] not in (" + tags + ")";
But I don't want to dynamically build the SQL because as far as I know that would be bad.
I can imagine few way of splitting the string with comma separated list of values in SQL nd store the values into a in-memory table:
declare #tagscsv nvarchar(MAX)
declare #notags TABLE(Value nvarchar(50))
set #tagscsv = 'taxi,ivf'
Declare #x XML
select #x = cast('<A>'+ replace(#tagscsv,',','</A><A>')+ '</A>' as xml)
insert into #notags
select t.value('.', 'nvarchar(50)') as v from #x.nodes('/A') as x(t)
select * from [mytable] where [tags] not in (select * from #notags)
...but this too sounds like a dirty trick.
To me it looks like this should be a very common situation, and I figure out a lot of people out there faced this problem in the past, but searching on google I could not find an elegant solution.
Anyone can help?
Edit: Missed the stored procedure part.
With stored procedures you do not have a lot of options. I usually do what you did, split the comma seperated string and save the values to a temp table (or table variable).
CREATE PROCEDURE SplitAndStuff
#List nvarchar(MAX) = NULL,
#SplitOn nvarchar(5) = ',' --This can be replaced with a literal if it is always a comma.
AS
BEGIN
DECLARE #Pos int;
DECLARE #SplitOnLength int;
DECLARE #Results TABLE (value nvarchar(MAX))
SET #SplitOnLength = DATALENGTH(#SplitOn) / 2;
IF (RIGHT(#List, #SplitOnLength) <> #SplitOn) SET #List = #List + #SplitOn; --Add trailling split string if there is not one already.
SET #Pos = CHARINDEX(#SplitOn, #List, 1) --Initalize for loop. (The starting position returned is 1-based, not 0-based.)
WHILE #Pos > 0
BEGIN
INSERT INTO #Results (value) SELECT CAST(SUBSTRING(#List,1,#Pos-1)AS int);
SET #List = SUBSTRING(#List, #Pos+#SplitOnLength, DATALENGTH(#List) / 2);
SET #Pos = CHARINDEX(#SplitOn, #List, 1);
END
END
GO
If your stored procedure is not doing anything else (besides the look up) you could use my original LINQ answer below:
Since you are using LINQ you need to split tagscsv and use the resulting array to perform an "where in" query:
string[] tags = tagscsv.Split(',');
var output = from q in db.Table where !tags.Contains(q) select q;
See also:
http://msdn.microsoft.com/en-us/library/ms132407.aspx
Using LINQ to Perform "WHERE IN (Value1,Value2)" Queries
The NOT IN clause in LINQ to SQL

Passing SQL stored procedure entirety of WHERE clause

I have a SQL stored procedure of the form
SELECT [fields] FROM [table] WHERE #whereSql
I want to pass the procedure an argument (#whereSql) which specifies the entire WHERE clause, but the following error is returned:
An expression of non-boolean type specified in a context where a condition is expected
Can this be done?
The short answer is that you can't do it like this -- SQL Server looks at the contents of a variable as a VALUE. It doesn't dynamically build up the string to execute (which is why this is the correct way to avoid SQL injection attacks).
You should make every effort to avoid a dynamic WHERE as you're trying to do, largely for this reason, but also for the sake of efficiency. Instead, try to build up the WHERE clause so that it short-circuits pieces with lots of ORs, depending on the situation.
If there's no way around it, you can still build a string of your own assembled from the pieces of the command, and then EXEC it.
So you could do this:
DECLARE #mywhere VARCHAR(500)
DECLARE #mystmt VARCHAR(1000)
SET #mywhere = ' WHERE MfgPartNumber LIKE ''a%'' '
SELECT #mystmt = 'SELECT TOP 100 * FROM Products.Product AS p ' + #mywhere + ';'
EXEC( #mystmt )
But I recommend instead that you do this:
SELECT TOP 100 *
FROM Products.Product AS p
WHERE
( MfgPartNumber LIKE 'a%' AND ModeMfrPartNumStartsWith=1)
OR ( CategoryID = 123 AND ModeCategory=1 )
I believe this can be done using Dynamic SQL. See below:
CREATE PROCEDURE [dbo].[myProc]
#whereSql nvarchar(256)
AS
EXEC('SELECT [fields] FROM [table] WHERE ' + #whereSql)
GO
That said, you should do some serious research on dynamic SQL before you actually use it.
Here are a few links that I came across after a quick search:
http://www.sommarskog.se/dynamic_sql.html
http://msdn.microsoft.com/en-us/library/aa224806%28SQL.80%29.aspx
http://www.itjungle.com/fhg/fhg100505-story02.html
Make sure you read this fully
www.sommarskog.se/dynamic_sql.html
Dynamic SQL listed in some of the Answers is definitely a solution. However, if Dynamic SQL needs to be avoided, one of the solutions that I prefer is to make use of table variables (or temp tables) to store the parameter value that is used for comparison in WHERE clause.
Here is an example Stored Procedure implementation.
CREATE PROCEDURE [dbo].[myStoredProc]
#parameter1 varchar(50)
AS
declare #myTempTableVar Table(param1 varchar(50))
insert into #myTempTableVar values(#parameter1)
select * from MyTable where MyColumn in (select param1 from #myTempTableVar)
GO
In case you want to pass in multiple values, then the comma separated values can be stored as rows in the table variable and used in the same way for comparison.
CREATE PROCEDURE [dbo].[myStoredProc]
#parameter1 varchar(50)
AS
--Code Block to Convert Comma Seperated Parameter into Values of a Temporary Table Variable
declare #myTempTableVar Table(param1 varchar(50))
declare #index int =0, #tempString varchar(10)
if charindex(',',#parameter1) > 0
begin
set #index = charindex(',',#parameter1)
while #index > 0
begin
set #tempString = SubString(#parameter1,1,#index-1)
insert into #myTempTableVar values (#tempString)
set #parameter1 = SubString(#parameter1,#index+1,len(#parameter1)-#index)
set #index = charindex(',',#parameter1)
end
set #tempString = #parameter1
insert into #myTempTableVar values (#tempString)
end
else
insert into #myTempTableVar values (#parameter1)
select * from MyTable where MyColumn in (select param1 from #myTempTableVar)
GO
http://sqlmag.com/t-sql/passing-multivalued-variables-stored-procedure
try this it works!!
CHARINDEX (',' + ColumnName + ',', ',' +
REPLACE(#Parameter, ' ', '') + ',') > 0
execute syntax set #Parameter= 'nc1,nc2'

Dynamic SQL Comma-Delimited Value Query

[Update: Using SQL Server 2005]
Hi, what I want to do is query my stored procedure with a comma-delimited list of values (ids) to retrieve rows of data.
The problem I am receiving is a conversion error:
Conversion failed when converting the varchar value ' +
#PassedInIDs + ' to data type int.
The statement in my where-clause and error is:
...
AND (database.ID IN (' + #PassedInIDs + '))
Note: database.ID is of int type.
I was following the article at:
http://www.sql-server-helper.com/functions/comma-delimited-to-table.aspx
but did not complete because of the error.
In my execution script I have:
...
#PassedInIDs= '1,5'
Am I doing something wrong here?
Thank you for your help.
I would strongly suggest that you use the second method from that link. Create a user-defined function that turns your comma-delimited string into a table, which you can then select from easily.
If you do a Google on Erland and "Dynamic SQL" he has a good writeup of the pitfalls that it entails.
For one, you are passing a string to the IN function in SQL. If you look back at the original article, you'll see that instead of issuing a direct SQL statement, it instead is building a string which is the SQL statement to execute.
There is no string evaluation in SQL. This:
database.ID IN (' + #PassedInIDs + ')
will not be turned to:
database.ID IN (1,2,3)
just because the #PassedInIDs parameter happens to contain '1,2,3'. The parameter is not even looked at, because all you have is a string containing " + #PassedInIDs + ". Syntactically, this is equivalent to:
database.ID IN ('Bob')
To make it short, you can't do what you attempt here in SQL. But there are four other possibilities:
you construct the SQL string in the calling language and abandon the stored procedure altogether
you use a dynamic prepared statement with as many parameters in the IN clause as you pan to use
you use a fixed prepared statement with, say, 10 parameters: IN (?,?,?,?,?,?,?,?,?,?), filling only as many as you need, setting the others to NULL
you create a stored procedure with, say, 10 parameters and pass in as many as you need, setting the others to NULL: IN (#p1, #p2, ..., #p10).
I would create a CLR table-valued function:
http://msdn.microsoft.com/en-us/library/ms131103.aspx
In it, you would parse the string apart and perform a conversion to a set of rows. You can then join on the results of that table, or use IN to see if an id is in the list.
You need to treat ufn_CSVToTable like it's a table. So you can join the function:
JOIN ufn_CSVToTable(#PassedInIDs) uf ON database.ID = uf.[String]
I suggest using XML for this in SQL 2005. Somewhat bulkier, but it can be easier. It allows you to select the XML into a table which can then be joined or inserted etc.
Look at Sql Server's OPENXML() if you haven't already.
For example, you could pass in something like:
'12...'
and then use:
exec sp_xml_preparedocument #doc OUTPUT, #xmlParam
SELECT element
FROM OPENXML (#doc, 'Array/Value', 2) WITH (element varchar(max) 'text()')
That should be a start
this may be solved by 6 ways as mentioned in Narayana's article Passing a list/array to an SQL Server stored procedure
And my most strait forward implementation is
declare #statement nvarchar(256)
set #statement = 'select * from Persons where Persons.id in ('+ #PassedInIDs +')'
exec sp_executesql #statement
-
Here is what I have found and tested:
SET QUOTED_IDENTIFIER ON
SET ANSI_NULLS ON
GO
CREATE FUNCTION [dbo].[SplitStrings] ( #IDsList VARCHAR(MAX) )
RETURNS #IDsTable TABLE ( [ID] VARCHAR(MAX) )
AS
BEGIN
DECLARE #ID VARCHAR(MAX)
DECLARE #Pos VARCHAR(MAX)
SET #IDsList = LTRIM(RTRIM(#IDsList)) + ','
SET #Pos = CHARINDEX(',', #IDsList, 1)
IF REPLACE(#IDsList, ',', '') <> ''
BEGIN
WHILE #Pos > 0
BEGIN
SET #ID = LTRIM(RTRIM(LEFT(#IDsList, #Pos - 1)))
IF #ID <> ''
BEGIN
INSERT INTO #IDsTable
( [ID] )
VALUES ( CAST(#ID AS VARCHAR) )
END
SET #IDsList = RIGHT(#IDsList, LEN(#IDsList) - #Pos)
SET #Pos = CHARINDEX(',', #IDsList, 1)
END
END
RETURN
END
GO
Here is how function Call:
SELECT * FROM dbo.SplitStrings('123,548,198,547,965')
Try this:
DECLARE #Ids varchar(50);
SET #Ids = '1,2,3,5,4,6,7,98,234';
SELECT *
FROM sometable
WHERE ','+#Ids+',' LIKE '%,'+CONVERT(VARCHAR(50),tableid)+',%';