T-SQL While Loop and concatenation - sql

I have a SQL query that is supposed to pull out a record and concat each to a string, then output that string. The important part of the query is below.
DECLARE #counter int;
SET #counter = 1;
DECLARE #tempID varchar(50);
SET #tempID = '';
DECLARE #tempCat varchar(255);
SET #tempCat = '';
DECLARE #tempCatString varchar(5000);
SET #tempCatString = '';
WHILE #counter <= #tempCount
BEGIN
SET #tempID = (
SELECT [Val]
FROM #vals
WHERE [ID] = #counter);
SET #tempCat = (SELECT [Description] FROM [Categories] WHERE [ID] = #tempID);
print #tempCat;
SET #tempCatString = #tempCatString + '<br/>' + #tempCat;
SET #counter = #counter + 1;
END
When the script runs, #tempCatString outputs as null while #tempCat always outputs correctly. Is there some reason that concatenation won't work inside a While loop? That seems wrong, since incrementing #counter works perfectly. So is there something else I'm missing?

Looks like it should work but for somereason it seems to think #tempCatString is null which is why you are always getting a null value as nullconcatenated to anything else is still null. Suggest you try with COALESCE() on each of the variables to set them to " " if they are null.

this would be more efficient....
select #tempCatString = #tempCatString + Coalesce(Description,'') + '<br/>' from Categories...
select #fn
also look at concat_null_yields_null as an option to fix your concatenation issue, although I would avoid that route

I agree with keithwarren, but I would always be sure to add an ORDER BY clause to the query. You can then be sure as to exactly what order the values are being concatenated in.
Also, the COALESCE to replace the NULL value with '' will effectively yield blank rows. I don't know if you want them or not, but if not just filter in the WHERE clause instead...
Finally, you appear to have a temp table including the IDs you're interested in. This table can just be included in a JOIN to filter the source table...
DELCARE #output VARCHAR(8000)
SET #output = ''
SELECT
#output = #output + [Categories].Description + '<br/>'
FROM
Categories
INNER JOIN
#vals
ON #vals.val = [Categories].ID
WHERE
[Categories].Description IS NOT NULL
ORDER BY
[Categories].Description

Related

Variable in query doesn't work

