How to iterate in SQL Server without while loop - sql

I have a temp table with few table names as below:
Id TableName
-------------
1 TableA
2 TableB
3 TableC
These table names should be replaced in the query shown, and should get executed. I can achieve it with a while loop, but I don't want to use while. Is there any alternative concept?
I have the following SQL statement:
SELECT
TDU.ColHeader, TDU.ColValues
FROM
(SELECT
' + #ColumnsCasted + '
FROM
' + #TableName + ' ' +
#WhereCondition + ') TD
UNPIVOT
(ColValues FOR ColHeader IN (' + #ColumnsUnpivot + ')
) AS TDU;
The #TableName, #ColumnsCasted, #ColumnsUnpivot are based upon the table name which is stored in a temp table. I used while loop to iterate each table name and replace it here in the statement.
Can anyone suggest a method without using while loop?

You may try to generate and execute dynamic SQL. Next example is just for SELECT * FROM Table statement, you must change it for your query:
-- Create table
CREATE TABLE #TempTable (
Id int,
TableName nvarchar(50)
)
INSERT INTO #TempTable (Id, TableName)
VALUES
(1, 'TableA'),
(2, 'TableB'),
(3, 'TableC')
-- Dynamic SQL
DECLARE #stm nvarchar(max)
DECLARE #err int
SET #stm = N''
SELECT #stm =
#stm +
'SELECT * FROM ' +
QUOTENAME(TableName) +
'; '
FROM #TempTable
-- Statement execution
PRINT #stm
EXEC #err = sp_executesql #stm
IF #err <> 0 PRINT 'Error'
ELSE PRINT 'OK'
Generated statement:
SELECT * FROM [TableA]; SELECT * FROM [TableB]; SELECT * FROM [TableC];

There is non-documented function sp_MSforeachtable that executes query for each table in databse, maybe it can help you:
EXEC sp_MSforeachtable 'SELECT COUNT(*) FROM ?'

Related

How to retrieve a column name from a table that is stored as a value in another table

