Related
A SQL database field has an array as the content (comma separated values, all integer numbers). I need to check if a number is in this array and, if yes, then the record is filtered on a select query.
A simple solution would be: suppose a function like 'Is_In' below:
select * from table where #number Is_In([fieldWithArrayContent])
I expect there is a SQL in function or even a function that can be written and used to solve this issue.
You need a splitter function - for best performane I suggest DelimitedSplit8k. Then you could just do this:
-- Sample Data
DECLARE #sometable TABLE (someid INT IDENTITY, someArray VARCHAR(1000));
INSERT #sometable(someArray)
VALUES('1,2,10,12'),('5,6,7'),('10,12,10,20,10,10'),('1,2,3'); -- id 1 & 3 have the value "10"
-- Variable
DECLARE #number INT = 10;
SELECT DISTINCT t.someid, t.someArray
FROM #sometable AS t
CROSS APPLY dbo.delimitedSplit8k(t.someArray,',') AS s
WHERE #number = s.item;
Returns:
someid someArray
----------- ------------------------
1 1,2,10,12
3 10,12,10,20,10,10
Using the same example as Alan, we can do that without a splitting function with some string manipulation as below:-
DECLARE #sometable TABLE (someid INT IDENTITY, someArray VARCHAR(1000));
INSERT #sometable(someArray)
VALUES('1,2,10,12'),('5,6,7'),('10,12,10,20,10,10'),('1,2,3'); -- id 1 & 3 have the value "10"
-- Variable
DECLARE #number INT = 1 --or 10 will work
Declare #seperator varchar(1)=','
Declare #search varchar(50)=CONCAT('%',#seperator,cast(#number as varchar(10)),#seperator,'%')
SELECT t.someid, t.someArray
FROM #sometable AS t
WHERE CONCAT(#seperator,someArray,#seperator) like #search
Create table test(Names varchar(100) primary key )
insert into test values('Hugeman')
insert into test values('Jack')
insert into test values('William')
insert into test values('Kevin')
insert into test values('Peter')
Query 1:
declare #sql varchar(100)
select #sql = coalesce(#sql+'+','')+Names from test order by names-- where object_id =object_id('temp')
print #sql
This will result as
Hugeman+Jack+Kevin+Peter+William
Query 2
declare #sql varchar(100)
select #sql = coalesce(Names+'+','') from test order by names-- where object_id =object_id('temp')
print #sql
This will results William+
As per the documentation of coalesce, will return the first not null value. So it has to result Hugeman+. But it returns the entire rows.
Why query2 haven't done the same ?
This is not stricly connected to COALESCE.
Try these SELECTs:
DECLARE #sql1 AS VARCHAR(1000)
SELECT #sql1 = ISNULL(#sql1, '') + Names FROM test ORDER BY Names
PRINT #sql1
DECLARE #sql2 AS VARCHAR(1000)
SELECT #sql2 = Names FROM test ORDER BY Names
PRINT #sql2
So what happened? For EACH record selected:
in query 1 you keep adding values to #sql
in query 2 you reset #sql as the last name extracted
I don't understand exactly what you want to obtain from your SELECT, but a better example of COALESCE could be:
CREATE TABLE TEST2(Name VARCHAR(100) PRIMARY KEY, Telephone VARCHAR(10), Mobile VARCHAR(10))
INSERT INTO TEST2 VALUES('Hugeman', 1, 2)
INSERT INTO TEST2 VALUES('Jack', NULL, 3)
INSERT INTO TEST2 VALUES('William', 4, NULL)
INSERT INTO TEST2 VALUES('Kevin', 5, 6)
INSERT INTO TEST2 VALUES('Peter', NULL, NULL)
SELECT Name,
COALESCE(Telephone, Mobile) AS Tel
FROM TEST2
#sql is on the right hand side on the first to get appended
On the last #sql is all alone on the left side and has the value of the last row
Query-1: Appending the values of all rows to #sql
Query-2: Re-setting values to #sql
The below is the rows with order by names
Hugeman
Jack
Kevin
Peter
William
Query-1 Appends all values to #sql.
Query-2 Re-writing values to #sql, so the last row in the list is William so when you print the variable the last re-assigned value is printed.
The COALESCE() function returns the first non null value from the from the columns as parameters in COALESCE() function.
e.g.
SELECTCOALESCE(null,null,1,2) ///return --1
SELECT Id, COALESCE(FirstName, MiddleName, LastName) AS Name
FROM tblEmployee //return 1st non null value from FirstName/MiddleName/LastName
I want to use a variable inside IN clause, similar to this:
Declare #tt NVARCHAR(MAX)
SET #tt = '02ea2b81-07f0-4660-bca1-81563f65bf65','07728975-cb1d-484c-8894-14f5b793cbef','1071ee4f-a214-443f-8694-0b3e9d2dc77e','120d2881-b04f-4707-a925-e4d941f03201','23af54a7-6666-4747-a74a-c2101cda59b0','260d2ce5-f4f0-4a0b-aa0b-3e1d2b5fcfeb','2710a913-13e7-4300-91f1-2646e2f8449e','2cebc482-4917-4aa3-973b-2481619a78e7','2d2269a4-9164-4dae-a732-90448d761509','2d29c707-1c5f-4e00-bd3c-bfd2ec2c6e29','3ead72a1-de91-47e9-8038-cc504a5274ec','40a03f53-7fd7-488d-922d-3435652219cb','43c93954-2e75-4d47-a53f-848eee609cf1','441e1a59-d397-4981-b770-01fb4594152e','4dacc9df-0536-46f6-af5d-78610ed998cd','4e4910ee-db9b-45ba-8872-2819dcefdc2c','4f9fd3ef-ba81-44bb-8c75-7cf6998e115e','60d9c73f-46c3-4ab1-9a4e-5440d18a0fd8','63e0cc57-1803-473f-847d-f3318f70c993','6510de61-9a1d-4f69-bec4-a744ea2bb847','799e2e55-2ba8-4772-8aff-331ed1817225','7be022db-4d37-4964-9005-3de7c6286027','85ba80c3-5c8b-4097-b5c9-c0d55ac6cf2f','8bc45b07-6a65-43c2-a41e-e791b085a053','8ca2d4a7-f4d6-4b56-aa41-42550e3a11b5','8fa7c3f6-e042-4b93-829f-79b8946a909e','ab34d18a-9482-4146-adb2-7e45e32f8cdd','ac43b44b-651c-4a98-a55f-82878cc8c656','ad9f222c-a98e-44eb-af9e-6f083941be9e','af7e8d24-9126-4d9b-a48a-75bf344c3529','b0e95518-0fef-46ba-81f4-0d1356ebc135','b1f1810f-3044-40b3-b218-5bb02d8922bd','b32ebf2b-f247-4032-8a37-285e4c3488a9','b93a8bb7-c62f-47b7-86ba-0421eb67ca14','c5342d7e-1667-47cb-bccf-91c5e8e9f18c','e2cf46f6-a522-4a96-8a84-f1ce3818c364','f01f4010-a192-43ca-a3bf-157379f4779d','f0f168ec-f043-41ef-90d3-3eac68b90334','f99af706-e1bb-42ba-bdf9-348a3b02c25e','fe691dee-b133-4d1c-90a3-8889cd3482d2';
Select * from table where assessmentId IN(#tt)
But this query is failing, saying Incorrect syntax near ','. The same query will work if I will not use variable in IN clause and directly pass the Id's
Select * from table where AssessmentId IN
('02ea2b81-07f0-4660-bca1-81563f65bf65','07728975-cb1d-484c-8894-14f5b793cbef','1071ee4f-a214-443f-8694-0b3e9d2dc77e','120d2881-b04f-4707-a925-e4d941f03201','23af54a7-6666-4747-a74a-c2101cda59b0','260d2ce5-f4f0-4a0b-aa0b-3e1d2b5fcfeb','2710a913-13e7-4300-91f1-2646e2f8449e','2cebc482-4917-4aa3-973b-2481619a78e7','2d2269a4-9164-4dae-a732-90448d761509','2d29c707-1c5f-4e00-bd3c-bfd2ec2c6e29','3ead72a1-de91-47e9-8038-cc504a5274ec','40a03f53-7fd7-488d-922d-3435652219cb','43c93954-2e75-4d47-a53f-848eee609cf1','441e1a59-d397-4981-b770-01fb4594152e','4dacc9df-0536-46f6-af5d-78610ed998cd','4e4910ee-db9b-45ba-8872-2819dcefdc2c','4f9fd3ef-ba81-44bb-8c75-7cf6998e115e','60d9c73f-46c3-4ab1-9a4e-5440d18a0fd8','63e0cc57-1803-473f-847d-f3318f70c993','6510de61-9a1d-4f69-bec4-a744ea2bb847','799e2e55-2ba8-4772-8aff-331ed1817225','7be022db-4d37-4964-9005-3de7c6286027','85ba80c3-5c8b-4097-b5c9-c0d55ac6cf2f','8bc45b07-6a65-43c2-a41e-e791b085a053','8ca2d4a7-f4d6-4b56-aa41-42550e3a11b5','8fa7c3f6-e042-4b93-829f-79b8946a909e','ab34d18a-9482-4146-adb2-7e45e32f8cdd','ac43b44b-651c-4a98-a55f-82878cc8c656','ad9f222c-a98e-44eb-af9e-6f083941be9e','af7e8d24-9126-4d9b-a48a-75bf344c3529','b0e95518-0fef-46ba-81f4-0d1356ebc135','b1f1810f-3044-40b3-b218-5bb02d8922bd','b32ebf2b-f247-4032-8a37-285e4c3488a9','b93a8bb7-c62f-47b7-86ba-0421eb67ca14','c5342d7e-1667-47cb-bccf-91c5e8e9f18c','e2cf46f6-a522-4a96-8a84-f1ce3818c364','f01f4010-a192-43ca-a3bf-157379f4779d','f0f168ec-f043-41ef-90d3-3eac68b90334','f99af706-e1bb-42ba-bdf9-348a3b02c25e','fe691dee-b133-4d1c-90a3-8889cd3482d2');
How can I use variable in IN clause using the first approach?
You will have to insert the values into a temp table.
Something like
DECLARE #TempTable TABLE(
assessmentId VARCHAR(50)
)
INSERT INTO #TempTable
VALUES
('02ea2b81-07f0-4660-bca1-81563f65bf65'),
('07728975-cb1d-484c-8894-14f5b793cbef'),
('1071ee4f-a214-443f-8694-0b3e9d2dc77e'),
('120d2881-b04f-4707-a925-e4d941f03201'),
('23af54a7-6666-4747-a74a-c2101cda59b0'),
('260d2ce5-f4f0-4a0b-aa0b-3e1d2b5fcfeb'),
('2710a913-13e7-4300-91f1-2646e2f8449e'),
('2cebc482-4917-4aa3-973b-2481619a78e7')
SELECT *
FROM table
where AssessmentId IN (SELECT assessmentId FROM #TempTable)
Since you want to specify multiple values, use a data type that supports multiple values (as opposed to a scalar variable). Here we're using a table variable:
Declare #tt table (value nvarchar(50) not null)
insert into #tt (value) values
('02ea2b81-07f0-4660-bca1-81563f65bf65'),('07728975-cb1d-484c-8894-14f5b793cbef'),('1071ee4f-a214-443f-8694-0b3e9d2dc77e'),
('120d2881-b04f-4707-a925-e4d941f03201'),('23af54a7-6666-4747-a74a-c2101cda59b0'),('260d2ce5-f4f0-4a0b-aa0b-3e1d2b5fcfeb'),
...
Select * from table where assessmentId IN(select value from #tt)
how do I loop through a comma separated variable using tsql in a stored proc
So for instance my list would look like this
"1,2,3,4,5,6,7,8,9,10"
and I would loop thought this list and made some necessary table
insert based on this list
You could do it a couple ways, but if this would be a list of ID's it could be done like this as well. It would change your list format a bit.
UPDATE table
SET column = value
WHERE ID in ('1','2','3','4','5','6','7','8','9','10')
You could do a loop as well
DECLARE #List CHAR(100)
DECLARE #ListItem int
DECLARE #Pos int
SET #List = '1,2,3,4,5,6,7,8,9,10'
WHILE LEN(#List) > 0
BEGIN
--Pull Item Frim List
SET #Pos = CHARINDEX(',', #List)
IF #Pos = 0
BEGIN
SET #ListItem = #List
END
ELSE
BEGIN
SET #ListItem = SUBSTRING(#List, 1, #Pos - 1)
END
UPDATE table
SET column = value
WHERE ID = #ListItem
--Remove Item Frim List
IF #Pos = 0
BEGIN
SET #List = ''
END
ELSE
BEGIN
SET #List = SUBSTRING(#List, #Pos + 1, LEN(#List) - #Pos)
END
END
I'd try to avoid looping and insert the rows directly from your comma list.
Use a table values parameter (new in SQl Server 2008). Set it up by creating the actual table parameter type:
CREATE TYPE IntTableType AS TABLE (ID INTEGER PRIMARY KEY)
Your procedure would then be:
Create Procedure up_TEST
#Ids IntTableType READONLY
AS
SELECT *
FROM ATable a
WHERE a.Id IN (SELECT ID FROM #Ids)
RETURN 0
GO
if you can't use table value parameters, see: "Arrays and Lists in SQL Server 2005 and Beyond, When Table Value Parameters Do Not Cut it" by Erland Sommarskog, then there are many ways to split string in SQL Server. This article covers the PROs and CONs of just about every method. in general, you need to create a split function. This is how a split function can be used to insert rows:
INSERT INTO YourTableA (colA)
SELECT
b.col1
FROM dbo.yourSplitFunction(#Parameter) b
I prefer the number table approach to split a string in TSQL but there are numerous ways to split strings in SQL Server, see the previous link, which explains the PROs and CONs of each.
For the Numbers Table method to work, you need to do this one time table setup, which will create a table Numbers that contains rows from 1 to 10,000:
SELECT TOP 10000 IDENTITY(int,1,1) AS Number
INTO Numbers
FROM sys.objects s1
CROSS JOIN sys.objects s2
ALTER TABLE Numbers ADD CONSTRAINT PK_Numbers PRIMARY KEY CLUSTERED (Number)
Once the Numbers table is set up, create this split function:
CREATE FUNCTION [dbo].[FN_ListToTable]
(
#SplitOn char(1) --REQUIRED, the character to split the #List string on
,#List varchar(8000)--REQUIRED, the list to split apart
)
RETURNS TABLE
AS
RETURN
(
----------------
--SINGLE QUERY-- --this will not return empty rows
----------------
SELECT
ListValue
FROM (SELECT
LTRIM(RTRIM(SUBSTRING(List2, number+1, CHARINDEX(#SplitOn, List2, number+1)-number - 1))) AS ListValue
FROM (
SELECT #SplitOn + #List + #SplitOn AS List2
) AS dt
INNER JOIN Numbers n ON n.Number < LEN(dt.List2)
WHERE SUBSTRING(List2, number, 1) = #SplitOn
) dt2
WHERE ListValue IS NOT NULL AND ListValue!=''
);
GO
You can now easily split a CSV string into a table and join on it:
Create Procedure up_TEST
#Ids VARCHAR(MAX)
AS
SELECT * FROM ATable a
WHERE a.Id IN (SELECT ListValue FROM dbo.FN_ListToTable(',',#Ids))
GO
or insert rows from it:
Create Procedure up_TEST
#Ids VARCHAR(MAX)
,#OtherValue varchar(5)
AS
INSERT INTO YourTableA
(colA, colB, colC)
SELECT
ListValue, #OtherValue, GETDATE()
FROM dbo.FN_ListToTable(',',#Ids)
GO
Using CTE (Common Table Expression) is the most elegant solution I think check this question on stackoverflow,
T-SQL: Opposite to string concatenation - how to split string into multiple records
-- Given a CSV string like this:
declare #roles varchar(800)
select #roles = 'Pub,RegUser,ServiceAdmin'
-- Question: How to get roles into a table view like this:
select 'Pub'
union
select 'RegUser'
union
select 'ServiceAdmin'
After posting this, I started playing with some dynamic SQL. This seems to work, but seems like there might be some security risks by using dynamic SQL - thoughts on this?
declare #rolesSql varchar(800)
select #rolesSql = 'select ''' + replace(#roles, ',', ''' union select ''') + ''''
exec(#rolesSql)
If you're working with SQL Server compatibility level 130 then the STRING_SPLIT function is now the most succinct method available.
Reference link: https://msdn.microsoft.com/en-gb/library/mt684588.aspx
Usage:
SELECT * FROM string_split('Pub,RegUser,ServiceAdmin',',')
RESULT:
value
-----------
Pub
RegUser
ServiceAdmin
See my answer from here
But basically you would:
Create this function in your DB:
CREATE FUNCTION dbo.Split(#origString varchar(max), #Delimiter char(1))
returns #temptable TABLE (items varchar(max))
as
begin
declare #idx int
declare #split varchar(max)
select #idx = 1
if len(#origString )<1 or #origString is null return
while #idx!= 0
begin
set #idx = charindex(#Delimiter,#origString)
if #idx!=0
set #split= left(#origString,#idx - 1)
else
set #split= #origString
if(len(#split)>0)
insert into #temptable(Items) values(#split)
set #origString= right(#origString,len(#origString) - #idx)
if len(#origString) = 0 break
end
return
end
and then call the function and pass in the string you want to split.
Select * From dbo.Split(#roles, ',')
Here's a thorough discussion of your options:
Arrays and Lists in SQL Server
What i do in this case is just using some string replace to convert it to json and open the json like a table. May not be suitable for every use case but it is very simple to get running and works with strings and files. With files you just need to watch your line break character, mostly i find it to be "Char(13)+Char(10)"
declare #myCSV nvarchar(MAX)= N'"Id";"Duration";"PosX";"PosY"
"•P001";223;-30;35
"•P002";248;-28;35
"•P003";235;-26;35'
--CSV to JSON
--convert to json by replacing some stuff
declare #myJson nvarchar(MAX)= '[['+ replace(#myCSV, Char(13)+Char(10), '],[' ) +']]'
set #myJson = replace(#myJson, ';',',') -- Optional: ensure coma delimiters for json if the current delimiter differs
-- set #myJson = replace(#myJson, ',,',',null,') -- Optional: empty in between
-- set #myJson = replace(#myJson, ',]',',null]') -- Optional: empty before linebreak
SELECT
ROW_NUMBER() OVER (ORDER BY (SELECT 0))-1 AS LineNumber, *
FROM OPENJSON( #myJson )
with (
col0 varchar(255) '$[0]'
,col1 varchar(255) '$[1]'
,col2 varchar(255) '$[2]'
,col3 varchar(255) '$[3]'
,col4 varchar(255) '$[4]'
,col5 varchar(255) '$[5]'
,col6 varchar(255) '$[6]'
,col7 varchar(255) '$[7]'
,col8 varchar(255) '$[8]'
,col9 varchar(255) '$[9]'
--any name column count is possible
) csv
order by (SELECT 0) OFFSET 1 ROWS --hide header row
Using SQL Server's built in XML parsing is also an option. Of course, this glosses over all the nuances of an RFC-4180 compliant CSV.
-- Given a CSV string like this:
declare #roles varchar(800)
select #roles = 'Pub,RegUser,ServiceAdmin'
-- Here's the XML way
select split.csv.value('.', 'varchar(100)') as value
from (
select cast('<x>' + replace(#roles, ',', '</x><x>') + '</x>' as xml) as data
) as csv
cross apply data.nodes('/x') as split(csv)
If you are using SQL 2016+, using string_split is better, but this is a common way to do this prior to SQL 2016.
Using BULK INSERT you can import a csv file into your sql table -
http://blog.sqlauthority.com/2008/02/06/sql-server-import-csv-file-into-sql-server-using-bulk-insert-load-comma-delimited-file-into-sql-server/
Even the accepted answer is working fine. but I got this function much faster even for thousands of record. create below function and use.
IF EXISTS (
SELECT 1
FROM Information_schema.Routines
WHERE Specific_schema = 'dbo'
AND specific_name = 'FN_CSVToStringListTable'
AND Routine_Type = 'FUNCTION'
)
BEGIN
DROP FUNCTION [dbo].[FN_CSVToStringListTable]
END
GO
CREATE FUNCTION [dbo].[FN_CSVToStringListTable] (#InStr VARCHAR(MAX))
RETURNS #TempTab TABLE (Id NVARCHAR(max) NOT NULL)
AS
BEGIN
;-- Ensure input ends with comma
SET #InStr = REPLACE(#InStr + ',', ',,', ',')
DECLARE #SP INT
DECLARE #VALUE VARCHAR(1000)
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
---Test like this.
declare #v as NVARCHAR(max) = N'asdf,,as34df,234df,fs,,34v,5fghwer,56gfg,';
SELECT Id FROM dbo.FN_CSVToStringListTable(#v)
I was about you use the solution mentioned in the accepted answer, but doing more research led me to use Table Value Types:
These are far more efficient and you don't need a TVF (Table valued function) just to create a table from csv. You can use it directly in your scripts or pass that to a stored procedure as a Table Value Parameter. The Type can be created as :
CREATE TYPE [UniqueIdentifiers] AS TABLE(
[Id] [varchar](20) NOT NULL
)