Как получить различное количество значений всех столбцов таблицы на основе условия where в sql server?

#sql #sql-server

#sql-сервер #tsql

Вопрос:

У меня есть таблица с записями, в которой 100 столбцов, мне нужно получить количество различных значений всех столбцов из этой таблицы на основе некоторого условия (предложение where).

Приведенный ниже запрос работает нормально, но я не могу использовать предложение where. Таким образом, он выдает результат для всех записей таблицы. Но я хочу, чтобы это было основано на некотором условии, скажем, столбец file_id = 1; . Мой вопрос заключается в том, как использовать предложение where с приведенным ниже запросом. Или, если есть какой-либо другой альтернативный способ решения этой проблемы.

 declare @SQL nvarchar(max)
set @SQL = ''
;with cols as (
select Table_Schema, Table_Name, Column_Name, Row_Number() over(partition by Table_Schema, Table_Name
order by ORDINAL_POSITION) as RowNum
from INFORMATION_SCHEMA.COLUMNS
)

select @SQL = @SQL   case when RowNum = 1 then '' else ' union all ' end
  ' select '''   Column_Name   ''' as Column_Name, count(distinct '   quotename (Column_Name)   ' ) As DistinctCountValue, 
count( '  quotename (Column_Name)   ') as CountValue FROM '   quotename (Table_Schema)   '.'   quotename (Table_Name)
from cols
where Table_Name = 'table_name' --print @SQL

execute (@SQL)
  

Я использую динамический запрос, потому что мне нужно повторно использовать этот запрос и для других таблиц.

Комментарии:

1. 1) Что вы подразумеваете под получением отличного количества всех столбцов таблицы, есть ли в вашей таблице повторяющиеся столбцы??? Ни в коем случае. 2) Зачем вообще для этого нужен динамический SQL?

2. Извините, это отличное количество значений всех столбцов

3. @Sami Я использую динамический запрос, потому что мне нужно повторно использовать этот запрос и для других таблиц.

4. @viveknuna можете ли вы показать пример ожидаемого результата?

Ответ №1:

Сначала получите столбцы и используйте stuff для генерации select таким образом:

 SELECT COUNT(ColumnA) AS ColumnA, COUNT(ColumnB AS ColumnB), COUNT(ColumnC) AS ColumnC....
  

Таким образом, вы выбираете в своей таблице только один раз, чтобы получить все подсчеты, после этого используйте CROSS APPLY для «открепления» этих столбцов и возврата выходных данных по одной строке на столбец

 CROSS APPLY(
    VALUES(1, 'ColumnA', ColumnA), (2, 'ColumnB', ColumnB), (3, 'ColumnC', ColumnC)
)(ID, ColumnName, DistinctCountValue)
  

Для фильтра используйте sp_executesql и отправьте file_id в качестве параметра

 exec SP_executesql @SQL, N'@FID INT', @FID = @FileID
  

Поскольку вы используете все столбцы таблицы, Row_Number() over(partition by Table_Schema, Table_Name order by ORDINAL_POSITION) as RowNum становится избыточным, ORDINAL_POSITION уже имеет значение, которое вы ищете

 declare @tablename nvarchar(50) = 'MyTestTable'
declare @fileID int = 1
declare @SQL nvarchar(max)
set @SQL = ''
;with cols as (
select TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, ORDINAL_POSITION
from INFORMATION_SCHEMA.COLUMNS
where TABLE_NAME = @TableName
)

select @SQL = ';WITH  CTE AS (SELECT 
'   
    STUFF((
    SELECT ', COUNT(DISTINCT '   QUOTENAME(COLUMN_NAME)   ') AS '   QUOTENAME(COLUMN_NAME)
    FROM cols
    ORDER BY ORDINAL_POSITION
    FOR XML PATH('')
    ), 1, 1, '')
  '
FROM '   @TableName   '
WHERE File_ID = @FID
)
SELECT B.*
FROM CTE
CROSS APPLY (
    VALUES '  STUFF((
    SELECT ',( '   CAST(ORDINAL_POSITION AS VARCHAR)   ','   QUOTENAME(COLUMN_NAME,'''')   ','   QUOTENAME(COLUMN_NAME)   ')'
    FROM cols
    ORDER BY ORDINAL_POSITION
    FOR XML PATH('')
    ), 1, 1, '')   '
)B (ID,ColumnName,DistinctCountValue) 
'
from cols


exec SP_executesql @SQL, N'@FID INT', @FID = @FileID
  

Ответ №2:

Приведенный ниже запрос создает таблицу всех имен столбцов и использует цикл while для выбора count для любого предложения WHERE, которое вы хотите использовать. Это должно быть довольно гибким для любой таблицы; просто обновите верхние переменные. Обратите внимание, что при этом не будет учитываться столбец, значение которого равно null. Вы можете добавить регистр к параметру @Query, если это то, что вы хотите. Поскольку он обрабатывает каждую строку по отдельности, я добавил временную таблицу, чтобы вы обращались к базе данных только один раз.

 IF OBJECT_ID('tempdb..##SourceValues') IS NOT NULL
    DROP TABLE ##SourceValues
