While loop without a counter in SQL - help - sql-server

I am running the following code. The code does not loop through all the records that are there in the #result. What is the correct syntax. Do I have to use counter in this case? I want it to be flexible so it print all values that are there in the variable.
declare #result varchar(1000)
select #result = item from items
while #result != ''
begin
print #result
end
Select item from items
result in this query
What I am getting from print is an endless loop that I have to manually stop and it prints this ...
small bed
small bed
small bed
small bed
What is decent way to print all values in the variable. I am talking about text data only, not numbers.

SET #result = ''
SELECT #result = #result + CHAR(10) + CHAR(13) + Item from Items
PRINT #Result
That's all you need. The WHILE loop is superfluous.

This might be difficult to explain, because it appears you're missing a fundamental understanding of the way SQL/databases work.
The code you wrote can only store a single value of the item column from the items table. Think of a varchar(1000) as the equivalent of a string in your favorite procedural language. The select #result = ... statement is essentially going to each row and setting #result to item, which means #result keeps getting replaced with the next value (and, upon conclusion of the statement, it's contents will be the last value in the table).
In general, you shouldn't be using loops in SQL code. That is best left for whatever front-end application needs the data. Of course, there are exceptions.
I would strongly suggest reading a primer on SQL, particularly on set-based operations vs procedural operations.
JNK's solution may give you what you're looking for in this instance, but I would question what exactly you would need that type of solution for in the first place.

You could use a cursor to iterate over all of the data returned from your query.
declare a_cursor cursor for select Item from Items
declare #Item varchar(1000)
open a_cursor
fetch next from a_cursor into #Item
while ##fetch_status = 0
begin
print #Item
fetch next from a_cursor into #Item
end
close a_cursor
deallocate a_cursor

Related

Can't query and put data inside a cursor when using variable inside the query

