I have to check for value existence in a subset of tables in a subset of databases of a sql server instance. Beware I need to do this because I have 30 databases with same schema name and similar structure. Querying all databases separately is a waste of time.
The query generates correctly code for existing tables, but the additional check for column existence in table fails.
The column in some tables does not exist so the generated code must not include queries on tables without this column.
To solve this I need to realiably find a way to join sys.databases with sys.tables and then sys.columns. Or an alternative way to query all the required databases in a time saving manner.
SET NOCOUNT ON;
IF OBJECT_ID (N'tempdb.dbo.#temp') IS NOT NULL
DROP TABLE #temp
CREATE TABLE #temp
(
exist INT
, DB VARCHAR(50)
, tbname VARCHAR(500)
)
/*tables common root,
all tables i need to query start with this prefix and a number between 1 and 50
and some resulting tables do not exist
ex: dbo.Z_WBL_ASCHEDA23 exist in wbcto, while dbo.Z_WBL_ASCHEDA23 does not exist in db wbgtg
*/
DECLARE #TableName NVARCHAR(200)
SELECT #TableName = 'dbo.Z_WBL_ASCHEDA'
DECLARE #SQL NVARCHAR(MAX)
;WITH n(n) AS
(
SELECT 1
UNION ALL
SELECT n+1 FROM n WHERE n < 50
)
SELECT #SQL = STUFF((
SELECT CHAR(13)+'SELECT COUNT(1), ''' + db.name + ''', '''+
#TableName+CONVERT(VARCHAR, n.n)+''' FROM ' +#TableName+CONVERT(VARCHAR, n.n)
+ ' WHERE COALESCE(s_dettagli,'''') = ''CONTROLLATO'' '
+CHAR(13)
FROM sys.databases db
INNER JOIN n ON 1=1
INNER JOIN sys.tables t ON OBJECT_ID(db.name + '.' + #TableName+CONVERT(VARCHAR, n.n)) IS NOT NULL
INNER JOIN sys.columns c ON t.OBJECT_ID = c.OBJECT_ID and c.name = 's_dettagli'
/*join on columns not working, generates sql for tables without 's_dettagli' column and query fails*/
WHERE db.name like 'wb%' --check only databases starting with 'wb'
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '')
select #SQL
INSERT INTO #temp (exist, DB, tbname)
EXEC sys.sp_executesql #SQL
SELECT *
FROM #temp t
where exist <> 0
EDIT: adding some sql generated from query
SELECT COUNT(1), 'wb360', 'dbo.Z_WBL_ASCHEDA23' FROM wb360.dbo.Z_WBL_ASCHEDA23 WHERE COALESCE(s_dettagli,'') = 'CONTROLLATO'
SELECT COUNT(1), 'Wbbim', 'dbo.Z_WBL_ASCHEDA32' FROM Wbbim.dbo.Z_WBL_ASCHEDA32 WHERE COALESCE(s_dettagli,'') = 'CONTROLLATO'
the table of first query doesn't contain 's_dettagli' column
EDIT2: SOLUTION
EXEC sp_MSforeachdb '
IF ''?'' not like ''wb%''
RETURN
USE [?]
EXEC sp_MSforeachtable
#replacechar = ''!'',
#command1 = ''SELECT ''''?'''' AS db_name, ''''!'''' AS table_name, COUNT(*) FROM ! '',
#whereand = '' And Object_id In (
Select t.Object_id
From sys.objects t
INNER JOIN sys.columns c on c.Object_id = t.Object_id
Where t.name like ''''Z_WBL_ASCHEDA%''''
AND c.name = ''''s_dettagli'''' )'' '
Sys.columns can be joined to sys.tables using the object_id field (the object_id is the representation of the table itself).
sys.tables is run in the context of the database you are querying, hence you cannot see a table contained in another database. sys.databases can be run on any database on an instance and allow you to view other databases on the same instance. As such you don't need to join the table to the database (also the reason why there is no database_id field within sys.tables).
I hope that helps. Any clarification please let me know.
I would suggest alternative ways:
use registered Servers in SSMS and run the script on each database here
use exec sys.sp_MSforeachdb here
use sqlcmd and powershell to switch databases
I believe this script can help you :
SET NOCOUNT ON;
IF OBJECT_ID (N'tempdb.dbo.#Temp') IS NOT NULL
DROP TABLE #Temp
CREATE TABLE #Temp
(
exist INT
, DB VARCHAR(50)
, tbname VARCHAR(500)
)
DECLARE #SchemaName NVARCHAR(200)
DECLARE #TableName NVARCHAR(200)
DECLARE #ColumnName NVARCHAR(200)
DECLARE #SearchText NVARCHAR(200)
DECLARE #DBNameStartWith NVARCHAR(200)
DECLARE #SQL NVARCHAR(MAX)
SET #DBNameStartWith = 'wb'
SET #SchemaName = 'dbo'
SET #TableName = 'Z_WBL_ASCHEDA'
SET #ColumnName = 's_dettagli'
SET #SearchText = 'CONTROLLATO'
DECLARE #DatabaseName varchar(100)
DECLARE Crsr CURSOR FOR
SELECT name
FROM MASTER.sys.sysdatabases
WHERE name LIKE ''+#DBNameStartWith+'%'
OPEN Crsr
FETCH NEXT FROM Crsr INTO #DatabaseName
WHILE ##FETCH_STATUS = 0
BEGIN
IF ISNULL((SELECT COUNT(1) FROM SYS.TABLES T,SYS.COLUMNS C WHERE T.object_id=C.object_id AND T.name=#TableName AND C.name=#ColumnName),0)>0
BEGIN
SET #SQL = '
IF EXISTS (SELECT 1 FROM '+#DatabaseName+'.SYS.TABLES T,'+#DatabaseName+'.SYS.COLUMNS C WHERE T.object_id=C.object_id AND T.name='''+#TableName+''' AND C.name='''+#ColumnName+''')
BEGIN
SELECT COUNT(1),'''+#DatabaseName+''','''+#TableName+'''
FROM '+#DatabaseName+'.'+#SchemaName+'.'+#TableName+'
WHERE '+#ColumnName+'=''' +#SearchText+'''
END'
PRINT(#SQL)
INSERT INTO #Temp
EXEC sp_executesql #SQL
END
FETCH NEXT FROM Crsr INTO #DatabaseName
END
CLOSE Crsr
DEALLOCATE Crsr
SELECT * FROM #Temp
Related
I want to execute the below query against all databases.
DECLARE #DB_NAME VARCHAR(30)
SET #DB_NAME = 'Employee';
WITH D
AS
(
SELECT #DB_NAME AS DB_NAME, T.NAME AS TABLE_NAME, C.NAME AS COLUMN_NAME
FROM SYS.tables T
INNER JOIN SYS.columns C
ON T.object_id = C.object_id
WHERE C.name LIKE '%AFFINITY%'
or c.name = 'affinity'
)
SELECT DB_NAME, TABLE_NAME, MAX(COLUMN_NAME) AS COLUMN_NAME FROM D
GROUP BY DB_NAME, TABLE_NAME
ORDER BY TABLE_NAME`
There is the undocumented
EXEC sp_MSforeachdb #command
(A ? in the command will be replaced by the DB name.)
Otherwise you'll need sp_executesql and a cursor over sys.databases.
The latter is supported, sp_MSforeachdb isn't, so for any kind of production system the cursor is the better option (even if a little more work initially). And of course your own query on sys.databases can filter the database list.
USE CAREFULLY:
I assume you have a MYTABLE in each required database, otherwise this wont work.
DECLARE #MYDATABASES NVARCHAR(50)
CREATE TABLE #MYBASE(DB NVARCHAR(50))
DECLARE dbcur CURSOR FOR
SELECT name
FROM sys.databases s
WHERE name not in ('master', 'tempdb', 'msdb') --use this to select all databases but system
--WHERE NAME IN ('myfirstDB', 'mysecondDB', 'mythirdDB') --use this to select only certain databases
OPEN dbcur
FETCH dbcur INTO #MYDATABASES
WHILE ##FETCH_STATUS = 0
BEGIN
/* MY STATEMENT */
DECLARE #SQLSTATEMENT NVARCHAR(MAX)
SELECT #SQLSTATEMENT = N'
SELECT #DB --EDIT WITH YOUR COLUMNS
FROM '+#MYDATABASES+'.dbo.MYTABLE --EDIT THIS
'
INSERT INTO #MYBASE
EXEC sp_executesql #SQLSTATEMENT, N'#DB NVARCHAR(50)', #DB = #MYDATABASES
/* END STATEMENT */
FETCH NEXT FROM dbcur INTO #MYDATABASES
END
CLOSE dbcur
DEALLOCATE dbcur
SELECT DISTINCT * FROM #MYBASE
ORDER BY DB
DROP TABLE #MYBASE
I am looking for a more appropriate way to execute several inserts into a nonexistent table.
To create the table beforehand is not easily possible, as I don't know the data type of the selected column.
An "insert with create" would do, but I don't think there is anything like that.
Is there any better way to do so than to select into and then to insert?
Here is the "bad" way I do it, in an example very much stripped down to demonstrate the problem.
set nocount on
declare
#name sysname = '',
#i int = 0,
#sql nvarchar(4000) = ''
declare test cursor for
select top 10 a.name from sys.tables a inner join sys.columns b on a.object_id = b.object_id --and b.name = 'description'
open test
fetch next from test into #name
while (##FETCH_STATUS <> -1)
begin
if #i = 0 begin
set #sql = 'select distinct top 10 description into #t1 from ' + #name + ''
select #sql
-- exec sp_executesql #sql
end
else begin
set #sql = 'insert #t1 select distinct top 10 description into #t1 from ' + #name + ''
select #sql
-- exec sp_executesql #sql
end
set #i = #i + 1
fetch next from test into #name
end
close test
deallocate test
if object_id ('tempdb..#t1') is not null select * from #t1
This solution is "bad" as you need the statement at two positions. In the case shown here this is trivial, but when the statement gets more complex this can become an issue.
You can simplify your query into this one:
set nocount on
declare
#name sysname = '',
#i int = 0,
#sql nvarchar(4000) = N''
if object_id ('tempdb..#t1') is not null DROP TABLE #t1
;WITH cte AS (
select top 10 a.[name]
from sys.tables a
inner join sys.columns b
on a.object_id = b.object_id --and b.name = 'description'
)
SELECT #sql = #sql + N'UNION ALL
select distinct top 10 description
from ' + QUOTENAME([name]) + CHAR(13)
FROM cte
SELECT #sql = N';WITH cte AS (' + STUFF(#sql,1,10,') SELECT * INTO #t1 FROM cte')
PRINT #sql
--EXEC (#sql)
select * from #t1
No cursor or while loop;
Temporary table is dropped (if exists) before query execution;
You got a weird query, as for now it takes the first table from sys.tables and SELECT TOP 10 Descriptions from this table as many times as there are columns in this table.
The SELECT INTO statement copies data from one table into a new table, this might help you.
Example:-
SELECT *
INTO newtable
FROM oldtable
WHERE condition
The above also supports joins.
This is my first post to StackOverflow. I've been using this amazing resource for a number of years to answer hundreds of SQL and PowerShell questions, however this one has me stumped for a number of days.
I am using SQL Server 2014 SP2 and I am trying to do an update to DATABASE1, FIELD1, then FIELD2 then FIELD3 from multiple other database.
FIELD1 may exist in one of multiple other databases.
FIELD1 may not exist in ALL databases - which is where I have the problem.
Database Design Link
I have the following (anonymised) query and it appears to be working:
EXEC sp_MSforeachdb 'IF ''?'' IN (''DATABASE2'',''DATABASE3'',''DATABASE4'')
BEGIN
UPDATE DATABASE1.PARAMETER
SET B.[VALUE] = A.[FIELD1]
FROM DATABASE1.TABLE1 B
INNER JOIN ?.dbo.[TABLE2] A
ON A.JOINVALUE = B.JOINVALUE
WHERE B.COLUMN2 = ''SOMETHING''
AND COLUMN3= ''PF.T.FIELD1''
END ;'
Until I get to say FIELD8, as it exists in DATABASE1 but not in DATABASE2, DATABASE3 or DATABASE4. I then get the following error:
Msg 207, Level 16, State 1, Line 30
Invalid column name 'FIELD8'.
From my Google and StackOverflow searches, I've tried to use (for the first time) a:
IF EXISTS (SELECT COLUMN1 FROM Database2.Table2 WHERE Column1='Field8')
EXEC .......
But that's where I started to really struggle.
Hope the above makes sense.
Any tips or assistance would be greatly appreciated.
N.B. I have about 3,000 fields in Database1 which require updating. I've so-far built all my UPDATE statements dynamically.
You can create stored proc, that will search for columns and tables in system tables:
ALTER PROCEDURE dbo.check_table_exists
-- Add the parameters for the stored procedure here
#table_name nvarchar(255),
#column_name nvarchar(255)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
DECLARE #SQLString nvarchar(max),
#ParmDefinition nvarchar(500) = N'#table_name nvarchar(255), #column_name nvarchar(255)';
IF OBJECT_ID(N'tempdb..#check_column_exists') is not null DROP TABLE #check_column_exists
CREATE TABLE #check_column_exists (
db nvarchar(500) NULL,
column_exists bit NULL
)
SELECT #SQLString =
(
SELECT N'USE '+QUOTENAME([name]) +'; '+
'INSERT INTO #check_column_exists '+
'SELECT '''+[name]+''' as [db], '+
' COUNT(*) as column_exists ' +
'FROM sys.tables t ' +
'INNER JOIN sys.columns c ' +
' ON t.[object_id] = c.[object_id] ' +
'WHERE t.[name] = #table_name and c.[name] = #column_name; '
FROM sys.databases
WHERE [name] NOT IN (
'msdb',
'model',
'tempdb',
'master'
)
FOR XML PATH('')
) + 'SELECT [db] FROM #check_column_exists WHERE column_exists = 1; DROP TABLE #check_column_exists;'
EXEC sp_executesql #SQLString, #ParmDefinition, #table_name = #table_name, #column_name = #column_name
END
GO
You can change it to search only for columns and output the database and table name or whatever.
The output is:
db
-----------
DATABASE1
DATABASE4
...
etc
After that you can write this to table and use for dynamic SQL update query:
DECLARE #table_name nvarchar(255) = 'SomeTable',
#column_name nvarchar(255) = 'SomeField'
DECLARE #results TABLE (
db nvarchar(500)
)
INSERT INTO #results
EXEC dbo.check_table_exists #table_name, #column_name
--...Here goes building of dynamic SQL query to update data
First, sp_MSforeachdb is not reliable. For a working alternative, check here: Making a more reliable and flexible sp_MSforeachdb - Aaron Bertrand
Second, you can use system views to check if a column exists in a given table using sys.columns like so:
if exists (
select 1
from sys.columns c
where c.name = 'pilots_id' /* column name */
and c.object_id = object_id(N'pilots') /* table name */
)
begin
select 'Pilots_Id exists' /* do stuff */
end
rextester demo: http://rextester.com/UUXCB18567
I've transaction Backup tables in sqlserver and all have a similar naming convention like RECEIVER_CHARGE_LOG_[MONYYYY].I've queried sys.tables to find out 82 backup tables
I now need the max and min of the date in the table,so that i can check if any data's missing.
I need the output to be like
Table_name,MAX(DATE_TIME),MIN(DATE_TIME)
Is there a way I can reference all the tables from sys.tables and use them in from part of the main query.
Using sp_MSForeachtable you can filter the tables using the #Whereand parameter to restrict it to only the tables beginning with "RECEIVER_CHARGE_LOG_":
exec sp_MSforeachtable
#command1 = 'select ''?'', MAX(DATE_TIME),MIN(DATE_TIME) from ?',
#Whereand = ' and o.name like ''RECEIVER_CHARGE_LOG_%'''
EDIT
To make this even easier to use, you can insert into a temp table and then select from this:
CREATE TABLE #result
(
TableName SysName,
MaxDateTime DateTime,
MinDateTime DateTime
)
exec sp_MSforeachtable
#command1 = 'INSERT INTO #result select ''?'', MAX(DATE_TIME), MIN(DATE_TIME) from ?',
#Whereand = ' and o.name like ''RECEIVER_CHARGE_LOG_%''',
#postcommand = 'select * from #result;drop table #result'
You can use dynamic SQL like that. You may have to tweak it as you didn't give the exact schema of your tables:
declare #sql nvarchar(max) = N'create table #temp(Table_Name nvarchar(max), MaxDate datetime, MinDate datetime); '
select #sql = #sql + 'insert into #temp select ''' + name + ''', MAX(DATE_TIME),MIN(DATE_TIME) from [' + name + ']; '
from sys.tables where type = 'U' and name like 'RECEIVER_CHARGE_LOG%'
set #sql = #sql + ' select * from #temp; drop table #temp;'
exec (#sql)
#Damien_The_Unbeliever: Unbelievable. I didn't know sp_msforeachtable had such potential. I've always made these pages long scripts with while loops and a lot of dynamic SQL.
So OP, as an answer, you might try something like this for instance:
EDIT
You can also get the set out by using a temporary table to store the results, such as:
IF OBJECT_ID('tempdb..#TEMP') IS NOT NULL
DROP TABLE #TEMP
CREATE TABLE #TEMP (TableName VARCHAR(256) COLLATE DATABASE_DEFAULT, MaxDate DATETIME, MinDate DATETIME)
EXEC SP_msForeachTable 'IF OBJECT_ID(''?'') IN
(SELECT T.object_id
FROM sys.tables T
JOIN sys.columns C on C.object_id = T.object_id
JOIN sys.schemas S on S.schema_id = T.schema_id
WHERE T.name like ''RECEIVER_CHARGE_LOG_%'' AND C.name LIKE ''DATE_TIME''
AND S.name = ''dbo'')
EXEC(''INSERT INTO #TEMP SELECT ''''?'''' Table_Name, MAX(DATE_TIME) MaxDate, MIN(DATE_TIME) MinDate FROM ?'')'
SELECT *
FROM #TEMP
ORDER BY MinDate
Though I recommend you use Steve Ford's template for the sp_msForEachTable procedure, since it's far more reasonable and efficient.
SNAHi
anyone please suggest a query in sql to find the unused tables.
I have a legacy application migrated to .net from coldfusion.But lots of tables are unused now
What is the best way to find all the unused objects from database. (sql 2005)
thanks
SNA
In SQL Server, the acutal table data IS the clustered index. Using this query on the Dynamic Management Views (DMV) in SQL Server 2005 and up, you can find unused indices - if you find any clustered index (index_id=1) being unused over an extended period of time, the table is not being used anymore:
DECLARE #dbid INT
SELECT #dbid = DB_ID(DB_NAME())
SELECT
OBJECTNAME = OBJECT_NAME(I.OBJECT_ID),
INDEXNAME = I.NAME,
I.INDEX_ID
FROM
SYS.INDEXES I
JOIN
SYS.OBJECTS O ON I.OBJECT_ID = O.OBJECT_ID
WHERE
OBJECTPROPERTY(O.OBJECT_ID, 'IsUserTable') = 1
AND I.INDEX_ID NOT IN
(SELECT S.INDEX_ID
FROM SYS.DM_DB_INDEX_USAGE_STATS S
WHERE S.OBJECT_ID = I.OBJECT_ID
AND I.INDEX_ID = S.INDEX_ID
AND DATABASE_ID = #dbid)
ORDER BY
OBJECTNAME,
I.INDEX_ID,
INDEXNAME ASC
Another option would be to temporarily rename a table if you suspect it's not being used, and then see if your app(s) still work as expected. If they do for e.g. 30 days or so, then you're pretty sure you don't need that table anymore.
Marc
-- Query to find the tables not used by any stored procedure, function nor view
-- Using SQL 2005 system tables, all programatical objects for dependencies, and one&only query:
select tables.name, progr.name
from sys.objects tables (nolock)
left join sys.syscomments comm (nolock) on comm.text like '%' + tables.name +'%'
left join sys.objects progr (nolock) on progr.object_id = comm.id and progr.type in ('P', 'FN', 'TR', 'V' )
where tables.type = 'U'
and comm.id is null
Here is a query i have written to find the tables not used by any store procedures..
......................................................................................
Declare #tablename nvarchar(40)
Declare tablecursor cursor for
Select name from sysobjects where xtype = 'U'
DECLARE #sqlCommand nvarchar(1000)
declare #rowCount int
DECLARE #searchstring varchar(50)
DECLARE #ParmDefinition nvarchar(500);
create table #temp
(
UnusedTables nvarchar(40)
)
open tablecursor
fetch next from tablecursor into #tablename
while ##fetch_status = 0
begin
set #searchstring='p'
SET #sqlCommand = N'SELECT #rows = count(o.name) from sysobjects o ,
syscomments c where o.type='+char(39)+#searchstring + char(39)+' and
o.id=c.id and c.text like '+ char(39)+'%' + #tablename +'%'+char(39);
SET #ParmDefinition = N'#rows int OUTPUT';
EXECUTE sp_executesql #sqlCommand, #ParmDefinition,#rows=#rowCount OUTPUT;
if #rowCount = 0
begin
insert into #temp values (#tablename)
end
fetch next from tablecursor into #tablename
end
close tablecursor
deallocate tablecursor
select UnusedTables from #temp
drop table #temp
thanks
SA
Try something like below
DECLARE #TableNameTemp TABLE
(
id INT IDENTITY (1, 1),
tablename VARCHAR(1000)
)
INSERT INTO #TableNameTemp
SELECT table_name
FROM information_schema.tables
WHERE table_type = 'BASE TABLE'
ORDER BY table_name
DECLARE #CursorTestID INT = 1;
DECLARE #TotalCount INT = (SELECT Count(1)
FROM #TableNameTemp)
DECLARE #FinalResult TABLE
(
unsedtables VARCHAR(max)
)
DECLARE #TemExecInsert TABLE
(
testvalue VARCHAR(max),
type VARCHAR(max)
)
DECLARE #TableaName VARCHAR(max) = ''
WHILE #CursorTestID <= #TotalCount
BEGIN
DELETE FROM #TemExecInsert
SET #TableaName = (SELECT tablename
FROM #TableNameTemp
WHERE id = #CursorTestID)
INSERT INTO #TemExecInsert
EXEC Sp_depends
#objname = #TableaName
SET #CursorTestID = #CursorTestID + 1
IF ( (SELECT Count(1)
FROM #TemExecInsert) = 0 )
BEGIN
INSERT INTO #FinalResult
VALUES (#TableaName)
END
END
SELECT *
FROM #FinalResult
PS: Sorry, I am not certain how to bring the answer in shape. Hope this helps