Retrieve Max loaded date across all tables on a DB - sql

Output I'm trying to get to;
(Database name = ATT)
Table Name
Column name
MAX loaded date = MAX(loaded_date) for this column only
loaded_date is a column in around 50 tables in a database with the same name and datatype (Datetime)
select * FROM sys.tables
select * FROM syscolumns
I've been exploring the system tables without much luck, looking at some posts it may be done dynamic SQL which I've never done.

You can write an sql that writes an sql..
SELECT REPLACE(
'select ''{tn}'' as table_name, max(loaded_date) as ld from {tn} union all'
,'{tn}',table_name)
FROM
information_schema.columns
WHERE
column_name = 'loaded_date'
Run that, then copy all but the final UNION ALL out of the results window and into the query window, and run again
If you wanted to get all this into a single string for dynamic exec, i guess it'd look like (untested) a procedure that contained:
DECLARE #x NVARCHAR(MAX);
SELECT #x =
STRING_AGG(
REPLACE(
'select ''{tn}'' as table_name, max(loaded_date) as ld from {tn}'
,'{tn}',table_name)
,' union all ')
FROM
information_schema.columns
WHERE
column_name = 'loaded_date';
EXECUTE sp_executesql #x;
If your SQLS is old and doesnt have string_agg it's a bit more awkward - but there are many examples of "turn rows into CSV" in sql server that look like STUFF..FOR XML PATH - https://duckduckgo.com/?t=ffab&q=rows+to+CSV+SQLS&ia=web

