Anyone have a sample of how to do a WHILE Loop in 2005 SQL without using a cursor? I'd like to loop through based on a counter
You can do:
DECLARE #i INT = 10
WHILE #i > 0
BEGIN
SELECT #i -- Output: 10, 9, 8, etc...
SET #i = #i - 1
END
But you should first examine more carefully if what you are trying to do can be achieved using set based operations.
DECLARE #intFlag INT
SET #intFlag = 1
WHILE (#intFlag <=10000)
BEGIN
PRINT #intFlag
-- DO YOUR WORK HERE
SET #intFlag = #intFlag + 1
END
GO
Related
This is redshift. I have an update command that looks like this:
UPDATE users
SET birthday = temp_users.birth_date
FROM public.users as gpu
INNER JOIN temp_schema.users_birth_dates_with_row_numbers AS temp_users ON gpu.id = temp_users.id
WHERE mod(temp_users.row_id, 10) = 0 -- this is what I want to change from 0 -> 9
;
For context: I want to run this time times with a different modulo number. How can I do this? Row id is basically created with the row_number function and I want to do this in batches so that it doesn't hold any locks for too long.
SQL WHILE loop syntax and example
The syntax of the WHILE loop in SQL looks like as follows:
WHILE condition BEGIN {...statements...} END
Example:
DECLARE #Counter INT
SET #Counter=0
WHILE ( #Counter <= 9)
BEGIN
PRINT 'The counter value is = ' + CONVERT(VARCHAR,#Counter)
SET #Counter = #Counter + 1
END
This nested while loop is only successfully executing the lowest level loop. It refuses to add 1 to #wKey even though I tell it to SET #wKey = #wKey + 1.... What am I doing wrong here? Does SQL not allow nested loops?
DECLARE #fMinKey INT;
DECLARE #fMaxKey INT;
DECLARE #wMaxKey INT;
DECLARE #wKey INT;
DECLARE #wDate date;
DECLARE #fcStart DATE;
SET #fMinKey = (select min([fcKey]) from dbo.fact_Fc);
SET #fMaxKey = (select max([fcKey]) from dw.fact_Fc);
SET #wMaxKey = (select max([WellKey]) from dw.fact_Fc);
SET #wKey = 1;
SET #wDate =
(select min([fapDate]) from dbo.dim_W where [Key] = #wKey);
SET #fcStart =
(select min([Date]) from dw.fact_Fc where [wKey] = #wKey);
WHILE (#fMinKey <= #fMaxKey)
BEGIN
WHILE (#wKey <= #wMaxKey)
BEGIN
WHILE (#wDate < #fcStart)
BEGIN
INSERT INTO dw.fact_FcTemp2 ([wKey], [Date], [pAmount], [fcKey], [AddedDate])
VALUES (#wKey, #wDate, 0, #fMinKey, CURRENT_TIMESTAMP)
SET #wDate = dateadd(DAY, 1, #wDate)
END
SET #wKey = #wKey + 1
END
SET #fMinKey = #fMinKey + 1
END
The resulting table is only showing [wKey] = 1, but it should have rows for multiple different wKeys
Once when #wDate reach #fcStart looks like you never reset #wDate to initial state
So next loop run again
You need some how to reset #wDate for next loop
Also having 3 loops perhaps is miss of design, sql performances does not like loops at all.
Can you show us data structure and needed result maybe tehe is way to constuct sql whitout 3 nested loops
I would like to declare a variable within an if/else statement in a SQL Server stored procedure. I understand that this is fairly impossible because SQL Server doesn't do memory management with respect to declaration of variables within procedures. Is there a way to have a variable scoped in an if/else statement, then redeclare a variable with the same name in another if/else statement? For example:
create procedure Foo
as
begin
if exists (x)
begin
declare #bob int
set bob = 1
end
else
begin
declare #bob int
set bob = 2
end
end
From books online:
The scope of a variable is the range of Transact-SQL statements that can reference the variable. The scope of a variable lasts from the point it is declared until the end of the batch or stored procedure in which it is declared.
However. Nothing keeps you from doing this:
create procedure Foo as begin
declare #bob int
if exists (x)
begin
set #bob = 1
end
else
begin
set #bob = 2
end
end
No, SQL is pretty funny/weird like that
Declare the variable before the if exists block of code
so
declare #bob int
set #bob = 2
if exists(x)
begin
set #bob = 1
end
Now, take a look at these examples and try to guess what happens
WHILE 1 = 2 --not true of course
BEGIN
DECLARE #VAR INT;
END
SET #VAR = 1;
SELECT #VAR;
This of course works, but it is not initialized every time
DECLARE #loop INT
SET #loop = 0
WHILE #loop <=6
BEGIN
DECLARE #VAR INT
SET #VAR = COALESCE(#VAR,0) + 1
SET #loop = #loop +1
END
SELECT #VAR
is there some reason why you can't do :
declare #bob int
if exists(x)
begin set #bob = 1 end
else
begin set #bob = 2 end
You could resort to using dynamic SQL:
if exists (x)
begin
exec sp_executesql N'
declare #bob int
set #bob = 1
';
end
else
begin
exec sp_executesql N'
declare #bob int
set #bob = 2
';
end
DECLARE #str VARCHAR (MAX);
SELECT #str = COALESCE(#str + CHAR(10), '') +
'EXECUTE CreateDeno ' + CAST(ID AS VARCHAR)
FROM GL_To_Batch_Details
WHERE TYPE = 'C' AND
Deno_ID IS NULL;
--PRINT #str;--SELECT #str;
**EXEC(#str);**
EDITED
Does EXECUTE statement truncate strings to 8,000 chars like PRINT? How can I execute a dynamic SQL statement having more than 8,000 chars?
Any suggestion would be warmly appreciated.
PRINT is limited to 8k in output.
There is also an 8k limit in SSMS results pane.
Go to
tools -> options -> query results
to see the options.
To verify the length of the actual data, check:
SELECT LEN(#str)
When concatenating strings and the result is of type VARCHAR(MAX) and is over 8000 characters, at least one parameter and/or element being used in the concatenation need to be of the VARCHAR(MAX) type otherwise truncation will occur in the resultant string and will not be executable in an EXEC statement.
Example:
DECLARE #sql AS VARCHAR(MAX);
/* DECLARE #someItem AS VARCHAR(100); -- WILL CAUSE TRUNCATION WHEN #sql HAS LEN > 8000 */
DECLARE #someItem AS VARCHAR(MAX); -- All string variables need to be VARCHAR(MAX) when concatenating to another VARCHAR(MAX)
SET #someItem = 'Just assume the resulting #sql variable goes over 8000 characters...';
SET #sql = 'SELECT Something FROM Somewhere WHERE SomeField = ''' + #someItem + '''';
EXEC (#sql);
--PRINT #sql;
More information on MSDN.
"If the result of the concatenation of strings exceeds the limit of
8,000 bytes, the result is truncated. However, if at least one of the
strings concatenated is a large value type, truncation does not
occur."
The default length of a varchar is 30 characters:
CAST (ID AS VARCHAR)
Is it possible that id is longer than 30 characters?
The PRINT command is certainly limited to 8000 chars, irrespective of the length of the output (or whether it is varchar(max)). To work around this you need to output the string in chunks of <8000 chars
Update: In answer to your edit, exec doesn't limit the string length. I've put together the following example to show this:
DECLARE #str VARCHAR (MAX);
;WITH CTE_Count AS
(
select counter = 1
union all
select counter = counter+1
from CTE_Count
Where counter < 2000
)
SELECT
#str=COALESCE(#str + CHAR (10) ,
'' ) + 'select value=' + CAST (counter AS VARCHAR)
from
CTE_Count
Option (MAXRECURSION 0)
PRINT len(#str);--SELECT #str;
exec (#str)
Running this prints the length as 34892 chars, and all 2000 execute statements do run (be warned, it may take a few mins!)
It happens when you concatenate literals if one is not a varchar(max) the result ill be "implicit casted" to varchar(8000).
To generate a literal varchar(max) all parts must be varchar(max).
Note: It happened to me doing updates on varchar(max) columns, never tested with the EXEC command.
Also as noted in previous answers the print command holds a limit but you can try selecting that variable instead of printing it. (also ther's a limit on that select length you can configure on MS-SMS)
I also wanted to see what I was sending to Exec, and was confused by the PRINT limit. Had to write a proc to print in chunks.
CREATE PROCEDURE [dbo].[KFX_PrintVarcharMax]
#strMax varchar(max)
AS
BEGIN
SET NOCOUNT ON;
DECLARE
#index int = 0,
#start int = 1,
#blkSize int = 2000;
WHILE #Start < LEN(#strMax)
BEGIN
IF #start + #blkSize >= LEN(#strMax)
BEGIN
-- If remainder is less than blocksize print the remainder, and exit.
PRINT SUBSTRING(#strMax, #start, #blkSize)
BREAK;
END
-- Else find the next terminator (beyond the blksize)
SET #index = CHARINDEX(CHAR(10), #strMax, #start + #blkSize);
if #index >= #start
BEGIN
PRINT SubString(#strMax, #start, #index - #start + 1)
SET #start = #index + 1;
SET #blkSize = CASE WHEN #start + 2000 < LEN(#strMax) THEN 2000
ELSE LEN(#strMax) - #start + 1 END
END
ELSE
BEGIN
-- No char(10) found. Just print the rest.
PRINT SUBSTRING(#strMax, #start, LEN(#strMax))
BREAK;
END
END
END
I'm am trying to use a levenshtein algorithm I found on the 'net to calculate the closest value to a search term. In order to implement fuzzy term matching. My current query runs about 45 seconds long. I'm hoping I can optimize it. I've already added indexes for the fields that I'm calculated the levenshtein value for. The levenshtein function I found may not be the most optimized and I take no credit in it's implementation. Here is that function:
CREATE FUNCTION [dbo].[LEVENSHTEIN]( #s NVARCHAR(MAX), #t NVARCHAR(MAX) )
/*
Levenshtein Distance Algorithm: TSQL Implementation
by Joseph Gama
http://www.merriampark.com/ldtsql.htm
Returns the Levenshtein Distance between strings s1 and s2.
Original developer: Michael Gilleland http://www.merriampark.com/ld.htm
Translated to TSQL by Joseph Gama
Fixed by Herbert Oppolzer / devio
as described in http://devio.wordpress.com/2010/09/07/calculating-levenshtein-distance-in-tsql
*/
RETURNS INT AS
BEGIN
DECLARE #d NVARCHAR(MAX), #LD INT, #m INT, #n INT, #i INT, #j INT,
#s_i NCHAR(1), #t_j NCHAR(1),#cost INT
--Step 1
SET #n = LEN(#s)
SET #m = LEN(#t)
SET #d = REPLICATE(NCHAR(0),(#n+1)*(#m+1))
IF #n = 0
BEGIN
SET #LD = #m
GOTO done
END
IF #m = 0
BEGIN
SET #LD = #n
GOTO done
END
--Step 2
SET #i = 0
WHILE #i <= #n BEGIN
SET #d = STUFF(#d,#i+1,1,NCHAR(#i)) --d(i, 0) = i
SET #i = #i+1
END
SET #i = 0
WHILE #i <= #m BEGIN
SET #d = STUFF(#d,#i*(#n+1)+1,1,NCHAR(#i)) --d(0, j) = j
SET #i = #i+1
END
--Step 3
SET #i = 1
WHILE #i <= #n BEGIN
SET #s_i = SUBSTRING(#s,#i,1)
--Step 4
SET #j = 1
WHILE #j <= #m BEGIN
SET #t_j = SUBSTRING(#t,#j,1)
--Step 5
IF #s_i = #t_j
SET #cost = 0
ELSE
SET #cost = 1
--Step 6
SET #d = STUFF(#d,#j*(#n+1)+#i+1,1,
NCHAR(dbo.MIN3(
UNICODE(SUBSTRING(#d,#j*(#n+1)+#i-1+1,1))+1,
UNICODE(SUBSTRING(#d,(#j-1)*(#n+1)+#i+1,1))+1,
UNICODE(SUBSTRING(#d,(#j-1)*(#n+1)+#i-1+1,1))+#cost)
))
SET #j = #j+1
END
SET #i = #i+1
END
--Step 7
SET #LD = UNICODE(SUBSTRING(#d,#n*(#m+1)+#m+1,1))
done:
RETURN #LD
END
And here is the query I'm using:
SELECT [Address], [dbo].[LEVENSHTEIN](#searchTerm, [Address]) As LevenshteinDistance
FROM Streets
Order By LevenshteinDistance
I'm not a DBA so please forgive my ignorance in any best practices - that's why I'm here to learn :). I really don't want to offload this processing in the business layer and am hoping to keep it in the data layer but with only 16k records taking 45 seconds to process it's currently not usable. This is only with a small subset of the records which will comprise the entire data store once I'm done processing the input files. Thanks in advance.
If you want it to run really fast, consider creating a dll in C#. It will improve your speed by 150% ;)
Here is my blog that explains you how to do it.
http://levenshtein.blogspot.com/2011/04/how-it-is-done.html
You should read this thread and those links: http://www.vbforums.com/showthread.php?t=575471