TSQL Control Flow Logic - sql-server

I am working on building a procedure that uses basic dynamic SQL. I want to use the result of the dynamic SQL (#query) in another part of said procedure. Below is a shorthand version of the code I am attempting to complete.
WITHOUT THE USE OF sp_executesql, how can I go about passing the result value of #query into the IF blocks?
DECLARE #table VARCHAR(MAX)
DECLARE #query VARCHAR(MAX)
DECLARE #map VARCHAR(MAX)
SET #table = 'SomeTable'
SET #query = '
;WITH Assignment AS
(
SELECT
''' + #table + ''' AS src
,Type
,RANK () OVER(ORDER BY COUNT(type) as rnk
FROM ' + #table + '
GROUP BY Type
)
SELECT Type
FROM Assignment
WHERE rnk = ''1'''
IF (#query = 'typeA')
BEGIN
/* preform an upsert dynamically */
END
IF (#query = 'typeB')
BEGIN
/* preform a delete dynamically */
END
IF (#query = 'typeC')
BEGIN
/* preform an alter dynamically */
END

Why are you testing #query right after it has been set with some SQL?
You could do it with a temp table:
Create Table #temp(type...)
SET #query = '
;WITH Assignment AS
(
SELECT
''' + #table + ''' AS src
,Type
,RANK () OVER(ORDER BY COUNT(type) as rnk
FROM ' + #table + '
GROUP BY Type
)
Insert Into #temp(type)
SELECT Type
FROM Assignment
WHERE rnk = ''1'''
You can also build your dynamic query in your if statement although I am not sure it would work in your case:
SET #q1 = '
;WITH Assignment AS
(
SELECT
''' + #table + ''' AS src
,Type
,RANK () OVER(ORDER BY COUNT(type) as rnk
FROM ' + #table + '
GROUP BY Type
)'
set #q2 = 'SELECT Type
FROM Assignment
WHERE rnk = ''1'''
Case When #type = 'A' then #query = #q1 + 'Insert into... ' + #q2
Case When #type = 'B' then #query = #q1 + 'Update... ' + #q2
Case When #type = 'B' then #query = #q1 + 'delete from where type in (' + #q2 + ')' end
If you change you mind, it is also easy with sp_executesql:
create table #temp(type int)
insert into #temp
exec sp_executesql #query
or if there are not thousands of rows:
declare #temp table(type int)
insert into #temp
exec sp_executesql #query
If there is only one row, still with sp_executesql and a parameter, this is the best option:
declare #type varchar(10)
SET #query = '
declare #type varchar(10)
;WITH Assignment AS
(
SELECT
''' + #table + ''' AS src
,Type
,RANK () OVER(ORDER BY COUNT(type) as rnk
FROM ' + #table + '
GROUP BY Type
)
SELECT #type = Type
FROM Assignment
WHERE rnk = ''1''';
exec sp_executesql #query, N'#type varchar(10)', #type = #type

This is one way to get data out of dynamic SQL
DECLARE #SQL VARCHAR(MAX)
--Dynamic SQL
SET #SQL = '
--Do anything you like in here as long as you select the results in the #Data Table format at the end
SELECT 132'
--How to get the result out of the dynamic SQL (into a table)
DECLARE #Data TABLE (Value INT)
INSERT INTO #Data(Value)
EXEC(#SQL)
--Get the result out of the table into a local (if you need to)
DECLARE #MyValue INT
SELECT #MyValue = Value FROM #Data
--Do what you like with the value now we are back in normal SQL
PRINT #MyValue

Related

How can I turn this dynamic query into a procedure or function?

I have a dynamic query that pulls from a list of tables with the names of those stored in another table but I would like to be able to use the resulting set in another query.
declare #t table( tablename varchar(50))
declare #sql varchar(max)
set #sql = ''
insert into #t
SELECT t.svc_table AS table_name FROM svc_defs AS t
SELECT #sql = #sql + 'Select convert(varchar(5),svc_defs.svc_id) as svcid, data_id, crid, d_custid, d_active From ' + tablename +
' inner join svc_defs on svc_defs.svc_table = ' + '''' + tablename + '''' + ' union ' from #t
--remove the trailing 'union'
Select #sql = substring(#sql, 1, len(#sql) - 6)
exec (#sql)
You can create scalar user defined function, which returns the sql statement.
CREATE FUNCTION dbo.udf_GenerateSelectQuery()
Returns nvarchar(max)
AS
BEGIN
declare #t table( tablename SYSNAME)
declare #sql Nvarchar(max)
set #sql = ''
insert into #t
SELECT t.TABLE_NAME AS table_name FROM INFORMATION_SCHEMA.TABLES AS t
SELECT #sql = #sql + 'Select convert(varchar(5),svc_defs.svc_id) as svcid, data_id, crid, d_custid, d_active From ' + tablename +
' inner join svc_defs on svc_defs.svc_table = ' + '''' + tablename + '''' + ' union ' from #t
--remove the trailing 'union'
Select #sql = substring(#sql, 1, len(#sql) - 6)
RETURN #sql
END
you can call it as
declare #sqlstmt NVARCHAR(max) = dbo.udf_GenerateSelectQuery()
SELECT #sqlstmt
or as
declare #sqlstmt NVARCHAR(max)
SET #sqlstmt = (SELECT dbo.udf_GenerateSelectQuery())
SELECT #sqlstmt

Select all unique values from all columns in a table

I need to select all unique values from all columns in a table.
I have tried to implement the query below which I found in the thread How to get unique values from all columns of a table in SQL Server.
declare #Sql_Str varchar(8000)='';
select #Sql_Str=#Sql_Str+' select cast (' +name +' as varchar(500))
from <yourtable> union'
from sys.columns
where [object_id]=object_id('<yourtable>');
set #Sql_Str=SUBSTRING(#Sql_Str,1,len(#Sql_Str)-6);
exec(#Sql_Str)
I cannot get that query to work however. My table has 118 columns. I think that may be more data than the query above may handle.
Try something like this:
DECLARE #Schema VARCHAR(500)='dbo';
DECLARE #tableName VARCHAR(500)='SomeTable';
DECLARE #cmd NVARCHAR(MAX)=
(
SELECT STUFF(
(
SELECT ' UNION ALL SELECT ''' + c.TABLE_SCHEMA + ''' AS TableSchema '
+ ',''' + c.TABLE_NAME + ''' AS TableName '
+ ',''' + c.COLUMN_NAME + ''' AS ColumnName '
+ ',''' + c.DATA_TYPE + ''' AS ColumnType '
+ ',CAST(' + QUOTENAME(c.COLUMN_NAME)+' AS NVARCHAR(MAX)) AS Value '
+ ' FROM ' + QUOTENAME(c.TABLE_SCHEMA) + '.' + QUOTENAME(c.TABLE_NAME)
+ ' WHERE ' + QUOTENAME(c.COLUMN_NAME) + ' IS NOT NULL '
+ ' GROUP BY ' + QUOTENAME(c.COLUMN_NAME) + ' '
FROM INFORMATION_SCHEMA.COLUMNS AS c
WHERE TABLE_NAME=#TableName
AND TABLE_SCHEMA=#Schema
--exclude not supported types
--AND c.DATA_TYPE NOT IN('xml') --add more types
FOR XML PATH(''),TYPE
).value('.','nvarchar(max)'),1,10,'')
);
--PRINT #cmd
EXEC(#cmd);
This statement will first create a long list of UNION ALL SELECT with GROUP BY (better than DISTINCT) as dynamically created SQL and executes this with EXEC().
You can decomment PRINT to examine the statement created.
This should work in tSQL:
declare #table_name varchar(55)
set #table_name= 'IV00101' ---- <-- Change this to your table name
create table #colcount (
colname varchar(55),
dct int,
tot int
)
create table #colContent (
colname varchar(55),
col_val nvarchar(max),
col_val_count int
)
create table #sqlexecs( s varchar(max))
declare #col_name varchar(max), #sql nvarchar(max), #sql2 nvarchar(max)
declare c cursor for
select name from sys.columns where [object_id]=object_id(#table_name)
open c
fetch next from c into #col_name
while ##FETCH_STATUS = 0
begin
set #sql = 'select cn.name, count(distinct '+#col_name+') as dct_numrow, count('+#col_name+') as tot_numrow from '+#table_name+' join (select name from sys.columns where name = '''+#col_name+''' and [object_id]=object_id('''+#table_name+''')) cn on cn.name = '''+#col_name+''' group by cn.name'
set #sql2 = 'select ' +#col_name+', count('+#col_name+') as colvalcnt from '+#table_name+' group by '+#col_name
--insert into #sqlexecs values (#sql) --uncomment to view sql selects produced by #sql
--insert into #sqlexecs values (#sql2) --uncomment to view sql selects produced by #sql2
insert into #colcount execute sp_executesql #sql
------
declare #d int, #t int
set #d = (select dct from #colcount where colname = #col_name)
set #t = (select tot from #colcount where colname = #col_name)
if (#d <> #t)
begin
insert into #colContent (colname) values (#col_name)
insert into #colContent (col_val,col_val_count) execute sp_executesql #sql2
end
else
begin
insert into #colContent values (#col_name,1,1)
end
fetch next from c into #col_name
end
close c
deallocate c
--select * from #sqlexecs -- uncomment to view sql code produced by #sql and #sql2
select * from #colcount --order by dct desc
select * from #colContent
drop table #colcount
drop table #colContent
drop table #sqlexecs
The first table shows column name, distinct value count, and total value count.
The second table shows column name, distinct values, and the number of times a distinct value appears. If values in column are all distinct (column is a candidate key), colname | 1 | 1 is shown. This should work if copy/pasted, please let me know it doesn't. Dev for use in Dynamics GP.

SQL - Remove (Eliminate) columns which have no data

I wonder that;
in SQl, is it possible to not bring the columns which have no data (or zero value)?
Select * from PLAYER_TABLE where PLAYER_NAME='cagri'
it is bringing just 1 row. because there is only one player which PLAYER_NAME is "cagri".
And there are 30 columns for example statistics.
Score-Rebound-PlayedMinutes-Fauls etc....
Score=2
Rebound=0
PlayedMinutes=2
Fauls=0
and I want to see only [Score] and [PlayedMinutes] columns when call my query.
is it possible?
You can use this logic over a stored procedure in SQL
DDL
create table usr_testtable
(player varchar(30),col1 float, col2 float, col3 float, col4 float)
insert into usr_testtable
values ('Jordan',10,20,3,0)
Convert to Stored Proc
declare #playername varchar(30) = 'Jordan' --- pass this value
declare #ctr smallint = 2 -- start from ordinal 2
declare #maxctr smallint = (SELECT max(ORDINAL_POSITION)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'usr_testTable')
declare #columns varchar(max) = ''
declare #columnswithvalues varchar(max) = ''
declare #coltocheck varchar(30)
declare #mysql nvarchar(max)
declare #coloutput varchar(30)
while #ctr <= #maxctr
begin
SELECT #coltocheck = COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'usr_testTable'
and ORDINAL_POSITION = #ctr
set #mysql = N'select #output = ' + #coltocheck + ' from usr_testTable where player =''' + #playername + ''' and cast(' + #coltocheck +' as float) > 0'
EXECUTE sp_executesql #mysql,N'#output int OUTPUT',#output = #coloutput OUTPUT;
if #coloutput > 0
begin
set #columns = coalesce(#columns + ',' + #coltocheck,#columns)
set #columnswithvalues = coalesce(#columnswithvalues + char(13) + char(10) + #coltocheck + ' : ' + #coloutput,#columnswithvalues) --- text form
end
set #coloutput = ''
set #ctr= #ctr + 1
end
-- final result in table format
set #mysql = N'select player' + #columns + ' from usr_testTable where player =''' + #playername + ''' '
EXECUTE sp_executesql #mysql
-- final result in text format appended with columnname
select #columnswithvalues -- format to display in text
First create dynamic SQL to select all columns names in the table PLAYER_TABLE except PLAYER_NAME, then unpivot data from PLAYER_TABLE into table PLAYER_TABLE1, then you can search values <> 0 and select this column in second dynamic SQL.
DROP TABLE PLAYER_TABLE1
DECLARE #Player NVARCHAR(50);
DECLARE #columns NVARCHAR(max);
DECLARE #sql NVARCHAR(max);
DECLARE #columns2 NVARCHAR(max);
DECLARE #sql2 NVARCHAR(max);
SET #player='cagri'
SET #columns = Stuff((SELECT ','
+ Quotename(Rtrim(Ltrim(x.columns)))
FROM (SELECT COLUMN_NAME as columns FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME= 'PLAYER_TABLE' and COLUMN_NAME<>'PLAYER_NAME' ) AS x
ORDER BY X.columns
FOR xml path('')), 1, 1, '');
SET #sql = N' SELECT
PLAYER_NAME, Un_Pivot.Field, Un_Pivot.Value
INTO PLAYER_TABLE1
FROM
(
SELECT * FROM PLAYER_TABLE
) Data
UNPIVOT
(
Value FOR Field IN ('+#columns+')
) AS Un_Pivot';
EXECUTE sp_executesql #sql;
SET #columns2 = Stuff((SELECT ','
+ Quotename(Rtrim(Ltrim(y.Field)))
FROM (SELECT Field FROM PLAYER_TABLE1 WHERE VALUE<>0 AND PLAYER_NAME=#Player) AS y
ORDER BY y.Field
FOR xml path('')), 1, 1, '');
SET #sql2 = N'SELECT PLAYER_NAME,'+#columns2+'FROM PLAYER_TABLE WHERE PLAYER_NAME='+char(39)+#Player+char(39);
EXECUTE sp_executesql #sql2

