SQL Calculating balance based on inventory and transactions - sql

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

Related

SQL Server : get column name of a table using condition

I have a sample table here - I want to get all the columns that have the value of 1 only. Is it possible?
Its absolutely possible but the process is lengthy, I am using loop to check each column's
data exists by retrieving column name from sys.columns. Please try this if it helps you in any term:
Here I am checking each column for value 1 only
CREATE TABLE testing(val1 INT, val2 INT, val3 INT)
INSERT INTO testing VALUES
(1, 0, 1),(1, 0, 1),(1, 1, 1)
Table: testing
val1 val2 val3
1 0 1
1 0 1
1 1 1
DECLARE #sql NVARCHAR(500), #list VARCHAR(500)
DECLARE #num INT=1, #col_name VARCHAR(100) = NULL, #cnt INT
WHILE(#num<=3)
BEGIN
SELECT #col_name = name FROM sys.columns
WHERE object_id = OBJECT_ID('testing') and column_id = #num
SET #cnt = 0
SET #sql = '
IF NOT EXISTS(SELECT 1 FROM testing WHERE ' + #col_name + ' = 0) SET #cnt = 1'
EXEC sp_executesql #sql, N'#cnt INT OUT', #cnt OUT
IF #cnt = 1
SET #list = COALESCE(#list + ',', '') + #col_name
SET #num = #num+1
END
SET #sql = '
SELECT ' + #list + ' FROM testing'
EXEC(#sql)
OUTPUT:
val1 val3
1 1
1 1
1 1

Learning Dynamic 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)

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

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)

Cross Join 'n' times a table

It is possible to write a generic function/procedure/select/somethingElse to cross-join a table against himself 'n' times? (yes, 'n' is a given parameter : )
How would you do it?
Example
Having this table:
Value
-------
1
2
3
cross join it 2 times, would return:
Value | Value
------------------
1 1
1 2
1 3
2 1
2 2
2 3
3 1
3 2
3 3
Using dynamic SQL, SQL Server 2005+ (#table_name and #numCrossJoins are stored procedure parameters):
DECLARE #upperLimit INT
SET #upperLimit = 1
DECLARE #SQL NVARCHAR(MAX)
SET #SQL = 'SELECT * FROM '+ #table_name +' '
BEGIN
WHILE (upperLimit <= #numCrossJoins)
BEGIN
SET #SQL = #SQL + 'CROSS JOIN '+ QUOTENAME(#table_name) +' '
SET #upperLimit = #upperLimit + 1
END
EXEC sp_executesql #SQL
END
You can generate dynamic sql to output as many cross joins as you need:
create table #t (value int)
insert into #t values (1)
insert into #t values (2)
insert into #t values (3)
declare #n int
set #n = 4
declare #sql varchar(max)
set #sql = 'SELECT * FROM #t t'
declare #i int
set #i = 0
while (#i <= #n)
begin
set #sql = #sql + ' cross join #t t' + CAST(#i as varchar)
set #i = #i + 1
end
print #sql
execute(#sql)
drop table #t
Try this:
SET #SQL = 'SELECT * FROM ' + replicate('[' + #table_name + '],', #N);
set #SQL = LEFT(LEN(#SQL) - 1);
EXEC sp_executesql #SQL;
If you need to come up with all possible permutations, here is an example:
All Permutations For A String