Declaring SQL variables - SQL Server - sql-server

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

Related

Creating table of DB and table info on SQL Server Instance using while loop

I was wondering why i can't substitute the DB name for the variable #DBNAME in the following while loop:
I can use the variable as the DB_NAME but it wont let me substitute the DB name for sys.tables
it is giving me the following error: Incorrect syntax near '.'.
DROP TABLE #TABLE_INFO;
CREATE TABLE #TABLE_INFO
(
DB_NAME VARCHAR(100),
TABLE_NAME VARCHAR(100),
CREATE_DATE DATE
);
DECLARE #COUNT AS INT = 1
DECLARE #DBNAME AS VARCHAR(100)
WHILE #COUNT < (SELECT MAX(DATABASE_ID) +1 FROM SYS.DATABASES)
BEGIN
SET #DBNAME = (SELECT NAME FROM SYS.DATABASES WHERE DATABASE_ID = #COUNT)
INSERT INTO #TABLE_INFO
SELECT
#DBNAME AS DB_NAME,
A1.NAME AS TABLE_NAME,
A1.CREATE_DATE
FROM #DBNAME.SYS.TABLES A1
SET #COUNT = #COUNT+1
END
SELECT * FROM #TABLE_INFO;
T-SQL does not allow database object-identifiers to be parameteried.
...So you have to use Dynamic SQL (i.e. building up a query inside a string value and passing it to sp_executesql).
Obviously this is dangerous due to the risk of SQL injection, or simply forgetting to escape names correctly (use QUOTENAME) which may cause inadvertent data-loss (e.g. if someone actually named a table as [DELETE FROM Users]).
So it's important to use Dynamic SQL as little as possible.
What you can do is use INSERT INTO #tableVariable EXECUTE sp_executesql #query which allows you to store the results of a stored procedure - or any dynamic SQL - into a table-variable or temporary-table without them being output directly to your client connection and then process the results using non-dynamic SQL.
You can also use CROSS APPLY too if you can move all of your dynamic-SQL logic to a stored-procedure.
Try this:
BTW, I changed your WHILE loop to use a STATIC READ_ONNLY CURSOR which avoids your assumptions about how sys.databases's database_id work (e.g. what if sys.databases lists only these 4 database_id values 1, 2, 99997, 99998? Your WHILE loop would be wasting time checking for 3, 4, 5, 6, ..., 99996).
I also use a table-variable rather than a temporary-table because the scope and lifetime of a table-variable is easier to reason about compared to a temporary-table. That said, there aren't really any performance benefits of TVs over TTs (though you can pass TVs as table-valued-parameters, which is nice and generally much better than passing data to sprocs using TTs).
DECLARE #results TABLE (
DatabaseName nvarchar(100) NOT NULL,
SchemaName nvarchar(50) NOT NULL,
TableName nvarchar(100) NOT NULL,
Created datetime NOT NULL
);
DECLARE #dbName nvarchar(100);
DECLARE c CURSOR STATIC READ_ONLY FOR
SELECT QUOTENAME( [name] ) FROM sys.databases;
OPEN c;
FETCH NEXT FROM c INTO #dbName;
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE #dbQuery nvarchar(1000) = N'
SELECT
''' + #dbName + N''' AS DatabaseName,
s.[name] AS SchemaName,
t.[name] AS TableName,
t.create_date AS Created
FROM
' + #dbName + N'.sys.tables AS t
INNER JOIN sys.schemas AS s ON t.schema_id = s.schema_id
ORDER BY
s.[name],
t.[name]
';
INSERT INTO #results
( DatabaseName, SchemaName, TableName, Created )
EXECUTE sp_executesql #dbQuery;
FETCH NEXT FROM c INTO #dbName;
END;
CLOSE c;
DEALLOCATE c;
--------------------------
SELECT
*
FROM
#results
ORDER BY
DatabaseName,
SchemaName,
TableName;
I am able to copy+paste this query into SSMS and run it against my SQL Server 2017 box without modifications and it returns the expected results.

Update value in all tables that have a column name in SQL Server

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.