I wrote up a more permanent type of script that does this. It returns a result set of the list of tables in the current database with a column named loaded_date along with the MAX(loaded_date) result from each table. This script individually queries each table by looping through and running the query on each table individually and keeping track of the max value for each table in a table variable. It also has a #Debug variable that allows you to see the text of the queries that would be run instead of actually running them and implements custom error message to troubleshoot any issues.
/*disable row count messages*/
SET NOCOUNT ON;
/*set to 1 to debug (aka just print queries instead of running)*/
DECLARE #Debug bit = 0;
/*get list of tables to query and assign a unique index to row to assist in looping*/
DECLARE #TableList TABLE(
SchemaAndTableName nvarchar(257) NOT NULL
,OrderToQuery bigint NOT NULL
,MaxLoadedDate datetime NULL
,PRIMARY KEY (OrderToQuery)
);
INSERT INTO #TableList (SchemaAndTableName,OrderToQuery)
SELECT
CONCAT(QUOTENAME(s.name),N'.', QUOTENAME(t.name)) AS SchemaAndTableName
,ROW_NUMBER() OVER(ORDER BY s.name, t.name) AS OrderToQuery
FROM
sys.columns AS c
INNER JOIN sys.tables AS t ON c.object_id = t.object_id
INNER JOIN sys.schemas AS s ON t.schema_id = s.schema_id
WHERE
c.name = N'loaded_date';
/*declare and set some variables for loop*/
DECLARE #NumTables int = (SELECT TOP (1) OrderToQuery FROM #TableList ORDER BY OrderToQuery DESC);
DECLARE #I int = 1;
DECLARE #CurMaxDate datetime;
DECLARE #CurTable nvarchar(257);
DECLARE #CurQuery nvarchar(max);
/*start loop*/
WHILE #I <= #NumTables
BEGIN
/*build text of current query*/
SET #CurTable = (SELECT SchemaAndTableName FROM #TableList WHERE OrderToQuery = #I);
SET #CurQuery = CONCAT(N'SELECT #MaxDateOut = MAX(loaded_date) FROM ', #CurTable, N';');
/*check debugging status*/
IF #Debug = 0
BEGIN
BEGIN TRY
EXEC sys.sp_executesql #stmt = #CurQuery
,#params = N'#MaxDateOut datetime OUTPUT'
,#MaxDateOut = #CurMaxDate OUTPUT;
END TRY
BEGIN CATCH
DECLARE #ErrorMessage nvarchar(max) = CONCAT(
N'Error querying table ', #CurTable, N'.', NCHAR(13), NCHAR(10)
,N'Errored query: ', NCHAR(13), NCHAR(10), #CurQuery, NCHAR(13), NCHAR(10)
,N'Error message: ', ERROR_MESSAGE()
);
RAISERROR(#ErrorMessage,16,1) WITH NOWAIT;
/*on error end loop so error can be investigated*/
SET #I = #NumTables + 1;
END CATCH;
END;
ELSE /*currently debugging*/
BEGIN
PRINT(CONCAT(N'Debug output: ', #CurQuery));
END;
/*update value in our table variable*/
UPDATE #TableList
SET MaxLoadedDate = #CurMaxDate
WHERE
OrderToQuery = #I;
/*increment loop*/
SET #I = #I + 1;
END;
SELECT
SchemaAndTableName AS TableName
,MaxLoadedDate AS Max_Loaded_date
FROM
#TableList;
I like this solution better as querying each table one at a time would be much less system impact than attempting one large UNION ALL query. Querying a large set of a tables all at once could cause some serious resource semaphore or locking contention (depending on usage of your db).
It is fairly well commented, but let me know if something is not clear.
Also, just a note, dynamic SQL should be used as a last resort. I provided this script to answer your question, but you should explore better options than something like this.

You can go for undocumented stored procedure sp_MSforeachtable. But, don't use in production code, as this stored procedure might not be available in future versions.
Read more on sp_MSforeachtable
EXEC sp_MSforeachtable 'SELECT ''?'' as tablename, max(loaded_Date) FROM ?'

Related

Dynamic query inside if exists is not working

I am writing Dynamic SQL inside If exists. I have a query on sys.columns to check that department Id column exists or not. If it exists I have to check row count and go inside if statement. I am not able to get mistake in my query. Someone please point out.
DECLARE #TableName VARCHAR(100) = '[Student]'
DECLARE #ColName VARCHAR(10) = 'DeptId'
DECLARE #Query NVARCHAR(1000) = '
SELECT *
FROM sys.columns
WHERE Name =' + #ColName + 'AND Object_ID = Object_ID(' + #TableName + ')'
DECLARE #rowcnt INT
EXEC sp_executesql #query
SELECT #rowcnt = ##ROWCOUNT
IF (#rowcnt > 0)
BEGIN
PRINT 'row present'
END
ERROR - Incorrect syntax near 'Object_ID'.
And it always returns row count as 1.
I'm going to tell what's wrong, but I'll let you fix the query because there are a lot of options. The query returns an error. I'm not sure why ##ROWCOUNT is returned as 1, but you are missing single quotes for the Name. So, in all likelihood, you are going to get an error that DEPTID is not a valid column.
You can naively fix this by putting in the single quotes. But, you should be using that arguments to sp_execute_sql to pass arguments in.
And, I'll add that this is probably unnecessary, because normally one would do:
if (exists (select 1
from information_schema.columns
where table_name = #table_name and column_name = #column_name
)
)
begin
. . .
end;
Of course, this doesn't do the object resolution from the name, but that is rarely needed, especially if you are not putting strange characters in table and column names.
Fortunately dynamic SQL isn't needed in this case. You can use a parameterized query.
SELECT #rowcnt = COUNT(*)
FROM sys.columns
WHERE [name] = #ColName
AND [object_id] = OBJECT_ID(#TableName)
DECLARE #TableName SYSNAME = 'dbo.test',
#ColName SYSNAME = 'col1'
IF COL_LENGTH(#TableName, #ColName) IS NOT NULL
PRINT 'column present'

SQL Server - Convert SQL to Stored Procedure

Suppose I have the following structure to a set of tables in my SQL Server (2012) DB:
StartDate: Col1: Col2: .... Coln:
And, the way the DBA set up the database (have no control over that - I only have query access), all the tables with this structure that I'd want to query have, say, names beginning with MyTbl....
So, I would like to create a query that queries ALL these tables at once to get data for a specific StartDate and I've done it using the following SQL:
declare #t table(tablename varchar(50))
declare #sql varchar(max)
set #sql = ''
insert into #t
SELECT t.name AS table_name FROM sys.tables AS t
WHERE t.name LIKE 'MyTbl%'
select #sql = #sql + 'Select ''' + tablename + ''' as Table_Name, t.* From ' + tablename +
' t where StartDate = ''2015-01-01'' +
' union ' from #t
Select #sql = substring(#sql, 1, len(#sql) - 6)
exec(#sql)
In other words:
Find all tables in my DB with names beginning with MyTbl
Query each table for any data with StartDate = '2015-01-01`
Union all those queries together to get one big dataset result
The SQL works perfectly, but I'm getting quite stuck in creating a stored procedure from this query that can take in a parameter for StartDate and I don't know enough about stored procedures to do this correctly.
How could I convert this into a stored procedure that takes a date in for StartDate (to replace the ''2015-01-01'' in the query)?
Any help / guidance would be GREATLY appreciated!!!
THANKS!!!
I noticed you were not looping through each table .. here is something I had put together
CREATE PROCEDURE get_tabledata (#date DATE)
AS
BEGIN
DECLARE #t TABLE (
id INT IDENTITY(1, 1)
,tablename VARCHAR(50)
)
DECLARE #id INT
DECLARE #tablename VARCHAR(max)
DECLARE #sql VARCHAR(max)
SET #sql = ''
INSERT INTO #t
SELECT t.NAME AS table_name
FROM sys.tables AS t
WHERE t.NAME LIKE 'MyTbl%'
SET #id = ##ROWCOUNT
IF (#id > 0)
BEGIN
WHILE (#id > 0)
BEGIN
SET #tablename = (
SELECT tablename
FROM #t
WHERE id = #id
)
SELECT #sql = #sql + 'Select ' + #tablename + ''' as Table_Name, t.* From ' + #tablename + ' t where StartDate = ' + '' + convert(VARCHAR, #date) + ''
SET #sql = #sql + ' union'
Set #id = #id -1;
END
SELECT #sql = substring(#sql, 1, len(#sql) - 6)
END
EXEC (#sql)
END
While it can be a little dense if you're not used to the styling Microsoft uses on these pages, the best place to start would be the Create Procedure documentation on MSDN
https://msdn.microsoft.com/en-us/library/ms187926.aspx
That said, creating a stored procedure is pretty straight forward. Here's a really simple procedure that takes a #startDate parameter and then just returns it back. This is just to illustrate how and where you define your parameters
create procedure dbo.MyProcedure
-- put your input parameters here
#StartDate date
as
--put the body of your procedure (i.e. everything you've written in your OP) here
select #StartDate
go
YOu'll notice however that if you run this twice in a row, you get an error, because it tries to build the same procedure again. Another thing which can come in handy is adding some code before your procedure which will basically check to see if it already exists, and if it does, alter the procedure rather than just blindly re-create it.
This is a snippet from a template I use quite often which handles all of that logic for you. The simplest way to use this is press CTRL-SHIFT-M, which brings up a dialogue to replace all those tags with values you provide.
use [<Database Name, sysname,>]
go
if not exists (select 1
from sys.procedures with(nolock)
where name = '<Procedure Name, sysname,>'
and [schema_id] = schema_id('<Schema, sysname,dbo>')
and type = 'P'
)
exec ('create procedure [<Schema, sysname,dbo>].[<Procedure Name, sysname,>]
as
select ''Procedure not defined.'' as ErrorDescription
return')
--Executed as dynamic SQL since SQL Server Management Studio considures the straight SQL code a syntax error for some reason on the create procedure statement
GO
alter procedure [<Schema, sysname,dbo>].[<Procedure Name, sysname,>]
<Parm 1 Name, sysname,include [#]> <Parm 1 Datatype, sysname,><Parm 1 Default, sql_variant,include [=] if used>,
<Parm 2 Name, sysname,include [#]> <Parm 2 Datatype, sysname,><Parm 2 Default, sql_variant,include [=] if used>
as
/*******************************************************************************************************
********************************************************************************************************/
---------------------------------------------
-- declare variables
---------------------------------------------
---------------------------------------------
-- create temp tables
---------------------------------------------
---------------------------------------------
-- set session variables
---------------------------------------------
set nocount on
---------------------------------------------
-- body of stored procedure
---------------------------------------------
return

Find table information where part of column matches char variable

I'm trying to find any tables with columns containing the word date somewhere in the column name.
All of my queries are either all or nothing: they return all tables in the DB, or no results at all.
When I run a query without the variable, it works, as seen here.
select *
from MyDB.INFORMATION_SCHEMA.COLUMNS
where column_name like '%date%'
However, I can't get it to work by using a variable.
declare #temp varchar = 'date'
select*
from MyDB.INFORMATION_SCHEMA.COLUMNS
where column_name like '%' + #temp + '%'
The reason I'd like to do this is because I need to run this on more than one DB (such as below), and I have to perform this several times (for more than just date), and I'd like the process to go more smoothly.
select *
from MyDB1.INFORMATION_SCHEMA.COLUMNS
where column_name like '%date%'
union all
select *
from MyDB2.INFORMATION_SCHEMA.COLUMNS
where column_name like '%date%'
union all
select *
from MyDB3.INFORMATION_SCHEMA.COLUMNS
where column_name like '%date%'
One query with cursor to loop through all the database on your server or you can explicitly loop through certain database
Query
DECLARE #DB_Name SYSNAME;
DECLARE #Sql NVARCHAR(MAX)= '';
DECLARE #cur CURSOR;
SET #Cur = CURSOR FOR
SELECT name
FROM sys.sysdatabases
--WHERE name IN ('DBName1', 'DBName2', 'DBName3'); --<-- uncomment this line and
-- specify the database names
OPEN #cur
FETCH NEXT FROM #Cur INTO #DB_Name
WHILE (##FETCH_STATUS = 0)
BEGIN
SET #Sql = N'
SELECT t.name
,c.name
FROM '+ QUOTENAME(#DB_Name) + '.sys.tables t
INNER JOIN ' + QUOTENAME(#DB_Name) + '.sys.columns c ON c.object_id = t.object_id
WHERE t.name LIKE ''%test%'''
EXEC(#Sql)
FETCH NEXT FROM #Cur INTO #DB_Name
END
CLOSE #cur
DEALLOCATE #cur
You can use what ever source of column info you prefer but the issue you are having is because of your variable declaration.
declare #temp varchar = 'date' is equivalent to
declare #temp varchar(1) = 'date' so
select #temp returns 'd' so you are getting any columns containing 'd'
when you declare your variable make sure it has a length that is sufficient to store the longest of the strings you will be searching for. Jason's answer will work too but it is because of the variable declaration not the source of the data.
I guess problem is with your variable declaration part.
By default any variable declared with datatype VARCHAR will be considered as VARCHAR(1) in SQLServer.
declare #temp varchar = 'date'
print #temp --d
So try declaring your variable as VARCHAR(4).
declare #temp varchar(4) = 'date'
print #temp --date

Looping through a column in SQL table that contains names of other tables

I have fairly new to using SQL, currently I have a table that has a column that contains the names of all the tables I want to use for one query, so what I want to do is to loop through that column and go to every single one of these tables and then search one of their columns for a value (there could be multiple values), so whenever a table contains the value, I will list the name of the table. Could someone give me a hint of how this is done? Is cursor needed for this?
I don't have enough reputation to comment but is the table with the column that contain the table names all in one column, meaning that all the table names are comma separated or marked with some sort of separator? This would cause the query to be a little more complicated as you would have to take care of that before you start looping through your table.
However, this would require a cursor, as well as some dynamic sql.
I will give a basic example of how you can go about this.
declare #value varchar(50)
declare #tableName varchar(50)
declare #sqlstring nvarchar(100)
set #value = 'whateveryouwant'
declare #getTableName = cursor for
select tableName from TablewithTableNames
OPEN #getTableName
fetch NEXT
from #getTableName into #tableName
while ##FETCH_STATUS = 0
BEGIN
set #sqlstring = 'Select Count(*) from ' + #tableName + 'where ColumnNameYouwant = ' + #value
exec #sqlstring
If ##ROWcount > 0
insert into #temptable values (#tableName)
fetch next
from #getTableName into #tableName
END
select * from #temptable
drop table #temptable
close #getTableName
deallocate #getTableName
I'm currently not able to test this out as for time constraint reasons, but this is how I would go about doing this.
You could try something like this:
--Generate dynamic SQL
DECLARE #TablesToSearch TABLE (
TableName VARCHAR(50));
INSERT INTO #TablesToSearch VALUES ('invoiceTbl');
DECLARE #SQL TABLE (
RowNum INT,
SQLText VARCHAR(500));
INSERT INTO
#SQL
SELECT
ROW_NUMBER() OVER (ORDER BY ts.TableName) AS RowNum,
'SELECT * FROM ' + ts.TableName + ' WHERE ' + c.name + ' = 1;'
FROM
#TablesToSearch ts
INNER JOIN sys.tables t ON t.name = ts.TableName
INNER JOIN sys.columns c ON c.object_id = t.object_id;
--Now run the queries
DECLARE #Count INT;
SELECT #Count = COUNT(*) FROM #SQL;
WHILE #Count > 0
BEGIN
DECLARE #RowNum INT;
DECLARE #SQLText VARCHAR(500);
SELECT TOP 1 #RowNum = RowNum, #SQLText = SQLText FROM #SQL;
EXEC (#SQLText);
DELETE FROM #SQL WHERE RowNum = #RowNum;
SELECT #Count = COUNT(*) FROM #SQL;
END;
You would need to change the "1" I am using as an example to the value you are looking for and probably add a CONVERT/ CAST to make sure the column is the right data type?
You actually said that you wanted the name of the table, so you would need to change the SQL to:
'SELECT ''' + ts.TableName + ''' FROM ' + ts.TableName + ' WHERE ' + c.name + ' = 1;'
Another thought, it would probably be best to insert the results from this into a temporary table so you can dump out the results in one go at the end?

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)