Is there a better way to programatically access tables using SQL - tsql

Using MS SQL2000 at present if that makes any difference.
Is there a better way than the method below to be able to programatically access a table ?
declare #tableName as varchar(50)
declare #sql varchar(4000)
set #tableName = 'User'
print #tableName
If EXISTS(
select TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE table_name = ''+#TableName+''
)
BEGIN
set #sql = 'select * from [' + #tableName + ']'
exec(#sql)
end
Essentially Im trying to create a simple Mapping tool for CRUD operations so that I need only one Sproc for each operation, and I can pass in my parameterised object, a table name and let the database do the rest. This is purely for my own personal education, hence why Im not using an established framework, so if there are any major gotcha's with my idea or the code above, I'd appreciate knowing as well.
Thanks

This is complete example to create a SP by follow your initial code:
CREATE PROCEDURE dbo.CustomSelect (#tableName as varchar(50))
AS
SET NOCOUNT ON
DECLARE #sql varchar(4000)
If EXISTS(
select TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE table_name = #tableName
)
BEGIN
set #sql = 'select 1 as Found, * from [' + #tableName + ']'
exec(#sql)
END
ELSE
BEGIN
select 0 as Found
END
This SP always return a recordset so you can check the value of the field FOUND to know if the table exist or not
usage:
EXEC CustomSelect 'User'
Hope it helps

Related

DDL trigger to remove the article from the replication when it is altered

Using SQL Server 2008 for Transnational replication.
Some times my team folks altering the table during development before removing the table from subscription in the replication. So it leads to replication failure in the environment. To avoid that, planned to write a DDL trigger which will remove the table from subscription when it is getting altered.
Below is the trigger i have written to achieve the same.
CREATE TRIGGER RemoveArticleFromRepliction ON DATABASE
FOR DROP_TABLE
, ALTER_TABLE AS
BEGIN
SET NOCOUNT ON;
DECLARE #EventData XML = EVENTDATA();
DECLARE #TableName VARCHAR(255) = #EventData.value('(/EVENT_INSTANCE/ObjectName)[1]', 'NVARCHAR(255)')
DECLARE #CMD VARCHAR(255) = 'IF EXISTS (SELECT 1 FROM SYS.TABLES WHERE NAME = ''' + #TableName + ''') EXEC sp_mck_DropSubscriptionArticle ''' + #TableName + ''''
EXEC #CMD
END
The trigger applied successfully in SQL server 2008.
Note: sp_mck_DropSubscriptionArticle is a custom procedure. the logic to remove the article from subscription is added in it.
When i execute the alter statement, i am getting the below error
Could not find stored procedure 'IF EXISTS (SELECT 1 FROM SYS.TABLES WHERE NAME = 'NOTE') EXEC sp_mck_DropSubscriptionArticle 'NOTE''.
Please help me resolve this issue or suggest me any other approach to fix this problem
Looks like you are missing the brackets after EXEC. Please try the below changed code
CREATE TRIGGER RemoveArticleFromRepliction ON DATABASE
FOR DROP_TABLE
, ALTER_TABLE AS
BEGIN
SET NOCOUNT ON;
DECLARE #EventData XML = EVENTDATA();
DECLARE #TableName VARCHAR(255) = #EventData.value('(/EVENT_INSTANCE/ObjectName)[1]', 'NVARCHAR(255)')
DECLARE #CMD VARCHAR(255) = 'IF EXISTS (SELECT 1 FROM SYS.TABLES WHERE NAME = ''' + #TableName + ''') EXEC sp_mck_DropSubscriptionArticle ''' + #TableName + ''''
EXEC (#CMD)
END

Listing all databases, all tables, and all columns on a SQL Server instance

I am trying to write a query that for a single SQL Server instance, lists all columns in all tables in all databases on the server instance. I have found examples that list all columns in all tables but you have to know the database name. I have found examples that list databases on a SQL Server instance but not the tables in the databases. Now I am trying to find something that combines the two, but I am not having much luck.
Would anyone know if such a thing exists or is it a manual process to bridge the gap?
Thanks,
Tom
You can use dynamic query. I did not find any other way.
declare #str varchar(max) = ''
;with dbs as (
select *
from sys.databases
where dbs.name not in ('master', 'tempdb', 'model', 'msdb')
)
select #str = #str + 'select ''' + dbs.name + ''', tbl.name, col.name from ' +
dbs.name + '.sys.tables tbl inner join ' +
dbs.name + '.sys.columns col ON col.object_id = tbl.object_id; '
from dbs
print #str
exec(#str)
Please try this and feed back with comments.
The requirement was to find out one particular table from all the database. This was not possible by visual inspection as it might take lots of time and human error was possible. She was aware of the system view sys.tables.
SELECT *
FROM sys.Tables
WHERE name LIKE '%Address%'
The limitation of query mentioned above is that it only searches in one database and user has to keep on changing database manually and run the query again. I wrote down following quick script which looks into all the database on the server and provides the database name, schema name and table containing searched word in its name.
CREATE PROCEDURE usp_FindTableNameInAllDatabase
#TableName VARCHAR(256)
AS
DECLARE #DBName VARCHAR(256)
DECLARE #varSQL VARCHAR(512)
DECLARE #getDBName CURSOR
SET #getDBName = CURSOR FOR
SELECT name
FROM sys.databases
CREATE TABLE #TmpTable (DBName VARCHAR(256),
SchemaName VARCHAR(256),
TableName VARCHAR(256))
OPEN #getDBName
FETCH NEXT
FROM #getDBName INTO #DBName
WHILE ##FETCH_STATUS = 0
BEGIN
SET #varSQL = 'USE ' + #DBName + ';
INSERT INTO #TmpTable
SELECT '''+ #DBName + ''' AS DBName,
SCHEMA_NAME(schema_id) AS SchemaName,
name AS TableName
FROM sys.tables
WHERE name LIKE ''%' + #TableName + '%'''
EXEC (#varSQL)
FETCH NEXT
FROM #getDBName INTO #DBName
END
CLOSE #getDBName
DEALLOCATE #getDBName
SELECT *
FROM #TmpTable
DROP TABLE #TmpTable
GO
EXEC usp_FindTableNameInAllDatabase 'Address'
GO

Need help in creating a stored procedure to iterate tables in a database, then run a SQL statement on each table

Our application does not delete data as we retain it for a period of time, instead we have a column "deleted" (bit) in most tables of the database that store data which we mark 1 when deleted, otherwise the default is 0.
I'd like to create a stored procedure that iterates all tables in the database, checks for the existence of a column named "deleted" and if it exists, I run a check against the LastUpdatedUtc column (datetime2) and if the date is over 6 months old and deleted = 1 then we delete the row.
This application is under continuous development so tables could be added which is why I want to create a script that iterates tables instead of having to add a line for each table and remember to add them as new tables are added.
Any help in a SQL Server 2008 R2 stored procedure to this would be a great help.
Thank you.
EDIT (thank you Omaer) here is what I've come up with so far. Anyone that knows a better way let me know.
IF OBJECT_ID('tempdb..#tmpTables') IS NOT NULL DROP TABLE #tmpTables
GO
CREATE TABLE #tmpTables
(
ID INT,
TableName NVARCHAR(100) NOT NULL
)
GO
SET NOCOUNT ON
GO
INSERT #tmpTables
SELECT [object_id], [name] FROM sys.all_objects WHERE type_desc = 'USER_TABLE' ORDER BY [name]
DECLARE #TN NVARCHAR(100)
DECLARE #SQL NVARCHAR(max)
DECLARE #PurgeDate VARCHAR(50)
SET #PurgeDate = DATEADD(MONTH, -6, GETUTCDATE())
WHILE (SELECT COUNT(*) FROM #tmpTables) > 0
BEGIN
SELECT TOP 1 #TN = TableName FROM #tmpTables
IF EXISTS(SELECT * FROM sys.columns WHERE name = 'deleted' AND OBJECT_ID = OBJECT_ID(#TN))
BEGIN
IF EXISTS(SELECT * FROM sys.columns WHERE name = 'LastUpdatedUtc' AND OBJECT_ID = OBJECT_ID(#TN))
BEGIN
SET #SQL = 'SELECT Count(*) As Counter FROM ' + #TN + ' WHERE [deleted] = 1 AND [LastUpdatedUtc] < ''' + #PurgeDate + '''' -- this will be the delete line when the code is final, just outputting results for now
EXEC(#SQL)
END
END
DELETE #tmpTables WHERE TableName=#TN
END
DROP TABLE #tmpTables
This is my first attempt, not tested it so there might be some typos/syntax errors but this should get you started:
declare #date6MonthsBack varchar(50)
select #date6MonthsBack = dateadd(month, -6, getdate());
declare c cursor for
select 'delete from ' + quotename(name) + ' where [deleted] = 1 and [LastUpdatedUtc] <= ''' + #date6MonthsBack + '''' from sys.tables
where object_id in (select object_id from sys.columns where name = 'deleted')
and object_id in (select object_id from sys.columns where name = 'LastUpdatedUtc')
declare #sql varchar(max)
open c; fetch next from c into #sql
while (##fetch_status = 0) begin
print(#sql)
--exec(#sql) --uncomment this line to do the actual deleting once you have verified the commands.
fetch next from c into #sql; end
close c; deallocate c
You could use undocummented sp_MSforeactable procedure instead of loop or cursor. Something like code below. I created procedure that runs your code and is executed with sp_MSforeachtable. The disadvantage is - the procedure is undocumented and may not be supported in next SQL Server releases
IF OBJECT_ID('dbo.usp_cleanup') IS NULL
EXEC ('CREATE PROCEDURE dbo.usp_cleanup AS SELECT 1')
GO
ALTER PROCEDURE dbo.usp_cleanup
#sTblName VARCHAR(200)
AS
BEGIN
-- your variables
DECLARE #PurgeDate VARCHAR(50)
DECLARE #SQL VARCHAR(MAX)
SET #PurgeDate = DATEADD(MONTH, -6, GETUTCDATE())
-- we can check columns existence in one condition
IF
EXISTS(SELECT * FROM sys.columns WHERE name = 'deleted' AND OBJECT_ID = OBJECT_ID(#sTblName))
AND EXISTS(SELECT * FROM sys.columns WHERE name = 'LastUpdatedUtc' AND OBJECT_ID = OBJECT_ID(#sTblName))
BEGIN
SET #SQL = 'SQL CODE GOES HERE' -- this will be the delete line when the code is final, just outputting results for now
PRINT #SQL
--EXEC(#SQL) -- uncomment for execution
END
ELSE
-- for debugging
BEGIN
PRINT #sTblName + ' has no [delete] and [LastUpdatedUtc] columns'
END
END
EXEC sp_MSforeachtable 'exec usp_cleanup ''?'''
GO

Subtleties of SQL Server Variables

I have the following SQL Server stored procedure:
CREATE PROCEDURE ispsDcOhcAgg #TmpTableName NVARCHAR(50), #ListItem NVARCHAR(50)
AS
IF EXISTS (SELECT name
FROM sys.tables
WHERE name = #TmpTableName)
DROP TABLE #TmpTableName; -- This will not work.
GO
This will clearly not work (see the comment in the above snippit). The only (and very ugly) way I have found to get around this problem is to do the following
CREATE PROCEDURE ispsDcOhcAgg #TmpTableName NVARCHAR(50), #ListItem NVARCHAR(50)
AS
DECLARE #SQL NVARCHAR(4000)
SET #SQL = N'IF EXISTS (SELECT name ' +
N'FROM sys.tables ' +
N'WHERE name = N' + N'''' + #TmpTableName + N''') ' +
N'DROP TABLE ' + #TmpTableName + N';'
EXEC sp_executesql #SQL;
GO
which truly stinks and for large stored procedures, it's horrendous!
Is there another way of doing this that I don't know about?
Thanks for your time.
No, if you want to use a table name dynamically like this, you need to use dynamic SQL.
So you should make sure you don't open yourself up to nasty SQL injection risks!
Try something like this:
SET #SQL = 'IF EXISTS (SELECT name ' +
N'FROM sys.tables ' +
N'WHERE name = #TableName) ' +
N'DROP TABLE ' + QUOTENAME(#TmpTableName) + ';'
EXEC sp_executesql #SQL, N'#TableName sysname', #TmpTableName;
No, if you want to determine the table to be dropped at runtime, there is no alternative to dynamic SQL.
There is a slightly less ugly way: you only use dynamic SQL for the command that needs to be dynamic (the DROP command):
DECLARE #SQL NVARCHAR(100)
IF EXISTS (SELECT name
FROM sys.tables
WHERE name = #TmpTableName)
BEGIN
SET #SQL = N'DROP TABLE ' + #TmpTableName + N';'
EXEC sp_executesql #SQL;
END

How to execute the stored procs from a dynamically generated query + sql server

I have a query that is dynamically fetching the stored proc names from all the databases.
Now I want to execute the stored procs and store the result in a temptable or table variable.
How to do that. Here is my SP so far
Declare #GetDBNames sysname
Declare #DynSql nvarchar(max)
Declare DBNames cursor for
Select '['+name+']' from master.dbo.sysdatabases
open DBNames
FETCH NEXT FROM DBNames into #GetDBNames
WHILE ##FETCH_STATUS=0
BEGIN
SET #DynSql = '
Select Specific_Catalog as Database_Name, Routine_Name as ''Stored Procedure Name''
From '+ #GetDBNames+'.Information_Schema.Routines '
EXEC (#DynSql)
FETCH NEXT FROM DBNames into #GetDBNames
END
Close DBNames
Deallocate DBNames
Please help.
Thanks in advance
Here's my soluton. Cursors are evil :)
Declare #DynSql nvarchar(max)
declare #result table ([Database_Name] nvarchar(128), [Stored Procedure Name] sysname)
SET #DynSql = ''
select #DynSql = #DynSql + '
Select SPECIFIC_CATALOG COLLATE DATABASE_DEFAULT as Database_Name, ROUTINE_NAME COLLATE DATABASE_DEFAULT as [Stored Procedure Name]
From ' + NAME + '.INFORMATION_SCHEMA.ROUTINES' + CHAR(13) + 'union all' + CHAR(13)
from master.dbo.sysdatabases
--remove last "union all"
SET #DynSql = left(#DynSql, LEN(#DynSql) - 10)
insert #result
exec sp_executesql #DynSql
select * from #result
INSERT [TableName] EXEC (#DynSql)
Note that if you introduce data you may want to use sp_ExecuteSQL, and you probably want to use [/] object-name escaping.