I'm pretty new to sql so any help will be much appreciated
I have a table containing a list of table names in a column of a table and I need to retrieve a column called [Last Refreshed] from all the tables listed. The tables all have different structures but they all have the [Last Refreshed] Column. I have managed to insert the tablenames into a sql variable but up to this point I am kind of stuck.
I hope I managed to explain what I need but I have attached my code as well.
Declare #tables nvarchar(max)
Declare #sql nvarchar(max)
Declare #cnt int
DECLARE #Counter int
SET #Counter = 1
DECLARE #RowCount INT
SET #RowCount = (SELECT COUNT(*)
FROM (
SELECT * FROM TABLE_LIST1
UNION
SELECT * FROM TABLE_LIST2) data )
DROP TABLE #DB_DUMMY
CREATE TABLE #DB_DUMMY (
[TABLENAME] VARCHAR(512),
[LAST_REFRESHED] VARCHAR(533)
);
WHILE ( #Counter <= #RowCount)
BEGIN
SELECT #tables = FinalTable, #cnt = Row_num from (
SELECT FinalTable , ROW_NUMBER() OVER(ORDER BY FinalTable DESC) AS Row_num
FROM (
SELECT FinalTable FROM TABLE_LIST1
UNION
SELECT FinalTable FROM ABLE_LIST2) data
group by FinalTable) a
where Row_num = #Counter
--This part doesnt work
INSERT INTO #DB_DUMMY(TABLENAME,LAST_REFRESHED)
SELECT #tables , [Last Refreshed] from #tables
SET #Counter = #Counter + 1
END
exec(#sql)
I expect to see a list of tablenames as well as the last refresh in the temporary table #DB_DUMMY
i add the [Last Refreshed] column to my tables and write this query and give me the correct answer
DROP TABLE IF EXISTS #DB_DUMMY
CREATE TABLE #DB_DUMMY (
[TABLENAME] VARCHAR(512),
[LAST_REFRESHED] VARCHAR(533)
);
DECLARE #COMMAND NVARCHAR(MAX)
SELECT #COMMAND = STRING_AGG(' INSERT INTO #DB_DUMMY SELECT DISTINCT '+CHAR(39)+T.name+CHAR(39)+',['+C.name+'] FROM '+S.name+'.'+T.name + ' GO', CHAR(13)+CHAR(10))
FROM sys.all_columns C
INNER JOIN sys.tables T ON C.object_id = T.object_id
INNER JOIN sys.schemas S ON T.schema_id = S.schema_id
WHERE C.name = 'Last Refreshed'
PRINT(#COMMAND)
EXEC(#COMMAND)
SELECT * FROM #DB_DUMMY
two first line with IF EXISTS is new syntax in sql server 2017
Just a suggestion You could use a INSERT SELECT
INSERT INTO #DB_DUMMY(TABLENAME,LAST_REFRESHED)
SELECT 'TABLE_LIST1', LAST_REFRESHED
FROM TABLE_LIST1
UNION ALL
SELECT 'TABLE_LIST2', LAST_REFRESHED
FROM TABLE_LIST2
UNION ALL
.....
SELECT 'TABLE_LISTN', LAST_REFRESHED
FROM TABLE_LISTN
Try something like this:
declare cur cursor for Select TableName From TABLE_LIST
declare #tablename nvarchar(max)
declare #sqlstring nvarchar(max)
open cur
fetch next from cur into #tablename
while ##fetch_status=0
begin
set #sqlstring = 'SELECT ''' + #tablename + ''' AS ''TABLE'', [LAST_REFRESHED] FROM ' + #tablename
exec sp_executesql #sqlstring
fetch next from cur into #tablename
end
close cur
deallocate cur
;
It is the weekend and I don't have access to a database to test on, so it may need some adjusting. Here is a fiddle with the sample code, but it only returns the first table http://sqlfiddle.com/#!18/a5b55b/2 (I think the fiddle execution mechanism interferes with the cursor.)
This answer is based upon the code here: I have the same column in multiple tables, and want to update that column in all tables to a specific value. How can I do this?
Note that there is no need to maintain a list of tables with the column. You can generate it dynamically from INFORMATION_SCHEMA.COLUMNS
Another possible approach is to generate and execute a dynamic statement (it's not possible to use a variable for the name of a column or a table):
Table:
CREATE TABLE #TableNames (
[TableName] nvarchar(128)
)
INSERT INTO #TableNames
([TableName])
VALUES
(N'Table1'),
(N'Table2'),
(N'Table3'),
(N'Table4'),
(N'Table5')
Statement:
-- Generate statement
DECLARE #stm nvarchar(max) = N''
SELECT #stm = CONCAT(
#stm,
N'INSERT INTO #DB_DUMMY (TABLENAME, LAST_REFRESHED) ',
N'SELECT ''',
[TableName],
N''' AS [TableName], [LastRefreshed] FROM ',
QUOTENAME([TableName]),
N'; '
)
FROM #TableNames
-- Execute statement
PRINT #stm
EXEC sp_executesql #stm

How to pass table name as parameter in select statement in SQL Sever

Table:
Col
------
Table1
table2
table3
Query:
select count(*)
from #tablename
I wanted to pass table1, table2, table3 as parameters for #tablename in the select query and get the count for each table
Desired output:
2 (table 1 count) 3 (table 2 count) 4 (table 3 count)
you can use dynamic sql and a cursor to run through them:
Create temp table for testing:
DECLARE #tablenametable TABLE(tablename VARCHAR(100));
INSERT INTO #tablenametable
VALUES('table1'), ('table2'), ('table3');
Use a cursor to run through all tablenames in the table
DECLARE #tablename VARCHAR(100);
DECLARE dbcursor CURSOR
FOR
SELECT tablename
FROM #tablenametable;
OPEN dbcursor;
FETCH NEXT FROM dbcursor INTO #tablename;
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE #sql VARCHAR(MAX);
SET #sql = 'select count(*) from '+#tablename;
PRINT(#sql);
FETCH NEXT FROM dbcursor INTO #tablename;
END;
CLOSE dbcursor;
DEALLOCATE dbcursor;
Give the following results:
select count(*) from table1
select count(*) from table2
select count(*) from table3
Just change PRINT(#SQL) to EXEC(#SQL) when your happy with it
You can use dynamic sql query.
Query
declare #sql as varchar(max);
select #sql = stuff((
select ' union all '
+ 'select cast(count(*) as varchar(100))
+ ' + char(39) + '(' + [Col] +' Count)' + char(39)
+ ' as [table_counts] '
+ ' from ' + [col]
from [your_table_name]
for xml path('')
)
, 1, 11, ''
);
exec(#sql);
Find a demo here
As mentioned here, you have to use dynamic SQL.
First approach is where you specify table name yourself:
declare #tablename varchar(30), #SQL varchar(30)
set #tablename = 'Table1' --here you specify the name
set #SQL = concat('SELECT COUNT(*) FROM ', #tablename) --here you build the query
EXEC(#SQL)
Second approach lets you use table with names of tables:
declare #SQL varchar(8000)
set #SQL = ''
declare #TableNames table(name varchar(30))
insert into #TableNames values ('Table1'), ('Table2'), ('Table3')
--here you build the query
select #SQL = #SQL + ' SELECT ''' + name + ''' AS [TableName], COUNT(*) AS [Count] FROM ' + name + ' UNION ALL' from #TableNames
-- get rid of last "UNION ALL"
set #SQL = LEFT(#SQL, LEN(#SQL) - 10)
--execute the query
EXEC(#SQL)
The result of it will be:
TableName Count
Table1 3
Table2 6
Table3 4
You can use sys.dm_db_partition_stats like this [1]:
select
t.col tableName, sum(s.row_count) tableCount
from
yourTable t
join
sys.dm_db_partition_stats s
on
(object_name(s.object_id) = t.col )
and
(s.index_id < 2)
group by
t.col;
[1]. Related answer
One line output version will be:
select
sum(s.row_count), ' ('+t.col +' count) '
from
yourTable t
join
sys.dm_db_partition_stats s
on
(object_name(s.object_id) = t.col )
and
(s.index_id < 2)
group by
t.col
for xml path('');
output:
2 (Table1 count) 3 (table2 count) 4 (table3 count)

Dynamic SQL Result INTO Temporary Table

I need to store dynamic sql result into a temporary table #Temp.
Dynamic SQL Query result is from a pivot result, so number of columns varies(Not fixed).
SET #Sql = N'SELECT ' + #Cols + ' FROM
(
SELECT ResourceKey, ResourceValue
FROM LocaleStringResources where StateId ='
+ LTRIM(RTRIM(#StateID)) + ' AND FormId =' + LTRIM(RTRIM(#FormID))
+ ' AND CultureCode =''' + LTRIM(RTRIM(#CultureCode)) + '''
) x
pivot
(
max(ResourceValue)
for ResourceKey IN (' + #Cols + ')
) p ;'
--#Cols => Column Names which varies in number
Now I have to insert dynamic sql result to #Temp Table and use this #Temp Table with another existing table to perform joins or something else.
(#Temp table should exist there to perform operations with other existing tables)
How can I Insert dynamic SQL query result To a Temporary table?
Thanks
Can you please try the below query.
SET #Sql = N'SELECT ' + #Cols + '
into ##TempTable
FROM
(
SELECT ResourceKey, ResourceValue
FROM LocaleStringResources where StateId ='
+ LTRIM(RTRIM(#StateID)) + ' AND FormId =' + LTRIM(RTRIM(#FormID))
+ ' AND CultureCode =''' + LTRIM(RTRIM(#CultureCode)) + '''
) x
pivot
(
max(ResourceValue)
for ResourceKey IN (' + #Cols + ')
) p ;'
You can then use the ##TempTable for further operations.
However, do not forget to drop the ##TempTable at the end of your query as it will give you error if you run the query again as it is a Global Temporary Table
As was answered in (https://social.msdn.microsoft.com/Forums/sqlserver/en-US/144f0812-b3a2-4197-91bc-f1515e7de4b9/not-able-to-create-hash-table-inside-stored-proc-through-execute-spexecutesql-strquery?forum=sqldatabaseengine),
you need to create a #Temp table in advance:
CREATE TABLE #Temp(columns definition);
It seems that the task is impossible, if you know nothing about the dynamic list of columns in advance. But, most likely you do know something.
You do know the types of dynamic columns, because they come from PIVOT. Most likely, you know the maximum possible number of dynamic columns. Even if you don't, SQL Server has a limit of 1024 columns per (nonwide) table and there is a limit of 8060 bytes per row (http://msdn.microsoft.com/en-us/library/ms143432.aspx). So, you can create a #Temp table in advance with maximum possible number of columns and use only some of them (make all your columns NULLable).
So, CREATE TABLE will look like this (instead of int use your type):
CREATE TABLE #Temp(c1 int NULL, c2 int NULL, c3 int NULL, ..., c1024 int NULL);
Yes, column names in #Temp will not be the same as in #Cols. It should be OK for your processing.
You have a list of columns in your #Cols variable. You somehow make this list of columns in some external code, so when #Cols is generated you know how many columns there are. At this moment you should be able to generate a second list of columns that matches the definition of #Temp. Something like:
#TempCols = N'c1, c2, c3, c4, c5';
The number of columns in #TempCols should be the same as the number of columns in #Cols. Then your dynamic SQL would look like this (I have added INSERT INTO #Temp (#TempCols) in front of your code):
SET #Sql = N'INSERT INTO #Temp (' + #TempCols + N') SELECT ' + #Cols + N' FROM
(
SELECT ResourceKey, ResourceValue
FROM LocaleStringResources where StateId ='
+ LTRIM(RTRIM(#StateID)) + ' AND FormId =' + LTRIM(RTRIM(#FormID))
+ ' AND CultureCode =''' + LTRIM(RTRIM(#CultureCode)) + '''
) x
pivot
(
max(ResourceValue)
for ResourceKey IN (' + #Cols + ')
) p ;'
Then you execute your dynamic SQL:
EXEC (#Sql) OR sp_executesql #Sql
And then do other processing using the #Temp table and temp column names c1, c2, c3, ...
MSDN says:
A local temporary table created in a stored procedure is dropped
automatically when the stored procedure is finished.
You can also DROP the #Temp table explicitly, like this:
IF OBJECT_ID('tempdb..#Temp') IS NOT NULL
DROP TABLE #Temp'
All this T-SQL code (CREATE TABLE, EXEC, ...your custom processing..., DROP TABLE) would naturally be inside the stored procedure.
Alternative to create a temporary table is to use the subquery
select t1.name,t1.lastname from(select * from table)t1.
where "select * from table" is your dyanmic query. which will return result which you can use as temp table t1 as given in example .
IF OBJECT_ID('tempdb..##TmepTable') IS NOT NULL DROP TABLE ##TmepTable
CREATE TABLE ##TmepTable (TmpCol CHAR(1))
DECLARE #SQL NVARCHAR(max) =' IF OBJECT_ID(''tempdb..##TmepTable'') IS NOT
NULL DROP TABLE ##TmepTable
SELECT * INTO ##TmepTable from [MyTableName]'
EXEC sp_executesql #SQL
SELECT Alias.* FROM ##TmepTable as Alias
IF OBJECT_ID('tempdb..##TmepTable') IS NOT NULL DROP TABLE ##TmepTable
Here is step by step solution for your problem.
Check for your temporary tables if they exist, and delete them.
IF OBJECT_ID('tempdb..#temp') IS NOT NULL
DROP TABLE #temp
IF OBJECT_ID('tempdb..##abc') IS NOT NULL
DROP TABLE ##abc
Store your main query result in first temp table (this step is for simplicity and more readability).
SELECT *
INTO #temp
FROM (SELECT ResourceKey, ResourceValue
FROM LocaleStringResources
where StateId ='+ LTRIM(RTRIM(#StateID)) + ' AND FormId =' + LTRIM(RTRIM(#FormID))
+ ' AND CultureCode =' + LTRIM(RTRIM(#CultureCode)) + ') AS S
Write below query to create your pivot and store result in another temp table.
DECLARE #str NVARCHAR(1000)
DECLARE #sql NVARCHAR(1000)
SELECT #str = COALESCE(#str+',', '') + ResourceKey FROM #temp
SET #sql = N'select * into ##abc from (select ' + #str + ' from (SELECT ResourceKey, ResourceValue FROM #temp) as A
Pivot
(
max(ResourceValue)
for ResourceKey in (' + #str + ')
)as pvt) as B'
Execute below query to get the pivot result in your next temp table ##abc.
EXECUTE sp_executesql #sql
And now you can use ##abc as table where-ever you want like
select * from ##abc
Hope this will help you.

Stored procedure to find number of rows in a table

In a stored procedure I pass a table name as the input variable.
I want to return the number of rows of this table with that stored procedure.
I tried something like this but it did not work:
declare #maxRowCount bigint
exec('set '+ #maxRowCount + ' =(select COUNT(1) from ' + #tableName + ')')
This is SQL Server 2008.
You can try this
CREATE PROCEDURE dbo.sp_selectcount
#tablename NVARCHAR(200)
AS
DECLARE #cmd NVARCHAR (255)
SET #cmd = 'SELECT count(*) from ' + #tablename
EXEC sp_executesql #cmd
The following example should give you something to work with.
-- fully qualify your table name (this is probably an input value in your sproc?)
-- please note that I use system view master.sys.tables as an example table here
DECLARE #tablename NVARCHAR(MAX) = N'[master].[sys].[tables]';
-- build the sql statement that you will execute
DECLARE #sql NVARCHAR(MAX) = N'SELECT COUNT(*) FROM ' + #tablename;
-- create a variable to hold the number of rows later on
DECLARE #nrofrows BIGINT;
-- create a temp table to store the result of executing the sql statement
CREATE TABLE #temp (NrOfRows BIGINT);
-- insert the result of the execution of the sql statement into the temp table
INSERT INTO #temp
EXECUTE(#sql);
-- extract the number of rows from the temp table
SET #nrofrows = (SELECT NrOfRows FROM #temp);
-- check the result so you can test!
PRINT #nrofrows;
If you want good background information on dynamic SQL, check out Erland Sommarskogs article The Curse and Blessings of Dynamic SQL.
You should remove the quotes around #maxRowCount.
Try this:
declare #maxRowCount bigint
exec('set #maxRowCount =(select COUNT(*) from ' + #tableName + ')')
OR
exec('SELECT #maxRowCount = COUNT(*) from ' + #tableName)
Analysis:
With the query you tried, it will execute:
set blablabla = (select count(1) from MyTable)
By removing the quotes:
set #maxRowCount = (select count(*) from MyTable)
You can try this instead.
declare #maxRowCount bigint(5)
exec('SELECT COUNT(*) INTO #maxRowCount FROM ' + #tableName)

using temp tables in SQL Azure

I am writing a query to pivoting table elements where column name is generated dynamically.
SET #query = N'SELECT STUDENT_ID, ROLL_NO, TITLE, STUDENT_NAME, EXAM_NAME, '+
#cols +
' INTO ##FINAL
FROM
(
SELECT *
FROM #AVERAGES
UNION
SELECT *
FROM #MARKS
UNION
SELECT *
FROM #GRACEMARKS
UNION
SELECT *
FROM #TOTAL
) p
PIVOT
(
MAX([MARKS])
FOR SUBJECT_ID IN
( '+
#cols +' )
) AS FINAL
ORDER BY STUDENT_ID ASC, DISPLAYORDER ASC, EXAM_NAME ASC;'
EXECUTE(#query)
select * from ##FINAL
This query works properly in my local database, but it doesn't work in SQL Azure since global temp tables are not allowed there.
Now if i change ##FINAL to #FINAL in my local database, but it gives me error as
Invalid object name '#FINAL' .
How can I resolve this issue?
Okay, after saying I didn't think it could be done, I might have a way. It's ugly though. Hopefully, you can play with the below sample and adapt it to your query (without having your schema and data, it's too tricky for me to attempt to write it):
declare #cols varchar(max)
set #cols = 'object_id,schema_id,parent_object_id'
--Create a temp table with the known columns
create table #Boris (
ID int IDENTITY(1,1) not null
)
--Alter the temp table to add the varying columns. Thankfully, they're all ints.
--for unknown types, varchar(max) may be more appropriate, and will hopefully convert
declare #tempcols varchar(max)
set #tempcols = #cols
while LEN(#tempcols) > 0
begin
declare #col varchar(max)
set #col = CASE WHEN CHARINDEX(',',#tempcols) > 0 THEN SUBSTRING(#tempcols,1,CHARINDEX(',',#tempcols)-1) ELSE #tempcols END
set #tempcols = CASE WHEN LEN(#col) = LEN(#tempcols) THEN '' ELSE SUBSTRING(#tempcols,LEN(#col)+2,10000000) END
declare #sql1 varchar(max)
set #sql1 = 'alter table #Boris add [' + #col + '] int null'
exec (#sql1)
end
declare #sql varchar(max)
set #sql = 'insert into #Boris (' + #cols + ') select ' + #cols + ' from sys.objects'
exec (#sql)
select * from #Boris
drop table #Boris
They key is to create the temp table in the outer scope, and then inner scopes (code running within EXEC statements) have access to the same temp table. The above worked on SQL Server 2008, but I don't have an Azure instance to play with, so not tested there.
If you create a temp table, it's visible from dynamic sql executed in your spid, if you create the table in dynamic sql, it's not visible outside of that.
There is a workaround. You can create a stub table and alter it in your dynamic sql. It requires a bit of string manipulation but I've used this technique to generate dynamic datasets for tsqlunit.
CREATE TABLE #t1
(
DummyCol int
)
EXEC(N'ALTER TABLE #t1 ADD foo INT')
EXEC ('insert into #t1(DummyCol, foo)
VALUES(1,2)')
EXEC ('ALTER TABLE #t1 DROP COLUMN DummyCol')
select *from #t1