I need to select from table where the the table name suffix are from another table, like this :
declare #value nvarchar(3),
#table nvarchar(1000),
#SQLST NVARCHAR(255);
set #value = N'select column1 from tableX';
EXEC #value
set #table ='partoftableY'
Set #SQLST ='select * from' +#tabel + #value -- here I create the table name
However there are multiple values in TableX (0-999) and so this doesn't work. Do I need a For Each type construct.
here in an example I created with two tables (partoftableY1 & partoftableY2) with different data in each
/*
create table tableX (column1 int);
insert into tablex
select 1
union all select 2;
create table partoftableY1 (data nvarchar(50));
create table partoftableY2 (data nvarchar(50));
insert into partoftableY1 select 'hey 1 here';
insert into partoftableY2 select 'hey 2 here';
*/
declare #sql nvarchar(max)
-- use the ability of SQL to build up string of all the sql you need to run
set #sql = 'select data from (select '''' as data'
select #sql = COALESCE(#sql + ' union all ', '')
+ 'select data from partoftableY'
+ cast(column1 as nvarchar(4)) from tableX
select #sql = #sql + ') X where data <>'''''
-- DEBUG for seeing what SQL you created
print #sql
-- Now execute the SQL
exec sp_executesql #sql= #sql
which gives me the results of
hey 1 here
hey 2 here
You will need to adjust it for types of your data, but this should give you the main idea
For reference here is the sql that was created and executed:
select data
from (
select '' as data
union all select data from partoftableY1
union all select data from partoftableY2
) X
where data <>''
N.B.
I put formatted it for easier reading, as it's actually created as one long line
I used selet data and not select * as the number of columns needs to be the same for each select in the union. You will need to select the columns you need and then make changes ensure that all the columns in the selects in the union are the same.
There is a dummy select at the top of the union to make the union code easy - no conditionals needed as whether the union all needs to present
I used the out select over the whole union to enable you to get sid of the dummy select
You can try this
DECLARE #SQLST NVARCHAR(max)='';
DECLARE #select nvarchar(max)=N'select * from partoftableY'
DECLARE #union nvarchar(max)=N'
UNION ALL
'
SELECT #SQLST=#select+column1+#union
FROM tablex
SELECT #SQLST=substring(#SQLST,1,LEN(#SQLST)-11)
EXEC sp_executesql #SQLST
Related
I'm trying to build a stored procedure that will query multiple database depending on the databases required.
For example:
SP_Users takes a list of #DATABASES as parameters.
For each database it needs to run the same query and union the results together.
I believe a CTE could be my best bet so I have something like this at the moment.
SET #DATABASES = 'DB_1, DB_2' -- Two databases in a string listed
-- I have a split string function that will extract each database
SET #CURRENT_DB = 'DB_1'
WITH UsersCTE (Name, Email)
AS (SELECT Name, Email
FROM [#CURRENT_DB].[dbo].Users),
SELECT #DATABASE as DB, Name, Email
FROM UsersCTE
What I don't want to do is hard code the databases in the query. The steps I image are:
Split the parameter #DATABASES to extract and set the #CURRENT_DB Variable
Iterate through the query with a Recursive CTE until all the #DATABASES have been processed
Union all results together and return the data.
Not sure if this is the right approach to tackling this problem.
Using #databases:
As mentioned in the comments to your question, variables cant be used to dynamically select a database. Dynamic sql is indicated. You can start by building your template sql statement:
declare #sql nvarchar(max) =
'union all ' +
'select ''#db'' as db, name, email ' +
'from [#db].dbo.users ';
Since you have sql server 2016, you can split using the string_split function, with your #databases variable as input. This will result in a table with 'value' as the column name, which holds the database names.
Use the replace function to replace #db in the template with value. This will result in one sql statement for each database you passed into #databases. Then, concatenate the statements back together. Unfortunately, in version 2016, there's no built in function to do that. So we have to use the famous for xml trick to join the statements, then we use .value to convert it to a string, and finally we use stuff to get rid of the leading union all statement.
Take the results of the concatenated output, and overwrite the #sql variable. It is ready to go at this point, so execute it.
I do all that is described in this code:
declare #databases nvarchar(max) = 'db_1,db_2';
set #sql = stuff(
(
select replace(#sql, '#db', value)
from string_split(#databases, ',')
for xml path(''), type
).value('.[1]', 'nvarchar(max)')
, 1, 9, '');
exec(#sql);
Untested, of course, but if you print instead of execute, it seems to give the proper sql statement for your needs.
Using msForEachDB:
Now, if you didn't want to have to know which databases had 'users', such as if you're in an environment where you have a different database for every client, you can use sp_msForEachDb and check the structure first to make sure it has a 'users' table with 'name' and 'email' columns. If so, execute the appropriate statement. If not, execute a dummy statement. I won't describe this one, I'll just give the code:
declare #aggregator table (
db sysname,
name int,
email nvarchar(255)
);
insert #aggregator
exec sp_msforeachdb '
declare #sql nvarchar(max) = ''select db = '''''''', name = '''''''', email = '''''''' where 1 = 2'';
select #sql = ''select db = ''''?'''', name, email from ['' + table_catalog + ''].dbo.users''
from [?].information_schema.columns
where table_schema = ''dbo''
and table_name = ''users''
and column_name in (''name'', ''email'')
group by table_catalog
having count(*) = 2
exec (#sql);
';
select *
from #aggregator
I took the valid advice from others here and went with this which works great for what I need:
I decided to use a loop to build the query up. Hope this helps someone else looking to do something similar.
CREATE PROCEDURE [dbo].[SP_Users](
#DATABASES VARCHAR(MAX) = NULL,
#PARAM1 VARCHAR(250),
#PARAM2 VARCHAR(250)
)
BEGIN
SET NOCOUNT ON;
--Local variables
DECLARE
#COUNTER INT = 0,
#SQL NVARCHAR(MAX) = '',
#CURRENTDB VARCHAR(50) = NULL,
#MAX INT = 0,
#ERRORMSG VARCHAR(MAX)
--Check we have databases entered
IF #DATABASES IS NULL
BEGIN
RAISERROR('ERROR: No Databases Provided,
Please Provide a list of databases to execute procedure. See stored procedure:
[SP_Users]', 16, 1)
RETURN
END
-- SET Number of iterations based on number of returned databases
SET #MAX = (SELECT COUNT(*) FROM
(SELECT ROW_NUMBER() OVER (ORDER BY i.value) AS RowNumber, i.value
FROM dbo.udf_SplitVariable(#DATABASES, ',') AS i)X)
-- Build SQL Statement
WHILE #COUNTER < #MAX
BEGIN
--Set the current database
SET #CURRENTDB = (SELECT X.Value FROM
(SELECT ROW_NUMBER() OVER (ORDER BY i.value) AS RowNumber, i.value
FROM dbo.udf_SplitVariable(#DATABASES, ',') AS i
ORDER BY RowNumber OFFSET #COUNTER
ROWS FETCH NEXT 1 ROWS ONLY) X);
SET #SQL = #SQL + N'
(
SELECT Name, Email
FROM [' + #CURRENTDB + '].[dbo].Users
WHERE
(Name = #PARAM1 OR #PARAM1 IS NULL)
(Email = #PARAM2 OR #PARAM2 IS NULL)
) '
+ N' UNION ALL '
END
PRINT #CURRENTDB
PRINT #SQL
SET #COUNTER = #COUNTER + 1
END
-- remove last N' UNION ALL '
IF LEN(#SQL) > 11
SET #SQL = LEFT(#SQL, LEN(#SQL) - 11)
EXEC sp_executesql #SQL, N'#CURRENTDB VARCHAR(50),
#PARAM1 VARCHAR(250),
#PARAM2 VARCHAR(250)',
#CURRENTDB,
#PARAM1 ,
#PARAM2
END
Split Variable Function
CREATE FUNCTION [dbo].[udf_SplitVariable]
(
#List varchar(8000),
#SplitOn varchar(5) = ','
)
RETURNS #RtnValue TABLE
(
Id INT IDENTITY(1,1),
Value VARCHAR(8000)
)
AS
BEGIN
--Account for ticks
SET #List = (REPLACE(#List, '''', ''))
--Account for 'emptynull'
IF LTRIM(RTRIM(#List)) = 'emptynull'
BEGIN
SET #List = ''
END
--Loop through all of the items in the string and add records for each item
WHILE (CHARINDEX(#SplitOn,#List)>0)
BEGIN
INSERT INTO #RtnValue (value)
SELECT Value = LTRIM(RTRIM(SUBSTRING(#List, 1, CHARINDEX(#SplitOn, #List)-1)))
SET #List = SUBSTRING(#List, CHARINDEX(#SplitOn,#List) + LEN(#SplitOn), LEN(#List))
END
INSERT INTO #RtnValue (Value)
SELECT Value = LTRIM(RTRIM(#List))
RETURN
END
I have several tables with common table names. I want to select all data from them without specifying all tables and union them. I'm thinking of using information_schema.tables to accomplish this.
Tables:
tbl_20160201
tbl_20160202
tbl_20160203
tbl_20160204
tbl_20160205
Query:
SELECT *
FROM (
SELECT *
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME LIKE 'tbl_201602%'
) a
However, this query only returns the table names and not the data in the tables. I need to union all the tables included in the query. Thank you.
Assuming these tables have the same number of columns and data types, you can use dynamic sql:
DECLARE #sql NVARCHAR(MAX) = N'';
SELECT #sql = #sql +
'SELECT * FROM ' + TABLE_NAME + ' UNION ALL' + CHAR(10)
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME LIKE 'tbl_201602%';
SELECT #sql = SUBSTRING(#sql,1, LEN(#sql) - 11);
PRINT(#sql)
EXEC (#sql)
Otherwise, you'll get an error saying:
All queries combined using a UNION, INTERSECT or EXCEPT operator must
have an equal number of expressions in their target lists
Below query might help you :-
DECLARE #SQL VARCHAR(max)
DECLARE #T as sysname
-- Make sure below temporary table structure same as the tables e.g tbl_201602 which you want to union
declare #TT as Table(
Col1 [decimal](18, 2) NOT NULL,
Col2 [date] NOT NULL,
Col3 [varchar](200) NOT NULL
)
DECLARE TName CURSOR FORWARD_ONLY STATIC FOR
select TABLE_NAME
from
information_schema.tables where TABLE_NAME like 'tbl_201602%'
OPEN TName
FETCH NEXT FROM TName INTO #T
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT #SQL = 'select * from' + ' ' + #T
INSERT INTO #TT
EXEC(#SQL)
FETCH NEXT FROM TName INTO #T
END
CLOSE TName
DEALLOCATE TName
select * from #TT
This will give the union of all tables in a single temporary table #TT
NOTE : Just make sure that all comman tables are having same no of columns and in same ordering, otherwise it may raise error
I've been trying for a while to use SQL Server pivot but I just don't seem to be getting it right. I've read a bunch of SO answers, but don't understand how pivot works.
I'm writing a stored procedure. I have Table 1 (received as a TVP), and need to make it look like Table 2 (see this image for tables).
Important: the values in Table1.valueTypeID cannot be hard coded into the logic because they can always change. Therefore, the logic must be super dynamic.
Please see the code below. The pivot is at the end of the stored procedure.
-- Create date: 12/10/2013
-- Description: select all the contacts associated with received accountPassport
-- =============================================
ALTER PROCEDURE [dbo].[selectContactsPropsByAccountPassport]
-- Add the parameters for the stored procedure here
#accountPassport int,
#valueTypeFiltersTVP valueTypeFiltersTVP READONLY
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 #accountID int;
DECLARE #contactsAppAccountPassport int;
DECLARE #searchResults TABLE
(
resultContactID int
);
DECLARE #resultContactID int;
DECLARE #contactsPropsForReturn TABLE
(
contactID int,
valueTypeID int,
value varchar(max)
);
create table #contactsPropsForReturnFiltered(contactID int,valueTypeID int, value varchar(max))
/*
DECLARE #contactsPropsForReturnFiltered TABLE
(
contactID int,
valueTypeID int,
value varchar(max)
);
*/
--2. get #contactsAppAccountPassport associated with recieved #accountPassport
-- go into dbo.accounts and get the #accountID associated with this #accountPassport
SELECT
#accountID = ID
FROM
dbo.accounts
WHERE
passport = #accountPassport
-- go into dbo.accountsProps and get the value (#contactsAppAccountPassport) where valueType=42 and accountID = #accountID
SELECT
#contactsAppAccountPassport = value
FROM
dbo.accountsProps
WHERE
(valueTypeID=42) AND (accountID = #accountID)
--3. get all the contact ID's from dbo.contacts associated with #contactsAppAccountPassport
INSERT INTO
#searchResults
SELECT
ID
FROM
dbo.contacts
WHERE
contactsAppAccountPassport = #contactsAppAccountPassport
--4. Get the props of all contact ID's from 3.
--start for each loop....our looping object is #resultContactID row. if there are more rows, we keep looping.
DECLARE searchCursor CURSOR FOR
SELECT
resultContactID
FROM
#searchResults
OPEN searchCursor
FETCH NEXT FROM searchCursor INTO #resultContactID
WHILE (##FETCH_STATUS=0)
BEGIN
INSERT INTO
#contactsPropsForReturn
SELECT
contactID,
valueTypeID,
value
FROM
dbo.contactsProps
WHERE
contactID = #resultContactID
FETCH NEXT FROM searchCursor INTO #resultContactID
END --end of WHILE loop
--end of cursor (both CLOSE and DEALLOCATE necessary)
CLOSE searchCursor
DEALLOCATE searchCursor
-- select and return only the props that match with the requested props
-- (we don't want to return all the props, only the ones requested)
INSERT INTO
#contactsPropsForReturnFiltered
SELECT
p.contactID,
p.valueTypeID,
p.value
FROM
#contactsPropsForReturn as p
INNER JOIN
#valueTypeFiltersTVP as f
ON
p.valueTypeID = f.valueTypeID
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(ValueTypeId)
FROM #contactsPropsForReturnFiltered
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'');
set #query = 'SELECT contactid, ' + #cols + ' from
(
select contactid
, Value
,ValueTypeId
from #contactsPropsForReturnFiltered
) x
pivot
(
min(Value)
for ValueTypeId in (' + #cols + ')
) p ';
execute(#query);
END
You need to use dynamic pivot in your case. Try the following
create table table1
(
contactid int,
ValueTypeId int,
Value varchar(100)
);
insert into table1 values (56064, 40, 'Issac');
insert into table1 values (56064, 34, '(123)456-7890');
insert into table1 values (56065, 40, 'Lola');
insert into table1 values (56065, 34, '(123)456-7832');
insert into table1 values (56068, 40, 'Mike');
insert into table1 values (56068, 41, 'Gonzalez');
insert into table1 values (56068, 34, '(123)456-7891');
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(ValueTypeId)
FROM table1
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'');
set #query = 'SELECT contactid, ' + #cols + ' from
(
select contactid
, Value
,ValueTypeId
from table1
) x
pivot
(
min(Value)
for ValueTypeId in (' + #cols + ')
) p ';
execute(#query);
drop table table1
Why do you need to present the data in this way?
In many cases, clients are better at pivoting than the database engine. For example, SQL Server Reporting Services easily does this with the matrix control. Similarly, if you are coding a web page in, say, Asp.Net, you can run through the recordset quickly to pass your data into a new data representation (meanwhile collecting unique values) and then in a single pass through the new data object spit out the HTML to render the result.
If at all possible, have your client do the pivoting instead of the server.
UPDATE:
If you really want to use table variables in dynamic SQL, you can just fine in SQL Server 2008 and up. Here's an example script:
USE tempdb
GO
CREATE TYPE IDList AS TABLE (
ID int
);
GO
DECLARE #SQL nvarchar(max);
SET #SQL = 'SELECT * FROM #TransactionIDs WHERE ID >= 4;'
DECLARE #TransactionIDs IDLIst;
INSERT #TransactionIDs VALUES (1), (2), (4), (8), (16);
EXEC sp_executesql #SQL, N'#TransactionIDs IDList READONLY', #TransactionIDs;
GO
DROP TYPE IDList;
I have a table that contains many rows of SQL commands that make up a single SQL statement (to which I am grateful for this answer, step 5 here)
I have followed the example in this answer and now have a table of SQL - each row is a line of SQL that build a query. I can copy and paste the contents of this table into a new query window and get the results however due to my lack of SQL knowledge I am not sure how I go about copying the contents of the table into a string variable which I can then execute.
Edit: The SQL statement in my table comprises of 1 row per each line of the statement i.e.
Row1: SELECT * FROM myTable
Row2: WHERE
Row3: col = #value
This statement if copied into a VARCHAR(MAX) exceeds the MAX limit.
I look forward to your replies. in the mean time I will try myself.
Thank you
You can use coalesce to concatenate the contents of a column into a string, e.g.
create table foo (sql varchar (max));
insert foo (sql) values ('select name from sys.objects')
insert foo (sql) values ('select name from sys.indexes')
declare #sql_output varchar (max)
set #sql_output = '' -- NULL + '' = NULL, so we need to have a seed
select #sql_output = -- string to avoid losing the first line.
coalesce (#sql_output + sql + char (10), '')
from foo
print #sql_output
Note: untested, just off the top of my head, but a working example of this should produce the following output:
select name from sys.objects
select name from sys.indexes
You can then execute the contents of the string with exec (#sql_output) or sp_executesql.
You can try something like this
DECLARE #TABLE TABLE(
SqlString VARCHAR(MAX)
)
INSERT INTO #TABLE (SqlString) SELECT 'SELECT 1'
DECLARE #SqlString VARCHAR(MAX)
SELECT TOP 1 #SqlString = SqlString FROM #TABLE
EXEC (#SqlString)
Concatenate string from multiple rows
DECLARE #Table TABLE(
ID INT,
Val VARCHAR(50)
)
INSERT INTO #Table (ID,Val) SELECT 1, 'SELECT *'
INSERT INTO #Table (ID,Val) SELECT 2, 'FROM YourTable'
INSERT INTO #Table (ID,Val) SELECT 3, 'WHERE 1 = 1'
DECLARE #SqlString VARCHAR(MAX)
--Concat
SELECT DISTINCT
#SqlString =
(
SELECT tIn.Val + ' '
FROM #Table tIn
ORDER BY ID
FOR XML PATH('')
)
FROM #Table t
PRINT #SqlString
if you want to execute a string of sql then use Exec() or sp_executeSql
The code is as follows:
ALTER PROCEDURE dbo.pdpd_DynamicCall
#SQLString varchar(4096) = null
AS
Begin
create TABLE #T1 ( column_1 varchar(10) , column_2 varchar(100) )
insert into #T1
execute ('execute ' + #SQLString )
select * from #T1
End
The problem is that I want to call different procedures that can give back different columns.
Therefore I would have to define the table #T1 generically.
But I don't know how.
Can anyone help me on this problem?
Try:
SELECT into #T1 execute ('execute ' + #SQLString )
And this smells real bad like an sql injection vulnerability.
correction (per #CarpeDiem's comment):
INSERT into #T1 execute ('execute ' + #SQLString )
also, omit the 'execute' if the sql string is something other than a procedure
You can define a table dynamically just as you are inserting into it dynamically, but the problem is with the scope of temp tables. For example, this code:
DECLARE #sql varchar(max)
SET #sql = 'CREATE TABLE #T1 (Col1 varchar(20))'
EXEC(#sql)
INSERT INTO #T1 (Col1) VALUES ('This will not work.')
SELECT * FROM #T1
will return with the error "Invalid object name '#T1'." This is because the temp table #T1 is created at a "lower level" than the block of executing code. In order to fix, use a global temp table:
DECLARE #sql varchar(max)
SET #sql = 'CREATE TABLE ##T1 (Col1 varchar(20))'
EXEC(#sql)
INSERT INTO ##T1 (Col1) VALUES ('This will work.')
SELECT * FROM ##T1
Hope this helps,
Jesse
Be careful of a global temp table solution as this may fail if two users use the same routine at the same time as a global temp table can be seen by all users...
create a global temp table with a GUID in the name dynamically. Then you can work with it in your code, via dyn sql, without worry that another process calling same sproc will use it. This is useful when you dont know what to expect from the underlying selected table each time it runs so you cannot created a temp table explicitly beforehand. ie - you need to use SELECT * INTO syntax
DECLARE #TmpGlobalTable varchar(255) = 'SomeText_' + convert(varchar(36),NEWID())
-- select #TmpGlobalTable
-- build query
SET #Sql =
'SELECT * INTO [##' + #TmpGlobalTable + '] FROM SomeTable'
EXEC (#Sql)
EXEC ('SELECT * FROM [##' + #TmpGlobalTable + '] ')
EXEC ('DROP TABLE [##' + #TmpGlobalTable + ']')
PRINT 'Dropped Table ' + #TmpGlobalTable
INSERT INTO #TempTable
EXEC(#SelectStatement)
Try Below code for creating temp table dynamically from Stored Procedure Output using T-SQL
declare #ExecutionName varchar(1000) = 'exec [spname] param1,param2 '
declare #sqlStr varchar(max) = ''
declare #tempTableDef nvarchar(max) =
(
SELECT distinct
STUFF(
(
SELECT ','+a.[name]+' '+[system_type_name]
+'
' AS [text()]
FROM sys.dm_exec_describe_first_result_set (#ExecutionName, null, 0) a
ORDER BY a.column_ordinal
FOR XML PATH ('')
), 1, 1, '') tempTableDef
FROM sys.dm_exec_describe_first_result_set (#ExecutionName, null, 0) b
)
IF ISNULL(#tempTableDef ,'') = '' RAISERROR( 'Invalid SP Configuration. At least one column is required in Select list of SP output.',16,1) ;
set #tempTableDef='CREATE TABLE #ResultDef
(
' + REPLACE(#tempTableDef,'
','') +'
)
INSERT INTO #ResultDef
' + #ExecutionName
Select #sqlStr = #tempTableDef +' Select * from #ResultDef '
exec(#sqlStr)
DECLARE #EmpGroup INT =3 ,
#IsActive BIT=1
DECLARE #tblEmpMaster AS TABLE
(EmpCode VARCHAR(20),EmpName VARCHAR(50),EmpAddress VARCHAR(500))
INSERT INTO #tblEmpMaster EXECUTE SPGetEmpList #EmpGroup,#IsActive
SELECT * FROM #tblEmpMaster
CREATE PROCEDURE dbo.pdpd_DynamicCall
AS
DECLARE #SQLString_2 NVARCHAR(4000)
SET NOCOUNT ON
Begin
--- Create global temp table
CREATE TABLE ##T1 ( column_1 varchar(10) , column_2 varchar(100) )
SELECT #SQLString_2 = 'INSERT INTO ##T1( column_1, column_2) SELECT column_1 = "123", column_2 = "MUHAMMAD IMRON"'
SELECT #SQLString_2 = REPLACE(#SQLString_2, '"', '''')
EXEC SP_EXECUTESQL #SQLString_2
--- Test Display records
SELECT * FROM ##T1
--- Drop global temp table
IF OBJECT_ID('tempdb..##T1','u') IS NOT NULL
DROP TABLE ##T1
End
Not sure if I understand well, but maybe you could form the CREATE statement inside a string, then execute that String? That way you could add as many columns as you want.