TSQL: Rowcounts of All Tables In A Server - tsql

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).

Related

How to create PK in thousands of datases where not exist

I have trouble working out how to create a pk on thousands of databases. I have tried using
sp_ineachdb by Aaron Bertrand, but it only works on the first database. I need that the script that finds the PK's to be created, runs against the current Db, which doesn't seem to be the case.
DECLARE #PKScript2 VARCHAR(max)='';
SELECT #PKScript2 += ' ALTER TABLE ' + QUOTENAME(SCHEMA_NAME(obj.SCHEMA_ID))+'.'+
QUOTENAME(obj.name) + ' ADD CONSTRAINT PK_'+ obj.name+
' PRIMARY KEY CLUSTERED (' + QUOTENAME(icol.name) + ')' + CHAR(13)
FROM sys.identity_columns icol INNER JOIN
sys.objects obj on icol.object_id= obj.object_id
WHERE NOT EXISTS (SELECT * FROM sys.key_constraints k
WHERE k.parent_object_id = obj.object_id AND k.type = 'PK')
AND obj.type = 'U'
Order by obj.name
PRINT (#PKScript2);
EXEC [master].[dbo].[sp_ineachdb] #command = #PKScript2, #database_list= '[vosk][vpb][vpbk][vsb][vsh][vst]'
For the sake of the example, I have only used 6 databases.

Rename multiple tables at once

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';';

SQL 2008R2: What's the fastest way to do a 'INSERT INTO target <all columns except two> SELECT <all columns> FROM source'

I'm doing a SQL-to-SQL conversion, and have 50+ tables to convert from old (source) to new (target) database. I think the answer is 'there's no really fast way to do this', but I'll ask the question anyways.
Each 'group' has..
Two Source tables: Anywhere from 10 to 700 columns.
These two tables have the same schema, although some columns have different collations.
Target tables: Number of columns = Columns in source tables + 2, as I added start_dt and end_dt.
I can't do a 'INSERT INTO Target SELECT * FROM Source' because of the two extra columns.
Question: What's the fastest way to do a 'INSERT INTO target SELECT FROM source'
Using a view in the designer I don't see a way to select all and have it show all columns, and then just remove the two I don't need. * displays as * instead of all column names.
I'll entertain third party apps on this one.
Thanks.
Jim
In SQL Server Management studio, expand your table. You will see a couple nodes appear below the table name. Columns, Keys, Constraints, etc... Drag the "Column" node in to a query window and all of the columns will be added to the query window. Tack on your 2 extra columns and execute it.
This is still somewhat manual, but it will save you a ton of typing.
Always try to stay away from SELECT *. In SSMS right click on the table/view -> script as -> select. (The wording may not be exact. I am working from memory.) Then you don't have to type out all the fields.
If you mean the speed of getting the data over.
If you can on the destination:
turn off all triggers
drop indexes
change the recovery of the DB to
bulk logged
Do the inserts in batches
What about using SSIS?
How many records are you talking about?
Question was answered here.
DECLARE #source_table sysname
DECLARE #target_table sysname
DECLARE #col_list varchar(max)
DECLARE #sql varchar(max)
--naturally replaced by cursor table loop in final code
SET #source_table = 'dbs'
SET #target_table = 'dbs'
SELECT #col_list = STUFF((
SELECT ', ' + src.name
FROM sys.columns src
INNER JOIN sys.columns trg ON
trg.name = src.name
WHERE
src.object_id = OBJECT_ID(#source_table) AND
trg.object_id = OBJECT_ID(#target_table)
ORDER BY src.column_id
FOR XML PATH('')
), 1, 1, '')
SET #sql = 'INSERT INTO ' + #target_table + ' ( ' + #col_list + ' ) SELECT ' + #col_list + ' FROM ' + #source_table + ' '
EXEC (#sql)
SET #sql = 'INSERT INTO ' + #target_table + ' ( ' + #col_list + ' ) SELECT ' + #col_list + ' FROM ' + #source_table + '_History '
EXEC (#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

Iterate through result-sets in T-SQL

I would like to write a stored procedure using a statement to iterate through a result-set of records provided by another statement, and union the end results into one single result-set. Can anyone advise on an approach for this?
For example, a generic set of records to iterate through:
SELECT sys.schemas.name + '.' + sys.objects.name as [schm_obj]
FROM sys.objects
INNER JOIN sys.schemas
ON sys.objects.schema_id = sys.schemas.schema_id
AND sys.schemas.name IN ('dbo')
Generic query to be executed on each record:
SELECT DISTINCT referenced_schema_name + '.' + referenced_entity_name
FROM sys.dm_sql_referenced_entities(#schm_obj,'OBJECT')
The parameter #schm_obj to be replaced by a single field value returned in each row of the first query; eventually, I would like to union all results. Any advice would be greatly appreciated.
You need to do dyanmic sql to do this. I am confused as you are stating a procedure then showing a table function. Table Functions unless something changed in 2012 cannot do dynamic sql. If you want to basically create a programmable object you can use to get data that YOU SUPPLY the schema or meta detail to get to it I would use a dynamic procedure that would label what you got and then give you the value of it. Then you could insert this into a table variable and iterate or insert into it till your heart's content.
create proc dbo.Dynaminizer
(
#ObjName nvarchar(64)
)
as
BEGIN
declare #SQL nvarchar(1024)
Select #SQL = 'Select ''' + #ObjName + ''' as Name, ' + #ObjName + ' from sys.tables'
EXECUTE sp_executesql #SQL
END
declare #temp table ( name varchar(32), value varchar(128))
insert into #Temp
exec Dynaminizer 'name'
insert into #Temp
exec Dynaminizer 'object_id'
select *
from #Temp
UPDATE LATER THAT DAY....
If you just want to take values from one table or set and then perform operations in a function that takes a schema and a name combo then there is another method to do that. I would use a cte to make a custom column and then do a cross apply to perform that dataset in a function for how many rows that are in the set. This will evaluate your function like it was executing it for each value and then posting the results. There is no need to union unless you are more specific for what you are wanting to do. You can take it even further and if you want the dependencies inline(as opposed to seperate rows) you can relate the dataset back to itself and then do an xml type to cast out the dependencies into a comma seperated list.
Example usage of both:
-- multi row example to show referencing relationships
with procs as
(
Select
schema_name(schema_id) + '.' + name as Name
from sys.procedures p
)
select
p.Name
, referenced_schema_name + '.' + referenced_entity_name as Ref
from procs p
cross apply sys.dm_sql_referenced_entities(p.Name,'OBJECT')
;
-- take it a step further and put relationships inside a single column
with procs as
(
Select
schema_name(schema_id) + '.' + name as Name
from sys.procedures p
)
, setup as
(
select
p.Name
, referenced_schema_name + '.' + referenced_entity_name as Ref
from procs p
cross apply sys.dm_sql_referenced_entities(p.Name,'OBJECT')
)
Select distinct
Name
, stuff(
(
select
', ' + Ref
from setup x
where x.Name = m.Name
for xml path('')
)
, 1, 2, '') as Dependencies
from setup m