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.
Related
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 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
I have about 20 views in a database that reference another database, let's call it database A. I need a way to update these views with a script to point to a different database, database B. Is there a way to replace the name of database A in the view with the name of database B using a single tsql script and keep the views intact? I can do a replace and output the views to the query window but I want to execute the ALTER statements that are generated and not have to run the output manually.
Update What I would like to do is similar to this: https://stackoverflow.com/a/2983927/6084613 but also have the output executed by the script. Is that possible?
I have created below SQL to do the same. this script will take 2 input
1. Old DB Name
2. New DB Name
3. List of view you want to modify
declare #OldDb varchar(250), #newDB varchar(250)
select #OldDb = '' , ---------->>> provide old db name
#newDB = '' ---------->>> provide new db name
create table #ViewList (Id int identity , ViewName varchar(250))
insert into #ViewList
select TABLE_NAME from INFORMATION_SCHEMA.VIEWS where TABLE_NAME IN ( ) ---------->>> provide you view list
create table #ViewDef( ViewDef nvarchar(max) , ViewName varchar(250) ,Id int )
declare #minId int , #maxid int , #sql nvarchar(max) ='' , #ViewName varchar(250)
select #minId = min(Id) , #maxid = max(id) from #ViewList
while #minId <= #maxid
begin
select #ViewName = ViewName from #ViewList where id = #minId
set #sql = ' insert into #ViewDef (ViewDef)
exec sp_helptext '+ #ViewName +'
update #ViewDef
set ViewName = '''+ #ViewName +''',
id = ' + cast(#minId as varchar(10)) +'
where id is null
update #ViewDef
set ViewDef = replace(ViewDef , '''+ #OldDb+''','''+ #newDB +''')
where id = ' + cast(#minId as varchar(10)) +'
update #ViewDef
set ViewDef = replace(ViewDef , ''create'',''alter'')
where id = ' + cast(#minId as varchar(10)) +'
'
exec sp_executesql #sql
SET #sql = ''
select #sql = #sql + ViewDef from #ViewDef where id = #minId
exec sp_executesql #sql
--print #sql
set #minId = #minId +1
end
**Please test the script and save you old definition to avoid any loss in case of any bug in above scrip
You just need to put your query definition into a variable and then execute with sp_executeSQL.
BUT: I'd be cautious using the referenced script without more work.
For example, not all views have the exact text 'CREATE VIEW'.
If hand written , some might be ' CREATE VIEW' or 'CREATE VIEW' for example.
Also, 'DB1' might me written '[DB1]'.
So either add a lot more sophisticated logic, or validated everything by eye before exectuting.
Declare #queryDef nvarchar(max)
SELECT #queryDef = REPLACE (REPLACE (sm.definition, 'CREATE VIEW', 'ALTER VIEW'), 'DB1.', 'DB2.')
FROM sys.sql_modules sm JOIN sys.objects o
ON sm.object_id = o.object_id
WHERE
sm.definition LIKE '%DB1.%' AND o.type = 'V'
print #queryDef
exec sp_executeSql #querydef
Using SimonB idea, I created a loop to do it automatically:
DECLARE #queryDef NVARCHAR(max)
WHILE EXISTS (
SELECT 1
FROM sys.sql_modules sm
JOIN sys.objects o ON sm.object_id = o.object_id
WHERE sm.definition LIKE '%TEXT_TO_REPLACE%'
AND o.type = 'V'
)
BEGIN
-- TO ALTER THE VIEW AUTOMATICALLY
SET #queryDef = ( SELECT TOP 1 Replace (Replace (sm.definition, 'CREATE VIEW', 'ALTER VIEW'),
'TEXT_TO_REPLACE',
'NEW_TEXT')
FROM sys.sql_modules sm
JOIN sys.objects o ON sm.object_id = o.object_id
WHERE sm.definition LIKE '%TEXT_TO_REPLACE%'
AND o.type = 'V')
EXEC sp_executeSql #queryDef
END
I'm trying to make a query that return the name of all columns for a table, and for each columns the count distinct. I have more than 1400 tables to test, with some with 100 columns, so I can't imagine tiping colomn names one by one.
I have problem for the count disctinct part, And I whant to know if it is possible to do something like an EXECUTE IMMEDIATE in a subquery, and if no, if there is an other solution ?
Here is my actual query :
SELECT
sc.name AS columnName
, ('SELECT COUNT(DISTINCT ' || sc.name || ') FROM MyTableName') AS nb_distinct_row
FROM dbo.syscolumns sc INNER JOIN sysobjects so
ON so.id = sc.id
AND so.name = 'MyTableName'
GROUP BY sc.name
This return the good subquery but I don't know how to execute it immediatly ?
I've tried something like
, (SELECT count(distinct sc.name) from MyTableName) As nbDistinctRow
But the 'sc.name' is not interpreted and the count distinct return 1, so that's why I'm trying this way.
I work on a sybase IQ Database
Anyone can help me ?
thank you in advance.
I have found a way to make this work, but it's realy clumsy.
CREATE OR REPLACE PROCEDURE FLA_distinctCols(in table_obj_id INT)
RESULT (column_name VARCHAR(128), count_distinct INT)
BEGIN
DECLARE err_notfound EXCEPTION FOR SQLSTATE VALUE '02000';
DECLARE #tablename VARCHAR(100);
DECLARE #cols VARCHAR(128);
DECLARE #nb_dist INT;
DECLARE #sql VARCHAR(30000);
DECLARE #sqltemp VARCHAR(30000);
DECLARE tables NO SCROLL CURSOR FOR
SELECT
sc.name col
, ('SELECT COUNT(DISTINCT ' || sc.name || ') INTO #nb_dist FROM ' || #tablename)
FROM dbo.syscolumns sc INNER JOIN sysobjects so
ON so.id = sc.id
AND so.name = #tablename
|| '%' ORDER BY sc.name
;
SELECT so.name INTO #tablename FROM sysobjects so WHERE so.id = table_obj_id;
create table #tablestats (
column_name varchar(128) not null,
count_distinct INT,
);
open tables WITH HOLD;
LoopTables: loop
fetch next tables into #cols, #sqltemp;
if sqlstate = err_notfound then
leave LoopTables
else
EXECUTE IMMEDIATE WITH QUOTES #sqltemp;
set #sql= 'INSERT INTO #tablestats SELECT ''' || #cols || ''', ' || #nb_dist || ';';
EXECUTE IMMEDIATE WITH QUOTES #sql;
end if
end loop LoopTables;
close tables;
SELECT column_name, count_distinct FROM #tablestats ORDER BY count_distinct DESC;
END
GO
BEGIN
DECLARE #tablename VARCHAR(128);
DECLARE #tableid INT;
SET #tablename = 'Mytablename';
SELECT so.id INTO #tableid FROM sysobjects so WHERE so.name = #tablename;
SELECT * FROM FLA_distinctCols(#tableid);
END
Any other solution ?
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