How to write procedure to get all the data of table of tables?

I have a master table which contains the table names and columns corresponding to that table.
I want to write a procedure which iterates through all the records of tables and gets all the data and returns it as a single result set.
You need to use Dynamic Query
DECLARE #sql VARCHAR(max)=''
SET #sql = (SELECT #sql + 'select ' + column_name + ' from '
+ table_name + ' union all '
FROM master_table
FOR xml path(''))
SELECT #sql = LEFT(#sql, Len(#sql) - 9)
EXEC (#sql)
Note : The datatype of all the columns should be same. If it is not the case then you may have to do explicit conversion to varchar
SET #sql = (SELECT #sql + 'select cast(' + column_name + ' as varchar(4000)) from '
+ table_name
+ ' union all '
FROM Master_table
FOR xml path(''))
Assuming that all tables listed in your Master table is having same columns with same order and data types. Then it will be as follows:
create table ##a
(
Value int
)
create table ##b
(
Value int
)
create table ##c
(
Value int
)
declare #all table
(
Value int
)
declare #master table
(
TableName varchar(10)
)
declare #TableName varchar(10)
insert ##a values (1), (2), (3)
insert ##b values (4), (5), (6)
insert ##c values (7), (8), (9)
insert #master values ('##a'), ('##b'),('##c')
declare looper cursor local static forward_only read_only for
select TableName from #master
open looper
fetch next from looper into #TableName
while ##fetch_status = 0
begin
insert #all exec('select Value from ' + #TableName)
fetch next from looper into #TableName
end
close looper
deallocate looper
select * from #all
drop table ##a
drop table ##b
drop table ##c
If the tables are of different structures, please visit Stored procedures and multiple result sets in T-SQL. It will squeeze the content of each table into a single XML cell. The article also explains how to read them back.
I assume that you are using many tables with different columns in your master table. You should loop your master table. Try like this,
DECLARE #sql NVARCHAR(max) = ''
DECLARE #start INT = 1
,#end INT = 0
,#tablename VARCHAR(100) = ''
DECLARE #TableList TABLE (
id INT identity(1, 1)
,tablename VARCHAR(128)
)
INSERT INTO #TableList (tablename)
SELECT DISTINCT table_name
FROM YourMasterTableName
WHERE TABLE_NAME = 'productss'
SET #end = ##ROWCOUNT
WHILE (#start <= #end)
BEGIN
SET #tablename = (
SELECT tablename
FROM #TableList
WHERE id = #start
)
SET #sql = (
SELECT ',[' + column_name + ']'
FROM YourMasterTableName M
WHERE TABLE_NAME = #tablename
FOR XML path('')
)
SET #sql = 'SELECT ' + stuff(#sql, 1, 1, '') + ' FROM ' + #tablename
EXEC sp_executesql #sql
SET #start = #start + 1
END

