Nested While loops in SQL? - sql-server

I want to change my SQL code which generates n table using two nested While loops, like:
DECLARE #count1 INT
SET #count1 = 2012
DECLARE #count2 INT
SET #count2 = 1
WHILE #count1 <= 2016
BEGIN
WHILE #count2 <= 12
create table LGDfigRecov as
select ...
from ...
WHERE FD0.mo_id=count2
AND FD0.an_id= count1
...
SET #count2 += 1
END
SET #count1 += 1
END
How can I change every time the name of new table like "LGDfigRecov +count1+count2"? It means I want to create every time a new table with the name of year and month at the end.

You could use a query like this to create your statements.
WITH
MonthNumbers AS (SELECT * FROM(VALUES('01'),('02'),('03'),('04'),('05'),('06'),('07'),('08'),('09'),('10'),('11'),('12')) AS x(MonthNr))
,YearNumbers AS (SELECT * FROM(VALUES('2012'),('2013'),('2014'),('2015'),('2016')) AS x(YearNr))
SELECT ROW_NUMBER() OVER(ORDER BY YearNr,MonthNr) AS SortInx
,CONCAT('CREATE TABLE LGDfigRecov_',YearNr,'_',MonthNr,' AS ', CHAR(13) + CHAR(10)) +
CONCAT('SELECT ... FROM ... ', CHAR(13) + CHAR(10)) +
CONCAT('WHERE FD0.mo_id=',MonthNr,' AND FD0.an_id=',YearNr,';') AS Cmd
FROM MonthNumbers
CROSS JOIN YearNumbers
I always try to avoid unnecessary loops and procedural approaches...
Check them if they are valid syntax (just copy the output in a new query window)
Open a CURSOR from this SELECT
Use the CURSOR to fetch this row-wise into a variable #Cmd
Use EXEC (#Cmd)

Below code will help to achieve your goal.
DECLARE #count1 INT,
#w_SQL nvarchar(4000);
SET #count1 = 2012
DECLARE #count2 INT
SET #count2 = 1
WHILE #count1 <= 2016
BEGIN
WHILE #count2 <= 12
SET #w_SQL = 'create table LGDfigRecov' + CONVERT(nvarchar(10), #count1) + CONVERT(nvarchar(10), #count2) + 'as
select ...
from ...
WHERE FD0.mo_id=count2
AND FD0.an_id= count1
...'
EXEC sp_executesql #w_SQL
SET #count2 += 1
END
SET #count1 += 1

Related

#FETCH_STATUS as a Variable

I have a problem regarding using a stored procedure to build a view for a database. The stored procedure will use a cursor to cycle through child databases that are in a parent database (I have a company database, that holds facility database names in it). The stored procedure will get the facilities and then append the table to those databases.
For example:
OPEN cur_facdb
FETCH NEXT FROM cur_facdb INTO #fac_dw_db_name
WHILE ##FETCH_STATUS = 0
BEGIN
SET #sql_command = "CREATE VIEW dbo.BMK_LOCAL_FACILITY_VW
AS
SELECT *
FROM " + #fac_dw_db_name + " .dbo.BMK_FACILITY"
FETCH NEXT FROM cur_facdb INTO #fac_dw_db_name
END
CLOSE cur_facdb
DEALLOCATE cur_facdb
This right now will get me a list of 5 or 6 facilities that look like this:
SELECT * FROM facility_aaaa.dbo.BMK_FACILITY
SELECT * FROM facility_aaab.dbo.BMK_FACILITY
SELECT * FROM facility_aaac.dbo.BMK_FACILITY
Etc....
The problem is that I need to append at the end of each select statement UNION ALL, of course, all but the last record that is produced.
Now having said all of that, is there a way to dump the #FETCH_STATUS into a variable, and then append it to the statement like so:
SET #sql_command = "CREATE VIEW dbo.BMK_LOCAL_FACILITY_VW AS
SELECT * FROM " + #fac_dw_db_name + " .dbo.BMK_FACILITY
*CASE WHEN #Fetch_Variable = 0 THEN 'UNION ALL' ELSE '' END*
"
The reason I can't build a standard view and hard code the facilities is because facilities can be dropped and added monthly, so I am trying to dynamically create this view every month.
Thanks for taking a look.
Put the union all at the beginning, then stuff() the create view in place of the first union all. Like this:
declare #tbl table (name varchar(100))
insert into #tbl values ('facility_aaaa'),('facility_aaab'),('facility_aaac')
declare #sql varchar(max), #t varchar(100), #crlf char(2) = char(13) + char(10)
declare cr cursor local for
select name
from #tbl
order by name
set #sql = ''
open cr
fetch next from cr into #t
while ##fetch_status = 0
begin
set #sql = #sql + 'union all' + #crlf + 'select * from ' + #t + '.dbo.BMK_FACILITY' + #crlf
fetch next from cr into #t
end
close cr
deallocate cr
set #sql = stuff(#sql, 1, 9, 'create view dbo.BMK_LOCAL_FACILITY_VW AS')
print #sql
Which prints this:
create view dbo.BMK_LOCAL_FACILITY_VW AS
select * from facility_aaaa.dbo.BMK_FACILITY
union all
select * from facility_aaab.dbo.BMK_FACILITY
union all
select * from facility_aaac.dbo.BMK_FACILITY
Alternatively, you can accomplish the same thing and use a case statement to add union all to all but the last record. Add a new column to your cursor query rn = row_number() over (order by name desc) and then reverse sort it order by rn desc. You get a descending integer, where rn will equal 1 on the last record. Like this:
declare #tbl table (name varchar(100))
insert into #tbl values ('facility_aaaa'),('facility_aaab'),('facility_aaac')
declare #sql varchar(max), #t varchar(100), #crlf char(2) = char(13) + char(10), #rn int
declare cr cursor local for
select name, rn = row_number() over (order by name desc)
from #tbl
order by rn desc
set #sql = 'create view dbo.BMK_LOCAL_FACILITY_VW AS' + #crlf
open cr
fetch next from cr into #t, #rn
while ##fetch_status = 0
begin
set #sql = #sql + 'select * from ' + #t + '.dbo.BMK_FACILITY' + #crlf + case when #rn > 1 then 'union all' + #crlf else '' end
fetch next from cr into #t, #rn
end
close cr
deallocate cr
print #sql

Merging more than one table into one existing table

This is the table creation and insertion query
If not exists(select * from sysobjects where name='hrs')
Create table hrs(hr int)
declare #cnt int =1
while #cnt <= 12
begin
insert into hrs values(#cnt)
set #cnt=#cnt+1
end
The above code gives the output like
but I just want that
declare #cnt1 int = 1
while #cnt1<=12
begin
EXEC('select he'+#cnt1+' = case when hr = 1 then '+#cnt1+' end from hrs')
set #cnt1=#cnt1+1
end
The above code returns the 12 different table but i just want the all records in one table (without creating any new table).
So, how can i do this?
Please help me.
Thanks.
Here the all column are created dynamically through loop
Here are the full query
declare #s varchar(MAX)=''
declare #j int = 1
while #j<=12
begin
if #j = 12
Set #s = #s+'he'+convert(varchar,#j)+'=MAX(case when hr='+convert(varchar,#j)+' then '+convert(varchar,#j)+' end)'
else
set #s = #s+'he'+convert(varchar,#j)+'=MAX(case when hr='+convert(varchar,#j)+' then '+convert(varchar,#j)+' end),'
set #j=#j+1
end
set #s = 'select '+#s+' from hrs'
exec(#s)
Your query doesn't make a lot of sense, but you can build a list of columns and then exec that:
declare #columns nvarchar(max)
declare #cnt int = 1
while #cnt <= 12
begin
set #columns = isnull(#columns + ', ', '') + 'He' + cast(#cnt as nvarchar) +
' = sum(case when hr = ' + cast(#cnt as nvarchar) + ' then hr end)'
end
declare #sql nvarchar(max) = 'select ' + #columns ' + from hr'
exec (#sql)

Convert a SQL function into a stored procedure

I am having trouble converting an UDF into a stored procedure.
Here is what I've got: this is the stored procedure that calls the function (I am using it to search for and remove all UNICODE characters that are not between 32 and 126):
ALTER PROCEDURE [dbo].[spRemoveUNICODE]
#FieldList varchar(250) = '',
#Multiple int = 0,
#TableName varchar(100) = ''
AS
BEGIN
SET NOCOUNT ON;
DECLARE #SQL VARCHAR(MAX), #counter INT = 0
IF #Multiple > 0
BEGIN
DECLARE #Field VARCHAR(100)
SELECT splitdata
INTO #TempValue
FROM dbo.fnSplitString(#FieldList,',')
WHILE (SELECT COUNT(*) FROM #TempValue) >= 1
BEGIN
DECLARE #Column VARCHAR(100) = (SELECT TOP 1 splitdata FROM #TempValue)
SET #SQL = 'UPDATE ' + #TableName + ' SET ' + #Column + ' = dbo.RemoveNonASCII(' + #Column + ')'
EXEC (#SQL)
--print #SQL
SET #counter = #counter + 1
PRINT #column + ' was checked for ' + #counter + ' rows.'
DELETE FROM #TempValue
WHERE splitdata = #Column
END
END
ELSE IF #Multiple = 0
BEGIN
SET #SQL = 'UPDATE ' + #TableName + ' SET ' + #FieldList + ' = dbo.RemoveNonASCII(' + #FieldList + ')'
EXEC (#SQL)
--print #SQL
SET #counter = #counter + 1
PRINT #column + ' was checked for ' + #counter + ' rows.'
END
END
And here is the UDF that I created to help with the update (RemoveNonASCII):
ALTER FUNCTION [dbo].[RemoveNonASCII]
(#nstring nvarchar(max))
RETURNS varchar(max)
AS
BEGIN
-- Variables
DECLARE #Result varchar(max) = '',#nchar nvarchar(1), #position int
-- T-SQL statements to compute the return value
set #position = 1
while #position <= LEN(#nstring)
BEGIN
set #nchar = SUBSTRING(#nstring, #position, 1)
if UNICODE(#nchar) between 32 and 127
set #Result = #Result + #nchar
set #position = #position + 1
set #Result = REPLACE(#Result,'))','')
set #Result = REPLACE(#Result,'?','')
END
if (#Result = '')
set #Result = null
-- Return the result
RETURN #Result
END
I've been trying to convert it into a stored procedure. I want to track how many rows actually get updated when this is run. Right now it just says that all rows, however many I run this on, are updated. I want to know if say only half of them had bad characters. The stored procedure is already set up so that it tells me which column it is looking at, I want to include how many rows were updated. Here is what I've tried so far:
DECLARE #Result varchar(max) = '',#nchar nvarchar(1), #position int, #nstring nvarchar(max), #counter int = 0, #CountRows int = 0, #Length int
--select Notes from #Temp where Notes is not null order by Notes OFFSET #counter ROWS FETCH NEXT 1 ROWS ONLY
set #nstring = (select Notes from #Temp where Notes is not null order by Notes OFFSET #counter ROWS FETCH NEXT 1 ROWS ONLY)
set #Length = LEN(#nstring)
if #Length = 0 set #Length = 1
-- Add the T-SQL statements to compute the return value here
set #position = 1
while #position <= #Length
BEGIN
print #counter
print #CountRows
select #nstring
set #nchar = SUBSTRING(#nstring, #position, 1)
if UNICODE(#nchar) between 32 and 127
begin
print unicode(#nchar)
set #Result = #Result + #nchar
set #counter = #counter + 1
end
if UNICODE(#nchar) not between 32 and 127
begin
set #CountRows = #CountRows + 1
end
set #position = #position + 1
END
print 'Rows found with invalid UNICODE: ' + convert(varchar,#CountRows)
Right now I'm purposely creating a temp table and adding a bunch of notes and then adding in a bunch of invalid characters.
I created a list of 700+ Notes and then updated 2 of them with some invalid characters (outside the 32 - 127). There are a few that are null and a few that are not null, but that doesn't have anything in them. What happens is that I get 0 updates.
Rows found with invalid UNICODE: 0
Though it does see that the UNICODE for the one that it pulls is 32.
Obviously I'm missing something I just don't see what it is.
Here is a set based solution to handle your bulk replacements. Instead of a slow scalar function this is utilizing an inline table valued function. These are far faster than their scalar ancestors. I am using a tally table here. I keep this as a view on my system like this.
create View [dbo].[cteTally] as
WITH
E1(N) AS (select 1 from (values (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))dt(n)),
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
)
select N from cteTally
If you are interested about tally tables here is an excellent article on the topic. http://www.sqlservercentral.com/articles/T-SQL/62867/
create function RemoveNonASCII
(
#SearchVal nvarchar(max)
) returns table as
RETURN
with MyValues as
(
select substring(#SearchVal, N, 1) as MyChar
, t.N
from cteTally t
where N <= len(#SearchVal)
and UNICODE(substring(#SearchVal, N, 1)) between 32 and 127
)
select distinct MyResult = STUFF((select MyChar + ''
from MyValues mv2
order by mv2.N
--for xml path('')), 1, 0, '')
FOR XML PATH(''),TYPE).value('.','NVARCHAR(MAX)'), 1, 0, '')
from MyValues mv
;
Now instead of being forced to call this every single row you can utilize cross apply. The performance benefit of just this portion of your original question should be pretty huge.
I also eluded to your string splitter also being a potential performance issue. Here is an excellent article with a number of very fast set based string splitters. http://sqlperformance.com/2012/07/t-sql-queries/split-strings
The last step here would be eliminate the first loop in your procedure. This can be done also but I am not entirely certain what your code is doing there. I will look closer and see what I can find out. In the meantime parse through this and feel free to ask questions about any parts you don't understand.
Here is what I've got working based on the great help from Sean Lange:
How I call the Stored Procedure:
exec spRemoveUNICODE #FieldList='Notes,Notes2,Notes3,Notes4,Notes5',#Multiple=1,#TableName='#Temp'
The #Temp table is created:
create table #Temp (ID int,Notes nvarchar(Max),Notes2 nvarchar(max),Notes3 nvarchar(max),Notes4 nvarchar(max),Notes5 nvarchar(max))
Then I fill it with comments from 5 fields from a couple of different tables that range in length from NULL to blank (but not null) to 5000 characters.
I then insert some random characters like this:
update #Temp
set Notes2 = SUBSTRING(Notes2,1,LEN(Notes2)/2) + N'￿㹊潮Ņ᯸ࢹᖈư㹨ƶ槹鎤⻄ƺ綐ڌ⸀ƺ삸)䀤ƍ샄)Ņᛡ鎤ꗘᖃᒨ쬵Ğᘍ鎤ᐜᏰ>֔υ赸Ƹ쳰డ촜)鉀௿촜)쮜)Ἡ屰山舰霡ࣆ 耏Аం畠Ư놐ᓜતᏛ֔Ꮫ֨Ꮫ꯼ᓜƒ 邰఍厰ఆ邰఍드)抉鎤듄)繟Ĺ띨)᯸ࢹ䮸ࣉ᯸ࢹ䮸ࣉ샰)ԌƏ￿

updating Null Values in multiple columns of a table (SQL Server)

I have 64 columns and I am trying to automate the loop process. The loop runs but it shows 0 affected rows. If I update the table, column by column, it works.
Any idea why its showing 0 affected rows and what can be done ?
update temp set col1 = 'C' where col1 IS Null; -- works (276 rows affected)--
declare #count as int;
declare #name as varchar(max);
set #count = 2;
while #count < (SELECT Count(*) FROM INFORMATION_SCHEMA.Columns where TABLE_NAME = 'temp')+1
Begin
Set #name = (select name from (select colorder, name from (SELECT *
FROM syscolumns WHERE id=OBJECT_ID('temp')) colnames) as cl where colorder = #count)
Print #name
update temp set #name = 'C' where #name IS Null;
SET #count = #count + 1;
END;
You need to use dynamic sql to update the different columns during runtime as below.
Note: I just added/modified the dynamic sql part.
declare #count as int;
declare #name as varchar(max)
declare #sql nvarchar (1000)
set #count = 2
while #count < (SELECT Count(*) FROM INFORMATION_SCHEMA.Columns where TABLE_NAME = 'temp')+1
Begin
Set #name = (select name from (select colorder, name from (SELECT *
FROM syscolumns WHERE id=OBJECT_ID('temp')) colnames) as cl where colorder = #count)
Print #name
set #sql = N'update temp set ' + #name + '= ''C'' where ' + #name + ' is null ';
exec sp_executesql #sql
SET #count = #count + 1
END;

Need to repeat the result rows based on a column value

I need a help in generating SQL result based on a column value to calculate per day amount.
I have a SQL like below.
select guid, reccharge, dayCareDaysCharged, dayCareHoursPerDay, RATE from tableA.
Here if reccharge = 0 (weekly) then the result should have 7 rows with RATE as RATE/7 for each row (per day amount);
if reccharge = 1 (monthly) then the result should have 30 rows with RATE as RATE/30 for each row;
if reccharge = 2 (yearly) then the result should have 365 rows with RATE as RATE/365 for each row;
if reccharge = 3 (hourly) then there should be a row as calculate it for a day;
How can I achieve this. Please help.
The question seems a little strange to me, but if I've followed it correctly then maybe this;
CREATE TABLE [#temp1] (reccharge tinyint,rdays smallint)
GO
declare #rcount smallint
set #rcount=0
while #rcount<7
begin
set #rcount=#rcount+1
insert #temp1 values (0,7)
end
set #rcount=0
while #rcount<30
begin
set #rcount=#rcount+1
insert #temp1 values (1,30)
end
set #rcount=0
while #rcount<365
begin
set #rcount=#rcount+1
insert #temp1 values (2,365)
end
insert #temp1 values (3,1)
GO
select t1.[guid], t1.reccharge, t1.dayCareDaysCharged, t1.dayCareHoursPerDay, t1.RATE/t2.rdays as RATE from tableA t1
inner join #temp1 t2 on t1.reccharge=t2.reccharge
order by t1.[guid]
I have not idea to do it in single query, but you can do it by using CURSOR as like below :
declare #reccharge int
declare #count int
declare #i int
declare #strSQL nvarchar(max) = 'select 1, 1 where 1 <> 1'
declare CurTest cursor for select reccharge from test
open CurTest
fetch next from CurTest into #reccharge
while ##fetch_status = 0
begin
set #i = 0;
select #count = case when #reccharge = 0 then 7 when #reccharge = 1 then 30 when #reccharge = 2 then 365 else 1 end
while #i < #count
begin
set #strSQL = #strSQL + ' union all select reccharge, Rate/' + cast(#count as varchar(10)) + ' from test where reccharge = ' + cast(#reccharge as varchar(10))
set #i = #i + 1;
end
fetch next from CurTest into #reccharge
end
close CurTest
deallocate CurTest
exec(#strSQL)

Resources