Rename multiple tables at once - tsql

I am needing some sql code that I can use regularly to rename multiple tables at once. The code below is what I would want if I could use the update statement but cannot. Simple as possible.
UPDATE sys.tables
SET name = SUBSTRING(name, CHARINDEX('PP_', name),LEN(name))
WHERE CHARINDEX('PP_', name) > 0

Use sp_rename to rename objects. Below is an example that generates and executes the needed script.
DECLARE #SQL nvarchar(MAX) =
(SELECT 'EXEC sp_rename ''' + QUOTENAME(s.name) + '.' + QUOTENAME(t.name) + ''', ''' + SUBSTRING(t.name, CHARINDEX('PP_', t.name),LEN(t.name)) + ''';'
FROM sys.tables AS t
JOIN sys.schemas AS s ON s.schema_id = t.schema_id
WHERE CHARINDEX('PP_', t.name) > 0
FOR XML PATH(''), TYPE).value('.', 'nvarchar(MAX)');
EXEC sp_executesql #SQL;
To answer the additional question asked in the comments, you can generate a single DROP TABLE statement for these tables using the script below. Note that this method might not work if the tables have foreign key relationships since it doesn't drop in dependency order.
DECLARE #SQL nvarchar(MAX) =
N'DROP TABLE ' + STUFF((SELECT ',' + QUOTENAME(s.name) + '.' + QUOTENAME(t.name)
FROM sys.tables AS t
JOIN sys.schemas AS s ON s.schema_id = t.schema_id
--WHERE CHARINDEX('PP_', t.name) > 0
FOR XML PATH(''), TYPE).value('.', 'nvarchar(MAX)'),1,1,'')
+ N';';

Related

Error converting data type varchar to numeric in standard audit trigger

I have been using a standard block of TSQL for auditing of various tables for some time now. However I now have a problem when running the trigger on a new table: "Error converting data type varchar to numeric". This occurs when running the EXEC (#sql) line. I've determined that the code for #sql is:
insert Audit_AppointmentsWS
(Type,
TableName,
PK,
FieldName,
OldValue,
NewValue,
UpdateDate,
UserName)
SELECT 'U',
'AppointmentsWorkshop',
+convert(varchar(100), coalesce(i.UniqueID,d.UniqueID)),
'[JobHours]',
convert(varchar(1000),d.[JobHours]),
convert(varchar(1000),i.[JobHours]),
'20220816 12:32:43:410',
'DELLXPS\ian'
from #ins i full outer join #del d on i.UniqueID = d.UniqueID where ISNULL(i.JobHours],'') <> ISNULL(d.[JobHours],'')
I've tried deleting the trigger & the audit table and then recreating them but no joy. I've also tried copying an existing trigger and just changing the table details but I still get the same error. I'm completely stumped on this and would appreciate some feedback. Many thanks in advance!
Here is the trigger:
/****** Object: Trigger [dbo].[tr_AppointmentsWS] Script Date: 16/08/2022 12:02:10 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
create TRIGGER [dbo].[tr_AppointmentsWS] ON [dbo].AppointmentsWorkshop FOR UPDATE, DELETE
AS
DECLARE #bit INT ,
#field INT ,
#maxfield INT ,
#char INT ,
#fieldname VARCHAR(128) ,
#TableName VARCHAR(128) ,
#AuditTable VARCHAR(128) ,
#PKCols VARCHAR(MAX) ,
#sql VARCHAR(2000),
#UpdateDate VARCHAR(21) ,
#UserName VARCHAR(128) ,
#Type CHAR(1) ,
#PKSelect VARCHAR(MAX)
--Changes required:
-- 1. Change the name of the trigger and the table, above
-- 2. Change #TableName to match the table to be audited
-- 3. Change the #AuditTable to the table holding the changes
SELECT #TableName = 'AppointmentsWorkshop'
SELECT #AuditTable = 'Audit_AppointmentsWS'
-- date and user
SELECT #UserName = SYSTEM_USER ,
#UpdateDate = CONVERT(VARCHAR(8), GETDATE(), 112) + ' ' + CONVERT(VARCHAR(12), GETDATE(), 114)
-- Action
IF EXISTS (SELECT * FROM inserted)
IF EXISTS (SELECT * FROM deleted)
SELECT #Type = 'U'
ELSE
SELECT #Type = 'I'
ELSE
SELECT #Type = 'D'
-- get list of columns
SELECT * INTO #ins FROM inserted
SELECT * INTO #del FROM deleted
-- Get primary key columns for full outer join
SELECT #PKCols = COALESCE(#PKCols + ' and', ' on') + ' i.' + c.COLUMN_NAME + ' = d.' + c.COLUMN_NAME
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS pk, INFORMATION_SCHEMA.KEY_COLUMN_USAGE c
WHERE pk.TABLE_NAME = #TableName
AND CONSTRAINT_TYPE = 'PRIMARY KEY'
AND c.TABLE_NAME = pk.TABLE_NAME
AND c.CONSTRAINT_NAME = pk.CONSTRAINT_NAME
-- Get primary key select for insert
SELECT #PKSelect = COALESCE(#PKSelect+'+','') + '+convert(varchar(100), coalesce(i.' + COLUMN_NAME +',d.' + COLUMN_NAME + '))'
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS pk, INFORMATION_SCHEMA.KEY_COLUMN_USAGE c
WHERE pk.TABLE_NAME = #TableName
AND CONSTRAINT_TYPE = 'PRIMARY KEY'
AND c.TABLE_NAME = pk.TABLE_NAME
AND c.CONSTRAINT_NAME = pk.CONSTRAINT_NAME
IF #PKCols IS NULL
BEGIN
RAISERROR('no PK on table %s', 16, -1, #TableName)
RETURN
END
SELECT #field = 0, #maxfield = MAX(COLUMNPROPERTY(OBJECT_ID(TABLE_SCHEMA + '.' + #Tablename),COLUMN_NAME, 'ColumnID'))
FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = #TableName
WHILE #field < #maxfield
BEGIN
SELECT #field = MIN(COLUMNPROPERTY(OBJECT_ID(TABLE_SCHEMA + '.' + #Tablename),COLUMN_NAME, 'ColumnID'))
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = #TableName
AND COLUMNPROPERTY(OBJECT_ID(TABLE_SCHEMA + '.' + #Tablename),COLUMN_NAME, 'ColumnID') > #field
SELECT #bit = (#field - 1 )% 8 + 1
SELECT #bit = POWER(2,#bit - 1)
SELECT #char = ((#field - 1) / 8) + 1
IF SUBSTRING(COLUMNS_UPDATED(),#char, 1) & #bit > 0 OR #Type IN ('I','D')
BEGIN
SELECT #fieldname = COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = #TableName
AND COLUMNPROPERTY(OBJECT_ID(TABLE_SCHEMA + '.' + #Tablename),COLUMN_NAME, 'ColumnID') = #field
SELECT #sql = 'insert ' + #AuditTable + '
(Type,
TableName,
PK,
FieldName,
OldValue,
NewValue,
UpdateDate,
UserName)
SELECT ''' + #Type + ''','''
+ #TableName + ''',' + #PKSelect
+ ',''[' + #fieldname + ']'''
+ ',convert(varchar(1000),d.[' + #fieldname + '])'
+ ',convert(varchar(1000),i.[' + #fieldname + '])'
+ ',''' + #UpdateDate + ''''
+ ',''' + #UserName + ''''
+ ' from #ins i full outer join #del d'
+ #PKCols
+ ' where ISNULL(i.[' + #fieldname + '],'''') <> ISNULL(d.[' + #fieldname + '],'''')' --Skip identical values and excludes NULLS vs empty strings
EXEC (#sql)
END
END
Well I finally figured it out. The error is being generated with columns of data type 'decimal' and it is down to the ISNULL section of the last SELECT. I've fixed it by checking for the decimal type and then using the following code (which included a zero rather than an empty sting):
+ ' where ISNULL(i.[' + #fieldname + '],''0'') <> ISNULL(d.[' + #fieldname + '],''0'')' --Skip identical values and excludes NULLS vs empty strings

Rework query to include the Schema in the name

I am leeching off this post: Query to list number of records in each table in a database
With this procedure:
CREATE PROCEDURE ListTableRowCounts
AS
BEGIN
SET NOCOUNT ON
CREATE TABLE #TableCounts
(
TableName VARCHAR(500),
CountOf INT
)
INSERT #TableCounts
EXEC sp_msForEachTable
'SELECT PARSENAME(''?'', 1),
COUNT(*) FROM ? WITH (NOLOCK)'
SELECT TableName , CountOf
FROM #TableCounts
ORDER BY TableName
DROP TABLE #TableCounts
END
GO
The procedure works well enough but I need it to output the name as Schema.Name and sort by that.
Is that possible? I'm not sure how to change this but you can see what it is doing below:
I have several instances were the table names are the same from different schemas.
CREATE PROCEDURE ListTableRowCounts
AS
BEGIN
SET NOCOUNT ON
CREATE TABLE #TableCounts
( SchemaName VARCHAR(500),
TableName VARCHAR(500),
CountOf INT
)
INSERT #TableCounts
EXEC sp_msForEachTable
'SELECT PARSENAME(''?'', 2), PARSENAME(''?'', 1),
COUNT(*) FROM ? WITH (NOLOCK)'
SELECT SchemaName, TableName , CountOf
FROM #TableCounts
ORDER BY TableName, SchemaName
DROP TABLE #TableCounts
END
GO
Taking some code from: https://stackoverflow.com/a/1443723/4584335
and from: How do I list all tables in all databases in SQL Server in a single result set?
Could I suggest this one (just in case "sp_msForEachTable" doesn't exist anymore):
declare #sql nvarchar(max);
select #sql = isnull(#sql + N'union all ', '')
+ N'
select b.name as "DB"
,a.name collate Latin1_General_CI_AI
,a.object_id
,a.schema_id
,' + cast(database_id as nvarchar(10)) + N'
,p.[Rows]
from ' + quotename(name) + N'.sys.tables a
join
' + quotename(name) + N'.sys.indexes i
on a.OBJECT_ID = i.object_id
and i.index_id <= 1
join
' + quotename(name) + N'.sys.partitions p
on i.object_id = p.OBJECT_ID
and i.index_id = p.index_id
join sys.databases b
on database_id=' + cast(database_id as nvarchar(10)) + ' '
from sys.databases
where state = 0
and user_access = 0;
exec sp_executesql #sql;

Should I delete Hypothetical Indexes?

I have noticed that Hypothetical indexes exist in a certain database. I have searched around and it appeared that this type of indexes are created by Tuning Advisor and are not always deleted.
There are several topics including official documentation of how to clear/delete these indexed, but I was not able to find if these indexes have any impact to the server themselves.
What I have check using the script below is that there is no size information about them:
SELECT OBJECT_NAME(I.[object_id]) AS TableName
,I.[name] AS IndexName
,I.[index_id] AS IndexID
,8 * SUM(A.[used_pages]) AS 'Indexsize(KB)'
FROM [sys].[indexes] AS I
INNER JOIN [sys].[partitions] AS P
ON P.[object_id] = I.[object_id]
AND P.[index_id] = I.[index_id]
INNER JOIN [sys].[allocation_units] AS A
ON A.[container_id] = P.[partition_id]
WHERE I.[is_hypothetical] = 1
GROUP BY I.[object_id]
,I.[index_id]
,I.[name]
ORDER BY 8 * SUM(A.[used_pages]) DESC
and having them, I have decided to check if there are some usage information about them in order to leave these who are often used, but again nothing was return. (I have use the "Existing Indexes Usage Statistics" from this article).
Could anyone tell why keeping these indexes is wrong and if I can define which of them should be kept?
Just USE the database you want to clean and run this:
DECLARE #sql VARCHAR(MAX) = ''
SELECT
#sql = #sql + 'DROP INDEX [' + i.name + '] ON [dbo].[' + t.name + ']' + CHAR(13) + CHAR(10)
FROM
sys.indexes i
INNER JOIN sys.tables t
ON i.object_id = t.object_id
WHERE
i.is_hypothetical = 1
EXECUTE sp_sqlexec #sql
Just delete them, they aren't actually taking up any space or causing any performance hit/benefit at all, but if you're looking at which indexes are defined on a table and forget to exclude hypothetical indexes, it might cause some confusion, also in the unlikely event that you try to create an index with the same name as one of these indexes, it will fail as it already exists.
If you use custom schemas and checked analyzing indexing views, you need some further improvements to the above scripts:
DECLARE #sql VARCHAR(MAX) = ''
SELECT #sql = #sql
+ 'DROP INDEX [' + i.name + ']'
+ 'ON [' + OBJECT_SCHEMA_NAME(t.[object_id]) + '].[' + t.name + ']'
+ CHAR(13) + CHAR(10)
FROM sys.indexes i
INNER JOIN sys.[all_objects] t
ON i.object_id = t.object_id
WHERE i.is_hypothetical = 1
PRINT #sql
EXECUTE sp_sqlexec #sql

TSQL: Rowcounts of All Tables In A Server

I am trying to obtain the row count of all tables in a server (NOT a particular database, but all the databases on a server, excluding the msdb, model, master, etc). I don't need any other details to be returned other than the database name, the table name, and row count.
My approach to this problem is to get all the databases in a server and place an id on them, which will be referred to in a while loop (beginning with id one until the maximum id). Then, within the while loop, I obtain the tables and row counts in the matching database ID. My problem is that the USE DatabaseName doesn't seem to allow me to make it dynamic, meaning that I can't store a database name in a variable and use it as the referred to database when performing the table with row count query.
Is there another approach to this that I'm missing (I've looked at many other examples - often using cursors, which seem to be much longer in code and appear to use more resources - this is a relatively fast query even if I use the largest database by tables, except that it doesn't hit the next database and so on), or am I missing something obvious in the code to make this dynamic?
DECLARE #ServerTable TABLE(
DatabaseID INT IDENTITY(1,1),
DatabaseName VARCHAR(50)
)
DECLARE #count INT
DECLARE #start INT = 1
SELECT #count = COUNT(*) FROM sys.databases WHERE name NOT IN ('master','tempdb','model','msdb')
INSERT INTO #ServerTable (DatabaseName)
SELECT name
FROM sys.databases
WHERE name NOT IN ('master','tempdb','model','msdb')
WHILE #start < #count
BEGIN
DECLARE #db VARCHAR(50)
SELECT #db = DatabaseName FROM #ServerTable WHERE DatabaseID = #start
-- This is the problem, as the USE doesn't seem to allow it to be dynamic.
USE #db
GO
SELECT #db
,o.name [Name]
,ddps.row_count [Row Count]
FROM sys.indexes AS i
INNER JOIN sys.objects AS o ON i.OBJECT_ID = o.OBJECT_ID
INNER JOIN sys.dm_db_partition_stats AS ddps ON i.OBJECT_ID = ddps.OBJECT_ID AND i.index_id = ddps.index_id
WHERE i.index_id < 2 AND o.is_ms_shipped = 0
ORDER BY o.NAME
SET #start = #start + 1
END
Note: I tried checking in the sys.objects and sys.indexes to see if I could filter with a database name, but I had no luck.
Update: I tried turning the SELECT into something dynamic with no success (note the below code only shows the change SELECT):
SET #sql = '
SELECT ' + #db + ' [Database]
,o.name [Name]
,ddps.row_count [Row Count]
FROM ' + #db + '.sys.objects
INNER JOIN ' + #db + ' sys.objects AS o ON i.OBJECT_ID = o.OBJECT_ID
INNER JOIN ' + #db + ' sys.dm_db_partition_stats AS ddps ON i.OBJECT_ID = ddps.OBJECT_ID AND i.index_id = ddps.index_id
WHERE i.index_id < 2 AND o.is_ms_shipped = 0
ORDER BY o.NAME'
No, that is essentially the way you do it.
I'm not sure why you think a while loop is faster than a cursor (though this is a common misconception). They are essentially the same thing. I don't always use cursors, but when I do, I use LOCAL FAST_FORWARD - make sure that you do too. See this article for more info:
What impact can different cursor options have?
To reduce the code required for individual tasks like this, you might be interested in the sp_MSforeachdb replacement I wrote (sp_MSforeachdb is a built-in, undocumented and unsupported stored procedure that will repeat a command for every database, but it is not possible to, say, filter out system databases, and it also has a severe bug where it will sometimes halt execution):
Making a more reliable and flexible sp_MSforeachdb
Execute a Command in the Context of Each Database in SQL Server
Another way would be dynamic SQL.
DECLARE #sql NVARCHAR(MAX) = N'';
SELECT #sql += '
SELECT db = N''' + name + '''
,o.name [Name]
,ddps.row_count [Row Count]
FROM ' + QUOTENAME(name) + '.sys.indexes AS i
INNER JOIN ' + QUOTENAME(name) + '.sys.objects AS o
ON i.OBJECT_ID = o.OBJECT_ID
INNER JOIN ' + QUOTENAME(name) + '.sys.dm_db_partition_stats AS ddps
ON i.OBJECT_ID = ddps.OBJECT_ID AND i.index_id = ddps.index_id
WHERE i.index_id < 2 AND o.is_ms_shipped = 0
ORDER BY o.NAME;'
FROM sys.databases
WHERE database_id > 4;
PRINT #sql;
--EXEC sp_executesql #sql;
(The print is there so that you can inspect the command before executing. It may be truncated at 8K if you have a large number of databases, but don't be alarmed - that is just a display issue in SSMS, the command is complete.)
You could also build a #temp table first, and insert into that, so that you have a single resultset to work with, e.g.
CREATE TABLE #x(db SYSNAME, o SYSNAME, rc SYSNAME);
DECLARE #sql NVARCHAR(MAX) = N'';
SELECT #sql += 'INSERT #x(db,o,rc)
SELECT db = N''' + name + '''
,o.name [Name]
,ddps.row_count [Row Count]
FROM ' + QUOTENAME(name) + '.sys.indexes AS i
INNER JOIN ' + QUOTENAME(name) + '.sys.objects AS o
ON i.OBJECT_ID = o.OBJECT_ID
INNER JOIN ' + QUOTENAME(name) + '.sys.dm_db_partition_stats AS ddps
ON i.OBJECT_ID = ddps.OBJECT_ID AND i.index_id = ddps.index_id
WHERE i.index_id < 2 AND o.is_ms_shipped = 0
ORDER BY o.NAME;'
FROM sys.databases
WHERE database_id > 4;
EXEC sp_executesql #sql;
SELECT db, o, rc FROM #x ORDER BY db, o;
Now, don't be fooled into believing this isn't also using a cursor or loop - it is. But it is building the command in a loop as opposed to executing it in a loop.
In terms of you making a dynamic query, rather than use a using, you could do a fully qualified name for your table names, using your selected #db variable.
So it would be 'FROM ' + #db+'.sys.objects' etc.
You would have to check that your DB name is valid (for instance, if you had a name that needed brackets for some reason).

T-SQL: DROP Table cascade constraints equivalent?

In oracle, I can issue a DROP TABLE ... cascade constraints and it won't complain about FKs, etc.
Is there an equivalent in T-SQL?
For those who got here in the hope of a more generally applicable answer
This will find the constraint, drop it, and then the column
Thanks and a vote to Tim Lentine How to find the name of a default constraint for the start.
Declare #sql VarChar(255)
Declare #tableName Varchar(255)
Declare #columnName VarChar(255)
Select #tableName = 'MyTableName'
Select #columnName = 'MyColumnName'
select #sql = o.[name] from sysobjects o
inner join syscolumns c
on o.id = c.cdefault
inner join sysobjects t
on c.id = t.id
where o.xtype = 'd'
and t.name = #tableName
and c.name = #columnName
if #sql is not null
begin
select #sql = 'Alter Table ' + #tableName + ' Drop Constraint ' + #sql + ' Alter Table ' + #tablename + ' Drop Column ' + #columnName
exec(#sql)
end
NO, IN SSMS right click on the table, and select "script table as" then "drop to", then "new window", "file..." or "clipboard" and it will produce a script that will include all the necessary drops of FKs etc.