I am building a dynamic sql query which loops on the columns that are available and perform and innerjoin to return the duplicates values.
So far looping in the column is working as a charm. The only issue is when I am building the dynamic inner join which is returning an error message. code is as below
DECLARE #Check VARCHAR(250) = 'Check_1'
DECLARE #SQLQUERY VARCHAR(MAX)
SET #SQLQUERY = 'SELECT A.PostingDate,A.DocumentNumber,A.Account,A.Reference,A.'+#Check+'
FROM TblDataTemp A
INNER JOIN TblDataHistory B
ON A.'+#Check +' = B.'+#Check +';'
EXEC #SQLQUERY
Msg 203, Level 16, State 2, Line 12
The name 'SELECT A.PostingDate,A.DocumentNumber,A.Account,A.Reference,A.Check_1
FROM TblDataTemp A
INNER JOIN TblDataHistory B
ON A.Check_1 = B.Check_1;' is not a valid identifier.
When using EXECUTE with dynamic SQL, you must use parentheses. Otherwise, SQL Server thinks the variable contains a procedure name.
DECLARE #Check VARCHAR(250) = 'Check_1'
DECLARE #SQLQUERY VARCHAR(MAX)
SET #SQLQUERY = 'SELECT A.PostingDate,A.DocumentNumber,A.Account,A.Reference,A.'+#Check+'
FROM TblDataTemp A
INNER JOIN TblDataHistory B
ON A.'+#Check +' = B.'+#Check +';'
EXEC (#SQLQUERY)
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 am writing a stored proc that needs to search a database for all tables that have a certain column name. Once I get a list of tables that have that column I need to update a value in that column. So first I get a list of tables that have a certain column.
SELECT c.name AS 'ColumnName'
,t.name AS 'TableName'
FROM sys.columns c
JOIN sys.tables t ON c.object_id = t.object_id
WHERE c.name LIKE 'company'
ORDER BY TableName
Now that I have a list of tables that need to be updated I need to run a query similar to the following to update the data in each table.
update table1 set company = #newValue where company = #oldvalue
I'm not sure how to go about writing this part. My first thought was to write a dynamic update statement inside of a cursor like:
Declare #newValue
Declare #oldValue
SET #companyCursor = CURSOR FOR
SELECT t.name AS 'TableName'
FROM sys.columns c
JOIN sys.tables t ON c.object_id = t.object_id
WHERE c.name LIKE 'company'
OPEN #companyCursor;
FETCH NEXT FROM #companyCursor INTO #tableName;
WHILE ##FETCH_STATUS = 0
BEGIN
update #tableName set company = #newValue where company = #oldValue
FETCH NEXT FROM #companyCursor INTO INTO #tableName;
END
Is this a good strategy?
I really dislike cursors so even in cases like this where a cursor is a viable solution I like to leverage the system views to avoid looping. You still have to use dynamic sql because object names cannot be parameterized.
Please note that I am guessing the datatype for company here and you can change this easily. Make sure you change the variable definition both in your script AND in the dynamic sql. You entire script could be shortened to something like this.
declare #SQL nvarchar(max) = ''
, #newValue varchar(10) = 'new'
, #oldValue varchar(10) = 'old'
select #SQL = #SQL + 'Update ' + quotename(object_name(c.object_id)) + ' set company = #newValue where company = #oldValue;'
from sys.columns c
where c.name = 'company'
select #SQL
--uncomment the line below when you are satisfied the dynamic sql is correct.
--This dynamic sql is parameterized as much as possible
--exec sp_executesql #SQL, N'#newValue varchar(10), #oldValue varchar(10)', #newValue = #newValue, #oldValue = #oldValue
No the update at the end will not work. You need to use exec (#sql) like this:
declare #sql varchar(4000)
begin
set #sql = 'update ' + #tableName + 'set company = ' + #newValue + 'where company = ' + #oldValue
exec (#sql)
fetch next ...
end
This assumes that #newvalue and #oldvalue are being assigned values somewhere.
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.
source : MS-SQL 2005
destination : MS-SQL 2012
somehow I need to change developing database from one to another but unfortunately the "to database" does have had some tables and SPs already and the worse, those objects such as tables might have some columns with different name or types or even descriptions inconsistent between.
what am I supposed to do to achieve sth like, maybe easier or smarter,
append new columns to tables which already existed (and also put col's default value and description from source)
change types of columns consistent to source
prevent overwriting contents of SPs already appeared in destination (but will review manually later)
So far I can figure out some statistics by the follwing scripts
select name from sys.Tables order by name (export to left.txt and right.txt and compare them between)
select * from sys.all_objects where type='p' and is_ms_shipped=0 order by name (also compare them between)
get all column names in one line per table (and compare them between),
e.g.
--sth like SELECT * FROM INFORMATION_SCHEMA.COLUMNS, but select into only ONE line per table
declare #temp_table_list table(
id int identity not null,
name varchar(100) not null
)
insert #temp_table_list(name) select name from sys.tables
declare #id int
declare #name varchar(100)
declare #result as nvarchar(max)
set #result = N''
while 1 = 1
begin
select #id = min(id)
FROM #temp_table_list
where id > isnull(#id,0)
if #id is null break
select #name = name
FROM #temp_table_list
where id = #id
declare #tbName as nvarchar(max)
declare #sql as nvarchar(max)
declare #col as nvarchar(max)
Set #tbName = #name
DECLARE T_cursor CURSOR FOR
select c.name from sys.columns c
inner join sys.tables t on c.object_id = t.object_id
inner join sys.types tp on tp.user_type_id = c.system_type_id
where t.name =#tbName
OPEN T_cursor
FETCH NEXT FROM T_cursor into #col
set #sql = N'select '
WHILE ##FETCH_STATUS = 0
BEGIN
set #sql = #sql+#col+','
FETCH NEXT FROM T_cursor into #col
END
set #sql =substring( #sql,0,len(#sql)) +' from '+ #tbName
CLOSE T_cursor
DEALLOCATE T_cursor
set #result = #result + #sql + '/r/n'
end
select #result
This isn't free software, but it does have a trial period:
http://www.red-gate.com/products/sql-development/sql-compare/
As it sounds like your requirement is a one off sync, then that should do what you want.
Can anyone check on my statement...
DECLARE #tblName varchar(MAX),
#strSQL varchar(MAX)
SET #tblName ='SELECT DISTINCT o.name as TableName
FROM sysobjects o
JOIN sysindexes x on o.id = x.id
WHERE o.name LIKE ''%empty%'''
SET #strSQL = 'INSERT INTO #tblName VALUES(''trylng'', ''1'')'
EXEC (#strSQL)
my error is...
Msg 1087, Level 15, State 2, Line 1
Must declare the table variable "#tblName".
What I want to do is get the table name on the variable #tblName and insert some data in #strSQL variable
For example... the result in #tblName is CustomerInfo
then in #strSQL I will going to use the result in #tblName as my table name in my Insert Command.
So the #strSQL variable will be;
INSERT INTO CustomerInfo VALUES(......)
Try this from my answer to your other question:
SELECT TOP 1 #tblName = t.name
FROM sys.tables t
INNER JOIN sys.indexes i on i.object_id = t.object_id
WHERE t.name LIKE '%empty%'
SET #strSQL = 'INSERT INTO ' + #tblName + ' VALUES(''trylng'', ''1'')'
EXEC (#strSQL)
You're still not mentioning the SQL Server version you're using. But as of SQL Server 2005 or newer, you should stop using sysobjects and sysindexes - instead, use the new sys schema that contains more or less the same information - but more easily available.
See [MSDN: Querying the SQL Server System Catalog][1] for a lot more information on what's available in the new sys schema and how to make the most of it!
When you declare more than one variable with a single DECLARE statement, you only put the type once (at the end):
DECLARE #tblName, #strSQL varchar(MAX)
This should be something can really run:
DECLARE #tblName varchar(MAX),
#strSQL varchar(MAX)
SET #tblName = (SELECT DISTINCT TOP 1 o.name as TableName
FROM sysobjects o
JOIN sysindexes x on o.id = x.id
WHERE o.name LIKE '%empty%')
SET #strSQL = 'INSERT INTO ' + #tblName + ' VALUES(''trylng'', ''1'')'
EXEC (#strSQL)
Anything in quote means that's a string and don't expect sql server run it as statement, same thing to the variable in a string, you can't quote it