Loop through different tables for count - sql

I have a table that has thousands of records. Usually 100k-200k records. There is a field in this table (MATCH_ID). I have to get count of this ID from 100 different tables whose field(TABLE_ID) value matches with MATCH_ID.
I think implementing this using cursors would be a bad idea here. How can I achieve this with better performance.

This makes a couple of a couple of assumptions, but you could create a dynamic UNION ALL query; which'll be far faster than a CURSOR:
DECLARE #MatchID int; --Guessed data type; will need a value
DECLARE #SQL nvarchar(MAX),
#CRLF nchar(2) = NCHAR(13) + NCHAR(10);
SET #SQL = STUFF((SELECT #CRLF +
N'UNION ALL' + #CRLF +
N'SELECT N' + QUOTENAME(s.[name],'''') + N' AS SchemaName,' + #CRLF +
N' N' + QUOTENAME(t.[name],'''') + N' AS TableName,' + #CRLF +
N' COUNT(*) AS RowCount' + #CRLF +
N'FROM ' + QUOTENAME(s.[name]) + N'.' + QUOTENAME(t.[name]) + #CRLF +
N'WHERE MATCH_ID = #MatchID'
FROM sys.schemas s
JOIN sys.tables t ON s.schema_id = t.schema_id
JOIN sys.columns c ON t.object_id = c.object_id
WHERE c.[name] = N'MATCH_ID'
ORDER BY t.[name]
FOR XML PATH(N''),TYPE).value('.','nvarchar(MAX)'),1,13,N'') + N';';
--SELECT #SQL; --Your best friend
EXEC sp_executesql #SQL, N'#MatchID int', #MatchID;
As I can't test this, use your "best friend" if you get any syntax errors, or you don't quite get the results you expect.

Related

How to create a count in this T-SQL statement in order to get the result in INT value

Good morning,
I have this query in PL/SQL, but when I declare the count I get an error, I am declaring it in the query that I make after declaring the variables, I don't know if I have to create a new one...
DECLARE #DB_Name varchar(100)
DECLARE #Command nvarchar(1000)
DECLARE database_cursor CURSOR FOR
SELECT COUNT(name)
FROM MASTER.sys.sysdatabases
--WHERE name --NOT IN ('master','tempdb', 'msdb' )
OPEN database_cursor
FETCH NEXT FROM database_cursor INTO #DB_Name
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT #Command = '
SELECT ''' + #DB_Name + ''' AS DatabaseName,
''guest'' AS Database_User,
[permission_name],
[state_desc]
FROM ' +#DB_Name+ '.sys.database_permissions
WHERE [grantee_principal_id] = DATABASE_PRINCIPAL_ID(''guest'')
AND [state_desc] LIKE ''GRANT%''
AND [permission_name] = ''CONNECT'''
EXEC sp_executesql #Command
FETCH NEXT FROM database_cursor INTO #DB_Name
END
CLOSE database_cursor
DEALLOCATE database_cursor
Error code:
Msg 102, Level 15, Status 1, Line 7
Incorrect syntax near '4.
End time: 2021-06-21T11:45:39.7206852+02:00
I want to be able to count these values with the above query.
Your error message comes from the fact that your cursor doesn't have a name in each row, it has a count of databases.
This means that when you create your #command you have this...
FROM ' +#DB_Name+ '.sys.database_permissions
=>
FROM 4.sys.database_permissions
And clearly having a 4 there makes no sense.
Your cursor should be...
DECLARE database_cursor CURSOR FOR
SELECT QUOTENAME(name)
FROM MASTER.sys.sysdatabases
--WHERE name --NOT IN ('master','tempdb', 'msdb' )
If you want to count the number of permissions, and total them up, I'd avoid a cursor and use UNION ALL to combine the multiple source tablea into something like something like this...
DECLARE #permissions nvarchar(MAX)
DECLARE #Command nvarchar(MAX)
SELECT
#permissions = STRING_AGG(N'SELECT * FROM ' + QUOTENAME(name) + '.sys.database_permissions', N' UNION ALL ')
FROM
MASTER.sys.sysdatabases
--WHERE name --NOT IN ('master','tempdb', 'msdb' )
SELECT
#Command = '
SELECT COUNT(*)
FROM (' + #permissions + ') AS permissions
WHERE [grantee_principal_id] = DATABASE_PRINCIPAL_ID(''guest'')
AND [state_desc] LIKE ''GRANT%''
AND [permission_name] = ''CONNECT''
'
EXEC sp_executesql #Command
(Please forgive typos, I'm on my phone.)
An alternative method, which takes your query literally, would be to use a UNION ALL and the COUNT the rows returned dataset. I use the "old" FOR XML PATH method here, due to not knowing if you are on SQL Server 2017+ or not:
DECLARE #SQL nvarchar(MAX),
#CRLF NCHAR(2) = NCHAR(13) + NCHAR(10);
SET #SQL = N'SELECT COUNT(*) AS Users' + #CRLF +
N'FROM (' + #CRLF +
STUFF((SELECT N'UNION ALL' + #CRLF +
N'SELECT N' + QUOTENAME(d.[name],'''') + N' AS DatabaseName,' + #CRLF +
N' ''guest'' AS Database_User,' + #CRLF +
N' [permission_name],' + #CRLF +
N' [state_desc]' + #CRLF +
N'FROM ' + QUOTENAME(d.[name]) + '.sys.database_permissions' + #CRLF +
N'WHERE [grantee_principal_id] = DATABASE_PRINCIPAL_ID(''guest'')' + #CRLF +
N' AND [state_desc] LIKE ''GRANT%''' + #CRLF +
N' AND [permission_name] = ''CONNECT'''
FROM sys.databases d
FOR XML PATH(''),TYPE).value('(./text())[1]','nvarchar(MAX)'),1,11,N'') + N') AS C;';
--PRINT #SQL; --Your best friend.
EXEC sys.sp_executesql #SQL;