DECLARE @Schema VARCHAR(50) = 'SomeSchema'
DECLARE @Table VARCHAR(50) = 'SomeTable'
DECLARE @WhereClause VARCHAR(MAX) = ' Some WHERE clause'
DECLARE @ColumnName VARCHAR(50)
DECLARE @ProcessedRows TABLE(ColumnName VARCHAR(50), DistinctCount INT)
DECLARE @Columns TABLE(RowNumber INT,  ColumnName VARCHAR(100)) 
INSERT INTO @Columns SELECT ROW_NUMBER() OVER(ORDER BY COLUMN_NAME DESC),  COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = @Table
DECLARE @Count INT = (SELECT MAX(RowNumber) FROM @Columns)
DECLARE @Counter INT = 0
DECLARE @DistinctCount INT 
DECLARE @Query NVARCHAR(MAX)
EXEC('SELECT * INTO ##SourceValues FROM '   @Table  ' (NOLOCK)')
WHILE @Counter < @Count
BEGIN 
    SET @Counter  = 1
    SET @ColumnName = (SELECT ColumnName FROM @Columns WHERE RowNumber = @Counter)
    SET @Query = 'SELECT @OutPut = COUNT('   @ColumnName   ') FROM '   @Schema   '.'   ' ##SourceValues '   @WhereClause
    EXECUTE sp_executesql @Query, N'@Output INT OUT', @DistinctCount OUT
    INSERT INTO @ProcessedRows(ColumnName, DistinctCount) VALUES (@ColumnName, @DistinctCount)
END
SELECT * FROM @ProcessedRows
  

Ответ №3:

Давайте попробуем другой подход.

Получить все значения, не представленные как Param / Value:

1) Соберите список таблиц и столбцов, которые будут использоваться в динамическом SQL:

 DROP TABLE IF EXISTS #Base;
;WITH SchemaData AS (
    SELECT t.name AS [TableName],c.name AS [ColumnName],c.column_id AS [ColumnOrderID]
    FROM sys.tables t
    INNER JOIN sys.columns c ON c.object_id = t.object_id
)
SELECT t.TableName
    ,STUFF((SELECT ',CONVERT(NVARCHAR(MAX),'   QUOTENAME([ColumnName])   ') AS '   QUOTENAME([ColumnName]) 
            FROM SchemaData a WHERE (a.TableName = t.TableName) FOR XML PATH(''),TYPE).value('(./text())[1]','NVARCHAR(MAX)'),1,1,'') AS [SelectClause]
    ,STUFF((SELECT ','   QUOTENAME([ColumnName]) FROM SchemaData a WHERE (a.TableName = t.TableName) FOR XML PATH(''),TYPE).value('(./text())[1]','NVARCHAR(MAX)'),1,1,'') AS [UnpivotClause]
INTO #Base
FROM SchemaData t
GROUP BY t.TableName
;
  

2) Получить все данные внутри временной таблицы

 DROP TABLE IF EXISTS #Result;
CREATE TABLE #Result(TableName NVARCHAR(255),ColumnName NVARCHAR(255),[Value] NVARCHAR(MAX));

DECLARE @TableName NVARCHAR(255),@SelectClause NVARCHAR(MAX),@UnpivotClause NVARCHAR(MAX);
DECLARE crPopulateResult CURSOR LOCAL FAST_FORWARD READ_ONLY FOR SELECT b.TableName,b.SelectClause,b.UnpivotClause FROM #Base b;

OPEN crPopulateResu<
FETCH NEXT FROM crPopulateResult INTO @TableName,@SelectClause,@UnpivotClause;

DECLARE @dSql NVARCHAR(MAX);
WHILE @@FETCH_STATUS = 0
BEGIN
    SELECT @dSql = N'   INSERT INTO #Result(TableName,[ColumnName],[Value])
    SELECT up.TableName,up.Param AS [ColumnName],up.[Value]
    FROM (
        SELECT '''   @TableName   N''' AS [TableName]
            ,'   @SelectClause   N'
        FROM '   QUOTENAME(@TableName)   N'
    ) a
    UNPIVOT(Value FOR Param IN ('   @UnpivotClause   N')) up
    ';
    EXEC sp_executesql @stmt = @dSql;

    FETCH NEXT FROM crPopulateResult INTO @TableName,@SelectClause,@UnpivotClause;
END

CLOSE crPopulateResu<
DEALLOCATE crPopulateResu<
  

3) С #Results могут быть применены любые фильтры, включая имена таблиц, имена столбцов, фильтры данных и т. Д:

 SELECT r.TableName,r.ColumnName,COUNT(*) AS [CountValue],COUNT(DISTINCT r.[Value]) AS [DistinctCountValue]
FROM #Result r
--
--WHERE r.ColumnName = 'file_id' AND r.[Value] = '1'
--
GROUP BY r.TableName,r.ColumnName
ORDER BY r.TableName,r.ColumnName
;
  

Ответ №4:

Чтобы использовать это с предложением where в этом запросе, вам просто нужно поместить предложение where в конструкцию после имени таблицы, поэтому, если вы хотите выполнить фильтрацию по file_id =’1′, тогда у вас будет:

 FROM '   quotename (Table_Schema)   '.'   quotename (Table_Name)  'where file_id =''1'' '
  

Ответ №5:

Вы можете добавить @where переменную и объединить ее с вашей конструкцией big union (как часть вашей select ... from cols ). Например:

 declare @SQL nvarchar(max)
declare @where nvarchar(max) = ' where file_id = 1'

set @SQL = ''
;with cols as (
select Table_Schema, Table_Name, Column_Name, Row_Number() over(partition by Table_Schema, Table_Name
order by ORDINAL_POSITION) as RowNum
from INFORMATION_SCHEMA.COLUMNS
)
select @SQL = @SQL   case when RowNum = 1 then '' else ' union all ' end
  ' select '''   Column_Name   ''' as Column_Name, count(distinct '   quotename (Column_Name)   ' ) As DistinctCountValue, 
count( '  quotename (Column_Name)   ') as CountValue FROM '   quotename (Table_Schema)   '.'   quotename (Table_Name)
  @where
from cols
where Table_Name = 'table_name' --print @SQL

execute (@SQL)
  

Обратите внимание, что вам нужно будет экранировать одинарные кавычки в @where , если вы ищете строку. Например, declare @where nvarchar(max) = ' where state = ''CT''' .