Related
I am trying to write a stored procedure in SQL Server which will:
Take a list of integers as input ( let's assume these integers are "profile_id")
pick up all the table names which has a column named as "profile_id" into a cursor
loop through the cursor and print the profile_id value when it matches one of them in the input list of params.
Now the problem is: I am executing the procedure like this:
EXEC dbo.de_dup '1234,2345';
and getting a syntax error when trying to execute the commented out line below (Please see the procedure):
set #id = (select profile_id from #tname where profile_id in #a_profile_id );
Questions:
What would be the right way of executing and setting the value inside a cursor?
What is way (in our case) to pass a list of integers to this procedure?
This is my procedure:
ALTER PROCEDURE dbo.de_dup
(#a_profile_id nvarchar(MAX))
AS
DECLARE #tname VARCHAR(max),
#id int;
DECLARE tables_cursor CURSOR FOR
SELECT
a.TABLE_CATALOG +'.'+a.TABLE_SCHEMA + '.'+ a.TABLE_NAME AS table_name
FROM
JobApp.INFORMATION_SCHEMA.COLUMNS a
LEFT OUTER JOIN
JobApp.INFORMATION_SCHEMA.VIEWS b ON a.TABLE_CATALOG = b.TABLE_CATALOG
AND a.TABLE_SCHEMA = b.TABLE_SCHEMA
AND a.TABLE_NAME = b.TABLE_NAME
WHERE
a.COLUMN_NAME = 'profile_id'
GROUP BY
a.TABLE_CATALOG, a.TABLE_SCHEMA, a.TABLE_NAME, a.COLUMN_NAME;
OPEN tables_cursor;
FETCH NEXT FROM tables_cursor INTO #tname;
WHILE ##FETCH_STATUS = 0
BEGIN
PRINT #a_profile_id ;
PRINT #tname ;
--set #id= (select profile_id from #tname where profile_id in #a_profile_id );
--PRINT 'id : ' + #id;
FETCH NEXT FROM tables_cursor INTO #tname;
END;
CLOSE tables_cursor;
DEALLOCATE tables_cursor;
GO;
Please let me know should I provide more clarification. Thanks in advance.
This solution is using the dynamic SQL, As per my knowledge we need to use the dynamic SQL if we have the table name in a variable.
DBFIDDLE working code
Query:
CREATE PROCEDURE dbo.de_dup (#a_profile_id NVARCHAR(MAX))
AS
BEGIN
DECLARE #tname VARCHAR(max)
,#id INT
,#dynamicSQL NVARCHAR(MAX);
DECLARE #matched_tables TABLE (Name NVARCHAR(255));
DECLARE #matched_profileIds TABLE (profile_id INT);
DECLARE #profile_ids NVARCHAR(MAX) = #a_profile_id
INSERT INTO #matched_tables
SELECT DISTINCT a.TABLE_SCHEMA + '.' + a.TABLE_NAME AS table_name
FROM INFORMATION_SCHEMA.COLUMNS a
WHERE a.COLUMN_NAME = 'profile_id'
WHILE EXISTS (
SELECT 1
FROM #matched_tables
)
BEGIN
SELECT TOP 1 #tname = [Name]
FROM #matched_tables
SET #dynamicSQL = CONCAT (
'select profile_id from '
,#tname
,' WHERE '
,''','
,#profile_ids
,','''
,' LIKE '
,'''%,'
,''''
,' + CAST(profile_id AS NVARCHAR(MAX)) + '
,''',%'
,''''
)
PRINT #dynamicSQL;
INSERT INTO #matched_profileIds
EXEC (#dynamicSQL)
DELETE
FROM #matched_tables
WHERE [Name] = #tname
END
SELECT *
FROM #matched_profileIds
END
Dynamic SQL that gets formed is
SELECT profile_id
FROM dbo.TestTable
WHERE ',123,456,789,1011,1213,' LIKE '%,' + CAST(profile_id AS NVARCHAR(MAX)) + ',%'
So I have solved a similar issue with a table-valued function called Split. It splits a delimited list into rows in a table, which you can then JOIN or use as a subquery in your code.
CREATE FUNCTION [dbo].[Split]
(
#char_array varchar(500), #delimiter char(1)
)
RETURNS
#parsed_array table
(
Parsed varchar(50)
)
AS
BEGIN
DECLARE #parsed varchar(50), #pos int
SET #char_array = LTRIM(RTRIM(#char_array))+ #delimiter
SET #pos = CHARINDEX(#delimiter, #char_array, 1)
IF REPLACE(#char_array, #delimiter, '') <> ''
BEGIN
WHILE #pos > 0
BEGIN
SET #parsed = LTRIM(RTRIM(LEFT(#char_array, #pos - 1)))
IF #parsed <> ''
BEGIN
INSERT INTO #parsed_array (Parsed)
VALUES (#parsed)
END
SET #char_array = RIGHT(#char_array, LEN(#char_array) - #pos)
SET #pos = CHARINDEX(#delimiter, #char_array, 1)
END
END
RETURN
END
GO
You would use it like so
SELECT f.Parsed INTO #s FROM dbo.Split(#a_profile_id, ',') f;
Then in your query (only the relevant part for brevity)
select profile_id from #tname where profile_id in(select Parsed from #s);
I left out the set #id= because that will produce unpredictable results for the value of #id if the select statement returns multiple results. But you indicated this is not the actual code anyway so...
Disclaimer: I got the meat of the Split function from someone else online. If I could remember who I would attribute it properly.
I am not sure why this is happening, but when I run some code that takes a long multi-value sub-value string, I'm getting a random error. The error is supposedly that I'm trying to do a insert into a temp table that already exists, though I'm not sure how this is possible since inside the top of the first base loop, I'm dropping the tables if they do exist. That way the prior run can use them, them drop and run through again if need be.
Now I'm almost certain another error is failing, then causing a misleading return message but not sure.
Here is the full stored procedure. Also, I'm a stack dev so if you have any good pointers for this query please feel free to make note. Thanks in advance.
DATA string for testing that would possibly come into this procedure:
MITUTOYO~103-217~DESC~ ^BROWN & SHARPE~73~DESC~ ^MITUTOYO~103-188~DESC~ ^MITUTOYO~103-224A~DESC~ ^MITUTOYO~103-225A~DESC~ ^MITUTOYO~103-225A~DESC~ ^MITUTOYO~103-189A~DESC~ ^
For each one of the main multi values within the ^ character, we would look up the Manufacturer and Model and see if it exist. If so return it, if not return it with a new suggestions list of multi-values in the suggestionsList column. Here is the example of one item sent in, then no matches found so does the fuzzy lookup.
Manufacturer ManufacturerPartNumber ManufacturerDescription Price ItemType MfrFound ModelFound Score SuggestionGroup
Analyzer Network, 0.00, NA, 0, 1, 0, STANDARD,,USE FOR HYPRO STANDARD BORE
GAGES,31.00,6, ^ Harvey Wells,,FORCE GAUGE 0 35 GMS,93.50,0, ^
The full stored procedure:
ALTER PROCEDURE DATA_FuzzyAssetMatchLARGE
/*
PROP: #LISTIN - The entire multi file string sent in from the data file of records.
I=Since the records sent in are a multi value record of sub values meaning (v1.Mfr, v1.Model, v1.Description ^ v2.Mfr, v2.Model, v2.Description ^ ....)
we could in theory have thousands of records to n(3) of string length unknown (n). This being said, we set the value to varchar(max).
varchar(max) unlike varchar which holds (8000 characters max about 65k bytes), varchar(max) can hold 2 gigs worth of data. That equates to about
1 billion characters since in .NET and others one CHAR is a 2 byte system. This holds more than enough.
*/
#LISTIN VARCHAR(MAX)
AS
BEGIN
SET NOCOUNT ON;
SET ARITHABORT OFF
SET ANSI_WARNINGS OFF
SET FMTONLY OFF
declare #errorMessage varchar(150) -- ideally would be nice to append to each row so we knew what failed but not break and continue. Not like that at moment
-- ITEMS USED FOR TOP LEVEL RECURSIVE SPLIT LOOP --
declare #pos INT;
declare #len INT;
declare #value varchar(max); -- the value of the first splits (objects) delimed by the ^ character
-- Counter variables used to loop and build the one column Suggestion of all matching items
declare #MaxCount integer;
declare #Count integer;
declare #suggestionsStringBuilder varchar(350); --holds the built list of any suggestions.
declare #objectManufacturer varchar(100);
declare #objectModel varchar(150);
declare #objectDescription varchar(150); --the context for this instance to run the params selects for Mfr, Model, Desc
declare #counterIndex int = 0;
declare #objectString varchar(250);
--USED FOR SECOND LEVEL RECURSIVE SPLIT LOOP
declare #objectPosVal INT;
declare #objectLenVal INT;
declare #objectSubstring varchar(250); -- the value used for the secondary split (each object properties such as the actual Mfr, Model, Description, so on....)
--cursor declarations
DECLARE #suggestionsListCursor CURSOR;
DECLARE #CURSOR_Mfr varchar(150);
DECLARE #CURSOR_Model varchar(150);
DECLARE #CURSOR_Desc varchar(150);
DECLARE #CURSOR_Type varchar(20);
DECLARE #CURSOR_Price MONEY;
DECLARE #CURSOR_Score int;
--TEMP TABLE TO STORE FUZZY PREDFINED TABLES IN
IF OBJECT_ID('BASE') IS NOT NULL DROP TABLE BASE
--CREATE THE TEMPTABLE TO WORK WITH
CREATE TABLE BASE
(
Manufacturer varchar(150) null,
ManufacturerPartNumber Varchar(100) null,
ManufacturerDescription varchar(150) null,
Score int ,
Price money,
ItemType varchar(20),
ModelFound bit not null,
MfrFound bit not null,
SuggestionGroup varchar(MAX) null
)
--THIS IS THE LOOP TO PREPARE THE DELIM STRING AND DO A FUZZY MATH OR STRAIGHT MATCH ON EACH OBJECT ROW
set #pos = 0;
set #len = 0;
while charindex('^', #LISTIN, #pos+1)>0
begin
if object_id('tempdb..#TEMPPMFR') is not null drop table #TEMPPMFR
if object_id('tmpdb..#TEMPPMFRMODEL') is not null drop table #TEMPPMFRMODEL
-- stop if there was an error --
if(#errorMessage is not null and #errorMessage != '')
break;
set #suggestionsStringBuilder = '';
set #len = charindex('^', #LISTIN, #pos+1) - #pos;
set #VALUE = substring(#LISTIN,#POS,#LEN-1);
set #objectLenVal = 0;
set #objectPosVal = 0;
set #counterIndex = 0;
set #suggestionsStringBuilder = ''
set #objectManufacturer = ''
set #objectModel = ''
set #objectDescription = ''
--THE LIST COMING IN IS A MULTI-DEMENSIONAL ARRAY OR MULTI-VALUE. IT IS A GROUP OF ENTITYIES, THEN EACH
--ENTITYT IS A GROUP OF PROPERTIES. OUR OUTTER LOOP WE SPLIT ON ENTITITIES.
-- EXAMPLE: #F, #M, #D = Manufacturer, odel, Description
while charindex('~', #value, #objectPosVal+1)>0
begin
set #objectLenVal = charindex('~', #value, #objectPosVal+1) - #objectPosVal
set #objectSubstring = substring(#value, #objectPosVal, #objectLenVal)
if(#counterIndex=0)
set #objectManufacturer = LTRIM(RTRIM(#objectSubstring))
else if(#counterIndex=1)
set #objectModel = LTRIM(RTRIM(#objectSubstring))
else
begin
set #objectDescription = LTRIM(RTRIM(#objectSubstring))
break
end
set #objectPosVal = charindex('~', #value, #objectPosVal+#objectLenVal) +1
end
-- ****
-- **** WE HAVE THE MANUFACTURER AND THE MODEL SO JUST RETURN THE DATA
-- ****
if((select top 1 1 FROM Products_OurProducts_Products where Manufacturer = #objectManufacturer and ManufacturerPartNumber = #objectModel) > 0)
begin try
insert into BASE (
Manufacturer
,ManufacturerPartNumber
,ManufacturerDescription
,Price,ItemType
,MfrFound
,ModelFound
,Score
,SuggestionGroup
)
select
POP.Manufacturer
,POP.ManufacturerPartNumber as Model
,POP.Description
,CONVERT(money,POP.Price) as Price
,POP.ItemType
,CAST('1' as bit) as MfrFound
,CAST('1' as bit) as ModelFound
,CAST('-1' as int) as Score
,'' as SuggestionGroup
from
Products_OurProducts_Products as POP
where
POP.Manufacturer = #objectManufacturer
and
POP.ManufacturerPartNumber = #objectManufacturer
end try
begin catch
SET #errorMessage = (
select
'Number: ' + CAST(ERROR_NUMBER() as varchar(15)) + ' Message:' + ERROR_MESSAGE() AS ErrorMessage
);
end catch
else
-- ****
-- **** WE EITHER FOUND MANUFACTURER SO FUZZY ON MODEL OR VICE VERSA
-- ****
begin try
if((select top 1 1 from Products_OurProducts_Products where Manufacturer = #objectManufacturer) > 0)
begin
--we have to build these temp tables os dynamic columns exist such as MfrFound, ModelFound
select
PMFR.Manufacturer
,PMFR.ManufacturerPartNumber
,PMFR.Description AS ManufacturerDescription
,convert(money,PMFR.Price) as Price
,PMFR.ItemType
,cast('1' as bit) as MfrFound
,cast('0' as bit) as ModelFound
,'' as SuggestionGroup
into
#TEMPPMFR
from
Products_OurProducts_Products as PMFR
where
PMFR.Manufacturer = #objectManufacturer
set #SuggestionsListCursor = cursor for
select top 5
P.Manufacturer
,P.ManufacturerPartNumber as Model
,P.ManufacturerDescription AS 'Description'
,P.Price
,fms.score as Score
from #TEMPPMFR as P
cross apply (
select
dbo.FuzzyControlMatch(#objectModel, P.ManufacturerPartNumber) AS score
) as fms
where
P.Manufacturer = #objectManufacturer
order by
fms.score
desc
open #SuggestionsListCursor
fetch next from
#SuggestionsListCursor
into
#CURSOR_Mfr
,#CURSOR_Model
,#CURSOR_Desc
,#CURSOR_Price
,#CURSOR_Score
while ##FETCH_STATUS = 0
begin
if #suggestionsStringBuilder!=''
set #suggestionsStringBuilder=#suggestionsStringBuilder + #CURSOR_Mfr + ',' + #CURSOR_Model + ',' + #CURSOR_Desc + ',' + convert(varchar(20),#CURSOR_Price) + ',' + convert(varchar(4),#CURSOR_Score) + ', ^ '
else
set #suggestionsStringBuilder = #CURSOR_Mfr + ',' + #CURSOR_Model + ',' + #CURSOR_Desc + ',' + convert(varchar(20),#CURSOR_Price) + ',' + convert(varchar(4),#CURSOR_Score) + ', ^ '
fetch next from
#SuggestionsListCursor
into
#CURSOR_Mfr
,#CURSOR_Model
,#CURSOR_Desc
,#CURSOR_Price
,#CURSOR_Score
end
--Now we insert the original Mfr, Model, Desc, and the suggestions list we build
insert into BASE values(
#objectManufacturer
,#objectModel
,#objectDescription
,'0'
,'0'
,'NA'
,'1'
,'0'
,#suggestionsStringBuilder
)
close #SuggestionsListCursor
deallocate #SuggestionsListCursor
end
else
--IF HAVE A FUZZY AT MFR, THEN WE NEED TO GRAB BEST CHOICE AND GO DOWN.
--WE COULD HAVE POSIBLY CANDIDATES FOR THIS SO WHEN TO STOP RECURSIVENESS AND SAY ADDING NEW ENTRY?
begin
--AT MOMENT JUST RETURN TOP FOUND MFR THEN SELECT FROM THAT TO SEE RESULT TESTS
--FIRST LETS SEE IF SENT MODEL EXIST AND IF SO, PULL THAT THEN RANK AGAINST MFR FOR IT
if((select top 1 1 from Products_OurProducts_Products where ManufacturerPartNumber = #objectModel) > 0)
begin
select
Manufacturer
,ManufacturerPartNumber
,Description AS ManufacturerDescription
,CONVERT(money,Price) AS Price
,ItemType
,CAST('0' as bit) as MfrFound
,CAST('1' as bit) as ModelFound
,'' as SuggestionGroup
into
#TEMPPMFRMODEL
from
Products_OurProducts_Products
where
ManufacturerPartNumber = #objectModel
set #SuggestionsListCursor = cursor for
select top 5
P.Manufacturer
,P.ManufacturerPartNumber as Model
,P.ManufacturerDescription as 'Description'
,P.Price
,fms.score as Score
from #TEMPPMFRMODEL AS P
CROSS APPLY (
select
dbo.FuzzyControlMatch(#objectManufacturer, P.Manufacturer) AS score
) AS fms
where
P.ManufacturerPartNumber = #objectModel
order by
fms.score
desc
--OPEN CURSOR NOW--
open #SuggestionsListCursor
-- NOW LOOP THE RESULTS AND BUILD DEMLIMETER STRING OF SUGESTIONS FOR MFR--
fetch next from #SuggestionsListCursor into #CURSOR_Mfr, #CURSOR_Model, #CURSOR_Desc, #CURSOR_Price, #CURSOR_Score
while ##FETCH_STATUS = 0
begin
if #suggestionsStringBuilder != ''
set #suggestionsStringBuilder = #suggestionsStringBuilder + #CURSOR_Mfr + ',' + #CURSOR_Model + ',' + #CURSOR_Desc + ',' + convert(varchar(20),#CURSOR_Price) + ',' + convert(varchar(4),#CURSOR_Score) + ', ^ '
else
set #suggestionsStringBuilder = #CURSOR_Mfr + ',' + #CURSOR_Model + ',' + #CURSOR_Desc + ',' + convert(varchar(20),#CURSOR_Price) + ',' + convert(varchar(4),#CURSOR_Score) + ', ^ '
fetch next from #SuggestionsListCursor
into #CURSOR_Mfr, #CURSOR_Model, #CURSOR_Desc, #CURSOR_Price, #CURSOR_Score
end
insert into BASE values(
#objectManufacturer
,#objectModel
,#objectDescription
,'0'
,'0'
,'NA'
,'1'
,'0'
,#suggestionsStringBuilder
)
close #SuggestionsListCursor ;
deallocate #SuggestionsListCursor ;
end
else
insert into BASE values(
#objectManufacturer
,#objectModel
,#objectDescription
,'0'
,'0'
,'NA'
,'0'
, '0'
, ''
)
end
--THEN SEE IF EXIST USING FUZZY FOUND MFR, TO SEEK MODEL NUMBER ETC.
END TRY
BEGIN CATCH
SET #errorMessage = (
select
'Number: ' + CAST(ERROR_NUMBER() as varchar(15)) + ' Message:' + ERROR_MESSAGE() AS ErrorMessage
);
END CATCH
SET #pos = CHARINDEX('^', #LISTIN, #pos+#len) +1
END
if(#errorMessage is not null and #errorMessage != '')
select #errorMessage as 'ErrorInfo'
else
select
Manufacturer
,ManufacturerPartNumber
,ManufacturerDescription
,Price
,ItemType
,MfrFound
,ModelFound
,Score
,SuggestionGroup
from
BASE
--NOW REMOVE THE TEMP TABLE
If(OBJECT_ID('BASE') Is Not Null)
BEGIN
DROP TABLE BASE
END
IF(OBJECT_ID('tempdb..#TEMPPMFR') IS NOT NULL)
BEGIN
DROP TABLE #TEMPPMFR
END
IF(OBJECT_ID('tmpdb..#TEMPPMFRMODEL') IS NOT NULL)
BEGIN
DROP TABLE #TEMPPMFRMODEL
END
END
GO
I want to display a string in a table format as shown below:
For a string like 'hi,is,1,question,thanks,.,.,n'
I need this result:
column1 column2 column3 column4 ..... column
hi is 1 question ..... n
DECLARE #string VARCHAR(MAX);
SET #string = 'hi,is,1,question,thanks,.,.,n';
DECLARE #SQL VARCHAR(MAX);
SET #SQL = 'SELECT ''' + REPLACE(#string, ',', ''',''') + '''';
EXEC (#SQL);
Result:
Add SELECT ' at beginning and ' at the end of string
Replace all , with ',' inside string
So string 'hi,is,1,question,thanks,.,.,n' is replace by 'SELECT 'hi','is','1','question','thanks','.','.','n''
Executed as SQL query
PS: If you want to use it on column you will have to combine it with CURSOR
Update
DECLARE #table TABLE
(
ID INT IDENTITY,
string VARCHAR(MAX)
);
INSERT INTO #table
VALUES
('This,is,a,string,,n,elements,..');
INSERT INTO #table
VALUES
('And,one,more');
INSERT INTO #table
VALUES
('Ugly,but,works,,,Yay!,..,,,10,11,12,13,14,15,16,17,18,19,..');
SELECT * FROM #table
DECLARE #string_to_split VARCHAR(MAX);
DECLARE #sql_query_to_execute VARCHAR(MAX);
DECLARE #max_elements INT, #id INT, #i INT;
SET #i = 1;
DECLARE string_cursor CURSOR FOR SELECT ID, string FROM #table;
SELECT #max_elements = MAX(LEN(string) - LEN(REPLACE(string, ',', ''))) + 1 -- Find max number of elements */
FROM #table;
IF OBJECT_ID('tempdb..##my_temp_table_for_splitted_columns') <> 0 -- Create new temp table with valid amount of columns
DROP TABLE ##my_temp_table_for_splited_columns;
SET #sql_query_to_execute = 'create table ##my_temp_table_for_splitted_columns ( ID int,';
WHILE #i <= #max_elements
BEGIN
SET #sql_query_to_execute = #sql_query_to_execute + ' Col' + CAST(#i AS VARCHAR(max)) + ' varchar(25), ';
SET #i = #i + 1;
END;
SELECT #sql_query_to_execute = SUBSTRING(#sql_query_to_execute, 1, LEN(#sql_query_to_execute) - 1) + ')';
EXEC (#sql_query_to_execute);
/* Split string for each row */
OPEN string_cursor;
FETCH NEXT FROM string_cursor
INTO #id,
#string_to_split
WHILE ##FETCH_STATUS = 0
BEGIN
SET #i = MAX(LEN(#string_to_split) - LEN(REPLACE(#string_to_split, ',', ''))) + 1; -- check amount of columns for current string
WHILE #i < #max_elements
BEGIN
SET #string_to_split = #string_to_split + ','; -- add missing columns
SET #i = #i + 1;
END;
SET #sql_query_to_execute = 'SELECT ' + CAST(#id AS VARCHAR(MAX)) + ',''' + REPLACE(#string_to_split, ',', ''',''') + '''';
INSERT INTO ##my_temp_table_for_splitted_columns --insert result to temp table
EXEC (#sql_query_to_execute);
FETCH NEXT FROM string_cursor
INTO #id,
#string_to_split;
END;
CLOSE string_cursor;
DEALLOCATE string_cursor;
SELECT *
FROM ##my_temp_table_for_splitted_columns;
This is not trivial. You will find a lot of examples how to split your string in a set of fragments. And you will find a lot of examples how to pivot a row set to a single row. But - adding quite some difficulty - you have an unknown count of columns. There are three approaches:
Split this and return your set with a known maximum of columns
Use a dynamically created statement and use EXEC. But this will not work in VIEWs or iTVFs, nor will it work against a table.
Instead of a column list you return a generic container like XML
with a known maximum of columns
One example for the first was this
DECLARE #str VARCHAR(1000)='This,is,a,string,with,n,elements,...';
SELECT p.*
FROM
(
SELECT A.[value]
,CONCAT('Column',A.[key]+1) AS ColumnName
FROM OPENJSON('["' + REPLACE(#str,',','","') + '"]') A
) t
PIVOT
(
MAX(t.[value]) FOR ColumnName IN(Column1,Column2,Column3,Column4,Column5,Column6,Column7,Column8,Column9 /*add as many as you need*/)
) p
Hint: My approach to split the string uses OPENJSON, not available before version 2016. But there are many other approaches you'll find easily. It's just an example to show you the combination of a splitter with PIVOT using a running index to build up a column name.
Unknown count of columns
And the same example with a dynamically created column list was this:
DECLARE #str VARCHAR(1000)='This,is,a,string,with,n,elements,...';
DECLARE #CountElements INT=LEN(#str)-LEN(REPLACE(#str,',',''))+1;
DECLARE #columnList NVARCHAR(MAX)=
STUFF((
SELECT TOP(#CountElements)
CONCAT(',Column',ROW_NUMBER() OVER(ORDER BY (SELECT 1)))
FROM master..spt_values /*has a lot of rows*/
FOR XML PATH('')
),1,1,'');
DECLARE #Command NVARCHAR(MAX)=
N'SELECT p.*
FROM
(
SELECT A.[value]
,CONCAT(''Column'',A.[key]+1) AS ColumnName
FROM OPENJSON(''["'' + REPLACE(''' + #str + ''','','',''","'') + ''"]'') A
) t
PIVOT
(
MAX(t.[value]) FOR ColumnName IN(' + #columnList + ')
) p;';
EXEC(#Command);
Hint: The statement created is exactly the same as above. But the column list in the pivot's IN is created dynamically. This will work with (almost) any count of words generically.
If you need more help, please use the edit option of your question and provide some more details.
An inlineable approach for a table returning a generic container
If you need this against a table, you might try something along this:
DECLARE #tbl TABLE(ID INT IDENTITY,YourList NVARCHAR(MAX));
INSERT INTO #tbl VALUES('This,is,a,string,with,n,elements,...')
,('And,one,more');
SELECT *
,CAST('<x>' + REPLACE((SELECT t.YourList AS [*] FOR XML PATH('')),',','</x><x>') + '</x>' AS XML) AS Splitted
FROM #tbl t
This will return your list as an XML like
<x>This</x>
<x>is</x>
<x>a</x>
<x>string</x>
<x>with</x>
<x>n</x>
<x>elements</x>
<x>...</x>
You can grab - if needed - each element by its index like here
TheXml.value('/x[1]','nvarchar(max)') AS Element1
I wrote a stored procedure that gets:
"SearchKeys" - keys to search separated by ',' "key1,key2"
"ToSearch" - Tables to search in separated by ',' with colums after ':' separated by '.' "table1:column1.column2,table2:column1.column2"
At the end procedure returns table with name of table and row id were the key was found.
here is the code:
--Search keys in tables
CREATE PROCEDURE [dbo].[Search_All]
(
#SearchKeys nvarchar(50), --Keys to search separated by ','
#ToSearch varchar(200) --Tables to search in separated by ',' with colums after ':' separated by '.'
)
AS
BEGIN
--create table with found values
CREATE TABLE #Results (TargetId int, DBName varchar(20))
--Split SearchKeys to Keys
WHILE LEN(#SearchKeys) > 0
BEGIN
DECLARE #Key NVARCHAR(25)
IF CHARINDEX(',',#SearchKeys) > 0
SET #Key = SUBSTRING(#SearchKeys,0,CHARINDEX(',',#SearchKeys))
ELSE
BEGIN
SET #Key = #SearchKeys
SET #SearchKeys = ''
END
--Split ToSearch to Tables
WHILE LEN(#ToSearch) > 0
BEGIN
DECLARE #TableAndColums VARCHAR(200)
IF CHARINDEX(',',#ToSearch) > 0
SET #TableAndColums = SUBSTRING(#ToSearch,0,CHARINDEX(',',#ToSearch))
ELSE
BEGIN
SET #TableAndColums = #ToSearch
SET #ToSearch = ''
END
SET #ToSearch = REPLACE(#ToSearch,#TableAndColums + ',' , '')
--Split #TableAndColums to Table and Colums
--Select Table
DECLARE #Table VARCHAR(25)
SET #Table = SUBSTRING(#TableAndColums,0,CHARINDEX(':',#TableAndColums))
SET #TableAndColums = REPLACE(#TableAndColums,#Table + ':' , '')
--Split to Colums
WHILE LEN(#TableAndColums) > 0
BEGIN
DECLARE #Column VARCHAR(25)
IF CHARINDEX('.',#TableAndColums) > 0
SET #Column = SUBSTRING(#TableAndColums,0,CHARINDEX('.',#TableAndColums))
ELSE
BEGIN
SET #Column = #TableAndColums
SET #TableAndColums = ''
END
BEGIN
--insert result in to #Results table
INSERT INTO #Results
EXEC
(
'SELECT ' + #Table + '.Id AS ''TargetId'', '''+#Table+''' AS ''DBName''
FROM ' + #Table +
' WHERE ' + #Column + ' LIKE N''%' + #Key + '%'''
)
END
SET #TableAndColums = REPLACE(#TableAndColums,#Column + '.' , '')
END
END
SET #SearchKeys = REPLACE(#SearchKeys,#Key + ',' , '')
END
--return found values
SELECT DISTINCT TargetId , DBname FROM #Results
END
For some reason it searches only for the first key ignoring all the rest keys. I can not find out why this is happening. Please help!
The very first thing I'll warn you about here is your procedure is wide open to injection attack. Injection attack is in and of itself a broad topic. If you're interested, I suggest reading this article. If you do absolutely need this type of interface (i.e. you can't use static typed SQL or something like Entity Framework to take care of the queries for you), you must must MUST make sure that any strings being executed at run time (e.g. #column, #table, #key) are parametrized or bracketed. This procedure, as written, will also fail when an inputted table does not contain an ID column or when an inputted column doesn't exist.
http://www.sommarskog.se/dynamic_sql.html
In terms of how you're doing string splitting, I'd look at the article below. While there's no way to eliminate the need to loop over each table, by putting all your search strings into a table using a string splinting function like the ones mentioned in this article, you can search all search conditions on a single table at once. Something like this:
select *
from #SearchConditions a
inner join dbo.TargetTable b
on b.Name like '%' + a.SearchKey + '%'
http://www.sqlservercentral.com/articles/Tally+Table/72993/
I have code that I'd like to apply to a number of tables but rather than simply copy and replace table names, I'd like to use some kind of loop or cursor to simplify things.
I envision setting up an array of my tables names and using an index to iterate over the list, retrieving each table name and using dynamic SQL to intersperse the table name where applicable in my code.
Since there's no 'array' construct, as far as I know, within SQL, I'm not sure how this would work.
Any ideas about how to go about this?
Here is one way of doing it:
--Declare a table variable to hold your table names (and column names in case needed)
declare #listOfTablesToUpdate table (tableName varchar(100), columnNameToUpdate varchar(50))
--insert the tables that you want to work with.
insert into #listOfTablesToUpdate values ('Table1', 'column2')
insert into #listOfTablesToUpdate values ('Table2', 'column3')
insert into #listOfTablesToUpdate values ('Table3', 'column4')
--Cursor for iterating
declare #tableCursor cursor,
#tableName varchar(100),
#columnName varchar(50)
set #tableCursor = cursor for select * from #listOfTablesToUpdate
open #tableCursor
fetch next from #tableCursor into #tableName, #columnName
while(##fetch_status = 0)
begin
--dynamic sql
declare #sql varchar(max)
--Your logic here...this is just an example
set #sql = 'update '+#tableName+' set '+#columnName+' = '+<value>+' where '+#columnName +' = '+<someothervalue>
exec #sql
fetch next from #tableCursor into #tableName, #columnName
end
close #tableCursor
deallocate #tableCursor
Another approach involves preparing a helper function and a procedure that allow one to apply different SQL statements to each object (table, database, et cetera) in a list. The helper function comes from a SSRS Parameter question and splits apart a comma delimited list into a table.
-- from https://stackoverflow.com/questions/512105/passing-multiple-values-for-a-single-parameter-in-reporting-services
CREATE FUNCTION [dbo].[fn_MVParam]
(#RepParam NVARCHAR(4000), #Delim CHAR(1)= ',')
RETURNS #Values TABLE (Param NVARCHAR(4000))AS
BEGIN
DECLARE #chrind INT
DECLARE #Piece NVARCHAR(100)
SELECT #chrind = 1
WHILE #chrind > 0
BEGIN
SELECT #chrind = CHARINDEX(#Delim,#RepParam)
IF #chrind > 0
SELECT #Piece = LEFT(#RepParam,#chrind - 1)
ELSE
SELECT #Piece = #RepParam
INSERT #Values(Param) VALUES(CAST(#Piece AS VARCHAR))
SELECT #RepParam = RIGHT(#RepParam,LEN(#RepParam) - #chrind)
IF LEN(#RepParam) = 0 BREAK
END
RETURN
END
GO
Below is the code for the ProcessListSQL procedure.
-- #SQL to execute shall include {RP} as the replacement expression that
-- will evaluate to all the items in the comma delimited list
-- Also, please include a double quote " rather than two single quotes ''
-- in the input statement.
CREATE PROCEDURE [dbo].[ProcessListSQL] (
#CommaDelimitedList AS NVARCHAR(MAX),
#SQLtoExecute AS NVARCHAR(MAX) )
AS BEGIN
DECLARE #Statements TABLE
( PK INT IDENTITY(1,1) PRIMARY KEY,
SQLObject NVARCHAR (MAX)
)
SET #SQLtoExecute = REPLACE (#SQLtoExecute, '"', '''')
INSERT INTO #Statements
SELECT PARAM FROM [dbo].[fn_MVParam](#CommaDelimitedList,',')
DECLARE #i INT
SELECT #i = MIN(PK) FROM #Statements
DECLARE #max INT
SELECT #max = MAX(PK) FROM #Statements
DECLARE #SQL AS NVARCHAR(MAX) = NULL
DECLARE #Object AS NVARCHAR(MAX) = NULL
WHILE #i <= #max
BEGIN
SELECT #Object = SQLObject FROM #Statements WHERE PK = #i
SET #SQL = REPLACE(#SQLtoExecute, '{RP}', #Object)
-- Uncommend below to check the SQL
-- PRINT #SQL
EXECUTE sp_executesql #SQL
SELECT #Object = NULL
SELECT #SQL = NULL
SET #i = #i + 1
END
END
GO
The ProcessListSQL procedure take two parameters. The first is a comma delimited string that contains the list of objects that will be cycled through. The second parameter is a string that contains the SQL that will be executed with each of the objects in the first parameter.
In the below example, four databases are created. Note that {rp} is replaced with each of the objects in the first parameter and double quotes are needed in each place where single quotes are needed in the SQL statement.
EXECUTE ProcessListSQL 'rice,apples,cheese,tomatos',
'CREATE DATABASE [{rp}] CONTAINMENT = NONE
ON PRIMARY ( NAME = N"{rp}",
FILENAME = N"D:\data\user\{rp}.mdf" ,
SIZE = 4096KB ,
FILEGROWTH = 1024KB )
LOG ON
( NAME = N"{rp}_log",
FILENAME = N"D:\DATA\USER\{rp}_log.ldf" ,
SIZE = 1024KB ,
FILEGROWTH = 10%)'