I have the following query
select count(*) as row_count
, UN_PART
, 'ABC_PARAM_DETA' as TABLE_NAME
from ABC_PARAM_DETA
group by UN_PART
what I would like to do is extend this to use it from a select list rather than a single table as I have multiple tables with UN_PART eg
select count(*) as row_count
, UN_PART
, '#var_table' as TABLE_NAME
from (Select TABLE_NAME from INFORMATION_SCHEMA.COLUMNS where COLUMN_NAME = 'UN_PART')
group by UN_PART
Anyone any ideas on how to achieve this
Try this
IF OBJECT_ID('tempdb..#Output') IS NOT NULL
DROP TABLE #Output
CREATE TABLE #Output
(ID INT IDENTITY(1, 1),
TableName NVARCHAR(MAX),
RowCnt INT)
INSERT INTO #Output (TableName)
Select C.TABLE_NAME from INFORMATION_SCHEMA.COLUMNS C
INNER JOIN INFORMATION_SCHEMA.TABLES T ON C.TABLE_NAME = T.TABLE_NAME
WHERE COLUMN_NAME = 'UN_PART' AND T.TABLE_TYPE = 'BASE TABLE'
DECLARE #Step AS INT = 1
WHILE #Step <= (SELECT COUNT(*) FROM #Output) BEGIN
DECLARE #TableName NVARCHAR(MAX)
DECLARE #SQL NVARCHAR(MAX)
SELECT #TableName = TableName FROM #Output WHERE ID = #Step
SET #SQL = 'UPDATE #Output SET RowCnt = (SELECT COUNT(*) FROM ' + #TableName + ') WHERE TableName = ''' + #TableName + ''''
EXEC sys.sp_executesql #SQL
SET #Step = #Step + 1
END
SELECT * FROM #Output
DROP TABLE #Output
Haven't tested but I think this should work
Exec sp_MSforeachtable
#command1 ='select ''?'', un_part, count(1) from ? group by un_part'
,#whereand = ' and object_id in (select o.object_id from sys.objects o
inner join sys.columns c
on o.object_id = c.object_id
and c.name = ''UN_PART'')'
Related
Let’s say I’m looking for a specific column in my database so I have something like this
SELECT COLUMN_NAME, TABLE_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE COLUMN_NAME like ‘%employeeid%’
But I also want to know how many rows each table has, I was told I can do this using Dynamic SQL so I have this now
DECLARE
#tableName NVARCHAR(MAX),
#sql NVARCHAR(MAX),
#colName NVARCHAR(MAX);
DECLARE CUR_TABLE CURSOR FOR
SELECT TABLE_NAME FROM INFORMATION_SCHEMA.COLUMNS
OPEN CUR_TABLE
FETCH NEXT FROM CUR_TABLE
INTO #tableName
WHILE ##FETCH_STATUS = 0
BEGIN
SET #colName = '%employeeid%'
SET #sql = 'SELECT COLUMN_NAME, TABLE_NAME, (SELECT COUNT(*) FROM ' + #tableName +') AS ROWS FROM INFORMATION_SCHEMA.COLUMNS where column_name like ' + ''' + #colName + ''';
FETCH NEXT FROM CUR_TABLE
INTO #tableName
END;
CLOSE CUR_TABLE
DEALLOCATE CUR_TABLE
EXEC sp_executesql #sql
But this doesn't work, What I'm trying to do is query a table with the column I am looking for, with the table name, and number of rows in the table.
How can I fix this?
You can make use of SQL Server's dynamic management views to quickly obtain the row counts*.
Find all tables with a column named 'MyColumn' and their current rows:
select Schema_Name(t.schema_id) schemaName, t.name TableName, s.row_count
from sys.columns c
join sys.tables t on t.object_id = c.object_id
join sys.dm_db_partition_stats s on s.object_id = c.object_id and s.index_id <= 1
where c.name='MyColumn';
* Accurate except for frequently updated tables where there could be some lag
The following uses INFORMATION_SCHEMA, dynamic SQL, and STRING_AGG() to build a query that will return a single result set.
DECLARE #ColumnName sysname = 'ProductID'
DECLARE #Newline VARCHAR(2) = CHAR(13) + CHAR(10)
DECLARE #SqlTemplate NVARCHAR(MAX) =
+ 'SELECT'
+ ' ColumnName = <ColumnNameString>,'
+ ' TableName = <TableSchemaAndNameString>,'
+ ' Rows = (SELECT COUNT(*) FROM <TableSchemaAndName>)'
+ #Newline
DECLARE #UnionSql NVARCHAR(100) = 'UNION ALL ' + #Newline
DECLARE #Sql NVARCHAR(MAX) = (
SELECT STRING_AGG(
REPLACE(REPLACE(REPLACE(
#SqlTemplate
, '<ColumnNameString>', QUOTENAME(C.COLUMN_NAME, ''''))
, '<TableSchemaAndNameString>', QUOTENAME(C.TABLE_SCHEMA + '.' + C.TABLE_NAME, ''''))
, '<TableSchemaAndName>', QUOTENAME(C.TABLE_SCHEMA) + '.' + QUOTENAME(C.TABLE_NAME))
, #UnionSql)
WITHIN GROUP(ORDER BY C.TABLE_SCHEMA, C.TABLE_NAME)
FROM INFORMATION_SCHEMA.TABLES T
JOIN INFORMATION_SCHEMA.COLUMNS C
ON C.TABLE_SCHEMA = T.TABLE_SCHEMA
AND C.TABLE_NAME = T.TABLE_NAME
WHERE T.TABLE_TYPE = 'BASE TABLE' -- Omit views
AND C.COLUMN_NAME = #ColumnName
)
SET #Sql = #Sql + 'ORDER BY Rows DESC, TableName' + #Newline
--PRINT #Sql
EXEC (#Sql)
I generalized it a bit by adding TABLE_SCHEMA so that it could be used with the AdventureWorks database. See this db<>fiddle for a working demo. Also included is equivalent logic that uses FOR XML instead of STRING_AGG for older SQL Server versions.
Assuming that you are using SQL Server, here is a shorthand way using sp_msforeachtable.
DECLARE #ColumnName NVARCHAR(200) = 'ContactID'
CREATE TABLE #T
(
ColumnName NVARCHAR(200),
TableName NVARCHAR(200),
RecordCount INT
)
INSERT INTO #T (ColumnName, TableName)
SELECT
ColumnName = C.COLUMN_NAME,
TableName = '['+C.TABLE_SCHEMA+'].['+C.TABLE_NAME+']'
FROM
INFORMATION_SCHEMA.COLUMNS C
WHERE
C.COLUMN_NAME LIKE '%' + #ColumnName + '%'
EXEC SP_MSFOREACHTABLE 'IF EXISTS(SELECT * FROM #T WHERE TableName = ''?'') UPDATE #T SET RecordCount = (SELECT COUNT(*) FROM ? ) WHERE TableName = ''?'''
SELECT
ColumnName,TableName,
TableType = CASE
WHEN RecordCount IS NULL
THEN 'View'
ELSE 'Table'
END,
RecordCount
FROM
#T
ORDER BY
CASE WHEN RecordCount IS NULL THEN 'View' ELSE 'Table' END
DROP TABLE #T
I have a SQL database with 60+ tables, almost all of which are populated with a CLIENTID field. I want to count the number of unique client IDs in each table.
The results I'm looking for are:
TABLE_NAME; CLIENTID_COUNT
dbo.HISTORY; 650
dbo.VISITS; 596
dbo.SALES; 1053
...; ...
This seems like it should be so simple but I've been playing around with cursors for hours and can't figure this one out. Please help!
IF OBJECT_ID('tempdb..#temp_RESULTS') IS NOT NULL DROP TABLE #temp_RESULTS
CREATE TABLE #TEMP_RESULTS
(
TABLENAME VARCHAR(MAX),
CLIENTCNT BIGINT
)
DECLARE #TABLENAME VARCHAR(MAX)
DECLARE #command VARCHAR(MAX)
IF OBJECT_ID('tempdb..#temp_PROCESS') IS NOT NULL DROP TABLE #temp_PROCESS
SELECT * INTO #TEMP_PROCESS FROM sys.tables
WHILE EXISTS(SELECT * FROM [#TEMP_PROCESS])
BEGIN
SET #TABLENAME = (SELECT TOP 1 [NAME] FROM [#TEMP_PROCESS])
SET #command = ('SELECT ''' + #TABLENAME + ''', COUNT(DISTINCT CLIENTID) AS CLIENTCNT FROM ' + #TABLENAME)
SELECT #command
INSERT INTO #TEMP_RESULTS
EXEC(#command)
DELETE FROM [#TEMP_PROCESS] WHERE [NAME] = #TABLENAME
END
SELECT * FROM [#TEMP_RESULTS]
Assuming the column is exactly ClientId in every table, you should be able to use this as is:
DROP TABLE IF EXISTS #clientId
CREATE TABLE #clientId
(
TableName nvarchar(1000),
ClientIdCount bigint
)
DECLARE #TableName nvarchar(1000);
DECLARE #CurrentQuery nvarchar(2000);
DECLARE result_cursor CURSOR local fast_forward FOR
SELECT DISTINCT
'['+TABLE_SCHEMA + '].[' + TABLE_NAME + ']'
FROM
INFORMATION_SCHEMA.COLUMNS
WHERE
COLUMN_NAME = 'ClientId'
OPEN result_cursor
FETCH NEXT FROM result_cursor into #TableName
WHILE ##FETCH_STATUS = 0
BEGIN
SET #CurrentQuery = 'SELECT ''' + #TableName + ''', COUNT(DISTINCT ClientId) FROM ' + #TableName
--print #CurrentQuery
INSERT INTO
#clientId
(
TableName,
ClientIdCount
)
EXEC(#CurrentQuery)
FETCH NEXT FROM result_cursor into #TableName
END
--end loop
--clean up
CLOSE result_cursor
DEALLOCATE result_cursor
GO
SELECT
*
FROM
#clientId
You could use dynamic sql.
This will read through your system tables, find those that have a ClientID column, and build the text of a query that's in the general shape of 'Select Count(DISTINCT ClientID)' from each table.
DECLARE #SQLQuery as nvarchar(max) = ''
------------------------------------
-- GET THE TABLES THAT HAVE A CLIENTID FROM SCHEMA
SELECT #SQLQuery = #SQLQuery + qryTxt FROM (
SELECT DISTINCT 'SELECT ''' + tables.name + ''', COUNT(DISTINCT CLIENTID) FROM ' + tables.name + ' UNION ' AS qryTxt
FROM sys.columns left join sys.tables on columns.object_id = tables.object_id where columns.name = CLIENTID AND isnull(tables.name, '') <> '') subquery
------------------------------------
-- REMOVE THE LAST 'UNION' KEYWORD FROM SQLQUERY
SET #SQLQuery = left(#sqlQuery, len(#sqlQuery) - 5)
------------------------------------
-- EXECUTE
execute sp_executesql #SQLQuery
I really dislike cursors and loops. Even though this is not going to be much difference for a performance perspective I like to share how you can leverage the system tables and dynamic sql to avoid using a cursor, while loop or temp tables for something like this.
This code is literally all you need to to do this.
declare #SQL nvarchar(max) = ''
select #SQL = #SQL + 'select TableName = ''' + t.name + ''', ClientID_Count = count(distinct clientID)
from ' + QUOTENAME(t.name) + ' UNION ALL '
from sys.tables t
join sys.columns c on c.object_id = t.object_id
where c.name = 'clientID'
select #SQL = left(#SQL, len(#SQL) - 10) --removes the last UNION ALL
select #SQL
--once your comfortable the dynamic sql is correct just uncomment the line below.
--exec sp_executesql #SQL
A similar pattern to other answers here, but this is how I would tackle it:
IF OBJECT_ID('#Tables', 'U') IS NOT NULL
DROP TABLE #Tables;
SELECT ID = IDENTITY(INT, 1, 1),
SchemaName = OBJECT_SCHEMA_NAME([object_id]),
TableName = OBJECT_NAME([object_id]),
ColumnName = name,
DistinctCount = 0
INTO #Tables
FROM sys.columns
WHERE name = 'CLIENTID';
DECLARE #ID INT = 1,
#MaxID INT = (SELECT MAX(ID) FROM #Tables);
WHILE #ID < #MaxID
BEGIN;
DECLARE #SQLCommand VARCHAR(MAX);
SELECT #SQLCommand = FORMATMESSAGE('
UPDATE #Tables SET DistinctCount = (
SELECT COUNT(DISTINCT %s) FROM %s.%s
)
WHERE ID = %i;',
QUOTENAME(ColumnName), QUOTENAME(SchemaName), QUOTENAME(TableName), ID)
FROM #Tables
WHERE ID = #ID;
EXEC (#SQLCommand);
SET #ID += 1;
END;
SELECT *
FROM #Tables;
I am trying to get the maximum value MAX(ID) for each table I have which contains ID on my DB "Table_Example" and one schema_name in specific.
A single example:
SELECT MAX(ID) FROM Schema_name.Table_name1
This retrieve the maximum ID value that is located on Table_name1, but I have 84 tables. I would like to know the max of each table only in one column.
This is the code where I am working on currently:
I am using information_schema.columns to get the names of the tables automatic and the schema each table belongs to in order to get the whole DB IDs max(id) in one column.
USE TABLE_EXAMPLE
GO
DECLARE #ID NVARCHAR(MAX) --int
SET #ID = (SELECT DISTINCT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = 'SCHEMA_NAME' AND COLUMN_NAME IN ('ID') AND DATA_TYPE = 'INT')
SELECT #ID FROM (SELECT ('SCHEMA_NAME'+'.'+TABLE_NAME) AS TABLE_NAME FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = 'SCHEMA_NAME' AND COLUMN_NAME = 'ID' AND DATA_TYPE='INT') AS W
This Script retrieve wrong data but I think I am a bit closed to get the values, but I am not sure what I am doing wrong.
Could someone give me any good approach? Or any better option to get it done?
If you are wanting the max value in your identity columns, regardless of the names of those columns, then this is a very simple way of doing it. This will give you the Table Name, the name of the Identity Column, and the max value of that column:
SELECT sys.tables.name AS [Table Name],
sys.identity_columns.name AS [Column Name],
last_value AS [Last Value]
FROM sys.identity_columns
INNER JOIN sys.tables
ON sys.identity_columns.object_id = sys.tables.object_id
ORDER BY last_value DESC
This enumerate all tables with column Id and MAX value of this ID:
DECLARE #query nvarchar(MAX);
SELECT #query = COALESCE(#query + char(10)+'UNION ALL '+char(10)+'SELECT '''+QUOTENAME(s.name)+'.'+QUOTENAME(T.name)+''' [Table], MAX(Id) [Max] FROM '+QUOTENAME(s.name)+'.'+QUOTENAME(T.name),
'SELECT '''+QUOTENAME(s.name)+'.'+QUOTENAME(T.name)+''' [Table], MAX(Id) [Max] FROM '+QUOTENAME(s.name)+'.'+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 C.name='Id';
EXEC(#query);
Try like this,
This would give you the script.
SELECT DISTINCT 'SELECT MAX(' + + COLUMN_NAME + ') as ' + table_name + 'MaxId FROM ' + table_name
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = 'dbo'
AND COLUMN_NAME IN ('ID')
CREATE TABLE #MaxValues (SchemaName SYSNAME , TableName SYSNAME , MaxID INT)
GO
Declare #SchemaName SYSNAME = 'dbo' --<-- Pass you schema name to this variable
,#ColumnName SYSNAME = 'ID' --<-- Column Name
,#DataType SYSNAME = 'INT' --<-- Data type
DECLARE #TableName SYSNAME , #SchmaName SYSNAME
, #Sql NVARCHAR(MAX) , #ColName SYSNAME;
Declare Cur CURSOR LOCAL FAST_FORWARD FOR
SELECT s.name , t.name , c.name
FROM sys.columns c
Inner join sys.tables t on c.object_id = t.object_id
Inner join sys.schemas s on s.schema_id = t.schema_id
Inner join sys.types tp on tp.user_type_id = c.user_type_id
WHERE s.name = #SchemaName
AND c.name = #ColumnName
AND tp.name = #DataType
OPEN Cur
FETCH NEXT FROM Cur INTO #SchmaName , #TableName , #ColName
WHILE (##FETCH_STATUS =0)
BEGIN
SET #Sql = N'INSERT INTO #MaxValues (SchemaName, TableName, MaxID )'
+ N' SELECT #SchmaName ,#TableName, MAX(' + QUOTENAME(#ColName) + N') '
+ N' FROM ' + QUOTENAME(#SchmaName) + '.' + QUOTENAME(#TableName)
Exec sp_executesql #Sql
,N'#SchmaName SYSNAME , #TableName SYSNAME'
,#SchmaName
,#TableName
FETCH NEXT FROM Cur INTO #SchmaName , #TableName , #ColName
END
CLOSE Cur
DEALLOCATE Cur
SELECT * FROM #MaxValues
Perhaps a little dynamic SQL
Edit This will return the Table Name(s) and Max ID in one dataset
Declare #SQL varchar(max) = '>>>'
Select #SQL = #SQL + SQL
From (
Select SQL='Union All Select TableName='''+concat('[',Table_Schema,'].[',Table_Name,']')+''',MaxID=max(ID) From '+concat('[',Table_Schema,'].[',Table_Name,'] ')
From INFORMATION_SCHEMA.COLUMNS
Where Column_Name = 'ID'
) A
Set #SQL=Replace(#SQL,'>>>Union All ','')
Exec(#SQL)
This script will list all the max ids. It is assuming your first column is the ID, regardless of its name.
DECLARE #Script AS VARCHAR(MAX) = ''
SELECT #Script = #Script + 'SELECT MAX(' + COLUMN_NAME + ') AS ID FROM ' + c.TABLE_NAME + ' UNION ALL ' + CHAR(13)+CHAR(10)
FROM INFORMATION_SCHEMA.COLUMNS c
INNER JOIN INFORMATION_SCHEMA.TABLES t ON c.TABLE_NAME = t.TABLE_NAME
WHERE c.ORDINAL_POSITION = 1 and t.TABLE_TYPE = 'BASE TABLE' and c.TABLE_SCHEMA = 'dbo' and c.DATA_TYPE = 'int'
SELECT #Script = LEFT(#Script, LEN(#Script) - 12)
EXEC (#Script)
I am trying to create a Function that I could call to check which is the next ID in key of each table in my database. I figure out how to do it, but I can not create the function because I get this error:
"Invalid use of a side-effecting operator 'INSERT EXEC' within a function."
It seems that I can not use Exec in Functions. Which alternatives could I have to get this information?
This is my code:
CREATE FUNCTION FCN_ProximoID()
RETURNS #TablaID Table (Tabla nvarchar(370), ID int)
AS
BEGIN
-- Fill the table variable with the rows for your result set
DECLARE #Row INT
DECLARE #Filas INT
DECLARE #MaxID INT
DECLARE #Query As varchar(max)
DECLARE #TableName nvarchar(256), #ColumnName nvarchar(128)
SET #Filas = (SELECT MAX(Fila)
FROM (
SELECT ROW_NUMBER() OVER(ORDER BY A.TABLE_NAME) AS 'Fila',
A.TABLE_NAME As Tabla, A.COLUMN_NAME As Columna,
A.ORDINAL_POSITION As Indice, B.DATA_TYPE As TipoDato
FROM
INFORMATION_SCHEMA.KEY_COLUMN_USAGE A
LEFT JOIN INFORMATION_SCHEMA.COLUMNS B
ON A.COLUMN_NAME = B.COLUMN_NAME AND A.TABLE_NAME=B.TABLE_NAME
WHERE LEFT(A.TABLE_NAME,3)='EXT' AND OBJECTPROPERTY(OBJECT_ID
(constraint_name), 'IsPrimaryKey')=1 AND B.DATA_TYPE='int'
) As Tablas)
SET #Row = 1
WHILE (#Row <= #Filas)
BEGIN
BEGIN
SET #TableName = (SELECT Tabla
FROM (
SELECT ROW_NUMBER() OVER(ORDER BY A.TABLE_NAME) AS 'Fila',
A.TABLE_NAME As Tabla, A.COLUMN_NAME As Columna,
A.ORDINAL_POSITION As Indice, B.DATA_TYPE As TipoDato
FROM
INFORMATION_SCHEMA.KEY_COLUMN_USAGE A
LEFT JOIN INFORMATION_SCHEMA.COLUMNS B
ON A.COLUMN_NAME = B.COLUMN_NAME AND
A.TABLE_NAME=B.TABLE_NAME
WHERE LEFT(A.TABLE_NAME,3)='EXT' AND OBJECTPROPERTY(OBJECT_ID
(constraint_name), 'IsPrimaryKey')=1 AND B.DATA_TYPE='int'
) As Tablas WHERE Fila=#Row)
SET #ColumnName = (SELECT Columna
FROM (
SELECT ROW_NUMBER() OVER(ORDER BY A.TABLE_NAME) AS 'Fila',
A.TABLE_NAME As Tabla, A.COLUMN_NAME As Columna,
A.ORDINAL_POSITION As Indice, B.DATA_TYPE As TipoDato
FROM
INFORMATION_SCHEMA.KEY_COLUMN_USAGE A
LEFT JOIN INFORMATION_SCHEMA.COLUMNS B
ON A.COLUMN_NAME = B.COLUMN_NAME AND
A.TABLE_NAME=B.TABLE_NAME
WHERE LEFT(A.TABLE_NAME,3)='EXT' AND
OBJECTPROPERTY(OBJECT_ID
(constraint_name), 'IsPrimaryKey')=1
AND B.DATA_TYPE='int'
) As Tablas WHERE Fila=#Row)
INSERT INTO #TablaID
EXEC('SELECT ''' + #TableName + ''', ISNULL(MAX(' + #ColumnName + '),0)+1 FROM ' + #TableName )
SET #Row = #Row + 1
END
END
RETURN
END
GO
Exec is not allowed in functions, but it is allowed in stored procedures, so you can just rewrite the function as a stored procedure which retuns a resultset.
is there a way to show all tables that have a specific number of records?
For near 100% accuracy:
select object_schema_name(object_id) as schema_name,
object_name(object_id) as object_name,
sum(p.rows)
from sys.partitions p
where index_id in (1,0)
group by object_id
having sum(p.rows) = #numberOfRows;
For true 100% accuracy, you must COUNT(*) from a cursor
Hmmm
Quick and dirty way:
sp_MSForeachtable 'select ''?'' tblName, count(*) from ? where count(*) = X'
This should only output those rows with X records
This post has a nice query to do just that (as well as number of columns and row sizes):
USE DatabaseName
GO
CREATE TABLE #temp (
table_name sysname ,
row_count INT,
reserved_size VARCHAR(50),
data_size VARCHAR(50),
index_size VARCHAR(50),
unused_size VARCHAR(50))
SET NOCOUNT ON
INSERT #temp
EXEC sp_msforeachtable 'sp_spaceused ''?'''
SELECT a.table_name,
a.row_count,
COUNT(*) AS col_count,
a.data_size
FROM #temp a
INNER JOIN information_schema.columns b
ON a.table_name collate database_default = b.table_name collate database_default
GROUP BY a.table_name, a.row_count, a.data_size
ORDER BY CAST(REPLACE(a.data_size, ' KB', '') AS integer) DESC
DROP TABLE #temp
COUNT from a cursor:
Declare #NumberOfRecords Integer
Set #NumberOfRecords = 100
Create Table #Tables(TableName SysName)
Declare #More Bit
Declare CTable Cursor Local Fast_Forward For Select Table_Name From Information_Schema.Tables Where Table_Type = 'Base Table'
Declare #CTableName SysName
Declare #SQL National Character Varying(4000)
Set #More = 1
Open CTable
While (#More = 1)
Begin
Fetch Next From CTable Into #CTableName
If (##Fetch_Status != 0)
Set #More = 0
Else
Begin
Set #SQL = N'If (Select Count(*) From [' + #CTableName + ']) = ' + Cast(#NumberOfRecords As National Character Varying) + N' ' +
N'Insert Into #Tables(TableName) Values(''' + #CTableName + N''')'
Execute (#SQL)
End
End
Close CTable
Deallocate CTable
Select * From #Tables
Drop Table #Tables