Поиск MIN и MAX значений для всех столбцов таблицы

Этот запрос работает по назначению, но он очень медленный. У кого-нибудь здесь есть рекомендации по улучшению производительности?

По сути, я просто создаю временную таблицу для хранения всех имен таблиц и столбцов и циклически перебираю их с помощью оператора WHILE, чтобы создавать динамические вставки в другую таблицу с нужными мне деталями.

Мой последний запуск занял около 21 минуты, что не так уж и ужасно (учитывая задачу), но я хотел бы получить некоторую информацию о том, как и где его можно точно настроить.

USE <DATABASE>;

      IF NOT EXISTS(SELECT *
FROM sys.schemas WHERE name='temp')
BEGIN
EXEC ('CREATE SCHEMA temp');
END;

IF OBJECT_ID('temp.columns') IS NOT NULL
   BEGIN
      DROP TABLE temp.columns
   END;

SELECT [table_name]
    , [column_name]
    , [data_type]
    , [is_nullable]
    , [numeric_scale]
    , [ordinal_position]
INTO [temp].[columns]
FROM information_schema.columns c
WHERE table_schema = 'dbo'
      -- AND table_name = 'CONTACTS'
      ;

IF OBJECT_ID('_TableColumnsUsed') IS NOT NULL
   BEGIN
      DROP TABLE _TableColumnsUsed
   END;

      CREATE TABLE _TableColumnsUsed (Table_Name VARCHAR(255) NULL, Column_Position INT, Column_Name VARCHAR(255) NULL, Min_Value VARCHAR(MAX) NULL, Max_Value VARCHAR(MAX) NULL);


DECLARE
     @CurrentTable      VARCHAR(255)
    , @CurrentColumn     VARCHAR(255)
    , @CurrentIsNullable VARCHAR(3)
    , @CurrentNumeric    BIT
    , @CurrentPosition  INT
    , @SQL               VARCHAR(MAX);

WHILE
(
   SELECT COUNT(1)
   FROM temp.columns
) > 0
   BEGIN
      SELECT TOP 1 @CurrentTable = [Table_Name]
               , @CurrentColumn = [Column_Name]
               , @CurrentIsNullable = [is_nullable]
               , @CurrentNumeric = IIF([numeric_scale] IS NULL, 0, 1)
               , @CurrentPosition = [ordinal_position]
      FROM temp.columns c
      WHERE [table_name] NOT IN ('_TableColumnsUsed')
      ORDER BY [table_name]
            , [ordinal_position];

      SET @SQL = 'INSERT INTO _TableColumnsUsed (Table_Name, Column_Position, Column_Name, Min_Value, Max_Value)
         SELECT Table_Name = '''+@CurrentTable+'''
         , Column_Position = '+CAST(@CurrentPosition AS VARCHAR(3))+'
         , Column_Name = '''+@CurrentColumn+'''
         , Min_Value = MIN(CAST('+@CurrentColumn+' AS VARCHAR(MAX)))
         , Max_Value = MAX(CAST('+@CurrentColumn+' AS VARCHAR(MAX)))
         FROM '+@CurrentTable+'
         WHERE '+IIF(@CurrentIsNullable = 'NO', '1=1',
                                        CASE
                                            WHEN @CurrentNumeric = 0
                                            THEN 'ISNULL(CAST('+@CurrentColumn+' AS VARCHAR(MAX)),'''') <> '''''
                                            WHEN @CurrentNumeric = 1
                                            THEN 'ISNULL('+@CurrentColumn+',0.00) <> 0.00'
                                            ELSE '1=1'
                                        END);

      EXEC (@SQL);
      DELETE c
      FROM [temp].[columns] [c]
      WHERE [c].[table_name] = @CurrentTable
           AND [c].[column_name] = @CurrentColumn;
   END;


      /*  -- Dynamic SQL Output Example

         SELECT Table_Name = 'CONTACTS'
         , Column_Position = 17
         , Column_Name = 'ZIP'
         , Min_Value = MIN(CAST(ZIP AS VARCHAR(MAX)))
         , Max_Value = MAX(CAST(ZIP AS VARCHAR(MAX)))
         FROM CONTACTS
         WHERE 1=1

      */


SELECT Table_Name, Column_Position, Column_Name, Min_Value, Max_Value
FROM _TableColumnsUsed;

