I have a stored procedure and part of them as below:
#DRange is a incoming varchar value
declare #sql varchar(max)
set #sql = 'select * into #tmpA from TableA where create_date >= getDate - ' + #DRange + '' and is_enabled = 1'
exec (#sql)
select * from #tmpA
The problem is when I execute the stored procedure, an error message occurs:
Cannot find the object "#tmpA" because it does not exist or you do not have permissions.
Is it not possible to use temp table and execute it or did I do something wrong?
#tmpA is created in a different scope, so is not visible outside of the dynamic SQL. You can just make the ultimate SELECT a part of the dynamic SQL. Also a couple of other things:
Always use the schema prefix when creating/referencing objects
Always use sp_executesql for dynamic SQL; in this case it allows you to parameterize the #DRange value and avoid SQL injection risks.
Always prefix Unicode strings with N - Unicode is required for sp_executesql but if you get lazy about this in other areas of your code it can also lead to painful implicit conversions.
DECLARE #sql NVARCHAR(MAX);
SET #sql = N'select * into #tmpA from dbo.TableA
where create_date >= DATEADD(DAY, -#DRange, GETDATE())
AND is_enabled = 1; SELECT * FROM #tmpA';
EXEC sp_executesql #sql, N'#DRange INT', #DRange;
Of course if all you're doing is selecting, I have a hard time understanding why this is dynamic SQL in the first place. I assume your query (or what you later do with the temp table) is more complicated than this - if so, don't dumb it down for us. Telling us your whole problem will prevent a lot of back and forth, as the additional details could change the answer.
Here's what I'd do.
declare #sql varchar(max)
set #sql = 'select * from TableA where create_date >= getDate - ' + #DRange + '' and is_enabled = 1'
Select * Into #tmpA from TableA where create_date = '01/01/1000' -- to create a blank table
insert into #tmpA
exec (#sql)
select * from #tmpA
Related
I'm trying to fetch the data in a specific table name by passing tableName as a parameter to the stored procedure.
CREATE PROCEDURE schemaName.spDynamicTableName
#tableName NVARCHAR(100)
AS
BEGIN
DECLARE #sql nvarchar(max)
SET #sql = 'SELECT * FROM ' + #tableName
EXECUTE sp_executesql #sql
END;
--> EXEC schemaName.spDynamicTableName 'Employee';
Now, how can I pass list of table names to a procedure so that procedure will iterate over the list of table names and fetch the data from all the tables?
Ok, let's start off with the problems you have in your current set up. Firstly it sounds like you have a design flaw here. Most likely you are using a table's name to infer information that should be in a column. For example perhaps you have different tables for each client. In such a scenario the client's name should be a column in a singular table. This makes querying your data significantly easier and allows for good use for key constraints as well.
Next, your procedure. This is a huge security hole. The value of your dynamic object is not sanitised nor validated meaning that someone (malicious) has almost 100 characters to mess with your instance and inject SQL into it. There are many articles out there that explain how to inject securely (including by myself), and I'm going to cover a couple of processes here.
Note that, as per my original paragraph, you likely really have a design flaw, and so that is the real solution here. We can't address that in the answers here though, as we have no details of the data you are dealing with.
Fixing the injection
Injecting Securely
The basic's of injecting a dynamic object name is to make it secure. You do that by using QUOTENAME; it both delimit identifies the object name and escapes any needed characters. For example QUOTENAME(N'MyTable') would return an nvarchar with the value [MyTable] and QUOTENAME(N'My Alias"; SELECT * FROM sys.tables','"') would return the nvarchar value "My Alias""; SELECT U FROM sys.tables".
Validating the value
You can easily validate a value by checking that the object actually exists. I prefer to do this with the sys objects, so something like this would work:
SELECT #SchemaName = s.[name],
#TableName = t.[name]
FROM sys.schemas s
JOIN sys.tables t ON s.schema_id = t.schema_id
WHERE s.[name] = #Schema --This is a parameter
AND t.[name] = #Table; --This is a parameter
As a result, if the FROM returns no values, then the 2 variables in the SELECT won't have a value assigned and no SQL will be run (as {String} + NULL = NULL).
The Solution
Table Type Parameter
So, to allow for multiple tables, we need a table type parameter. I would create one with both the schema and table name in the columns, but we can default the schema name.
CREATE TYPE dbo.Objects AS table (SchemaName sysname DEFAULT N'dbo',
TableName sysname); --sysname is a sysnonym for nvarchar(128) NOT NULL
And you can DECLARE and INSERT into the TYPE as follows:
DECLARE #Objects dbo.Objects;
INSERT INTO #Objects (TableName)
VALUES(N'test');
Creating the dynamic statement
Assuming you are using a supported version of SQL Server, you'll have access to STRING_AGG; this removes any kind of looping from the procedure, which is great for performance. If you're using a version only in extended support, then use the "old" FOR XML PATH method.
This means you can take the values and create a dynamic statement along the lines of the below:
SET #SQL = (SELECT STRING_AGG(N'SELECT * 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 #Objects O ON s.name = O.SchemaName
AND t.name = O.TableName);
The Stored Proecure
Putting all this together, this will give you a procedure that would look like this:
CREATE PROC schemaName.spDynamicTableName #Objects dbo.Objects AS
BEGIN
DECLARE #SQL nvarchar(MAX),
#CRLF nchar(2) = NCHAR(13) + NCHAR(10);
SET #SQL = (SELECT STRING_AGG(N'SELECT N' + QUOTENAME(t.[name],'''') + N',* FROM ' + QUOTENAME(s.[name]) + N'.' + QUOTENAME(t.[name]) + N';',#CRLF) --I also inject the table's name as a column
FROM sys.schemas s
JOIN sys.tables t ON s.schema_id = t.schema_id
JOIN #Objects O ON s.name = O.SchemaName
AND t.name = O.TableName);
EXEC sys.sp_executesql #SQL;
END;
And then you would execute it along the lines of:
DECLARE #Objects dbo.Objects;
INSERT INTO #Objects (SchemaName,TableName)
VALUES(N'dbo',N'MyTable'),
(N'dbo',N'AnotherTable');
EXEC schemaName.spDynamicTableName #Objects;
This one accepts a comma delimited list of tables and guards against SQL injection with a simple QUOTENAME escape (not sure if this is quite enough though):
IF OBJECT_ID('dbo.spDynamicTableName') IS NOT NULL DROP PROC dbo.spDynamicTableName
GO
/*
EXEC dbo.spDynamicTableName 'Students,Robert--
DROP TABLE Students'
*/
CREATE PROC dbo.spDynamicTableName
#tableName NVARCHAR(100)
AS
BEGIN
DECLARE #sql nvarchar(max)
SELECT #sql = STRING_AGG('SELECT * FROM ' + QUOTENAME(value), ';')
FROM STRING_SPLIT(#tableName, ',')
--PRINT #sql
EXEC dbo.sp_executesql #sql
END;
GO
There are two ways you can do this: use a string that contains the names you want and are separated by a special character as:
Table1, Table2, Table3
and split it in the stored procedure (check this)
The second method: make a typo as follows:
CREATE TYPE [dbo].[StringList] AS TABLE
(
[TableName] [NVARCHAR(50)] NULL
)
Add a parameter for your stored procedure as StringList:
CREATE PROCEDURE schemaName.spDynamicTableName
#TableNames [dbo].[StringList] READONLY,
AS
BEGIN
END;
Then measure its length using the following code and make a repeat loop::
DECLARE #Counter INT
DECLARE #TableCount INT
SELECT #TableCount = Count(*), #Counter = 0 FROM #TableNames
WHILE #Counter < #TableCount
BEGIN
SELECT #TableName = Name
FROM #TableNames
ORDER BY Name
OFFSET #Counter ROWS FETCH NEXT 1 ROWS ONLY
SET #sql = 'SELECT * FROM ' + #TableName
EXECUTE sp_executesql #sql
SET #Counter = #Counter + 1
END
I build a query within a variable of type varchar I want to make test with isnull()
example:
declare #sql varchar(max)
set #sql = '
Select top (100) id
FROM RIGHT R inner join RIGHT_TYPE RT on
R.RIGHT_TYPE_CODE = RT.CODE
WHERE R.RIGHT_TYPE_CODE = isnull('+#rightType+', R.RIGHT_TYPE_CODE)
'
exec (#sql)
go
#rightType is a parameter of my stored procedure.
The problem that when I have #rightType equals to null nothings works
Thank you.
To have optional parameters in dynamic SQL, this is usually the best option:
DECLARE #rightType nvarchar(50)
DECLARE #sql nvarchar(max);
set #sql = '
Select top (100) id
FROM RIGHT R inner join RIGHT_TYPE RT on
R.RIGHT_TYPE_CODE = RT.CODE
WHERE 1=1'
if (#rightType is not NULL) set #sql = #sql + ' and R.RIGHT_TYPE_CODE = #rightType'
EXEC sp_executesql #sql, N'#rightType nvarchar(50)', #rightType
This way you're not concatenating the input string with the SQL, so there's no chance of SQL injection happening, and the optimizer likes this a lot more, because the parameter is in the SQL only when it's actually given.
The 1=1 is in the SQL so that you can append any number (or none) criteria to the SQL without having to worry to have correct amount of ands.
EDIT: Some of those who would offer help are unclear about the nature of the requirement, so I will try to state it as clearly as I can:
We need to instantiate a view of an underlying table, and this view must be able to be joined to another table; the difficulty is that the identity of the underlying table is not known until runtime of the ad hoc query doing the join.
We would like to do something like this:
select * from foo
inner join dynamicallyInstantiatedTable(condition) DT
on foo.zipcode = DT.zipcode
It doesn't seem possible to create a function that returns TABLE if the function uses dynamic SQL. This is not valid:
declare #tablename varchar(50);
-- <snip> code to determine the name of #tablename
declare #statement varchar(1000);
set #statement = 'select * from ' + #tablename;
exec( #statement);
The error:
Invalid use of a side-effecting operator 'EXECUTE STRING' within a
function.
If the table name is not known beforehand for whatever reason (e.g. tables are constantly being added and we must select against the most recent one, say), is it possible to do the select dynamically and return a table, either in a stored proc or function?
Here we go.
I don't use synonyms often, but CREATE SYNONYM supports dynamic SQL.
declare #tablename nvarchar(128);
-- <some code to set #tablename>
declare #sql nvarchar(500);
if object_id(N'dbo.TodaysData', N'SN') is not null
drop synonym dbo.TodaysData;
set #sql =
'create synonym dbo.TodaysData
for ' + #tablename;
execute(#sql);
select top 5
*
from
dbo.TodaysData as t
join
dbo.SomeOtherTable as s
on
s.FieldName = t.HeresHopingYourSchemaDoesntChange
Dynamic SQL in function. No.
is it possible to do the select dynamically and return a table, either
in a stored proc or function?
Perhaps I'm missing something (would not be a first) but this seems simple as a stored proc:
The Proc
create proc dbo.getRowsFrom #tablename varchar(50) as
exec('select * from ' + #tablename);
Use
exec dbo.getRowsFrom '<my table>';
Is that what you're looking for?
you should once explain your requirement with example.It is not clear to anybody.
I think everything thing can be done within single proc,no need of another proc or UDF.
declare #tblname varchar(500)
select #tblname=name from sys.objects
where type_desc ='USER_TABLE'
order by create_date DESC
declare #Sql varchar(max)=''
set #Sql='select * into #tmp from '+#tblname+' '
set #Sql=#Sql+' select * from #tmp drop table #tmp'
exec (#Sql)
Little detail given about your 'join' situation, but it might be easier to jump to your final joined results, rather than focusing on the input table in isolation.
Here I am joining my input table 'a' to a lookup table 'ref', and outputting joined results.
If tomorrow your have another input table 'b' - this proc will join that to the lookup table instead.
The only requirement is that the join column is consistent.
declare
#inputTableName nvarchar(128)
,#sqlExec nvarchar(max)
set #inputTableName = 'b';
if(not exists (select 1 from INFORMATION_SCHEMA.TABLES where table_schema = 'test' and TABLE_NAME = 'myView'))
begin
select #sqlExec = 'create view test.myView as
select I.*,R.[text] from test.[' + #inputTableName + '] I inner join test.ref R on I.col0 = R.col0'
end else begin
select #sqlExec = 'alter view test.myView as
select I.*,R.[text] from test.[' + #inputTableName + '] I inner join test.ref R on I.col0 = R.col0'
end
exec (#sqlExec)
select * from test.myView
I implement some code:
BEGIN
DECLARE
#SQL AS NVARCHAR(MAX),
#TempTable AS NVARCHAR(MAX)
SET #SQL = 'SELECT * from Employee where Instance_ID = 1';
BEGIN
CREATE TABLE ##tempResults (SQL NVARCHAR(4000))
INSERT INTO ##tempResults EXEC #SQL;
SET #TempTable= 'select * from #tempResults ORDER BY CASE WHEN ' + #index+ ' =1 THEN [First Name] END DESC '+ ',' + ' CASE WHEN ' + #index + '=2 THEN [Last name] END DESC'
END
EXEC sp_executesql #TempTable;
END
I want to insert the dynamic results into temp table but I can't execute statement and get error. Please advice me for how should I need to do ?
As the error shown:
"Msg 203 is not a valid identifier."
You should use EXEC(#SQL) See here
It is advisable to switch to exec sp_executesql #SQL, it gives you parameterization and helps agains sql injection. Especially since you already use this later on in your query (it's never a good idea to use different methods to accomplish the same thing). See here
I'm trying to compare two resultsets from queries that are stored in variables.
I've tried the following:
DECLARE #sql1 varchar(8000) = 'SELECT * FROM table1'
DECLARE #sql2 varchar(8000) = 'SELECT Col2, Col1 FROM table1'
IF EXISTS(
(EXEC sp_executesql #sql1
EXCEPT
EXEC sp_executesql #sql2)
UNION ALL
(EXEC sp_executesql #sql2
EXCEPT
EXEC sp_executesql #sql1))
This approach has two problems: the if statement doesn't like EXEC statements (EXCEPT UNION ALL EXCEPT structure works when you use the actual queries instead of the variables).
The second problem is that, even if you use the actual queries, the order of the columns of both queries have to be the same or the resulsets will not match. For my purposes however, I need those resultsets to match. I think I need a way to order the columns but I'm not sure if that's even possible.
EDIT:
I have no control over the incoming queries because this is code for an application that's meant to check an answer query of a student against the teacher's query. I can't choose to not use *.
What you try to achieve is something that is not meant to be done in SQL Server. A rather clean way would be to have a code that executes each query and compares both resulting data sets, by metadata and value by value.
I do not recommend you to use the following approach.
For testing purposes I created this stored procedure:
IF EXISTS (SELECT * FROM sys.objects WHERE type = 'P' AND name = 'SP_CompareQueryResults')
DROP PROCEDURE SP_CompareQueryResults
GO
CREATE PROCEDURE SP_CompareQueryResults
(
#sql1 NVARCHAR(4000)
, #sql2 NVARCHAR(4000)
)
AS
BEGIN
DECLARE #q1 NVARCHAR(MAX) = #sql1
, #q2 NVARCHAR(MAX) = #sql2
IF OBJECT_ID('tempdb..##q1') IS NOT NULL
DROP TABLE ##q1
IF OBJECT_ID('tempdb..##q2') IS NOT NULL
DROP TABLE ##q2
SET #q1 = 'SELECT * INTO ##q1 FROM (' + #q1 + ') r'
SET #q2 = 'SELECT * INTO ##q2 FROM (' + #q2 + ') r'
BEGIN TRY
EXEC (#q1)
EXEC (#q2)
END TRY
BEGIN CATCH
SELECT 'One of the source queries are not valid.'
RETURN
END CATCH
DECLARE #r NVARCHAR(MAX)
SELECT #r = COALESCE(#r + ', ', ' ') + COLUMN_NAME
FROM (
SELECT COLUMN_NAME
FROM tempdb.INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = '##q1'
INTERSECT
SELECT COLUMN_NAME
FROM tempdb.INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = '##q2'
) r
SET #r = 'SELECT 1 as SourceQuery, * FROM (SELECT ' + #r + ' FROM ##q1 EXCEPT SELECT' + #r + ' FROM ##q2) r'
+ ' UNION ALL SELECT 2 as SourceQuery, * FROM (SELECT ' + #r + ' FROM ##q2 EXCEPT SELECT' + #r + ' FROM ##q1) r'
BEGIN TRY
EXEC(#r)
END TRY
BEGIN CATCH
SELECT 'Queries have not matching metadata.'
RETURN
END CATCH
IF OBJECT_ID('tempdb..##q1') IS NOT NULL
DROP TABLE ##q1
IF OBJECT_ID('tempdb..##q2') IS NOT NULL
DROP TABLE ##q2
END
GO
It finds columns with the same names from both queries and compares each of the queries results, returning rows from query 1 not included in query 2 and the other way around.
Let's say you have two queries with following results:
and another one:
As you can see the second query has an additional column, columns are not in the same order and only one tuple is in both queries.
Executing the above SP like this:
EXEC SP_CompareQueryResults
#sql1 = N'
SELECT 1 AS ID
, ''test'' AS Value
, CAST(1 as BIT) AS Valid
UNION ALL
SELECT 2, ''test2'', 0',
#sql2 = N'
SELECT 1 AS ID
, CAST(1 as BIT) AS Valid
, ''test'' AS Value
, ''test'' AS AnotherValue
UNION ALL
SELECT 2, 1, ''test2'', ''whatever'''
gives you the none matching tuples from both queries:
If both queries yield the same results, the SP_CompareQueryResults will not return any rows, so you could say they have same values for the columns with matching names. Same thing happens if both queries have no resulting rows, giving a false positive. You can tweak the above procedure for your needs.
DO NOT USE this code in a production environment as it can be SQL injected and I have not tested this. Generally avoid dynamic sql, if not necessary.
As first stated, try to write for example a c# code is sql injection safe and compares the results.