Select same table name from all databases in SQL Server - sql-server

I have this dynamic query, that is union from all my databases (that is start with "Db") the same table ("Tbl_SameTable").
DECLARE #tableName nvarchar(256) = 'Tbl_SameTable'
DECLARE #sql nvarchar(max) = ''
SELECT #sql = #sql + CASE WHEN #sql <> '' THEN 'UNION ALL ' ELSE '' END
+ 'SELECT * FROM [' + dbs.name + ']..[' + #tableName + '] '
FROM sys.sysdatabases dbs
WHERE left(dbs.name,2) = 'Db'
EXEC(#sql)
I want to add two things to this query:
Add a column of database name
Assign the query result to a "temp table" or "table variable"
I do not know if this is important but, the "Tbl_SameTable" is a 5 column table (int, nvarchar, int,nvarchar,nvarchar)

This is untested, however, you'll want something like this. As this is pseudo SQL, you'll need to replace {Columns} with the actual names (not *) for it to work. For the CREATE TABLE you'll need to define the data type of said columns too.
DECLARE #SchemaName sysname = N'dbo',
#TableName sysname = N'YourTable';
CREATE TABLE #Temp (DatabaseName sysname,
{Columns});
DECLARE #SQL nvarchar(MAX),
#CRLF nchar(2) = NCHAR(13) + NCHAR(10);
SET #SQL = STUFF((SELECT #CRLF + N'UNION ALL' + #CRLF +
N'SELECT N' + QUOTENAME(d.[name],'''') + N' AS DatabaseName, {Columns}' + #CRLF +
N'FROM ' + QUOTENAME(d.[name]) + N'.' + QUOTENAME(#SchemaName) + N'.' + QUOTENAME(#TableName)
FROM sys.databases d
WHERE d.[name] LIKE 'Db%'
ORDER BY database_id
FOR XML PATH(N''),TYPE).value('(./text())[1]','nvarchar(MAX)'),1,13, N'') + N';'
--PRINT #SQL; --Your Best Friend
INSERT INTO #Temp(DatabaseName, {Columns})
EXEC sys.sp_executesql #SQL;
And, of course, if it doesn't work your best friend will be there to help you out.

How about using sp_MSforeachdb and adding the results to another table?
DROP TABLE IF EXISTS #tmp
CREATE TABLE #tmp (col1 INT, col2 INT,...)
DECLARE #command varchar(1000)
SELECT #command = 'IF ''?'' LIKE ''Db%'' BEGIN USE ?
EXEC(''INSERT INTO #tmp (col1, col2,...) SELECT col1, col2,... from Tbl_SameTable'') END'
EXEC sp_MSforeachdb #command
SELECT * FROM #tmp

Related

SQL Server Dynamic creation of global temp table and insert data issue

My requirement is to create a global temporary table and store data there which I would access later. I give a dynamic name to my global temporary table and getting error that
Invalid object name '##Tmp1_84'.
84 is #SPID
here is my script. please have a look and tell me what to rectify in code to get rid of runtime error Invalid object name '##Tmp1_84'
CREATE Proc USP_GetValuationValue
(
#Ticker VARCHAR(10),
#ClientCode VARCHAR(10),
#GroupName VARCHAR(10)
)
AS
DECLARE #SPID VARCHAR(MAX)
DECLARE #SQL nvarchar(MAX),
#CRLF nchar(2) = NCHAR(13) + NCHAR(10);
SELECT #SPID=CAST(##SPID AS VARCHAR)
SET #SQL = N'SELECT * INTO ##Tmp1_'+#SPID+' FROM (SELECT min(id) ID,f.ticker,f.ClientCode,f.GroupName,f.RecOrder,' + STUFF((SELECT N',' + #CRLF + N' ' +
N'MAX(CASE FieldName WHEN ' + QUOTENAME(FieldName,'''') + N' THEN FieldValue END) AS ' + QUOTENAME(FieldName)
FROM tblValuationSubGroup g
WHERE ticker=#Ticker AND ClientCode=#ClientCode AND GroupName=#GroupName
GROUP BY FieldName
ORDER BY MIN(FieldOrder)
FOR XML PATH(''),TYPE).value('(./text())[1]','nvarchar(MAX)'),1,10,N'') + #CRLF +
N'FROM (select * from tblValuationFieldValue' + #CRLF +
N'WHERE Ticker = '''+#Ticker+''' AND ClientCode = '''+#ClientCode+''' AND GroupName='''+#GroupName+''') f' + #CRLF +
N'GROUP BY f.ticker,f.ClientCode,f.GroupName,f.RecOrder) X';
--EXEC sys.sp_executesql #SQL
EXEC(#SQL)
EXEC('select * from ##Tmp1_'+#SPID+' ORDER BY Broker')
EXEC('DROP TABLE IF EXISTS ##Tmp1_'+#SPID)
George has mentioned why your attempt doesn't work in their answer, so I'm not going to touch on that.
I'm instead going to fix the problem, which I touch on in my comments. In truth, there is no need for a (global) temporary table, you just SELECT ... INTO it and then SELECT from it; you make no further transformations making it pointless. AS such you could just SELECT the data in the first place, no temporary table needed.
I also fix your injection issue; this is a fatal flaw. Dynamic SQL accepts parameters and you using them is a must; not using parameters and instead using injection opens you up to all sorts of errors and security issues.
I can't test this, but I suspect this will work. If not, use your best friend to debug and propagate fixes to the dynamic SQL on any bits I've missed (or provide code we can run above with an MRE).
CREATE Proc USP_GetValuationValue
(
#Ticker VARCHAR(10),
#ClientCode VARCHAR(10),
#GroupName VARCHAR(10)
)
AS
BEGIN
DECLARE #SPID VARCHAR(MAX), --Is this even used now?
#SQL nvarchar(MAX),
#CRLF nchar(2) = NCHAR(13) + NCHAR(10);
SELECT #SPID=CAST(##SPID AS VARCHAR);
SET #SQL = N'SELECT * FROM (SELECT min(id) ID,f.ticker,f.ClientCode,f.GroupName,f.RecOrder,' + STUFF((SELECT N',' + #CRLF + N' ' +
N'MAX(CASE FieldName WHEN ' + QUOTENAME(FieldName,'''') + N' THEN FieldValue END) AS ' + QUOTENAME(FieldName)
FROM tblValuationSubGroup g
WHERE ticker=#Ticker AND ClientCode=#ClientCode AND GroupName=#GroupName
GROUP BY FieldName
ORDER BY MIN(FieldOrder)
FOR XML PATH(''),TYPE).value('(./text())[1]','nvarchar(MAX)'),1,10,N'') + #CRLF +
N'FROM (select * from tblValuationFieldValue' + #CRLF +
N'WHERE Ticker = #Ticker AND ClientCode = #ClientCode AND GroupName= #GroupName) f' + #CRLF +
N'GROUP BY f.ticker,f.ClientCode,f.GroupName,f.RecOrder) X' + #CRLF +
N'ORDER Y Broker;';
--PRINT #SQL; --YOur best friend
--EXEC sys.sp_executesql #SQL --Why did you comment this out? This is correct!
EXEC sys.sp_executesql #SQL, N'#Ticker varchar(10), #ClientCode varchar(10), #GroupName varchar(10)', #Ticker, #ClientCode, #GroupName
END;

Function variable is not recognize in subquery

I want to write a function that counts non null and non empty entries of a field. My problem is that the query does not run since the #tableName variable is not recognized in the select statement and I do not know why
create function dbo.getCount(#cod int, #columnName as varchar(20), #tableName as varchar(20))
Returns int as
Begin
--Count all filled entries
Return (select COUNT(*) from #tableName
where #columnName <> '' and #columnName is not null)
End;
go
As mentioned in the comments, but to reiterate, as I'll delete them after this answer:
You can't do this with a function, for multiple reasons. SELECT
COUNT(*) FROM #TableName means count the number of rows in the
table variable #TableName not the table who's name is the value of #TableName. WHERE #ColumnName <> '' would mean where the value of the scalar variable doesn't have the value '',
not where the column (in the aforementioned table) with the name of value of #ColumnName doesn't have the value ''.
And you can't do this in a function as to do this type of thing, you
need dynamic SQL; and you can't use dynamic SQL in a function (as you
can't use the EXEC command).
You can, however, do this with a Stored Procedure:
CREATE PROC dbo.GetCount #SchemaName sysname = N'dbo', #TableName sysname, #ColumnName sysname, #Count int OUTPUT AS
BEGIN
DECLARE #SQL nvarchar(MAX),
#CRLF nchar(2) = NCHAR(13) + NCHAR(10);
SELECT #SQL = N'SELECT #Count = COUNT(NULLIF(' + QUOTENAME(c.[name]) + N',''''))' + #CRLF +
N'FROM ' + QUOTENAME(s.name) + N'.' + QUOTENAME(t.[name]) + N';'
FROM sys.schemas s
JOIN sys.tables t ON s.schema_id = t.schema_id
JOIN sys.columns c ON t.object_id = c.object_id
WHERE s.[name] = #SchemaName
AND t.[name] = #TableName
AND c.[name] = #ColumnName;
--PRINT #SQL; --Your debugging friend
EXEC sp_executesql #SQL, N'#Count int OUTPUT', #Count OUTPUT;
END
GO
And you run the SP like below (with sample table):
CREATE TABLE dbo.TestTable (SomeColumn varchar(10));
INSERT INTO dbo.TestTable (SomeColumn)
VALUES(''),('abc'),(NULL);
GO
DECLARE #Count int;
EXEC dbo.GetCount #TableName = N'TestTable', #ColumnName = N'SomeColumn', #Count = #Count OUTPUT;
SELECT #Count; --Returns 1
GO
DB<>Fiddle

Dynamically create tables from table parameters SQL Server

I want someway to automate table creations as every day customer can add some columns ,remove some ,so my idea is to pass table name and columns into a table then use this table in stored procedure to automatically creates the table.
This is table that will hold tables structure
create table nada_test
(
table_name varchar(500),
col_name varchar(100),
col_type varchar(100)
)
Sample data:
insert into nada_test
values ('data', 'salary', 'int'), ('data', 'id', 'int'),
('data', 'job', 'varchar(100)')
Could someone show me how to achieve this?
How about that
CREATE TABLE T
(
TableName varchar(500),
ColName varchar(100),
ColType varchar(100)
);
INSERT INTO T VALUES
('data','salary','int'),
('data', 'id', 'int'),
('data', 'job', 'varchar(100)');
DECLARE #SQL NVARCHAR(MAX);
SELECT #SQL = N'CREATE TABLE Data ('+ STUFF((
SELECT ',' + ColName + ' ' + ColType
FROM T
FOR XML PATH('')
), 1, 1, '') + N' );'
FROM T;
SELECT #SQL [CreateTable];
--EXECUTE sp_executesql #SQL;
But that won't help you
What will happen to the data already exists in your table?
What if the table already exists, ok you can pass that by IF OBJECT_ID() .., but still, what will happen to the data already in your table?
You will face another problem even if you store the data in temp table because the structure of both tables is not the same even the datatypes of the columns.
As it already been mentioned, your approach is very vulnerable to SQL injections.
See example:
insert into #nada_test
values ('TestTable] (TestColumn int);SELECT * FROM sys.tables--', 'TestColumn', 'INT')
GO
DECLARE #TableName sysname, #ColumnName sysname, #Type VARCHAR(100), #SQL VARCHAR(2000)
WHILE EXISTS (SELECT TOP 1 1 FROM #nada_test)
BEGIN
SELECT TOP 1 #TableName = table_name, #ColumnName = [col_name], #Type = col_type FROM #nada_test
DELETE FROM #nada_test WHERE #TableName = table_name and #ColumnName = [col_name]
IF NOT EXISTS ( SELECT TOP 1 1 FROM sys.tables WHERE name = #TableName)
SET #SQL = 'CREATE TABLE [' + #TableName + '] ([' + #ColumnName + '] ' + #Type + ');'
ELSE IF NOT EXISTS ( SELECT TOP 1 1 FROM sys.columns WHERE name = #ColumnName AND object_id = OBJECT_ID(#TableName))
SET #SQL = 'ALTER TABLE [' + #TableName + '] ADD [' + #ColumnName + '] ' + #Type + ';'
ELSE
SET #SQL = 'PRINT ''TABLE name [' + #TableName + '] with column [' + #ColumnName + '] is already exists'';'
PRINT #SQL
EXEC (#SQL)
END
Generally we can use like
create table x as select * from y ;
using some existing table structure say y in this case
You can create a ddl trigger on your existing requirement i.e. if theres any change to this table
then fire the same query above.

How to SELECT and UNION from a group of tables in schema in SQL Server 2008 R2 using a variable to define the database

This is a progression from the question asked here: How to SELECT and UNION from a group of Tables in the schema in SQL Server 2008 R2
I would like to do very much the same thing and the answer given by MarkD works perfectly for the database I am currently working with. Although admittedly I'd like to understand exactly how. How does the query below build the union query from the list of tables returned by the information_schema?
DECLARE #Select_Clause varchar(600) = N'SELECT [Patient_Number] AS [ID number]
,[Attendance Date] AS [Date Seen]
,[Attendance_Type] AS [New/Follow up]
,[Episode Type] AS [Patient Type]
,[Local Authority District]
,Postcode, N''Shaw'' AS Clinic '
,#Where_Clause varchar(100) = N' WHERE [EPISODE TYPE] LIKE N''HIV'''
,#Union_Clause varchar(100) = N' UNION ALL '
,#Query nvarchar(max) = N''
,#RawDataBase varchar(50) = N'BHT_1819_RawData'
,#Schema varchar(50) = N'HIVGUM'
,#Table_Count tinyint;
DECLARE #Table_Count_def nvarchar(100) = N'#TableSchema varchar(50)
,#Table_CountOUT tinyint OUTPUT'
,#Start_Position int = LEN(REPLACE(#Select_Clause, N' ', N'-'))
,#Length int;
SET #Query = N'SELECT #Table_CountOUT = COUNT(*) FROM ' + #RawDataBase +
N'.INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA LIKE #TableSchema';
EXEC sp_executesql #query, #Table_Count_def, #TableSchema=#Schema,
#Table_CountOUT=#Table_Count OUTPUT;
SET #Query = N'';
IF #Table_Count > 0
Begin
IF OBJECT_ID(N'dbo.HIV_Cumulative', N'U') is not null
DROP TABLE dbo.HIV_Cumulative;
SELECT #Query = #Query + #Select_Clause + N' FROM ' + #RawDataBase +
N'.HIVGUM.' + TABLE_NAME + #Where_Clause + #Union_Clause
FROM BHT_1819_RawData.INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA LIKE #Schema;
SET #Length = LEN(REPLACE(#query, N' ', N'-')) - #Start_Position -
LEN(REPLACE(#Where_Clause + #Union_Clause, N' ', N'-'));
SELECT #Query = SUBSTRING(#QUERY , #Start_Position+1, #Length)
SET #Query = #Select_Clause + N' INTO BHT_SLR..HIV_Cumulative ' + #QUERY
+ #Where_Clause;
EXEC sp_executesql #Query
End
ELSE
PRINT N'No tables present in database ' + #RawDataBase + N' for Schema ' +
#Schema + N'. You must import source data first.';
The added complication is that I am querying the tables on a separate DB - currently BHT_1819_RawData - so have hard coded the database where it queries the information_schema. What I would really like to do is to specify the separate database using a variable. So that it can be reconfigured to extract from BHT_1920_RawData. I am fairly familiar with exec and sp_executesql, but have only occasionally used output parameters so am not sure what is required here. The attempts that I have made haven't worked. Once I have got this right, I will need to create several other similar scripts that work on the same principle.
Once I realised what needed to happen, I went through some trial and error and came up with a solution:
SET #ParmDef = N'#QueryOut nvarchar(2500) OUTPUT';
SET #sql_string = N'SELECT #QueryOut = #QueryOut + N'''
+ #Select_Clause + ' FROM '
+ #RawDataBase
+ N'.[' + #Schema + N'].'' + TABLE_NAME + N'' '
+ #Where_Clause
+ #Union_Clause
+ N''' FROM '
+ #RawDataBase
+ N'.INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA LIKE '''
+ #Schema
+ N''' AND TABLE_NAME NOT LIKE N''%_YTD%''';
EXEC sp_executesql #sql_string, #ParmDef, #QueryOut=#Query OUTPUT;
SET #Length = LEN(REPLACE(#query, N' ', N'-')) - #Start_Position -
LEN(REPLACE(#Where_Clause + #Union_Clause, N' ', N'-'));
SELECT #Query = SUBSTRING(#QUERY , #Start_Position, #Length+1);
SET #Query = REPLACE(#Select_Clause, N'''''', '''') + N' INTO ' + #New_Table + N' ' +
#QUERY + REPLACE(#Where_Clause, N'''''', '''');
EXEC sp_executesql #Query;

How to create a UDF or View in another database that references the correct sys.objects table in the caller?

Using SQL Server 2008, I'd like to create a UDF that gives me the create date of an object. This is the code:
create function dbo.GetObjCreateDate(#objName sysname) returns datetime as
begin
declare #result datetime
select #result = create_date from sys.objects where name = #objname
return #result
end
go
I'd like to put this UDF in the master database or some other shared database so that it is accessible from anywhere, except that if I do that then the sys.objects reference pulls from the master database instead of the database that I'm initiating my query from. I know you can do this as the information_schema views sit in master and just wrap calls to local instances of sys.objects, so I'm hoping there's a simple way to do that with my UDF as well.
Try this:
CREATE FUNCTION dbo.GetObjCreateDate(#objName sysname, #dbName sysname)
RETURNS datetime AS
BEGIN
DECLARE #createDate datetime;
DECLARE #params nvarchar(50);
DECLARE #sql nvarchar(500);
SET #params = '#createDate datetime OUTPUT';
SELECT #sql = 'SELECT #createDate = create_date FROM ' + #dbName + '.sys.objects WHERE name = ''' + #objname + '''';
EXEC sp_executesql #sql, #params, #createDate = #createDate OUTPUT;
RETURN #createDate
END
;
Why not do this instead?
Create a stored procedure that creates a view in the master database containing all of the information in sys.objects from each database on the server.
Create a DDL Trigger that gets fired whenever a CREATE, ALTER or DROP statement is executed for a database. The trigger would then execute the stored procedure in step #1. This allows the view to be automatically updated.
(Optional) Create a user defined function that queries the view for the creation date of a given object.
Stored Procedure DDL:
USE [master];
GO
CREATE PROCEDURE dbo.BuildAllServerObjectsView
AS
SET NOCOUNT ON;
IF OBJECT_ID('master.dbo.AllServerObjects') IS NOT NULL
EXEC master..sp_SQLExec 'DROP VIEW dbo.AllServerObjects;';
IF OBJECT_ID('tempdb..Databases') IS NOT NULL
DROP TABLE #Databases;
DECLARE #CreateView varchar(8000);
SET #CreateView = 'CREATE VIEW dbo.AllServerObjects AS' + CHAR(13)+CHAR(10) + CHAR(13)+CHAR(10);
SELECT name COLLATE SQL_Latin1_General_CP1_CI_AS AS 'name'
INTO #Databases
FROM sys.databases
ORDER BY name;
DECLARE #DatabaseName nvarchar(100);
WHILE (SELECT COUNT(*) FROM #Databases) > 0
BEGIN
SET #DatabaseName = (SELECT TOP 1 name FROM #Databases ORDER BY name);
SET #CreateView +='SELECT N'+QUOTENAME(#DatabaseName, '''')+' AS ''database_name''' + CHAR(13)+CHAR(10)
+ ' ,name COLLATE SQL_Latin1_General_CP1_CI_AS AS ''object_name''' + CHAR(13)+CHAR(10)
+ ' ,object_id' + CHAR(13)+CHAR(10)
+ ' ,principal_id' + CHAR(13)+CHAR(10)
+ ' ,schema_id' + CHAR(13)+CHAR(10)
+ ' ,parent_object_id' + CHAR(13)+CHAR(10)
+ ' ,type' + CHAR(13)+CHAR(10)
+ ' ,type_desc' + CHAR(13)+CHAR(10)
+ ' ,create_date' + CHAR(13)+CHAR(10)
+ ' ,modify_date' + CHAR(13)+CHAR(10)
+ ' ,is_ms_shipped' + CHAR(13)+CHAR(10)
+ ' ,is_published' + CHAR(13)+CHAR(10)
+ ' ,is_schema_published' + CHAR(13)+CHAR(10)
+ ' FROM ' + QUOTENAME(#DatabaseName) + '.sys.objects';
IF (SELECT COUNT(*) FROM #Databases) > 1
SET #CreateView += CHAR(13)+CHAR(10) + CHAR(13)+CHAR(10) + ' UNION' + CHAR(13)+CHAR(10);
ELSE
SET #CreateView += ';';
DELETE #Databases
WHERE name = #DatabaseName;
END;
--PRINT #CreateView --<== Uncomment this to see the DDL for the view.
EXEC master..sp_SQLExec #CreateView;
IF OBJECT_ID('tempdb..Databases') IS NOT NULL
DROP TABLE #Databases;
GO
Function DDL:
USE [master];
GO
CREATE FUNCTION dbo.GetObjCreateDate(#DatabaseName sysname, #objName sysname) RETURNS DATETIME AS
BEGIN
DECLARE #result datetime;
SELECT #result = create_date
FROM master.dbo.AllServerObjects
WHERE [database_name] = #DatabaseName
AND [object_name] = #objname;
RETURN #result;
END
GO
Sample Usage:
SELECT master.dbo.GetObjCreateDate('MyDatabase', 'SomeObject') AS 'Created';
SELECT master.dbo.GetObjCreateDate(DB_NAME(), 'spt_monitor') AS 'Created';
Does it have to be a function? If you just want it accessible everywhere, a trick is to put your code in a varchar and sp_executesql it:
create procedure dbo.GetObjCreateDate(#objName sysname)
as
declare #sql nvarchar(max)
select #sql = 'select create_date from sys.objects where name = ''' + #objname + ''''
EXEC sp_executesql #sql
go
There seems to be an undocumented stored procedure that allows you to create your own system objects: sp_ms_marksystemobject
You can read more on http://www.mssqltips.com/tip.asp?tip=1612
Have a look at How to Write Your Own System Functions. I believe that it may help you

Resources