Not able to run a statement because of issue with the sql syntax

This solution is for an unbounded Gridview paging and having problem with the syntax of this query:
> #currTable varchar(20),
#startRowIndex int,
#maximumRows int,
#totalRows int OUTPUT
AS
DECLARE #first_id int, #startRow int
IF #startRowIndex = 1
SET #startRowIndex = 1
ELSE
SET #startRowIndex = ((#startRowIndex - 1) * #maximumRows)+1
SET ROWCOUNT #startRowIndex
DECLARE #sql varchar(250);
SET #sql = 'SELECT ID, StringID_from_Master, GUID, short_Text, lang_String, date_Changed, prev_LangString, needsTranslation, displayRecord, brief_Descrip FROM ' + #currTable + ' ';
EXECUTE(#sql);
PRINT #first_id
SET ROWCOUNT #maximumRows
SELECT #sql = 'SELECT ' + CAST(#first_id as varchar(20)) + ' = ID FROM ' + QUOTENAME(#currTable) + ' ORDER BY ID ' ;
EXEC (#sql);
SET ROWCOUNT 0
-- Get the total rows
SET #sql = 'SELECT ' + + CAST(#totalRowsas varchar(20)) + ' = COUNT(ID) FROM ' + #currTable + ' ';
EXECUTE(#sql);
RETURN
<
The errors is:
Conversion failed when converting the varchar value ''SELECT ' to data type int.
Tried also
nvarchar and varchar. = + CAST(#first_id as varchar(10)) +
If you're trying to implement paging, this is wrong in so many ways. First, you're using SET ROWCOUNT to limit to #startRowIndex, but then you're selecting ALL n rows (with no ORDER BY), then getting the first ID, then counting the total rows by selecting from the table? Might I suggest a better approach?
CREATE PROCEDURE dbo.PageSmarter
#Table NVARCHAR(128), -- table names should not be varchar(20)
#FirstRow INT,
#PageSize INT,
#TotalRows INT OUTPUT
AS
BEGIN
SET NOCOUNT ON; -- always, in every stored procedure
DECLARE
#first_id INT,
#startRow INT,
#sql NVARCHAR(MAX);
SET #sql = N'WITH x AS
(
SELECT
ID,
rn = ROW_NUMBER() OVER (ORDER BY ID)
FROM
' + #Table + '
)
SELECT rn, ID
INTO #x FROM x
WHERE rn BETWEEN ' + CONVERT(VARCHAR(12), #FirstRow)
+ 'AND (' + CONVERT(VARCHAR(12), #FirstRow)
+ ' + ' + CONVERT(VARCHAR(12), #PageSize) + ' - 1);
SELECT first_id = MIN(ID) FROM #x;
SELECT
ID, StringID_from_Master, GUID, short_Text, lang_String, date_Changed,
prev_LangString, needsTranslation, displayRecord, brief_Descrip
FROM ' + #Table + ' AS src
WHERE EXISTS
(
SELECT 1 FROM #x
WHERE ID = src.ID
);';
EXEC sp_executeSQL #sql;
SELECT #totalRows = SUM(row_count)
FROM sys.dm_db_partition_stats
WHERE [object_id] = OBJECT_ID(#Table);
END
GO
DECLARE #tr INT;
EXEC dbo.PageSmarter 'dbo.tablename', 10, 2, #tr OUTPUT;
SELECT #tr;
I haven't tested all edge cases with this specific implementation. I will confess, there are much better ways to do this, but they usually aren't complicated with the additional requirement of dynamic table names. This suggests that there is something inherently wrong with your design if you can run the exact same queries against any number of tables and get similar results.
In any case, you can review some of the (quite lengthy) discussion about various approaches to paging over at SQL Server Central:
http://www.sqlservercentral.com/articles/T-SQL/66030/
There are 62 comments following up on the article:
http://www.sqlservercentral.com/Forums/Topic672980-329-1.aspx
I am guessing your #first_id field is an int. If so, then you need to CAST/Convert your #first_id value to a string/varchar.
CAST(#first_id as varchar(10))
or
Convert(varchar(10), #first_id)
MSDN documentation on CAST/Convert for SQL server
EDIT: After looking at your query again, I notice that you are setting your #first_id = ID, This is incorrect syntax, the correct syntax would be below.
SELECT #sql = 'SELECT ID AS ' + CAST(#first_id as varchar(10)) + ' FROM ' +
QUOTENAME(#currTable) + ' ORDER BY ID ' ;
EXEC (#sql);
It appears you're trying to create an alias for your column ID. The string you're building won't result in a valid SQL statement if it contains a number. It would come out to something like this:
SELECT 123 = ID FROM dbo.MyTable ORDER BY ID
Try this:
SELECT ID AS '123' FROM dbo.MyTable ORDER BY ID
To achieve that:
SELECT #sql = 'SELECT ID AS ''' + CAST(#first_id as varchar(10)) +
''' FROM ' + QUOTENAME(#currTable) +
' ORDER BY ID ' ;
I would do it this way
create table #e (a int)
SET #sql = 'insert #e SELECT COUNT(ID) FROM ' + #currTable + ' ';
exec(#sql)
select #totalRows = a from #e
drop table #e

Resources