person Jermaine    schedule 05.04.2018    source источник
comment
Что вы хотите именно и что вы пробовали? Вопрос должен быть коротким и конкретным.   -  person Ali Azam    schedule 05.04.2018
comment
Извините, @AliAzam, но я не уверен, что еще я могу сказать, чтобы объяснить это, кроме указания вывода столбца, который я получаю: Table_Name, Column_Position, Column_Name, Min_Value, Max_Value   -  person Jermaine    schedule 05.04.2018
comment
Совет. При сборке имен объектов в динамические операторы SQL рекомендуется использовать QuoteName(), чтобы избежать проблем с нечетными именами, например New Table с пробелом.   -  person HABO    schedule 05.04.2018


Ответы (2)


Попробуйте это, это должно работать в секундах:

DECLARE @cmd NVARCHAR(MAX)=
(
    SELECT STUFF(
    (
            SELECT ' UNION ALL SELECT ''' + c.TABLE_SCHEMA + ''' AS TableSchema '
                 + ',''' + c.TABLE_NAME + ''' AS TableName '
                 + ',''' + c.COLUMN_NAME + ''' AS ColumnName '
                 + ',''' + c.DATA_TYPE + ''' AS ColumnType '
                 + ',CAST(MIN(' + QUOTENAME(c.COLUMN_NAME)+') AS NVARCHAR(MAX)) AS MinValue ' 
                 + ',CAST(MAX(' + QUOTENAME(c.COLUMN_NAME)+') AS NVARCHAR(MAX)) AS MaxValue ' 
                 + ' FROM ' + QUOTENAME(c.TABLE_SCHEMA) + '.' + QUOTENAME(c.TABLE_NAME)
                 + ' WHERE ' + QUOTENAME(c.COLUMN_NAME) + ' IS NOT NULL'
            FROM INFORMATION_SCHEMA.COLUMNS AS c
            WHERE c.DATA_TYPE IN('bigint','float','int','datetime') --add all types you want to check, be aware of implicit conversions!
            FOR XML PATH(''),TYPE
    ).value('.','nvarchar(max)'),1,10,'')
);
--PRINT @cmd
EXEC(@cmd);

Оператор создает запрос все-в-одном UNION ALL, который выполняется через EXEC

Вы можете раскомментировать PRINT, чтобы увидеть выполненный оператор.

person Shnugo    schedule 05.04.2018
comment
Я заметил, что в вашем примере все является числовым типом данных, при тестировании моей версии мне пришлось добавить явное преобразование при столкновении со смешанными типами (дата, строка, числовое значение и т. д.). Влияет ли это и на ваш пример, или это исправляет приведение типов в значении xml? - person Anthony Hancock; 05.04.2018
comment
Спасибо, @Shnugo!! Это решение блестяще превзошло мои ожидания. Браво, респект и любой другой комплиментарный ответ, который вы можете придумать. - person Jermaine; 05.04.2018

Не могу сказать, что он будет работать быстрее, зависит от размера вашей базы данных, но вот модифицированная версия созданного мной скрипта профилирования, который должен удовлетворить ваши потребности.

DECLARE @sqlStatement NVARCHAR(MAX) = '';

WITH CTE AS
(
    SELECT
        SCH.[name] AS [Schema]
        ,TAB.[name] AS [Table]
        ,COL.[name] AS [Column]
    FROM sys.columns AS COL
        JOIN sys.tables AS TAB
            ON COL.[object_id] = TAB.[object_id]
        JOIN sys.schemas AS SCH
            ON TAB.[schema_id] = SCH.[schema_id]
    WHERE COL.system_type_id NOT IN (104,240)
)

SELECT @sqlStatement += 

        'UNION ALL
        SELECT 
            '''+[Schema]+''' AS [Schema]
            ,'''+[Table]+''' AS [Table]
            ,'''+[Column]+''' AS [Column]
            ,CONVERT(NVARCHAR(MAX),MAX(['+[Column]+'])) AS ColumnMax
            ,CONVERT(NVARCHAR(MAX),MIN(['+[Column]+'])) AS ColumnMin
        FROM ['+[Schema]+'].['+[Table]+']
        '
FROM CTE
;

SET @sqlStatement = STUFF(@sqlStatement,1,10,'');

EXEC sp_executesql @sqlStatement;
person Anthony Hancock    schedule 05.04.2018
comment
Наши ответы очень похожи, +1 с моей стороны - person Shnugo; 05.04.2018
comment
Один совет: вы всегда должны использовать QUOTENAME вместо добавления [] через конкатенацию. - person Shnugo; 05.04.2018
comment
Я заметил, что в вашем примере я начну пытаться использовать это вместо этого. - person Anthony Hancock; 05.04.2018