How to join sys.databases, sys.tables and sys.columns

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

Execute the query against all databases

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

In T-SQL how to display columns for given table name?

I am trying to list all of the columns from whichever Adventureworks table I choose. What T-sQL statement or stored proc can I execute to see this list of all columns? I want to use my C# web app to input one input parameter = table_name and then get a list of all the column_names as output. Right now I am trying to execute the sp_columns stored proc which works, but I can't get just the one column with select and exec combined. Does anybody know how to do this?
Thanks everyone for all your replies, but none of your answers do what I need. Let me explain my problem more for you. The query below returns what I need. But my problem is incorporating this logic into an SP that takes an input parameter. So here is the successful SQL statement:
select col.name from sysobjects obj inner join syscolumns col
on obj.id = col.id
where obj.name = 'AddressType'
order by obj.name
And currently my SP looks like:
CREATE PROCEDURE [dbo].[getColumnNames]
#TableName VarChar(50)
AS
BEGIN
SET NOCOUNT ON;
SET #TableName = RTRIM(#TableName)
DECLARE #cmd AS NVARCHAR(max)
SET #cmd = N'SELECT col.name from sysobjects obj ' +
'inner join syscolumns col on obj.id = col.id ' +
'where obj.name = ' + #TableName
EXEC sp_executesql #cmd
END
But I run the above as
exec getColumnNames 'AddressType'
and it gives me error:
Invalid column name 'AddressType'
How do I accomplish this?
select * from sys.columns where object_id = object_id(#tablename);
You don't need to create the statement as a string -- in fact, that's what's messing you up. Try this:
CREATE PROCEDURE [dbo].[getColumnNames] #TableName VarChar(50) AS
BEGIN
SET NOCOUNT ON;
SELECT col.name FROM sysobjects obj
INNER JOIN syscolumns col ON obj.id = col.id
WHERE obj.name = #TableName
END
to display info for database.dbo.yourtable try this:
SELECT
*
FROM INFORMATION_SCHEMA.COLUMNS
WHERE
TABLE_CATALOG ='database'
AND TABLE_SCHEMA='dbo'
AND TABLE_NAME ='yourtable'
ORDER BY ORDINAL_POSITION
EDIT based on OP's edit
You don't need dynamic sql, just use the given parameter:
CREATE PROCEDURE [dbo].[getColumnNames]
#TableName VarChar(50)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #Rows int
,#ReturnValue int
SET #ReturnValue=0
SELECT
col.name
FROM sysobjects obj
inner join syscolumns col on obj.id = col.id
WHERE obj.name = #TableName
ORDER BY obj.name
SELECT #Rows=##ROWCOUNT
IF #Rows=0
BEGIN
SET #ReturnValue= 1 --table not found!!
END
RETURN #ReturnValue
END
if you check the return value and get a 1 then no table was found matching the given #TableName parameter. You can use this to give a error message in the application.
SELECT *
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'TableName' AND TABLE_CATALOG = 'DatabaseName'
Use Remus Rusanu answer or use SqlConnection.GetSchema() function from your c# code
A reply to your edited question:
Your script does not work because you're building a dynamic string, and within that dynamic string you must add quotes to the string you are placing in your where clause. The revised code would be:
BEGIN
SET NOCOUNT ON;
SET #TableName = RTRIM(#TableName)
DECLARE #cmd AS NVARCHAR(max)
SET #cmd = N'SELECT col.name from sysobjects obj ' +
'inner join syscolumns col on obj.id = col.id ' +
'where obj.name = ''' + #TableName + ''''
EXEC sp_executesql #cmd
END
However, why are you buliding a dynamic string? This would do the same thing and not require the overhead of dynamic SQL:
BEGIN
SET NOCOUNT ON;
SET #TableName = RTRIM(#TableName)
SELECT col.name from sysobjects obj
inner join syscolumns col on obj.id = col.id
where obj.name = #TableName
END
If you are worried about SQL injection, is not a problem here -- either obj.name = (whatever they passed in), or it doesn't.

Resources