I have to put a result of a query (single column and value is being pulled) into a variable. I'm trying to use a cursor however I choose the database to query based on a variable here is my query
SELECT productName, price FROM #ShopName.dbo.Products WHERE ProductName = #ProductName
#ShopName variable is being pulled from the database first and assigned to the variable using a cursor. #ProductName variable is being populated by an input parameter coming from API. I have to get ProductName from a specific database (there are multiple databases with products), but the query above throws syntax errors. Additionally when I tried ad hoc query assigned to a variable:
SET #Sql = N'SELECT productName, price FROM ' + QUOTENAME(#ShopName) + '.dbo.Products WHERE ProductName = ' + #ProductName
It doesn't allow to use it in
DECLARE cursorT CURSOR
FOR
#Sql
This throws Incorrect syntax near '#Sql', Expecting '(', SELECT, or WITH
Is there any way to make it possible to use that query in cursor while using the variable with database name in it?
Cursors should be right at the bottom of your bag of techniques, used sparingly and with great care, only when necessary. I can't tell if it's necessary in your case, there's not enough code to know. But I wanted to get that out before continuing.
As a point of purely academic interest, yes, there are some ways you can do this. Two main ways:
Declare a cursor in the dynamic SQL, as Dale suggested. You can still use the cursor in static code which follows the declaration if the cursor is global.
Use dynamic SQL to drop the results into something with scope outside of the dynamic sql, like a temp table. The cursor over the temp table.
1 is just bad. It is likely to result in code which is extremely difficult to understand in future. I include it for curiosity only. 2 is reasonable.
Examples:
-- some dummy schema and data to work with
create table t(i int);
insert t values(1), (2);
-- option 1: declare a cursor dynamically, use it statically (don't do this)
declare #i int;
exec sp_executesql N'declare c cursor global for select i from t';
open c;
fetch next from c into #i;
while (##fetch_status = 0)
begin
print #i;
fetch next from c into #i;
end
close c;
deallocate c;
-- option 2: dynamically dump data to a table, eg a temp table
create table #u(i int);
exec sp_executesql N'insert #u (i) select i from t';
declare c cursor local for select i from #u;
declare #i int;
open c;
fetch next from c into #i;
while (##fetch_status = 0)
begin
print #i;
fetch next from c into #i;
end
close c;
deallocate c;

How to create a SQL function which splits comma separated value?

I want to create a function in SQL Server which takes a comma separated string a parameter, splits it and returns one value at a time. The basic idea here is to call that function from a query. Something like this.
CREATE FUNCTION SPLIT_VALUE(#IN_CSV)
RETURN VARCHAR AS
-- LOGIC TO RETURN A SINGLE VALUE FROM CSV
END
I want to call this function from a stored procedure.
CREATE PROCEDURE DEMO_PROC #IN_CSV VARCHAR(5000), #OUT VARCHAR(5000) OUTPUT AS
BEGIN
SELECT #OUT= CONCAT(A.VALUE1,B.VALUE2) FROM TABLE1 A INNER JOIN TABLE2 B ON A.ID=B.ID WHERE A.ID
IN(--CALL THE FUNCTION AND GET ONE VALUE);
END;
I have to create a loop or cursor to point to a particular value every time. Is this practically possible to? If yes then how can I do that?
Like I mention, you'll have to use a CURSOR to do this, however, the fact you want to do it this way infers a (large) design flaw:
DECLARE #value varchar(8000)
DECLARE Delimited_Values CURSOR FAST_FORWARD
FOR
SELECT [value]
FROM STRING_SPLIT('a,b,c,d,e',',')
OPEN Delimited_Values;
FETCH NEXT FROM Delimited_Values
INTO #value;
WHILE ##FETCH_STATUS = 0 BEGIN
SELECT #value; --Do your stuff here
FETCH NEXT FROM Delimited_Values
INTO #value;
END;
CLOSE Delimited_Values;
DEALLOCATE Delimited_Values;

Use of cursor in SQL

I want to know what is the use of cursor? i search on google and i read that cursor is used for manipulate data like in this example cursor is use .. in this example the select statement Select firstName, lastName FROM myTable returns rows
and when i execute whole query with cursor then this returns same as select statement return so what is the difference? we may use only select instead of cursor? here cursor what is the use ? can anyone explain please in simple words
DECLARE #fName varchar(50), #lName varchar(50)
DECLARE cursorName CURSOR -- Declare cursor
LOCAL SCROLL STATIC
FOR
Select firstName, lastName FROM myTable
OPEN cursorName -- open the cursor
FETCH NEXT FROM cursorName
INTO #fName, #lName
PRINT #fName + ' ' + #lName -- print the name
WHILE ##FETCH_STATUS = 0
BEGIN
FETCH NEXT FROM cursorName
INTO #fName, #lName
PRINT #fName + ' ' + #lName -- print the name
END
CLOSE cursorName -- close the cursor
DEALLOCATE cursorName -- Deallocate the cursor
Many DBA's and developers have love/hate relationship with Cursors. Some will tell you not to use it, others that there is no danger and you can use it. As with many other tools, a cursor is just another tool to be used on some specific scenarios, correctly used can be an awesome tool. But incorrectly used can cause big performance issues.
Cursors work on a row basis and are a perfect sample of the RBAR "Row By Agonizing Row" instead of sets-based operations where tsql shines. The sample you provided is a really bad sample of correct cursor usage, yes, it can show you how a cursor works, but as you commented, that same action can be done with a simple SELECT.
If you do a quick search on your prefered search engine will find lot of good references about cursors, here are some to read:
https://www.brentozar.com/sql-syntax-examples/cursor-example/
RBAR vs. Set based programming for SQL
https://www.red-gate.com/simple-talk/sql/t-sql-programming/rbar-row-by-agonizing-row/

Table Variable inside cursor, strange behaviour - SQL Server

I observed a strange thing inside a stored procedure with select on table variables. It always returns the value (on subsequent iterations) that was fetched in the first iteration of cursor. Here is some sample code that proves this.
DECLARE #id AS INT;
DECLARE #outid AS INT;
DECLARE sub_cursor CURSOR FAST_FORWARD
FOR SELECT [TestColumn]
FROM testtable1;
OPEN sub_cursor;
FETCH NEXT FROM sub_cursor INTO #id;
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE #Log TABLE (LogId BIGINT NOT NULL);
PRINT 'id: ' + CONVERT (VARCHAR (10), #id);
INSERT INTO Testtable2 (TestColumn)
OUTPUT inserted.[TestColumn] INTO #Log
VALUES (#id);
IF ##ERROR = 0
BEGIN
SELECT TOP 1 #outid = LogId
FROM #Log;
PRINT 'Outid: ' + CONVERT (VARCHAR (10), #outid);
INSERT INTO [dbo].[TestTable3] ([TestColumn])
VALUES (#outid);
END
FETCH NEXT FROM sub_cursor INTO #id;
END
CLOSE sub_cursor;
DEALLOCATE sub_cursor;
However, while I was posting the code on SO and tried various combinations, I observed that removing top from the below line, gives me the right values out of table variable inside a cursor.
SELECT TOP 1 #outid = LogId FROM #Log;
which would make it like this
SELECT #outid = LogId FROM #Log;
I am not sure what is happening here. I thought TOP 1 on table variable should work, thinking that a new table is created on every iteration of the loop. Can someone throw light on the table variable scoping and lifetime.
Update: I have the solution to circumvent the strange behavior here.
As a solution, I have declared the table at the top before the loop and deleting all rows at the beginning of the loop.
There are numerous things a bit off with this code.
First off, you roll back your embedded transaction on error, but I never see you commit it on success. As written, this will leak a transaction, which could cause major issues for you in the following code.
What might be confusing you about the #Log table situation is that SQL Server doesn't use the same variable scoping and lifetime rules as C++ or other standard programming languages. Even when declaring your table variable in the cursor block you will only get a single #Log table which then lives for the remainder of the batch, and which gets multiple rows inserted into it.
As a result, your use of TOP 1 is not really meaningful, since there's no ORDER BY clause to impose any sort of deterministic ordering on the table. Without that, you get whatever order SQL Server sees fit to give you, which in this case appears to be the insertion order, giving you the first inserted element of that log table every time you run the SELECT.
If you truly want only the last ID value, you will need to provide some real ordering criterion for your #Log table -- some form of autonumber or date field alongside the data column that can be used to provide the proper ordering for what you want to do.

Inserting a string of form "GUID1, GUID2, GUID3 ..." into an IN statement in TSQL

I've got a stored procedure in my database, that looks like this
ALTER PROCEDURE [dbo].[GetCountingAnalysisResults]
#RespondentFilters varchar
AS
BEGIN
#RespondentFilters = '''8ec94bed-fed6-4627-8d45-21619331d82a, 114c61f2-8935-4755-b4e9-4a598a51cc7f'''
DECLARE #SQL nvarchar(600)
SET #SQL =
'SELECT *
FROM Answer
WHERE Answer.RespondentId IN ('+#RespondentFilters+'''))
GROUP BY ChosenOptionId'
exec sp_executesql #SQL
END
It compiles and executes, but somehow it doesn't give me good results, just like the IN statement wasn't working. Please, if anybody know the solution to this problem, help me.
You should definitely look at splitting the list of GUIDs into a table and joining against that table. You should be able to find plenty of examples online for a table-valued function that splits an input string into a table.
Otherwise, your stored procedure is vulnerable to SQL injection. Consider the following value for #RespondentFilters:
#RespondentFilters = '''''); SELECT * FROM User; /*'
Your query would be more secure parsing (i.e. validating) the parameter values and joining:
SELECT *
FROM Answer
WHERE Answer.RespondentId IN (SELECT [Item] FROM dbo.ParseList(#RespondentFilters))
GROUP BY ChosenOptionId
or
SELECT *
FROM Answer
INNER JOIN dbo.ParseList(#RespondentFilters) Filter ON Filter.Item = Answer.RespondentId
GROUP BY ChosenOptionId
It's slightly more efficient as well, since you aren't dealing with dynamic SQL (sp_executesql will cache query plans, but I'm not sure if it will accurately identify your query as a parameterized query since it has a variable list of items in the IN clause).
You need single quotes around each GUID in the list
#RespondentFilters = '''8ec94bed-fed6-4627-8d45-21619331d82a'', ''114c61f2-8935-4755-b4e9-4a598a51cc7f'''
It looks like you don't have closing quotes around your #RespondentFilters '8ec94bed-fed6-4627-8d45-21619331d82a, 114c61f2-8935-4755-b4e9-4a598a51cc7f'
Since GUIDs do a string compare, that's not going to work.
Your best bet is to use some code to split the list out into multiple values.
Something like this:
-- This would be the input parameter of the stored procedure, if you want to do it that way, or a UDF
declare #string varchar(500)
set #string = 'ABC,DEF,GHIJK,LMNOPQRS,T,UV,WXY,Z'
declare #pos int
declare #piece varchar(500)
-- Need to tack a delimiter onto the end of the input string if one doesn't exist
if right(rtrim(#string),1) ','
set #string = #string + ','
set #pos = patindex('%,%' , #string)
while #pos 0
begin
set #piece = left(#string, #pos - 1)
-- You have a piece of data, so insert it, print it, do whatever you want to with it.
print cast(#piece as varchar(500))
set #string = stuff(#string, 1, #pos, '')
set #pos = patindex('%,%' , #string)
end
Code stolen from Raymond Lewallen
I think you need quotes inside the string too. Try:
#RespondentFilters = '''8ec94bed-fed6-4627-8d45-21619331d82a'',''114c61f2-8935-4755-b4e9-4a598a51cc7f'''
You could also consider parsing the #RespondentFilters into a temporary table.
Tank you all for your ansewers. They all helped a lot. I've dealt with the problem by writing a split function, and it works fine. It's a litte bit overhead from what I could have done, but you know, the deadline is hiding around the corner :)

Resources