I'm facing some problem with a SQL query, I'm creating a variable :
declare #material varchar(500)
declare #typ varchar(200)
declare #strsql varchar(max)
set #typ = 'papier'
select #material = (SELECT + rtrim(ltrim([grupa])) + ','
FROM [test].[dbo].[GT]
WHERE (typ = #typ) FOR XML PATH(''))
set #material = left(#material, len(#material)-1)
set #material = replace(#material, ',' ,''',''')
set #material = '''' + #material + ''''
select #material
The output from variable is :
'test','test2'
And here is a little part of my main code :
SELECT
Number,
isnull(sum((case
when [Group] in ('test','test2')
then isnull(cast([Quantity] as int), 0)
end)), 0) as other
FROM
[dbo].[test-table]
which works correct but when I'm trying to do this like that :
SELECT
Number,
isnull(sum((case
when [Group] in (#material)
then isnull(cast([Quantity] as int), 0)
end)), 0) as other
FROM
[dbo].[test-table]
Output is wrong (different). Can anyone tell me why? It's kinda this same.
It's kind of the same, but it's not the same. What you are looking to get is: in ('test','test2'), what you get is: in (''test','test2'').
You will have to build the rest of your query dynamically, something like:
DECLARE #SQL NVARCHAR(MAX) = 'SELECT
Number
,isnull(sum((case when [Group] in ('+ #material + ')
then isnull(cast([Quantity] as int),0) end)),0) as other
from [dbo].[test-table]'
EXEC sys.sp_executesql #SQL
If you say the output is wrong, it's best to show the actual output and why it is wrong. Saying "Output is different comparing to first query - it should be the same" is not enough, in most cases.
edit:
If you don't want to use dynamic sql (which makes sense), you could do something like this (which is actually much more readable than dynamic):
SELECT Number
, ISNULL(SUM(data),0) as other
FROM (
SELECT Number
, CASE WHEN [Group] IN (SELECT grupa FROM [dbo].[GT] WHERE typ = #typ)
THEN ISNULL(CAST([Quantity] AS INT), 0)
END data
FROM [dbo].[test-table]
) TT
GROUP BY Number
I am making a lot of assumptions about your goal, your schema and your data here though. Without more info, this is probably the best I can do...(There may be some performance tweaking to be done, but this is the essence)
Try to use a function for split the string:
CREATE FUNCTION [dbo].[FN_SplitString](#String nvarchar(4000), #Delimiter char(1))
RETURNS #Results TABLE (Items nvarchar(4000))
AS
BEGIN
IF #String IS NULL RETURN
IF LTRIM(RTRIM(#String)) = '' RETURN
DECLARE #INDEX INT
DECLARE #SLICE nvarchar(4000)
SELECT #INDEX = 1
WHILE #INDEX !=0
BEGIN
SELECT #INDEX = CHARINDEX(#Delimiter,#STRING)
IF #INDEX !=0
SELECT #SLICE = LEFT(#STRING,#INDEX - 1)
ELSE
SELECT #SLICE = #STRING
INSERT INTO #Results(Items) VALUES(#SLICE)
SELECT #STRING = RIGHT(#STRING,LEN(#STRING) - #INDEX)
IF LEN(#STRING) = 0 BREAK
END
RETURN
Then use it in this way:
SELECT
Number
,isnull(sum((case when [Group] in (SELECT Items, FROM FN_SplitString(#material,','))
then isnull(cast([Quantity] as int),0) end)),0) as other
from [dbo].[test-table]
In SQL Server, if you pass a comma separated list as a variable in IN Clause in T-SQL, it would not give error but you will not even get expected result either.
There are two solutions to handle this:
Using Dynamic query
Using Split function

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.

Select and Set not working together in TSQL FUNCTION

I am using a function in SQL SERVER and setting a user variable using select and after that set . But seems set is not working .
--#id is input
Declare #result varchar(MAX)
SELECT #result = INFO FROM USER_INFO WHERE ID= +''+'DOMAIN\'+#id+''
IF #result IS NULL
SET #result = 'DOMAIN\'+#id+''
return (#result)
If user is not found I want to select id not the null values. Buts seems it coming null always
Set is not working in conditional statement.
What I am doing wrong?
Below is the update:
yes Slippery, I checked its coming null. If I set
SET #result ='MY ID'
it works but #result = 'DOMAIN\'+#id did not work
The whole function
FUNCTION [dbo].[USER_INFO]
(
-- Add the parameters for the function here
#id varchar(300)
)
RETURNS varchar(MAX)
AS
BEGIN
Declare #result varchar(MAX)
SELECT #result = INFO FROM USER_INFO WHERE ID= +''+'DOMAIN\'+#id+''
IF #result IS NULL
SET #result = 'DOMAIN\'+#id+''
return (#result)
END
Just in case that really is the entire function, you'll need that first line to be "CREATE FUNCTION" (or "ALTER FUNCTION" if it already exists), instead of just "FUNCTION"...
This will eliminate NULL's from being returned by your function:
DECLARE #result VARCHAR(MAX)
-- Eliminate NULL being passed in so string concatenation doesn't return NULL
IF #id IS NULL
SET #id = ''
-- Set the output value...if nothing exists, set it to 'x'
SET #result = (SELECT TOP 1 ISNULL(INFO,'x') FROM USER_INFO WHERE ID = 'DOMAIN\' + #id);
-- Now, neither #result OR #id will be NULL so the following should always return something other than NULL
IF #result = 'x'
SET #result = 'DOMAIN\' + #id
RETURN (#result)
Based on the other comments, try this:
Declare #result varchar(MAX)
SELECT #result = INFO FROM USER_INFO WHERE ID= 'DOMAIN\'+#id
IF #result IS NULL
SET #result = 'DOMAIN\'+ ISNULL(#id, '')
return (#result)
I don't think you need all that handing with the quotes and pluses
You should try like this. No need of those extra '' single quotes at end
Declare #result varchar(MAX)
SELECT #result = INFO FROM USER_INFO WHERE ID= 'DOMAIN\' + #id
IF #result IS NULL
SET #result = 'DOMAIN\' + #id
return (#result)
Few points to mention: your function name and the table name both are same user_info. Try naming them differently like
FUNCTION [dbo].[Get_USER_INFO]
(
-- Add the parameters for the function here
#id varchar(300)
)
SELECT #result = INFO FROM USER_INFO
Also, you should call your function like
select dbo.Get_USER_INFO('my-user_id')
The difference between select and set is that if set didn't found any value then it will return null but if select didn't found any value then it will show the previous value of the variable, the same case is happening for you when the #result value is null select don't assign any value and return the same previous value instead of assigning null and that is the reason the if statement is false and its not moving to the Set statement.
koppinjo, if the #result variable never set to anything then it should hit the block, but i guess the first time the #result variable value is not null so it's not hitting the block.
please view this for post more clarification.
http://www.mssqltips.com/sqlservertip/1888/when-to-use-set-vs-select-when-assigning-values-to-variables-in-sql-server/

How to use a variable in an Sybase update statement

This query is to prove a concept that I will eventually use to locate all columns with a specific value and then create a name/value pair for export to JSON. But I'm stuck.
I query the list of all columns from the sql table. I would then like to go through the columns in Table1 row by row and update the values using the variable to construct the query. For example as it reads through the list if Col4 = "Old text" then I would like to set the value of Col 4 = "New Text"
DECLARE #c varCHAR(100)
DECLARE ReadData CURSOR
FOR SELECT cname FROM sys.syscolumns WHERE creator = 'dbserver' AND tname = 'Table1'
DECLARE #RowCount INT
SET #RowCount = (SELECT COUNT(cname) FROM sys.syscolumns WHERE creator = 'dbserver' AND tname = 'Table1')
OPEN ReadData
DECLARE #I INT // iterator
SET #I = 1 // initialize
WHILE (#I <= #RowCount)
BEGIN
FETCH NEXT ReadData INTO #c
INSERT INTO serverdb.Table2 (cname)VALUES(#c)// this works inserting all 100 columns in the cname column of Table 2
UPDATE serverdb.Table1 SET #c = 'New text' WHERE #c = 'Old text'// this fails with a syntax error. #c is not being interpreted for the query. Note: If I hard code the #c var (for testing)to a known column name, the query works as well
SET #I = #I + 1
END;
Why won't the update statement recognize the variable? What am I missing?
When you use varibale as mentioned below it is considered as a character string.
UPDATE serverdb.Table1 SET #c = 'New text' WHERE #c = 'Old text'
You need to create a dynamic query. use the execute method to execute your dynamic query
declare #sql varchar(999)
SELECT #sql = 'UPDATE serverdb.Table1 SET '+ #c + '= ''New text'' WHERE '+ #c+ ' = ''Old text'' '
execute(#sql)
Hope this helps

Declare cursor in dynamic 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