SQL Server : get column name of a table using condition - sql

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

Related

T-SQL function in Select to Only Return Columns with Values

I have a query that will return only columns with values. How do I add that to a function so I can use that with any query? Would it be a function in the where clause.
create table test1
(
s_no int not null,
name varchar(10) not null,
address varchar(10) null,
emailid varchar(100) null
)
insert into test1 (s_no, name)
values (1,'A'),(2,'B'),(3,'C')
declare #column_list varchar(8000),
#counter int
set #column_list = ''
set #counter = 0
while (Select max(colid) from syscolumns where id = object_id('test1') and isnullable= 0) > #counter
begin
select #counter = min(colid)
from syscolumns
where id = object_id('test1')
and isnullable = 0
and colid > #counter
select #column_list = #column_list + ',' + (Select name from syscolumns where id = object_id('test1') and isnullable= 0 and colid = #counter)
end
select #column_list = SUBSTRING(#column_list, 2, len(#column_list))
declare #sql varchar(8000)
select #sql = 'select ' + #column_list + ' from test1'
print #sql
exec (#sql)
SELECT * FROM [dbo].[test1]
I guess you could make a stored procedure where you provide the table name as parameter, and then build your query like you are doing already
create procedure ShowOnlyFilledColumns (#tablename varchar(100)) as
begin
set nocount on
declare #column_list varchar(8000),
#counter int
set #column_list = ''
set #counter = 0
while (Select max(colid) from syscolumns where id = object_id(#tablename) and isnullable= 0) > #counter
begin
select #counter = min(colid) from syscolumns where id = object_id(#tablename) and isnullable= 0 and colid > #counter
select #column_list = #column_list + ',' + (Select name from syscolumns where id = object_id(#tablename) and isnullable= 0 and colid = #counter)
end
select #column_list = SUBSTRING(#column_list,2,len(#column_list))
declare #sql varchar(8000)
select #sql = 'select ' + #column_list + ' from ' + #tablename
--print #sql
exec (#sql)
end
and use it like this
exec ShowOnlyFilledColumns 'test1'
See the complete example in this DBFiddle
EDIT: The OP asked how he can add joins on this
There are a few tricks to join with a stored procedure, for example in these answers
However, this won't work on this solution, because it requires to create a temp table to store the result of the procedure.
The trick looks like this
-- create a temporary table to store the results of the procedure
CREATE TABLE #Temp (
s_no int not null,
name varchar(10) not null,
address varchar(10) null,
emailid varchar(100) null
)
-- call the procedure and store the result in the temporary table
INSERT INTO #Temp
exec ShowOnlyFilledColumns 'test1'
-- now I can query the temp table, and join on it and write a where clause, and I can do whatever I want
select * from #Temp
Now, this won't work in this case, because the stored procedure can return different columns every time you run it, and to make the insert into #Temp exec ShowOnlyFilledColumns 'test1' work, the table #Temp must have the same number and type of columns as the procedure returns. And you just don't know that.

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

SWAP Selected Rows in SQL Server

I am trying to swap the selected rows as a quick way for users to selected the opposite to what they have already selected.
For example:
Current Selection:
ID SelectedUID
------------------
1
2
3 CJ
4
5
SWAP Selection: (this is what I am trying to achieve)
ID SelectedUID
------------------
1 CJ
2 CJ
3
4 CJ
5 CJ
Is there a function or an easy way to do this in SQL Server?
EDIT
Would this be an appropriate Stored Procedure? I am receiving an error when I execute this.
CREATE PROCEDURE [dbo].[ProcSWAPSelections]
#FROMCLAUSE AS VARCHAR(8000),
#UPDATETABLE AS VARCHAR(50),
#GUID AS VARCHAR(3),
#WHERECLAUSE AS VARCHAR(8000)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #SQL NVARCHAR(MAX);
SET #SQL = N'UPDATE ' + #UPDATETABLE + '
SET Selected' + #GUID + '.UID = CASE WHEN Selected' + #GUID + '.UID = #GUID
THEN NULL
ELSE #GUID
END
' + #FROMCLAUSE + '
' + #WHERECLAUSE + ';';
EXEC sp_executesql #sql, N'#FROMCLAUSE VARCHAR(8000), #UPDATETABLE VARCHAR(50), #GUID VARCHAR(3), #WHERECLAUSE VARCHAR(8000)', #FROMCLAUSE,#UPDATETABLE, #GUID, #WHERECLAUSE;
END
Another way would be to have a third table
Selections
ID Selection
1 NULL
2 CJ
Then in the swap table link to it, then you only need to switch in the selections table. Same number of operations, but only two records updated.
You can do this easily with 2 updates.
Update YourTable Set SelectedUID = 'SomeValueThatDoesntExist'
where SelectedID = 'CJ'
Update YourTable Set SelectedUID = 'CJ'
where SelectedID = 'SomeValueThatDoesntExist'
Assuming only 2 different values in the table in one update, which means you don't need a transaction to span multiple UPDATEs
UPDATE
SomeTable
SET
SelectedUID = CASE WHEN SelectedUID <> 'CJ' THEN 'CJ' ELSE '' END
or...
SelectedUID = CASE WHEN SelectedUID = 'CJ' THEN '' ELSE 'CJ' END
or...
SelectedUID = CASE WHEN SelectedUID = 'CJ' THEN NULL ELSE 'CJ' END
This function should help.
CREATE FUNCTION udf_SwapValues
(
#InputStr VARCHAR(5),
#ReplaceStr1 VARCHAR(5),
#ReplaceStr2 VARCHAR(5)
)
RETURNS VARCHAR(5)
AS
BEGIN
DECLARE #Return VARCHAR(5)
SELECT #Return = CASE
WHEN #InputStr = #ReplaceStr1 THEN #ReplaceStr2
WHEN #InputStr = #ReplaceStr2 THEN #ReplaceStr1
ELSE #InputStr
END
-- Return the result of the function
RETURN #Return
END
GO
USAGE:
DECLARE #table TABLE (ID INT,
SelectedUID VARCHAR(5))
INSERT INTO #table
VALUES (1, ''),
(2, ''),
(3, 'CJ'),
(4, ''),
(5, '')
SELECT ID,SelectedUID
FROM #table
--USAGE WITH SELECT
SELECT ID,
dbo.udf_SwapValues(SelectedUID, 'CJ', '')
FROM #table
--USAGE WITH UPDATE
UPDATE t
SET t.SelectedUID = dbo.udf_SwapValues(t.SelectedUID, 'CJ', '')
FROM #table t
SELECT *
FROM #table

SQL One to Many join getting single result per join

I want to export data from a SQL 2008 server to a file, csv or excel doesn't matter. In the database I have two entities, questions and answers. The problem is that the questions has multiple answers. A join will then result in multiple rows per each question. How can I do so the result is just one row per question and answers, just adding a new column per each answer. Like this:
Table structure:
Question
-------------
Id
Text
Category
Answer
-------------
Id
Text
IsCorrect
QuestionId
Example of result:
Col1 Col2 Col3 Col4 Col5 Col6 Col7
Result1: Question1, Question1Text, Answer1Prop1, Answer1Prop2, Answer2Prop1, Answer2Prop2, null
Result2: Question2, Question2Text, Answer3Prop1, Answer3Prop2, Answer4Prop1, Answer4Prop2, Answer5Prop3
If you can produce a result with your joins containing all the questions and all the answers with multiple rows, like you describe, I think you could use a cursor to loop over all the questions and answers, and then have a temptable/"hashtable" (#tableOfAnswers) to hold your final result. For each answer for a question you alter the #table and add a new column.
Something like this might put you in the right direction.
If you have a result looking something like this:
(In my example I've stored the result below in a temp table called "#q")
QuestionId text
1 answer1
1 answer2
1 answer3
1 answer4
2 answer1
2 answer2
Then you can declare a cursor on the result
Declare #questionId int
, #questionText varchar(max)
, #prevId int
, #colNo int = 1
, #colMax int = 0
, #i int = 1
, #sql nvarchar(max)
, #nulls nvarchar(max) = ''
Create table #tempTable
(
QuestionId int not null,
Col1 varchar(max) null
)
Declare question_cursor cursor for
Select QuestionId, text
From #q
Open question_cursor
FETCH NEXT FROM question_cursor
INTO #questionId, #questionText
Set #prevId = 0
WHILE ##FETCH_STATUS = 0
BEGIN
If (#prevId = #questionId)
Begin
If (#colNo > #colMax)
Begin
Set #sql = N'alter table #tempTable add Col' + cast(#colNo as varchar) + ' varchar(max) null'
exec sp_executesql #statement = #sql
Set #colMax = #colMax + 1
End
Set #sql = N'update #tempTable set Col' + cast(#colNo as varchar) + ' = ''' + #questionText + ''' where QuestionId = ' + cast(#questionId as varchar)
print #sql
exec sp_executesql #statement = #sql
Set #colNo = #colNo + 1
End
Else Begin
Set #prevId = #questionId
Set #colNo = 1
while (#i <= #colMax)
begin
set #nulls = #nulls + ', null'
set #i = #i + 1
end
Set #sql = N'insert into #tempTable values (' + cast(#questionId as varchar) + ', ''' + #questionText + '''' + isnull(#nulls, '') +')'
print #sql
exec sp_executesql #statement = #sql
Set #colNo = #colNo + 1
End
FETCH NEXT FROM question_cursor
INTO #questionId, #questionText
END
Close question_cursor
Deallocate question_cursor
Select * from #tempTable
That will produce a result like this:
QuestionId Col1 Col2 Col3 Col4
1 text1 text2 text3 text4
2 text1 text2 NULL NULL
Not quite as elegant as .Net (or javascript even) would handle a problem like this.. But well, what you gonna do? :)
I hope that might shed some light on your problem :)
And for exporting it to a file I would just copy/paste the result in Sql Management Studio and paste it in Excel or something ;)

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