Search for a value in all column and all tables of a database

I want to find the column name that contain the value "Commerciale", but i do not know the column name or the table so I need to search in the whole database. How can i do that with a query?
I'm using SQL SERVER
If you are looking for columns where the name is Commerciale then you can simply use the sys objects:
SELECT s.[name] AS SchemaName,
t.[name] AS TableName,
c.[name] AS ColumnName
FROM sys.schemas s
JOIN sys.tables t ON s.schema_id = t.schema_id
JOIN sys.columns c ON t.object_id = c.object_id
WHERE c.[name] = N'Commerciale';
If, however, you need to search the contents of the values in the rows, you'll need to use dynamic SQL. This will return a dataset for every table in your database which has at least 1 string type column, and will return any rows where the value of one of those columns has the value 'Commerciale'. If it needs to contain the value, change the WHERE to use a LIKE in it's clauses instead (note the query will be horrifically slow with that):
DECLARE #SQL nvarchar(MAX),
#CRLF nchar(2) = NCHAR(13) + NCHAR(10);
SET #SQL = STUFF((SELECT #CRLF +
N'SELECT N' + QUOTENAME(s.[name],'''') + N' AS SchemaName,' + #CRLF +
N' N' + QUOTENAME(t.[name],'''') + N' AS TableName,' + #CRLF +
N' *' + #CRLF +
N'FROM ' + QUOTENAME(s.[name]) + N'.' + QUOTENAME(t.[name]) + #CRLF +
N'WHERE ' +
STUFF((SELECT #CRLF +
N' AND ' + QUOTENAME(c.[name]) + N' = ''Commerciale'''
FROM sys.columns c
JOIN sys.types ct ON c.system_type_id = ct.system_type_id
WHERE c.object_id = t.object_id
AND ct.[name] IN (N'char',N'varchar',N'nchar',N'nvarchar')
FOR XML PATH(''),TYPE).value('(./text())[1]','nvarchar(MAX)'),1,8,N'') + N';'
FROM sys.schemas s
JOIN sys.tables t ON s.schema_id = t.schema_id
FOR XML PATH(''),TYPE).value('(./text())[1]','nvarchar(MAX)'),1,2,N'');
--PRINT #SQL; --YOu best friend
EXEC sp_executesql #SQL;
This won't tell you what column has the value, you'll need to use your own eyes to do that, but I wasn't entertaining writing a dynamic table dynamic pivot.
you can use system tables :
SELECT
c.name ColumnName
, t.name TableName
FROM sys.columns AS c
JOIN sys.tables AS t
ON c.object_id = t.object_id
WHERE c.name like '%Commerciale%'

Get data from all tables ending with a specific string

I'm looking for a query that gets the nextval column from a lot of different tables ending with "_seq".
I know how to get the names of the tables...
SELECT TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_NAME LIKE '%seq'
But i couldn't find how to get the values from all those tables at once...
All those tables have the same column.
You can only achieve this with dynamic SQL. As you're using SQL Sevrer 2012, you'll need to use the "old" FOR XML PATH method to create the "delimited" UNION ALL query. I've also added the name of the schema and table into the query's dataset, as I assume this would be valuable information:
DECLARE #SQL nvarchar(MAX),
#CRLF nchar(2) = NCHAR(13) + NCHAR(10);
SET #SQL = STUFF((SELECT N'UNION ALL' + #CRLF +
N'SELECT N' + QUOTENAME(s.[name],'''') + N' AS SchemaName,' + #CRLF +
N' N' + QUOTENAME(t.[name],'''') + N' AS TableName,' + #CRLF +
N' NextVal' + #CRLF +
N'FROM ' + QUOTENAME(s.[name]) + N'.' + QUOTENAME(t.[name])
FROM sys.schemas s
JOIN sys.tables t ON s.schema_id = t.schema_id
JOIN sys.columns c ON t.object_id = c.object_id
WHERE t.[name] LIKE '%[_]seq'
AND c.[name] = N'NextVal'
FOR XML PATH(''),TYPE).value('.','nvarchar(MAX)'),1,11,N'') + N';'
--PRINT #SQL; --Your best friend.
EXEC sys.sp_executesql #SQL;
As there's no data to test with, you'll need to use your "best friend" to debug if it doesn't work.

