Declare cursor in dynamic sql - sql

This code in a store procedure worked for years, now on the OPEN line I am getting A cursor with the name ... does not exist.
Did something change in sp_executesql that might have caused this to break?
Is there another way of doing this (the need for dynamic SQL is because the #Types param is passed in as a pre-formatted string for the IN clause?)
Select #Query = 'DECLARE cur_person CURSOR FOR
SELECT *
FROM MyTable
WHERE PersonID = #PersonnelID
AND Type in ' + #Types + ' <== formatted list for IN clause
EXEC sp_executesql #Query
OPEN cur_person <== get cursor doesn't exist error

In your example, that means that the cursor is defined locally.
You can define it globally with database option (CURSOR_DEFAULT) but that might not be a good idea.
Another thing that you can do is put all code in the query and execute it.

I don't know why it fails, but here's a split function that avoids the need for the dynamic query:
CREATE FUNCTION StringToTable
(
#p_list varchar(MAX),
#p_separator varchar(5) = ',',
#p_distinct bit = null
)
RETURNS
#ParsedList table
(
element varchar(500)
)
AS
BEGIN
DECLARE #v_element varchar(500), #Pos int, #v_insert_ind bit
SET #p_list = LTRIM(RTRIM(#p_list))+ #p_separator
SET #Pos = CHARINDEX(#p_separator, #p_list, 1)
IF REPLACE(#p_list, #p_separator, '') <> ''
BEGIN
WHILE #Pos > 0
BEGIN
SET #v_insert_ind = 1
SET #v_element = LTRIM(RTRIM(LEFT(#p_list, #Pos - 1)))
IF #v_element <> ''
BEGIN
IF (#p_distinct = 1)
AND (SELECT count(element) FROM #ParsedList WHERE element = #v_element) > 0
SET #v_insert_ind = 0
IF #v_insert_ind = 1
BEGIN
INSERT INTO #ParsedList (element)
VALUES (#v_element)
END
END
--
SET #p_list = RIGHT(#p_list, LEN(#p_list) - #Pos)
SET #Pos = CHARINDEX(#p_separator, #p_list, 1)
END
END
RETURN
END

Related

SQL while inserting into temp tables error stating already exist but I clean up tables prior

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

SQL: how to check palindrome of a string without using reverse function?

I'm using SQL Server and I want to check whether the given string is a palindrome or not - but without using the reverse function.
There are multiple ways to achieve this. One of them is to check first and last character, slicing them if they're equal and continuing the process in a loop.
DECLARE #string NVARCHAR(100)
DECLARE #counter INT
SET #string = 'Your string'
SET #counter = LEN(#string)/2
WHILE (#counter > 0)
BEGIN
IF LEFT(#string,1) = RIGHT(#string,1)
BEGIN
SET #string = SUBSTRING(#string,2,len(#string)-2)
SET #counter = #counter - 1
END
ELSE
BEGIN
PRINT ('Given string is not a Palindrome')
BREAK
END
END
IF(#counter = 0)
PRINT ('Given string is a Palindrome')
A select without loops
DECLARE #Test VARCHAR(100)
SELECT #Test = 'qwerewq'
SELECT CASE WHEN LEFT(#Test, LEN(#Test)/2) =
(
SELECT '' + SUBSTRING(RIGHT(#Test, LEN(#Test)/2), number, 1)
FROM master.dbo.spt_values
WHERE type='P' AND number BETWEEN 1 AND LEN(#Test)/2
ORDER BY number DESC
FOR XML PATH('')
)
THEN 1
ELSE 0
END
Here's an example using LEFT and RIGHT. I use the #count variable to change position, then grab the left-most and right-most char:
DECLARE #mystring varchar(100) = 'redivider'
DECLARE #count int = 1
WHILE (#count < LEN(#mystring) / 2) AND #count <> 0
BEGIN
IF (RIGHT(LEFT(#mystring, #count), 1) <> LEFT(RIGHT(#mystring, #count), 1))
SET #count = 0
ELSE SET #count += 1
END
SELECT CASE WHEN #count = 0
THEN 'Not a Palindrome'
ELSE 'Palindrome'
END [Result]

Using SQL charindex to get string between quotes or multiple strings between occurrence or quotes

we are having to ID some data coming from a bad import and any help would be appreciated.
For instance a string like below and identifier char for the charindex.
SET #InputString = 'The quick brown fox jumped "over" or "under" the log'
SET #IdentifierChar = '"'
The issue we are having is that we can run our test against a hard coded string like the one above and get the result of 'over'. we have tried to put it in a while loop and then we get 'over','or','under'. The Expected Result for us would be only returning 'over', 'under' and not the or.
Our first go at a test was something like below just to see try and split:
DECLARE #InputString Nvarchar(MAX)
DECLARE #IdentifierChar NCHAR(1)
SET #InputString = 'The quick brown fox jumped "over" or "under" the log'
SET #IdentifierChar = '"'
declare #FirstID int
declare #SecondID int
declare #Length int
declare #TargetString Nvarchar(MAX)
Set #FirstID = CHARINDEX(#IdentifierChar,#InputString,1)
Set #SecondID = CHARINDEX(#IdentifierChar,#InputString,#FirstID+1)
Set #Length = #SecondID-#FirstID
Set #TargetString = SUBSTRING(#InputString,#FirstID+1,#Length-1)
Like I said then we literally just threw it in a hard coded loop and set the value of the substring to the last position of the identifier of the specialcharacter just to test and see how the charindex was splitting out the strings between the quotes and we did not think about it getting the 'or' as well.
so here is the dirty loop:
Set #COUNT = 0
Set #Length = 0
WHILE(#COUNT)<3
BEGIN
Set #FirstID = CHARINDEX(#IdentifierChar,#InputString,#Length)
Set #SecondID = CHARINDEX(#IdentifierChar,#InputString,#FirstID+1)
Set #Length = #SecondID-#FirstID
Set #TargetString = SUBSTRING(#InputString,#FirstID+1,#Length-1)
SET #COUNT = #COUNT+1
Set #Length =#SecondID
END
There's probably a better way to parse this out, but here's my minimal modification to your code to make it work, with comments where I changed things:
DECLARE #InputString Nvarchar(MAX)
DECLARE #IdentifierChar NCHAR(1)
SET #InputString = 'The quick brown fox jumped "over" or "under" the log'
SET #IdentifierChar = '"'
declare #FirstID int
declare #SecondID int
declare #Length int
declare #TargetString Nvarchar(MAX)
declare #COUNT int -- added this missing from your code above
Set #COUNT = 0
Set #Length = 0
WHILE(#COUNT)<2 -- only need 2 here now
BEGIN
Set #FirstID = CHARINDEX(#IdentifierChar,#InputString,#Length)
Set #SecondID = CHARINDEX(#IdentifierChar,#InputString,#FirstID+1)
Set #Length = #SecondID-#FirstID
Set #TargetString = SUBSTRING(#InputString,#FirstID+1,#Length-1)
SET #COUNT = #COUNT+1
Set #Length =#SecondID+1 -- added one
print #TargetString -- so we can see what it finds
END
Your main issue was updating #Length at the bottom of your loop -- when you thought you were pointing PAST the double quote after "over", you were actually pointing right at it and finding it a second time as an open-quote before " or ".
Here's a User Defined Function that I wrote and keep in my toolbox. This is a slightly off-label use, but it should work well for you.
If we consider this string to be five substrings, delimited by the four double-quote characters, then we can split on those just take substrings 2 and 4. (Getting a third or fourth quoted value would be as easy as getting substrings 6 or 8) Trying to get an element that doesn't exist will just return a NULL.
After executing the CREATE statement below, which will create the dbo.SPLIT_LIST function, you can call it like this:
declare #InputString varchar(255)
SET #InputString = 'The quick brown fox jumped "over" or "under" the log'
select dbo.SPLIT_LIST(#InputString, '"', 2, ''),
dbo.SPLIT_LIST(#InputString, '"', 4, '')
And you'll get your two output values. What's nice about a function like this is you can just throw it in your select statements and operate over many records at a time, instead of per each.
CREATE function dbo.SPLIT_LIST(
#string nvarchar(max),
#delimiter nvarchar(50),
#i int,
#text_qualifier nvarchar(1)
)
returns nvarchar(max)
/*
returns a selected element from a delimited list
select dbo.SPLIT_LIST_w_Qualifier('"twenty,one","twenty,two","twenty,three"', ',', 2,'"')
returns: 'twenty,two'
Note: can ignore embedded text qualifiers
*/
as
BEGIN
declare #value nvarchar(max),
#d_length int,
#next_delimiter nvarchar(51),
#q_length int, --length of the text qualifier
#trim int,
#using_qualifier int
set #d_length = len(#delimiter)
set #q_length = len(#text_qualifier)
set #string = ltrim(rtrim(#string))
--works by chopping off the leading value from the string each round
while #i > 0 and #string is not null and len(#string) > 0
begin
--if the remaining #string starts with the text qualifier,
--then the currently parsed value should end with the text qualifier+delimiter
if left(#string,1) = #text_qualifier
begin
set #using_qualifier = 1
--chop off leading qualifier
set #string = ltrim(right(#string,(len(#string)-len(#text_qualifier))))
end
else
begin
set #using_qualifier = 0
end
if (#using_qualifier = 0) -- If we are NOT using a text qualifier for this element
begin
if (charindex(#delimiter, #string) > 0) --If there is a remaining delimiter
begin
set #value = ltrim(rtrim(left(#string, charindex(#delimiter, #string)-1)))
set #string = ltrim(rtrim(right(#string, len(#string)-charindex(#delimiter, #string) - #d_length + 1)))
end
else --no remaining delimiters
begin
set #value = #string
set #string = null
end
end
else -- If we ARE using a text qualifier for this element
begin
if (charindex((#text_qualifier+#delimiter), #string) > 0) --If there is a remaining qualifier+delimiter
begin
set #value = ltrim(rtrim(left(#string, charindex((#text_qualifier+#delimiter), #string)-1)))
set #string = ltrim(rtrim(right(#string, len(#string)-charindex((#text_qualifier+#delimiter), #string) - #d_length - #q_length + 1)))
end
else --no remaining qualifier+delimiters
begin
--Does the remaining string END with the text qualifier?
if (charindex(REVERSE(#text_qualifier), REVERSE(#string)) = 1)
begin
set #value = ltrim(rtrim(left(#string, len(#string)-#q_length)))
set #string = null
end
else if (charindex((#text_qualifier), #string) > 0) --Is there a remaining qualifier at all?
begin
set #value = ltrim(rtrim(left(#string, charindex((#text_qualifier), #string)-1)))
set #string = null
end
else --no final closing qualifier
begin
set #value = #string
set #string = null
end
end
end
set #i = #i - 1
--print #value
end
if #i = 0 return #value --should exit here
return NULL --a parse too far exists here
END
Depending on the size of your data set and the complexity of your overall query, you could use a recursive CTE:
;with inputStr as (select 'The quick brown fox jumped "over" or "under" or "around" the log' as s)
,cte as (
select right(s,len(s) - charindex('"',s) + 1) as s --get the first occurence of "
from inputStr
union ALL
select right(right(s,len(s)-1),len(s) - charindex('"',s) + 1) as s --get the second occurence of " in the above string
from cte
where charindex('"',s) > 0 --where " exists
)
select left(s,charindex('"',right(s,len(s)-1))+1) as quoted --select from the first " to the second "
from cte
where (len(s) - len(replace(s,'"',''))) % 2 <> 1 --even number of "
and left(s,charindex('"',right(s,len(s)-1))+1) like '"%"'
Just wanted to update. I continued to toy around with the code and got something to work in case anyone wants to use similar logic in the future. This did what we discussed above.
DECLARE #TargetString NVARCHAR(MAX)
DECLARE #stringLen int
DECLARE #splitTbl TABLE(siteId NVARCHAR(MAX))
DECLARE #idChar NCHAR(1)
SET #TargetString = 'The quick brown fox jumped "over" or "under" the "log"'
SET #stringLen = CHARINDEX(' ', #TargetString)
SET #idChar = '"'
WHILE CHARINDEX(' ', #TargetString) > 0
BEGIN
SET #stringLen = CHARINDEX(' ', #TargetString);
INSERT INTO #splitTbl
SELECT SUBSTRING(#TargetString,1,#stringLen - 1);
SET #TargetString = SUBSTRING(#TargetString, #stringLen + 1,
LEN(#TargetString));
END
DECLARE #buildResults NVARCHAR(MAX)
INSERT INTO #splitTbl
SELECT #TargetString
DECLARE #buildLike NVARCHAR(MAX)
SET #buildLike = '%'+#idChar+'%'
SELECT #buildResults = COALESCE(#buildResults + ', ', '')
+SUBSTRING(siteId, 2, lEN(siteId) - 2)
FROM #splitTbl
WHERE siteId LIKE #buildLike

SQL - parametrized procedure with multiple parameters as array

I have very simple procedure:
CREATE PROCEDURE [Report]
#statusValue varchar(200) = '%'
AS
BEGIN
SELECT * FROM SomeTable
WHERE Something LIKE UPPER(#statusValue)
END
I'd like to provide user set multiple statusValue. Because there is 6 levels of statusValue in my table, I'd like to provide user to define required statusValue into procedure parameters - something like array.
I don't know, how it exactly works - I'm very new in this area - but I'm supposing to have procedure like this one:
EXEC Report #statusValue = 'statusValue1|statusValue2|statusValue3'
Do you happen to know, how can I adjust my procedure to have required output. Many thanks in advance.
Use following user defined function to return values from delimited string (say pipe):
CREATE FUNCTION [dbo].[stringlist_to_table]
(#list varchar(8000),
#delimiter nchar(1) = N',')
RETURNS #tbl TABLE (value varchar(8000)) AS
BEGIN
DECLARE #pos int,
#tmpstr varchar(8000),
#tmpval varchar(8000);
SET #tmpstr = #list;
SET #pos = charindex(#delimiter , #tmpstr);
WHILE #pos > 0
BEGIN
SET #tmpval = ltrim(rtrim(left(#tmpstr, #pos - 1)))
INSERT #tbl (value) VALUES(#tmpval)
SET #tmpstr = substring(#tmpstr, #pos + 1, len(#tmpstr))
SET #pos = charindex(#delimiter, #tmpstr)
END
INSERT #tbl(value) VALUES (ltrim(rtrim(#tmpstr)));
RETURN
END
Now use the following procedure to get the required output:
CREATE PROCEDURE [Report]
#statusValue varchar(200) = '%'
AS
BEGIN
DECLARE #iterator INT = 1;
DECLARE #total INT = 1;
DECLARE #keyword VARCHAR(100) = '';
SELECT ROW_NUMBER() OVER (ORDER BY value) SNo, value keyword
INTO #temp
FROM dbo.stringlist_to_table(#statusValue, '|')
SELECT *
INTO #output
FROM SomeTable
WHERE 1 = 0;
SELECT #total = MAX(SNo), #iterator = MIN(Sno)
FROM #temp
WHILE (#iterator <= #total)
BEGIN
SELECT #keyword = '%' + keyword + '%'
FROM #temp
WHERE SNo = #iterator;
INSERT INTO #output
SELECT *
FROM SomeTable
WHERE Something LIKE #keyword
SET #iterator = #iterator + 1;
END
SELECT *
FROM #output;
DROP TABLE #output, #temp;
END
You need the split function in this case. As there is no way to handle what you need. Another approach to add many variables. But in your case it will be enough to create split function and use it to parse your string. Please find the split function below:
CREATE FUNCTION [dbo].[ufnSplitInlineStringToParameters] (
#list NVARCHAR(MAX)
,#delim NCHAR(1) = ','
)
RETURNS TABLE
AS
RETURN
WITH csvTbl(START, STOP) AS (
SELECT START = CONVERT(BIGINT, 1)
,STOP = CHARINDEX(#delim, #list + CONVERT(NVARCHAR(MAX), #delim))
UNION ALL
SELECT START = STOP + 1
,STOP = CHARINDEX(#delim, #list + CONVERT(NVARCHAR(MAX), #delim), STOP + 1)
FROM csvTbl
WHERE STOP > 0
)
SELECT LTRIM(RTRIM(CONVERT(NVARCHAR(4000), SUBSTRING(#list, START, CASE
WHEN STOP > 0
THEN STOP - START
ELSE 0
END)))) AS VALUE
FROM csvTbl
WHERE STOP > 0
GO
One of the simplest way to achieve this is using a custom type. Sample snippet as follows:
CREATE TYPE dbo.StatusList
AS TABLE
(
statusValue varchar(200)
);
GO
CREATE PROCEDURE [Report]
#statusValue AS dbo.StatusList READONLY
AS
BEGIN
SELECT * FROM SomeTable
WHERE Something IN (SELECT * FROM #statusValue)
END
GO
--EDIT---
If you are using SSMS, you can execute the procedure as follows:
DECLARE #status dbo.StatusList
INSERT INTO #status VALUES('Pending')
EXEC [Report] #status

SQL Computed Column used in queries causing performance issues

I have a table with columns A,B,C and another table with column username.
In column C i have a function getName(A).
getName(A) is roughly
CREATE FUNCTION [dbo].[GetName] (
#name VARCHAR(100)
)
RETURNS VARCHAR(100)
WITH SCHEMABINDING
AS
BEGIN
DECLARE #retval VARCHAR(100);
DECLARE #nextWord VARCHAR(100);
SET #retval = #name
IF EXISTS (Select 1 from someTable where username = SUSER_NAME())
BEGIN
SET #name = Replace(Replace(Replace(RTRIM(LTRIM(#name)),',',' ,'),'(','( '),')',' )')
SET #retval = LEFT(#name, 1);
WHILE CHARINDEX(' ', #name, 1) > 0
BEGIN
SET #name = LTRIM(RIGHT(#name, LEN(#name) - CHARINDEX(' ', #name, 1)));
IF CHARINDEX(' ', #name, 1) > 0
BEGIN
SET #nextWord = LTRIM(LEFT(#name, CHARINDEX(' ', #name, 1) - 1))
END
ELSE
BEGIN
SET #nextWord = #name
END
SET #retval += ' ' + CASE
WHEN #nextWord IN (
'List'
,'Of'
,'Different'
,'Words'
)
THEN #nextWord
WHEN ISNUMERIC(#nextWord) = 1
THEN #nextWord
WHEN ISDATE(#nextWord) = 1
THEN #nextWord
ELSE LEFT(#nextWord, 1)
END
END
END
RETURN #retval;
END
Now when I try to use column C in queries it basically times out. Trying to figure out if there is a way to make it faster. If the computed function for C is just referencing A it runs normal. but when its either choose A or choose the first letter of each word in A along with words in the allowed list it goes slow. If I make this function true always it goes relatively quick. I tried with the exists and still it is not fast.
Any advice would be greatly appreciated.
EDIT: I updated the function above. I should note, when the EXISTS query returns True it runs quickly, when it returns false it runs slow. That is the bigger dilemma that I am confused about.
This is, alas, a very reasonable function, because it is the only way to create a computed column that references another table.
The following code is safer:
BEGIN
DECLARE #retval VARCHAR(100);
IF EXISTS (SELECT 1 FROM someTable WHERE username = SUSER_NAME)
BEGIN
SET #retval = LEFT(#name, 1);
END
ELSE SET #retval = #name;
RETURN #retval;
END
The isnull() method is clever, but the original code would generate an error if there were multiple rows in the table that matched the where condition. Also, it requires considering all values in the table, rather than just the first. EXISTS knows to stop at the first matching row.
You want an index on sometable(username). You can do this either by creating a unique constraint or by creating the index explicitly.