Нужны все столбцы в одной строке

Я написал следующий запрос:

IF OBJECT_ID ('tempdb..#ColumnsType') IS NOT NULL DROP TABLE #ColumnsType
DECLARE @vQuery NVARCHAR(MAX) =''

IF OBJECT_ID ('tempdb..#random') IS NOT NULL DROP TABLE #random

CREATE TABLE #random (
ColumnID INT PRIMARY KEY IDENTITY(1,1) NOT NULL
, randomname VARCHAR(50)
, randomvalue INT)


INSERT INTO #random (randomname, randomvalue)
VALUES ('a3', 123)
    , ('bla', 4325)
    , ('another_bla', 5643)
    , ('end_here', 3)

select *
from #random

CREATE TABLE #ColumnsType (
                ColumnID INT PRIMARY KEY IDENTITY(1,1) NOT NULL
                , ColumnName sysname 
                , DataType sysname
                )

 
INSERT INTO #ColumnsType (ColumnName, DataType)
SELECT [name],
        system_type_id    
FROM Tempdb.Sys.Columns
WHERE Object_ID = Object_ID('tempdb..#random')
AND system_type_id = 56



DECLARE @i INT = (SELECT MIN(ColumnID) FROM #random);
DECLARE @maxId INT = (SELECT MAX(ColumnID) FROM #random);
DECLARE @ColumnName VARCHAR(200);
DECLARE @DataType VARCHAR(200);

WHILE @i <= @maxId
BEGIN
    SET @ColumnName = (SELECT ColumnName FROM #ColumnsType WHERE ColumnId = @i)

    -- SET @DataType = (SELECT DataType FROM #ColumnsType WHERE ColumnId = @i)

    SELECT @vQuery =
    'SELECT 

            MIN(TRY_CONVERT(NUMERIC(30, 4), ' +@ColumnName+ ')) AS ' +@ColumnName+ '_MinValue
            , MAX(TRY_CONVERT(NUMERIC(30, 4), ' +@ColumnName+ ')) AS ' +@ColumnName+ '_MaxValue
            , AVG(TRY_CONVERT(NUMERIC(30, 4), ' +@ColumnName+ ')) AS ' +@ColumnName + '_AvgValue
            , STDEV(TRY_CONVERT(NUMERIC(30, 4), ' +@ColumnName+ ')) AS ' +@ColumnName+ '_StandardDeviation
            , SUM(TRY_CONVERT(NUMERIC(30, 4), ' +@ColumnName+  ')) AS ' +@ColumnName+ '_TotalSum      
    FROM tempdb..#random'   -- +@Schema+'.'+@Table+ ''

    EXEC sp_executesql @vQuery
    PRINT @vQuery

    SET @i = @i + 1
END

Для демонстрации я создаю временную таблицу со случайными значениями. Я выполняю профилирование части столбцов, состоящих только из числовых значений. Чтобы отфильтровать столбцы, я получаю их имена и фильтрую по типу, используя Tempdb.Sys.Columns. В обычном случае с моими исходными данными я использую INFORMATION_SCHEMA.COLUMNS, но я думаю, что это не так важно.

Запрос возвращает следующее:

Результат представлен в двух строках. Что я хотел бы сделать, так это получить этот результат в одной строке. Идея состоит в том, чтобы повернуть результат одной строки после и получить следующий результат:

UNION ALL ваши наборы данных. Не выполняйте SQL столбец за столбцом, сделайте его одним оператором для всех ваших столбцов.
Thom A 22.12.2020 10:30

@Larnu Но как я могу применить UNION ALL к одному динамическому запросу, который возвращает результаты отдельно?

Yana 22.12.2020 10:32

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

Thom A 22.12.2020 10:34

Это то, что я не знаю, как сделать. И я только придумал это не оптимальное решение. Не могли бы вы показать пример того, что вы имеете в виду?

Yana 22.12.2020 10:35
ReactJs | Supabase | Добавление данных в базу данных
ReactJs | Supabase | Добавление данных в базу данных
Это и есть ваш редактор таблиц в supabase.👇
Понимание Python и переход к SQL
Понимание Python и переход к SQL
Перед нами лабораторная работа по BloodOath:
1
4
1 016
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Как я уже упоминал, вам не нужно использовать цикл, использовать метод на основе набора и UNION ALL ваши динамические операторы. Я также предполагаю, что вы используете последнюю версию SQL Server. Если нет, вам нужно заменить STRING_AGG на старый метод FOR XML PATHSTUFF).

Этого должно быть достаточно для начала:

USE Sandbox;
GO

CREATE TABLE dbo.YourTable (Col1 int,
                            Col2 varchar(10));
GO

DECLARE @SchemaName sysname = N'dbo',
        @TableName sysname = N'YourTable';

DECLARE @SQL nvarchar(MAX),
        @CRLF nchar(2) = NCHAR(13) + NCHAR(10);

DECLARE @Delimiter nvarchar(50) = @CRLF + N'UNION ALL' + @CRLF;
        
SELECT @SQL = STRING_AGG(CONVERT(nvarchar(MAX),N'SELECT MIN(') + QUOTENAME(c.[name]) + N') AS ' + QUOTENAME(c.[name] + N'_MIN') + N',' + @CRLF +
                         N'       MAX(' + QUOTENAME(c.[name]) + N') AS ' + QUOTENAME(c.[name] + N'_MAX') + @CRLF + 
                         N'FROM ' + QUOTENAME(s.[name]) + N'.' + QUOTENAME(t.[name])
                         ,@Delimiter) WITHIN GROUP (ORDER BY c.column_id)
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 s.[name] = @SchemaName
 AND t.[name] = @TableName

PRINT @SQL;

EXEC sys.sp_executesql @SQL;
GO

DROP TABLE dbo.YourTable;

Я получаю эту ошибку: результат агрегации STRING_AGG превысил ограничение в 8000 байт. Используйте типы больших объектов, чтобы избежать усечения результатов. Кроме того, мне нужно фильтровать только числовые столбцы. Если я делаю это в операторе динамического запроса, это выдает ошибку, потому что он все еще проходит через нечисловые столбцы.

Yana 22.12.2020 11:07

«Кроме того, мне нужно отфильтровать только числовые столбцы. Если я сделаю это в операторе динамического запроса, возникнет ошибка, потому что он все еще проходит через нечисловые столбцы». Этого не было в вашем вопросе, но это легко сделать. Добавьте еще одно JOIN к FROM к sys.types и еще одно предложение к WHERE, чтобы отфильтровать нечисловые столбцы.

Thom A 22.12.2020 11:08

Я получаю эту ошибку: результат агрегации STRING_AGG превысил ограничение в 8000 байт. Простое исправление, измените 'SELECT MIN(' на CONVERT(nvarchar(MAX),'SELECT MIN(')

Thom A 22.12.2020 11:09

Ну, это не тот результат, который я искал, что-то вроде этого: Col1_MIN Col1_MAX Col2_Min Col2_max, в один ряд. Я получаю строки для каждого столбца при запуске вашего решения.

Yana 22.12.2020 11:30

Это не то, что показывает ваш пример изображения, @Yana, он показывает, что вам нужны 2 строки, а не 1.

Thom A 22.12.2020 11:32

Для этого вам понадобится динамический динамический (да, я сказал это дважды) SQL, а это путь к безумию, @Yana.

Thom A 22.12.2020 11:33

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

Yana 22.12.2020 11:47

Вы заявляете: «Идея состоит в том, чтобы повернуть результат одной строки после и получить следующий результат». Итак, чтобы получить результат на изображении, @Yana ... Это то, что делает вышеизложенное. Если это не то, что вам нужно, то ваш вопрос неясен, и вы (при всем уважении) потратили мое время здесь впустую.

Thom A 22.12.2020 11:53

Теперь я понимаю, где произошло недоразумение. Вы не зря потратили время. Но мне нужно время, чтобы понять, что вы сделали с STRING_AGG. Первый раз вижу его в использовании, никогда не пользовался. Спасибо и еще раз извините.

Yana 22.12.2020 12:03
Ответ принят как подходящий

Вот очень чистое и красивое решение, которое я искал:

  • Я выбираю столбцы из определенной таблицы, сканирую ее один раз и выполняю над ней несколько вычислений.
  • Я использую динамический запрос и делаю его одним оператором для всех столбцов.
  • Это работает очень быстро. Потребовалось чуть более 5 минут, чтобы вернуть результат для таблицы с 50 мил строк.

Единственное, что осталось сделать, это UNPIVOT, чтобы вставить результаты в нужную мне таблицу.

    DECLARE
    @q1 NVARCHAR(MAX)
,   @q2 NVARCHAR(MAX)
,   @q3 NVARCHAR(500)
,   @schema VARCHAR(50) = '' -- choose schema
,   @table VARCHAR(200) = '' -- choose table

SET @Q1 = 'SELECT ' + '''' + @table + '''' + ' as tableName, '
SET @Q3 = ' FROM ' + @schema + '.' + @table
SELECT @q2 = COALESCE(@q2 + ', ', '') 
+ ' max(' + columnName + ') as ' + columnName + '_max, ' 
+ ' min(' + columnName + ') as ' + columnName + '_min, '
+ ' avg(' + columnName + ') as ' + columnName + '_avg, '
+ ' stdev(' + columnName + ') as ' + columnName + '_stdev, '
+ ' sum(' + columnName + ') as ' + columnName + '_sum '
FROM (
SELECT s.[name] as schemaName, t.[name] as tableName, c.[name] as columnName, st.[name] as typeName 
FROM sys.schemas s
    INNER JOIN sys.tables t ON s.schema_id = t.schema_id
    INNER JOIN sys.columns c ON t.object_id = c.object_id
    INNER JOIN sys.types st ON st.user_type_id = c.user_type_id
WHERE 1=1
AND s.[name] = @schema
AND t.[name] = @table
AND st.[name] IN ('') -- choose columns of specific data type, that you want to profile
 ) data

SELECT @q1 = @q1 + @q2 + @q3

EXEC sys.sp_executesql @Q1 

Наслаждаться!

Другие вопросы по теме