How to use sp_MSforeachdb

I have a code that returns all indexes with a fragmentation % greater than 30
Iwant this code to run through all my databases and add the resultset to a table I have called IndexesToRebuild
is there a way I can use the sp_MSforeachdb to run this query thoughout all databases and insert the resultset to the IndexesToRebuild table
here is the code I have so far
if(not exists(select 1 from Utility..dtlIndexesToRebuild))
begin
insert into utility..dtlIndexesToRebuild
select
DB_NAME(),
dbschemas.[name],
dbtables.[name],
dbindexes.[name],
indexstats.avg_fragmentation_in_percent
from
sys.dm_db_index_physical_stats (DB_ID(), null, null, null, null) as indexstats
inner join sys.tables dbtables on dbtables.[object_id] = indexstats.[object_id]
inner join sys.schemas dbschemas on dbtables.[schema_id] = dbschemas.[schema_id]
inner join sys.indexes as dbindexes on dbindexes.[object_id] = indexstats.[object_id]
and indexstats.index_id = dbindexes.index_id
where
indexstats.database_id = DB_ID()
and avg_fragmentation_in_percent > 30
end
sp_msforeachdb has some "features". For something as simple as this, it'll likely be easier to simply leverage some dynamic SQL:
USE master;
GO
DECLARE #SQL nvarchar(MAX),
#CRLF nchar(2) = NCHAR(13) + NCHAR(10);
SET #SQL = STUFF((SELECT #CRLF + #CRLF +
N'USE ' + QUOTENAME([name]) + N';' + #CRLF +
N'INSERT INTO utility.dbo.dtlIndexesToRebuild (DatabaseName, SchemaName, TableName, IndexName, Fragmentation)' + #CRLF + --Guessed names of your columns
N'SELECT DB_NAME(),' + #CRLF +
N' dbschemas.[name],' + #CRLF +
N' dbtables.[name],' + #CRLF +
N' dbindexes.[name],' + #CRLF +
N' indexstats.avg_fragmentation_in_percent' + #CRLF +
N'FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, NULL) indexstats' + #CRLF +
N' INNER JOIN sys.tables dbtables ON dbtables.[object_id] = indexstats.[object_id]' + #CRLF +
N' INNER JOIN sys.schemas dbschemas ON dbtables.[schema_id] = dbschemas.[schema_id]' + #CRLF +
N' INNER JOIN sys.indexes dbindexes ON dbindexes.[object_id] = indexstats.[object_id]' + #CRLF +
N' AND indexstats.index_id = dbindexes.index_id' + #CRLF +
N'WHERE indexstats.database_id = DB_ID()' + #CRLF +
N' AND avg_fragmentation_in_percent > 30;'
FROM sys.databases d
WHERE d.database_id > 4
--AND d.[name] != N'utility' --Don't know if you want to skip this
FOR XML PATH(N''),TYPE).value('.','nvarchar(MAX)'),1,4,N'');
--PRINT #SQL; --Your best friend. Use SELECT for over 4,000 characters.
EXEC sys.sp_executesql #SQL;
Your best friend will help you debug any errors, but I've assumed the statement you supplied is valid.

Drop Several Tables at Once

I have some tables that I need to drop on a regular basis. The names of the tables sometimes change but the table names always begin with 'db_comp_temp'. Is it possible to write some SQL that will in effect do something like:
DROP TABLE db_comp_temp*
Thanks in advance,
No, there is no wildcard support in DDL.
DECLARE #sql nvarchar(max) = N'';
SELECT #sql += N'DROP TABLE ' + QUOTENAME(s.name)
+ '.' + QUOTENAME(t.name) + ';
' FROM sys.tables AS t
INNER JOIN sys.schemas AS s
ON t.[schema_id] = s.[schema_id]
WHERE t.name LIKE N'db[_]comp[_]temp%';
PRINT #sql;
-- EXEC sys.sp_executesql #sql;
Or:
DECLARE #sql nvarchar(max) = N'';
SELECT #sql += N'
,' + QUOTENAME(s.name) + '.' + QUOTENAME(t.name)
FROM sys.tables AS t
INNER JOIN sys.schemas AS s
ON t.[schema_id] = s.[schema_id]
WHERE t.name LIKE N'db[_]comp[_]temp%';
SET #sql = N'DROP TABLE ' + STUFF(#sql, 1, 1, '');
PRINT #sql;
-- EXEC sys.sp_executesql #sql;
You could also do it with FOR XML PATH but I don't think it's necessary when you're not grouping the result into another outer query.