Learning Dynamic SQL - sql

I am trying to learn dynamic SQL
I have set up a basic example and I am stuck, I don't understand why the #Month Variable wont update with each loop.
Here is the example I made:
declare #M int = 1
declare #SQL varchar(max) = ''
declare #Cnt int = 1
declare #Month varchar(25) = (datename(m, '2016-' + convert(varchar(2), #M) + '-01'))
while #Cnt <= 12
begin
set #SQL = #SQL + 'select ' + convert(varchar(25), #M) + ' as M, ''' + #Month + ''' as Month'
if #Cnt <> 12 set #SQL = #SQL + ' Union All '
set #Cnt = #Cnt + 1
set #M = #M + 1
end
exec (#SQL)
The results :
M Month
1 January
2 January
3 January
4 January
5 January
6 January
7 January
8 January
9 January
10 January
11 January
12 January
I am looking to generate the the MonthName from January to December
Why does the Month Name not Update Each loop?
Driving me mad and I cant wait until I get to work tomorrow to ask my Boss. Need to sleep tonight.
much appreciated.

you advanced the #m. but not the name of the month:
declare #M int = 0
declare #SQL varchar(max) = ''
declare #Cnt int = 1
declare #Month varchar(25)
while #Cnt <= 12
begin
set #M = #M + 1
set #Month = datename(m, '2016-' + convert(varchar(2), #M) + '-01')
set #SQL = #SQL + 'select ' + convert(varchar(25), #M) + ' as M, ''' + #Month + ''' as Month'
if #Cnt <> 12 set #SQL = #SQL + ' Union All '
set #Cnt = #Cnt + 1
end
exec (#SQL)

You need to add date every time in loop just like a #M variable. take another variable for date.
DECLARE #SQL varchar(max) = ''
DECLARE #Cnt int = 1
DECLARE #TempDate DateTime = Cast('2016-03-01' As DateTime)
Then in while loop add month
WHILE #Cnt <= 12
BEGIN
set #SQL = #SQL + 'select ' + CAST(MONTH(#TempDate) AS VARCHAR(2)) + ' as M, ''' + DateName(m,#TempDate) + ''' as Month '
IF #Cnt <> 12 SET #SQL = #SQL + ' Union All '
SET #TempDate = DateAdd(m, #Cnt, #TempDate)
SET #Cnt = #Cnt +1
END
EXEC (#SQL)

You are not working with #Month inside WHILE, here is the code you are looking for:
DECLARE #M INT = 1
DECLARE #SQL VARCHAR(MAX) = ''
DECLARE #Cnt INT = 1
DECLARE #Month VARCHAR(25)
WHILE #Cnt <= 12
BEGIN
SET #Month = ( DATENAME(m, '2016-' + CONVERT(VARCHAR(2), #M) + '-01') )
SET #SQL = #SQL + 'select ' + CONVERT(VARCHAR(25), #M) + ' as M, ''' + #Month + ''' as Month'
IF #Cnt <> 12
SET #SQL = #SQL + ' Union All '
SET #Cnt = #Cnt + 1
SET #M = #M + 1
END
EXEC (#SQL)

Related

Join using dynamic column names SQL Server

I have a table which can output any number of different columns (from 'Level1' to 'Level'N).
I need to perform a left join on each of these dynamic columns against a CTE
I have written the following script but keep getting this error:
Msg 102, Level 15, State 1, Line 15 Incorrect syntax near '10'.
To troubleshoot, I have tried removing the each of the variables in the CTE with no luck.
Any help would be much appreciated!
DECLARE #rel varchar(4) = CAST('X112' AS varchar(4))
DECLARE #todaysdate date = CONVERT(date,GETDATE())
--create cte
DECLARE #sqltext varchar(MAX) =
' WITH CTE AS
(
SELECT
ID
,STARTDATE
,ENDDATE
,NEWID
FROM Tbl
WHERE TYPE = ''' + #rel + '''
AND ENDDATE >= ' + CAST(#todaysdate AS varchar(30)) +' AND STARTDATE <= ' + CAST(#todaysdate AS varchar(30)) +'
)
SELECT ID, NEWID, Level';
--find max lvl, convert to str
DECLARE #counter int = (SELECT MAX(lvl) FROM tbl2)
DECLARE #counterstring varchar(3)
SET #counterstring = CAST(#counter AS varchar(3))
WHILE #counter != 0
BEGIN
SET #sqltext = #sqltext + #counterstring + ' INTO tbl3 '
+ ' FROM tbl2 a '
+ ' LEFT JOIN CTE c ON a.Level' + #counterstring + ' = c.NEWID'
SET #counter = #counter - 1
END
EXEC(#sqltext)
--edited version
DECLARE #rel varchar(4) = CAST('X112' AS varchar(4))
DECLARE #todaysdate date = CONVERT(date,GETDATE())
DECLARE #sqltext varchar(MAX) =
' WITH CTE AS
(
SELECT
ID
,STARTDATE
,ENDDATE AS mgmt_ENDDA
,NEWID
FROM tbl
WHERE SUBTY = ''' + #rel + '''
AND ENDDATE >= ' + CAST(#todaysdate AS varchar(30)) +' AND STARTDATE <= ' + CAST(#todaysdate AS varchar(30)) +'
)
INSERT INTO tbl3
SELECT ID, NEWID, Level';
DECLARE #counter int = (SELECT MAX(lvl) FROM tbl2)
DECLARE #counterstring varchar(3)
WHILE #counter != 0
BEGIN
SET #counterstring = CAST(#counter AS varchar(3))
SET #sqltext = #sqltext + #counterstring
+ ' FROM tbl2 a '
+ ' LEFT JOIN CTE c ON a.Level' + #counterstring + ' = c.NEWID'
SET #counter = #counter - 1
END
EXEC(#sqltext)
Since select * into query creates a new table each time, I am assuming that you are trying to create 'n' number of tables for 'n' number of levels, with the result obtained by joining with nth column. I have 2 suggestions for you
Bring the select query within the while loop and append ';' at the end of the loop to split select queries.
instead of INTO tbl3 use INTO tbl + #counterstring as select * into will create new table
Hope this helps you
Can you change it like this and try again?
--edited version
DECLARE #rel varchar(4) = CAST('A012' AS varchar(4))
DECLARE #todaysdate date = CONVERT(date,GETDATE())
DECLARE #sqltext varchar(MAX) =
' WITH CTE AS
(
SELECT
ID
,STARTDATE
,ENDDATE AS mgmt_ENDDA
,NEWID
FROM tbl
WHERE SUBTY = ''' + #rel + '''
AND ENDDATE >= ' + CAST(#todaysdate AS varchar(30)) +' AND STARTDATE <= ' + CAST(#todaysdate AS varchar(30)) +'
)
INSERT INTO tbl3';
DECLARE #counter int = (SELECT MAX(lvl) FROM tbl2)
DECLARE #counterstring varchar(3)
WHILE #counter != 0
BEGIN
SET #counterstring = CAST(#counter AS varchar(3))
SET #sqltext = #sqltext + CHAR(10) +'SELECT ID, NEWID, Level'+#counterstring
SET #sqltext = #sqltext + ' FROM tbl2 a '
+ ' LEFT JOIN CTE c ON a.Level' + #counterstring + ' = c.NEWID'
SET #counter = #counter - 1
IF #counter <> 0
SET #sqltext = #sqltext + CHAR(10) + ' UNION '
END
EXEC(#sqltext)
You can try 'UNION ALL' instead of 'UNION' if you dont want to get rid of duplicate data. Hope this helps

SQL Calculating balance based on inventory and transactions

I am creating a stored procedure that calculates a users inventory status.
Imagine the following table called user_inventory with many numbered columns:
id_inventory id_user 0 1 2 3
------------ ------- - - - -
2 4 5 0 14 21
And another one called user_transactions
id_tran id_user 0 1 2 3
------- ------- - - - -
54 4 1 0 3 7
55 4 2 0 9 8
56 4 1 0 2 4
What I would like is a way to calculate the remaining inventory status for each column after subtracting a sum of all of the users transactions, like so:
id_availableInventory id_user 0 1 2 3
--------------------- ------- - - - -
2 4 1 0 0 2
The additional obstacle is that there are columns labeled from 0 to 499.
I have tried to use a while loop and update one column at a time using dynamic sql and SUM(), but had both scope and performance issues - and I'm not sure if that was a good approach to this problem. I am using SQL Server 2012.
DECLARE #counter int
DECLARE #userid int
DECLARE #amount int
DECLARE #sum int
declare #sql nvarchar(1000)
SET #counter = 0
SET #userid = 4
WHILE #counter < 500
BEGIN
set #sql = 'SELECT #amount = [' + CAST(#counter AS nvarchar) + '] FROM user_inventory WHERE ID_User = ' +CAST(#userid AS nvarchar)
EXEC(#sql)
set #sql = 'SELECT #sum = SUM([' + CAST(#counter AS nvarchar) + ']) FROM user_transactions WHERE ID_User = ' +CAST(#userid AS nvarchar)
EXEC(#sql)
set #sql = 'UPDATE user_availableinventory SET [' + CAST(#counter AS nvarchar) + '] = #amount - #sum WHERE ID_User = ' +CAST(#userid AS nvarchar)
EXEC(#sql)
SET #counter = #counter + 1
END
This returned Must declare the scalar variable "#amount".multiple times.
I am aware this is an ugly approach, any suggestions to this problem are greatly appreciated.
You are getting the error because you are using the variables outside the scope of the variable. Query strings are executed as a separate session, so you need to declare the variables inside the query string.
You can try this by declaring the variables inside the query string
DECLARE #counter int
DECLARE #userid int
declare #sql nvarchar(1000)
SET #counter = 0
SET #userid = 4
WHILE #counter < 500
BEGIN
set #sql = '
DECLARE #sum int
DECLARE #amount int
SELECT
#amount = [' + CAST(#counter AS nvarchar) + ']
FROM user_inventory WHERE ID_User = ' +CAST(#userid AS nvarchar)+'
SELECT
#sum = SUM([' + CAST(#counter AS nvarchar) + '])
FROM user_transactions WHERE ID_User = ' +CAST(#userid AS nvarchar)+'
UPDATE user_availableinventory SET [' + CAST(#counter AS nvarchar) + '] = #amount - #sum WHERE ID_User = ' +CAST(#userid AS nvarchar)
EXEC(#sql)
SET #counter = #counter + 1
END

Dynamically decide number of joins

I have two tables.
Table 1: Question_Master which contains the questions
id question
1 Q1
2 Q2
3 Q3
Table 2: Option Master Which contains the Options
id option
1 H
2 N
3 S
I want all the combinations of options for all the questions.
Something Like this
Q1 Q2 Q3
H H H
H H N
H H s
H N H
NOTE: There can be any number of records in both table.If it has 4 records in option_master than i want all combination for 4 records.
You can do it dynamically by using some string concatenation queries to build out the Select statement based on the Question_Master table values
DECLARE #SelectSQL VARCHAR(MAX),
#JoinSQL VARCHAR(MAX),
#OrderSQL VARCHAR(MAX)
SELECT #SelectSQL = COALESCE(#SelectSQL + ',', '')
+ QUOTENAME(question) + '.[option] as ' + QUOTENAME(question),
#JoinSQL = COALESCE(#JoinSQL + ' CROSS JOIN ', '')
+ 'Option_Master as ' + QUOTENAME(question),
#OrderSQL = COALESCE(#OrderSql + ',', '')
+ QUOTENAME(question) + '.[option]'
FROM Question_Master
ORDER BY question
DECLARE #Sql AS NVARCHAR(MAX) = N'SELECT ' + #SelectSQL + ' FROM ' + #JoinSQL + ' ORDER BY ' + #OrderSQL
EXECUTE sp_executesql #Sql;
using QUOTENAME will allow you to have questions that have spaces or some other characters in the value.
SQL Fiddle Example
You need to CROSS JOIN the Option_Master with itself. And then you need to cross join the result again with Option_Master. This has to be repeated for each question. I think this has to be done by dynamically creating the SQL statement. Try this example to get an idea:
declare #NumberOfQuestions int
set #NumberOfQuestions = (
select count(*)
from question_master
)
declare #sql varchar(max)
set #sql = 'select om1.opt '
declare #counter int
set #counter = 2
while #Counter <= #NumberOfQuestions
begin
set #sql = #sql + '
, om' + cast (#counter as varchar(1)) + '.opt '
set #counter = #counter + 1
end
set #sql = #sql + '
from option_master om1 '
set #counter = 2
while #Counter <= #NumberOfQuestions
begin
set #sql = #sql + '
cross join option_master om' + cast(#counter as varchar(1)) + ' '
set #counter = #counter + 1
end
set #sql = #sql + '
order by om1.opt '
set #counter = 2
while #Counter <= #NumberOfQuestions
begin
set #sql = #sql + '
, om' + cast(#counter as varchar(1)) + '.opt '
set #counter = #counter + 1
end
exec (#sql)
Albert

Cannot store result into variable SQL

just have a general question today. I am trying to store the result in a variable however it's not working. I am not trying to do anything fancy rather a simple task. See below:
declare #prizeid bigint;
declare #today datetime;
declare #dayOfMonth int;
declare #year int;
declare #month int;
select #today = getdate();
select #dayOfMonth = Day(#today);
select #year = Year(#today);
select #month = Month(#today);
if #month = 1
begin
select #month = 12
select #year = #year - 1
end
else select #month = #month - 1;
declare #sqlQuery varchar(250);
declare #quantityForSnapShot bigint;
declare #filename varchar(25);
set #prizeid=31
set #filename = 'Prizes_' + REPLACE(STR(#month, 2, 0), ' ', '0') + '_' + ltrim(str(#year));
select #sqlQuery = 'select Quantity from ' + #filename +
' where PrizeID=' + convert(varchar,#prizeid)
EXEC #quantityForSnapShot=#sqlQuery
print #quantityForSnapShot
All I really want is to retreive the Quantity and store it in the var #quantityForSnapShot.
:-)
declare #prizeid bigint;
declare #today datetime;
declare #dayOfMonth int;
declare #year int;
declare #month int;
select #today = getdate();
select #dayOfMonth = Day(#today);
select #year = Year(#today);
select #month = Month(#today);
if (#month = 1)
begin
select #month = 12
select #year = #year - 1
end
else
begin
select #month = #month - 1;
end
declare #sqlQuery nvarchar(MAX); --<-- to be on safe side
declare #quantityForSnapShot bigint;
declare #filename varchar(25);
set #prizeid=31
set #filename = 'Prizes_' + REPLACE(STR(#month, 2, 0), ' ', '0') + '_' + ltrim(str(#year));
select #sqlQuery = N' select #quantityForSnapShot = Quantity ' +
N' from ' + QUOTENAME(#filename) +
N' where PrizeID = #prizeid'
EXECUTE sp_executesql #sqlQuery
,N'#prizeid bigint, #quantityForSnapShot bigint OUTPUT'
,#prizeid , #quantityForSnapShot OUTPUT
SELECT #quantityForSnapShot
You are trying to call this Dynamic sql as it were a stored procedure with a return value. You will need to use an OUTPUT parameter to retrieve the value of #quantityForSnapShot variable from your dynamic sql.
Also I have used QUOTENAME Function to put square brackets [] around the table name, to tell sql server explicitly that it is an object name. A good practice to get in as it can protect you from Sql injection attack.
Also use system stored procedure sp_executesql to execute dynamic sql.
Try This..
Begin Tran
declare #prizeid bigint;
declare #today datetime;
select #today = getdate();
declare #dayOfMonth int;
select #dayOfMonth = Day(#today);
declare #year int;
select #year = Year(#today);
declare #month int;
select #month = Month(#today);
if #month = 1
begin
select #month = 12
select #year = #year - 1
end
else select #month = #month - 1;
declare #sqlQuery varchar(250);
declare #quantityForSnapShot bigint;
declare #filename varchar(25);
set #prizeid=31
set #filename = 'Prizes_' + REPLACE(STR(#month, 2, 0), ' ', '0') + '_' + ltrim(str(#year));
select #sqlQuery = 'select Quantity from ' + #filename +
' where PrizeID=' + convert(varchar,#prizeid)
Set #quantityForSnapShot = #sqlQuery
Create Table #tmp ( Quantity bigint)
INSERT INTO #tmp (Quantity)
EXEC (#sqlQuery)
Set #quantityForSnapShot = (Select Quantity From #tmp)
Select #quantityForSnapShot
print #quantityForSnapShot
Rollback Tran

Dynamic declaration in tsql

is it possible to do dynamic declarations?
I will explain: I have a table COLUMNAMES:
ID|Name
1|Country
2|City
3|District
4|Neighbourhood
For each record in that table I would like to do something like:
declare #i int = 1
declare #number int
set #number = (SELECT count(*) FROM COLUMNNAMES)
While #i <= #number
BEGIN
Execute ('Declare column' + #i +'varchar(25)')
Execute ('set column' + #i +' = (Select NAME from COLUMNAMES where id = ' + #i)
set #i = #i + 1
END
The idea is that I get a list of variables (strings) that I can use to create SELECT statements with dynamic table-aliases:
Execute ('Select SOMECOLUMN as ' + #columname + #i +', ANOTHERCOLUMN as ' + #columname + #i +', ATHIRDCOLUMN as ' + #columname + #i + ' FROM SOMETABLE')
Can this be done? If so, how?
Each Execute function as a different session.
So, in order to declare the variable, all the code must be in one Execute function.
No you can't declare variables like this, but you can use a temporary table with the data filled in.
Here is some help, but it is not a whole solution, just an idea what you can do instead of the not working declaration:
Create Table #ColumnNames(
NAME varchar(64)
)
While #i <= #number
BEGIN
INSERT INTO #ColumNames
Select NAME from COLUMNAMES where id = #i
set #i = #i + 1
END
DECLARE #Columns varchar(max)
SET #Columns = ''
SElECT #Columns = #Columns + NAME + ', '
FROM #ColumNames
This is not complete solution but a direction . you need to get the value 'SomeColumn' dynamically someway ,likely the way you are getting the aliases in the below solution.
declare #i int = 1
declare #number INT
DECLARE #ColName VARCHAR(25)
DECLARE #SQL VARCHAR(4000)=''
DECLARE #ColumnsWithAlias VARCHAR(4000) = ''
set #number = (SELECT count(*) FROM COLUMNNAMES)
While #i <= #number
BEGIN
Select #ColName= NAME from COLUMNAMES where id = #i)
SET #ColumnsWithAlias =#ColumnsWithAlias + 'SomeColumn'+ ' AS '+ #ColName + ' , '
set #i = #i + 1
END
SET #SQL= 'SELECT '#ColumnsWithAlias+' FROM TableName'
EXECUTE(#SQL)