How to combine multiple results from a loop into one temp table - sql

I have this statement:
Declare #sql varchar(max) = ''
declare #tablename as varchar(255) = 'test'
select #sql = #sql + 'select [' + c.name + '],count(*) as ''' + c.name
+ ''' from [' + t.name + ']'
from sys.columns c
inner join sys.tables t on c.object_id = t.object_id
where t.name = #tablename
EXEC (#sql)
But it gives the output comes out in different results windows and when I try to combine it with a union all the text doesn't fit it. I want to try and get the results into a temp table for SQL server is there anyway i can do this?
I'm trying to get:
Column Name Count Distinct Count
a 100 1
b 100 5
c 100 73
d 100 9
The statement above isn't for distinct count but i'm hoping I could replicate the same logic.

I suspect the query you want to construct really looks like:
select 'a' as column_name, count(column_name), count(distinct column_name)
from t
union all
select 'a' as column_name, count(column_name), count(distinct column_name)
from t
union all
. . .
To construct this in SQL Server, you can use logic like this:
declare #q nvarchar(max);
set #q = '
select ''[column_name]'' as column_name, count([column_name]) as cnt, count(distinct [column_name]) as distinct_count
from [table_name]
';
declare #sql nvarchar(max);
select #sql = string_agg(replace(replace(#q,
'[column_name]',
quotename(column_name)
),
'[table_name]',
quotename(table_name)
), ' union all '
)
from information_schema.columns c
where table_name = #name; -- should probably check the schema too!
exec sp_executesql #sql;

Related

SQL count distinct or not null for each column for many columns

I need to analyze a large table with hundreds of columns. A lot of columns are unused.
To investigate I could do something like
SELECT DISTINCT Column1
FROM myTable
or
WITH C AS
(
SELECT DISTINCT Column1
FROM MyTable
)
SELECT COUNT(*)
FROM C
Then I do the same for column2 and so on. However these queries only work for one column which is time consuming and does not give overview in one glance.
Any idea how to build such investigation query for all columns in one?
You need only 1 query where you have to list all the columns of the table:
SELECT COUNT(DISTINCT Column1) column1_count,
COUNT(DISTINCT Column2) column2_count,
COUNT(DISTINCT Column3) column3_count
.....................................
FROM MyTable;
For local purposes only, you can make it dynamic like this:
Get the columns of the table
the query is created as the colleagues did and then it is executed with the EXEC()
DECLARE #columns as Table(RowId INT IDENTITY(1,1), ColumnName nVarchar(50))
DECLARE #ii int = 0
DECLARE #max int = 0
DECLARE #sqlQuery nVarchar(MAX)
INSERT INTO #columns
SELECT COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = N'Customer'
SET #sqlQuery = 'SELECT '
SELECT #max = COUNT(*) FROM #columns
WHILE #ii <= #max
BEGIN
SELECT #sqlQuery = CONCAT(#sqlQuery,'COUNT(DISTINCT ',ColumnName,') ',LOWER(ColumnName),'_count, ')
FROM #columns
WHERE RowId = #ii
SET #ii = #ii + 1
END
SELECT #sqlQuery = CONCAT(#sqlQuery,'FROM Customer')
SELECT #sqlQuery = REPLACE(#sqlQuery,', FROM',' FROM')
select #sqlQuery
EXEC (#sqlQuery)
You should flesh out your requirement a bit more. If all you want to know is if a column contains only NULLs, you'll want to check for max(ColumnName) is null
declare #sql table (id int identity(1,1), QueryString nvarchar(max))
create table ##emptyColumns (emptyColumn nvarchar(128))
declare #i int = 0
declare #iMax int
declare #runthis nvarchar(max)
insert #sql
select 'select ''' + QUOTENAME(s.name) + '.' + QUOTENAME(o.name) + quotename(c.name) + ''' as ''column''
from ' + QUOTENAME(s.name) + '.' + QUOTENAME(o.name) + '
having max(' + c.name + ') is null'
from sys.sysobjects o
inner join sys.syscolumns c on c.id = o.id
inner join sys.schemas s on s.schema_id = o.uid
where o.type = 'U'
order by s.name
, o.name
, c.colorder
select #iMax = count(*)
from #sql
print #iMax
while #i < #iMax
begin
set #i = #i + 1
select #runthis = 'insert into ##emptyColumns
' + QueryString
from #sql
where id = #i
execute sp_executesql #runthis
end
select *
from ##emptyColumns
drop table ##emptyColumns
One further option you might consider:
declare #sql nvarchar(max)
select #sql = isnull(#sql + ' union all ', '') + 'select ''' + COLUMN_NAME + ''',
sum(case when ' + COLUMN_NAME + ' is null then 1 else 0 end) as null_values,
count(distinct ' + COLUMN_NAME + ') as count_distinct
from ' + TABLE_SCHEMA + '.' + TABLE_NAME + '
'
from information_schema.columns
where TABLE_SCHEMA = 'MySchema' and TABLE_NAME = 'MyTable'
exec (#sql)
If you had very big tables with large numbers of columns and were only interested in empty columns you could look into something like checksum_agg(checksum(column_name)). It may help improve performance.
You'd need to be wary of column data types, as they are not all compatible with distinct.

Dynamic SQL to get rows from information_schema

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

How to get the length one column in all tables in one database?

I am having trouble to get the max length of records in one column in all tables.
I would only like to display the max length for each table for the specific column.
Below is what I have tried, I already found the way to return the column I need, but now, i need to get the max len. I know this is not the right way.
select max(len(site)) as site from
(
SELECT t.name AS TableName
FROM sys.columns c
JOIN sys.tables t ON c.object_id = t.object_id
WHERE c.name LIKE 'site%')A
The expected result will display the column name, the table name and also the max length of the records for that column.
Thanks in advance
I don't understand what are you really trying to do, but I think you want something like
CREATE TABLE T1(
Site1 VARCHAR(45)
);
CREATE TABLE T2(
Site2 VARCHAR(45)
);
INSERT INTO T1 VALUES ('A'), ('AA');
INSERT INTO T2 VALUES ('BBB'), ('BBBBB');
DECLARE #SQL NVARCHAR(MAX) = 'SELECT ';
SELECT #SQL = #SQL +
N'(SELECT MAX(LEN(' + --You can also add ISNULL([Col],0) to get 0
QUOTENAME(c.name) + ')) FROM '+
QUOTENAME(t.name) + ') AS ' +
QUOTENAME(t.name + '.'+c.name) + ', '
FROM sys.columns c
JOIN sys.tables t ON c.object_id = t.object_id
WHERE c.name LIKE 'site%';
SET #SQL = LEFT(#SQL, LEN(#SQL)-1);
EXEC sp_executesql #SQL;
Which will returns:
+----------+----------+
| T1.Site1 | T2.Site2 |
+----------+----------+
| 2 | 5 |
+----------+----------+
Live Demo
Try this:You will get individual Scripts to execute.
select 'select Max(len('+COLUMN_NAME+')),'''+COLUMN_NAME+ ''' as ColumnName ,'''+TABLE_NAME+''' as TableName from ' +table_name
from INFORMATION_SCHEMA.COLUMNS where COLUMN_NAME = 'YourColumnName'
If I understand your question, you may try to generate a dynamic SQL statement and execute this statement:
-- Declarations
DECLARE #stm nvarchar(max)
SET #stm = N''
-- Dynamic SQL
SELECT #stm = (
SELECT CONCAT(
N'UNION ALL ',
N'SELECT ''',
t.name,
N''' AS TableName, ''',
c.name,
N''' AS ColumnName, ',
N'ValueLength = (SELECT MAX(LEN(',
QUOTENAME(c.name),
')) FROM ',
QUOTENAME(t.name),
N')'
)
FROM sys.columns c
JOIN sys.tables t ON c.object_id = t.object_id
WHERE c.name LIKE 'site%'
ORDER BY t.name, c.name
FOR XML PATH('')
)
SET #stm = STUFF(#stm, 1, 10, N'')
-- Execution
PRINT #stm
EXEC sp_executesql #stm

How to get count across multiple tables

I am using this code to get ABC count from all tables having 72 table
if I use
declare #SQL nvarchar(max)
declare #Countt bigint
SELECT #SQL = STUFF(( SELECT ' ; SELECT COUNT(ABC) FROM ' + INFORMATION_SCHEMA.TABLES.TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES LEFT OUTER JOIN INFORMATION_SCHEMA.COLUMNS ON INFORMATION_SCHEMA.TABLES.TABLE_NAME = INFORMATION_SCHEMA.COLUMNS.TABLE_NAME where INFORMATION_SCHEMA.TABLES.TABLE_TYPE =N'BASE TABLE' AND INFORMATION_SCHEMA.COLUMNS.COLUMN_NAME =N'ABC'
FOR XML PATH('')),1,2,'')
SET #SQL = #SQL
PRINT #SQL
EXECUTE (#SQL)
but I am getting 72 results one by one but I just want to get sum of all 72 results,for example if ABC have 10 rows in 4 Tables so it should be return 40 please suggest where I am wrong or any other better way
Everyone is right just need to add schema if there is different ones:
declare #SQL nvarchar(max)
declare #Countt bigint
SELECT #SQL = STUFF((
SELECT DISTINCT ' UNION ALL SELECT COUNT(ABC) AS CountAmount FROM ' + INFORMATION_SCHEMA.TABLES.TABLE_SCHEMA + '.' + INFORMATION_SCHEMA.TABLES.TABLE_NAME AS [text()]
FROM INFORMATION_SCHEMA.TABLES
LEFT OUTER JOIN INFORMATION_SCHEMA.COLUMNS
ON INFORMATION_SCHEMA.TABLES.TABLE_NAME = INFORMATION_SCHEMA.COLUMNS.TABLE_NAME
WHERE INFORMATION_SCHEMA.TABLES.TABLE_TYPE =N'BASE TABLE'
AND INFORMATION_SCHEMA.COLUMNS.COLUMN_NAME =N'ABC'
FOR XML PATH('')),1,11,'')
SET #SQL = 'SELECT SUM( CountAmount ) AS TotalSum FROM (' + #SQL + ' ) AS T '
PRINT #SQL
EXECUTE (#SQL)
declare #SQL nvarchar(max)
declare #Countt bigint
SELECT #SQL = STUFF(( SELECT ' UNION ALL SELECT COUNT(ABC) AS noCount FROM ' + INFORMATION_SCHEMA.TABLES.TABLE_NAME FROM INFORMATION_SCHEMA.TABLES LEFT OUTER JOIN INFORMATION_SCHEMA.COLUMNS ON INFORMATION_SCHEMA.TABLES.TABLE_NAME = INFORMATION_SCHEMA.COLUMNS.TABLE_NAME where INFORMATION_SCHEMA.TABLES.TABLE_TYPE =N'BASE TABLE' AND INFORMATION_SCHEMA.COLUMNS.COLUMN_NAME =N'ABC' FOR XML PATH('')),1,10,'')
SET #SQL = 'SELECT COUNT(*) FROM (' + #SQL + ')A'
PRINT #SQL
EXECUTE (#SQL)
You need to use an aggregate function and group your results. So if Ive read your sql correctly, group by INFORMATION_SCHEMA.TABLES.TABLE_NAME
Use a cursor and iterate over each table one by one. When you generate your dynamic sql string, select the results into a variable like so:
select #TableCount = Count(ABC) From SomeTable
set #TotalCount = #Totalcount + #TableCount

SQL distinct and count for a column in multiple tables

So what I want is to have a table of distinct values and the count for those values. basically I want it to look like this:
DistinctValue | Count
Bob | 4
Fred | 5
George | 2
Joeseph | 1
for a single table I use :
SELECT ColumnName, COUNT(*) from TableName group by Column
How would I do this so that it would span across multiple tables. I have about say 30, possibly more, tables I need to do this for.
Any help would be greatly appreciated. Let me know if there's more information you need. Oh, and there's no worry about the column name because all the tables have the same column name.
WITH mytbl AS (
SELECT ColumnName, COUNT(*) AS myCount from TableName group by Column
UNION ALL
SELECT ColumnName, COUNT(*) from TableName2 group by Column
... a union all for every table
)
SELECT ColumnName, SUM(myCount)
FROM mytbl
GROUP BY ColumnName
-- If you are using an earlier version of MS SQL, the UNION statements can be put in a big sub select or a table variable.
-- IE, they'd take the place of mytbl in the last query replace mytbl in the bottom query with the UNIONS from the CTE
SELECT
t.name,
count(c.name) as columnsname
FROM
sys.tables t
inner join sys.columns c
ON t.object_id = c.object_id
group by t.name
You'll need to create and execute dynamic tsql to get your results:
DECLARE #Tsql NVARCHAR(MAX) = ''
DECLARE #ColumnName SYSNAME = 'YourColumnName'
SELECT #Tsql = #Tsql + 'SELECT ''[' + c.TABLE_SCHEMA + '].[' + c.TABLE_NAME + ']'' AS TableName, ' +
'[' + #ColumnName + '], COUNT(*) AS RecordCount FROM [' + c.TABLE_SCHEMA + '].[' + c.TABLE_NAME + '] GROUP BY [' + #ColumnName + '] UNION ' + CHAR(13) + CHAR(10)
FROM INFORMATION_SCHEMA.COLUMNS c
JOIN INFORMATION_SCHEMA.TABLES t
ON t.TABLE_SCHEMA = c.TABLE_SCHEMA
AND t.TABLE_NAME = c.TABLE_NAME
--Comment out the next line if you want data/counts for views too.
AND t.TABLE_TYPE = 'BASE TABLE'
WHERE c.COLUMN_NAME = #ColumnName
--Remove the last UNION (and carriage-return, line-feed)
SELECT #Tsql = LEFT(#Tsql, LEN(#Tsql) - 8)
--Verify query.
PRINT #Tsql
--Uncomment when ready to proceed.
--EXEC (#Tsql)