Перетворити рядки в стовпці, використовуючи "Зворотний" в SQL Server


279

Я прочитав матеріал у зведених таблицях MS, і у мене все ще виникають проблеми з правильним виправданням.

У мене створюється таблиця темп, яка створюється, ми скажемо, що стовпець 1 - це номер магазину, а колонка 2 - номер тижня, і нарешті стовпець 3 - це загальний деякий тип. Також номери тижнів динамічні, номери магазинів статичні.

Store      Week     xCount
-------    ----     ------
102        1        96
101        1        138
105        1        37
109        1        59
101        2        282
102        2        212
105        2        78
109        2        97
105        3        60
102        3        123
101        3        220
109        3        87

Я хотів би, щоб він вийшов як зведений стіл, наприклад:

Store        1          2          3        4        5        6....
----- 
101        138        282        220
102         96        212        123
105         37        
109

Зберігайте номери внизу та тижні вгорі.


Відповіді:


356

Якщо ви використовуєте SQL Server 2005+, ви можете використовувати PIVOTфункцію перетворення даних із рядків у стовпці.

Здається, вам потрібно буде використовувати динамічний sql, якщо тижні невідомі, але легше побачити правильний код, використовуючи спочатку жорстко кодовану версію.

По-перше, ось кілька швидких визначень таблиці та даних для використання:

CREATE TABLE #yt 
(
  [Store] int, 
  [Week] int, 
  [xCount] int
);

INSERT INTO #yt
(
  [Store], 
  [Week], [xCount]
)
VALUES
    (102, 1, 96),
    (101, 1, 138),
    (105, 1, 37),
    (109, 1, 59),
    (101, 2, 282),
    (102, 2, 212),
    (105, 2, 78),
    (109, 2, 97),
    (105, 3, 60),
    (102, 3, 123),
    (101, 3, 220),
    (109, 3, 87);

Якщо ваші значення відомі, ви будете жорстко кодувати запит:

select *
from 
(
  select store, week, xCount
  from yt
) src
pivot
(
  sum(xcount)
  for week in ([1], [2], [3])
) piv;

Дивіться демонстрацію SQL

Тоді, якщо вам потрібно буде динамічно генерувати номер тижня, ваш код буде:

DECLARE @cols AS NVARCHAR(MAX),
    @query  AS NVARCHAR(MAX)

select @cols = STUFF((SELECT ',' + QUOTENAME(Week) 
                    from yt
                    group by Week
                    order by Week
            FOR XML PATH(''), TYPE
            ).value('.', 'NVARCHAR(MAX)') 
        ,1,1,'')

set @query = 'SELECT store,' + @cols + ' from 
             (
                select store, week, xCount
                from yt
            ) x
            pivot 
            (
                sum(xCount)
                for week in (' + @cols + ')
            ) p '

execute(@query);

Дивіться демонстрацію SQL .

Динамічна версія генерує список weekномерів, які слід перетворити на стовпці. Обидва дають однаковий результат:

| STORE |   1 |   2 |   3 |
---------------------------
|   101 | 138 | 282 | 220 |
|   102 |  96 | 212 | 123 |
|   105 |  37 |  78 |  60 |
|   109 |  59 |  97 |  87 |

4
Дуже хороша! Але як усунути стовпчик, коли всі значення цього стовпця NULL?
ZooZ

1
@ZooZ Дивіться відповідь нижче . Я не пробував це дослівно, але концепція є здоровою.
ruffin

1
+1 "Здається, що вам потрібно буде використовувати динамічний sql, якщо тижні невідомі, але легше побачити правильний код, використовуючи спочатку тверду версію". На відміну від загальної функції Qlikview ( community.qlik.com/blogs/qlikviewdesignblog/2014/03/31/generic ), яка дозволяє не вимагати, щоб ви чітко називали інше "ЗА ____ IN (...)"
Червоний горох

1
Якщо ви будуєте зведену таблицю з cte раніше. cte3 AS (select ... )тоді у вас є визначена вище логіка з @colsі @query... є помилка. Неправильне ім'я об'єкта 'cte3'.` Як виправити це. -
Єлизавета

3
Це фантастично - приємно @bluefeet. Я ніколи STUFF(...)раніше не використовував (або те XML PATH). На користь інших читачів, все, що робиться, - це приєднання назв стовпців та відсікання провідної коми. Примітка. Я думаю, що наступне трохи простіше: виберіть @cols = (ВИБІР ВІДПОВІДНОГО КВІТЕНА (тиждень) + ',' з yt порядку на 1 ДЛЯ XML PATH ('')) встановити @cols = SUBSTRING (@cols, 1, LEN ( @cols) - 1) ... заміна group byна distinctі order by 1вручну рубати з суфіксом коми!
DarthPablo

26

Це для динамічних # тижнів.

Повний приклад тут: SQL Dynamic Pivot

DECLARE @DynamicPivotQuery AS NVARCHAR(MAX)
DECLARE @ColumnName AS NVARCHAR(MAX)

--Get distinct values of the PIVOT Column 
SELECT @ColumnName= ISNULL(@ColumnName + ',','') + QUOTENAME(Week)
FROM (SELECT DISTINCT Week FROM #StoreSales) AS Weeks

--Prepare the PIVOT query using the dynamic 
SET @DynamicPivotQuery = 
  N'SELECT Store, ' + @ColumnName + ' 
    FROM #StoreSales
    PIVOT(SUM(xCount) 
          FOR Week IN (' + @ColumnName + ')) AS PVTTable'
--Execute the Dynamic Pivot Query
EXEC sp_executesql @DynamicPivotQuery

Гей, у мене є загадка, де мені потрібно динамічно обертати таблиці, ти думаєш, ти можеш мені в цьому допомогти? dbfiddle.uk/…
Дурний Воллі

@SillyVolley ось один, ви не вказали, на що хочете звернутись. Також я не знаю, чи можна це зробити в Postgres, тому я це зробив у SQL Server: dbfiddle.uk/…
Enkode

16

Я досяг того самого, що раніше, використовуючи підзапити. Отже, якщо ваша початкова таблиця називалася StoreCountsByWeek, і у вас була окрема таблиця, в якій були вказані ідентифікатори магазину, то це виглядатиме так:

SELECT StoreID, 
    Week1=(SELECT ISNULL(SUM(xCount),0) FROM StoreCountsByWeek WHERE StoreCountsByWeek.StoreID=Store.StoreID AND Week=1),
    Week2=(SELECT ISNULL(SUM(xCount),0) FROM StoreCountsByWeek WHERE StoreCountsByWeek.StoreID=Store.StoreID AND Week=2),
    Week3=(SELECT ISNULL(SUM(xCount),0) FROM StoreCountsByWeek WHERE StoreCountsByWeek.StoreID=Store.StoreID AND Week=3)
FROM Store
ORDER BY StoreID

Однією з переваг цього методу є те, що синтаксис є більш зрозумілим і полегшує приєднання до інших таблиць, щоб також втягувати інші поля в результати.

Мої анекдотичні результати полягають у тому, що запуск цього запиту на пару тисяч рядків, виконаних менше ніж за одну секунду, і в мене було фактично 7 підзапитів. Але як зазначається в коментарях, зробити це таким чином обчислювально дорожче, тому будьте уважні до використання цього методу, якщо ви очікуєте, що він буде працювати на великих обсягах даних.


8
це простіше, але це дуже дорога операція, ці підзапити повинні виконуватися один раз для кожного повернутого з таблиці рядка.
Грег


5

Я пишу SP, який може бути корисним для цієї мети, в основному цей sp перемикає будь-яку таблицю і повертає нову таблицю, повороту або повертає лише набір даних, це спосіб її виконання:

Exec dbo.rs_pivot_table @schema=dbo,@table=table_name,@column=column_to_pivot,@agg='sum([column_to_agg]),avg([another_column_to_agg]),',
        @sel_cols='column_to_select1,column_to_select2,column_to_select1',@new_table=returned_table_pivoted;

зауважте, що в параметрі @agg імена стовпців повинні бути, '['а параметр повинен закінчуватися комою','

СП

Create Procedure [dbo].[rs_pivot_table]
    @schema sysname=dbo,
    @table sysname,
    @column sysname,
    @agg nvarchar(max),
    @sel_cols varchar(max),
    @new_table sysname,
    @add_to_col_name sysname=null
As
--Exec dbo.rs_pivot_table dbo,##TEMPORAL1,tip_liq,'sum([val_liq]),sum([can_liq]),','cod_emp,cod_con,tip_liq',##TEMPORAL1PVT,'hola';
Begin

    Declare @query varchar(max)='';
    Declare @aggDet varchar(100);
    Declare @opp_agg varchar(5);
    Declare @col_agg varchar(100);
    Declare @pivot_col sysname;
    Declare @query_col_pvt varchar(max)='';
    Declare @full_query_pivot varchar(max)='';
    Declare @ind_tmpTbl int; --Indicador de tabla temporal 1=tabla temporal global 0=Tabla fisica

    Create Table #pvt_column(
        pivot_col varchar(100)
    );

    Declare @column_agg table(
        opp_agg varchar(5),
        col_agg varchar(100)
    );

    IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(@table) AND type in (N'U'))
        Set @ind_tmpTbl=0;
    ELSE IF OBJECT_ID('tempdb..'+ltrim(rtrim(@table))) IS NOT NULL
        Set @ind_tmpTbl=1;

    IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(@new_table) AND type in (N'U')) OR 
        OBJECT_ID('tempdb..'+ltrim(rtrim(@new_table))) IS NOT NULL
    Begin
        Set @query='DROP TABLE '+@new_table+'';
        Exec (@query);
    End;

    Select @query='Select distinct '+@column+' From '+(case when @ind_tmpTbl=1 then 'tempdb.' else '' end)+@schema+'.'+@table+' where '+@column+' is not null;';
    Print @query;

    Insert into #pvt_column(pivot_col)
    Exec (@query)

    While charindex(',',@agg,1)>0
    Begin
        Select @aggDet=Substring(@agg,1,charindex(',',@agg,1)-1);

        Insert Into @column_agg(opp_agg,col_agg)
        Values(substring(@aggDet,1,charindex('(',@aggDet,1)-1),ltrim(rtrim(replace(substring(@aggDet,charindex('[',@aggDet,1),charindex(']',@aggDet,1)-4),')',''))));

        Set @agg=Substring(@agg,charindex(',',@agg,1)+1,len(@agg))

    End

    Declare cur_agg cursor read_only forward_only local static for
    Select 
        opp_agg,col_agg
    from @column_agg;

    Open cur_agg;

    Fetch Next From cur_agg
    Into @opp_agg,@col_agg;

    While @@fetch_status=0
    Begin

        Declare cur_col cursor read_only forward_only local static for
        Select 
            pivot_col 
        From #pvt_column;

        Open cur_col;

        Fetch Next From cur_col
        Into @pivot_col;

        While @@fetch_status=0
        Begin

            Select @query_col_pvt='isnull('+@opp_agg+'(case when '+@column+'='+quotename(@pivot_col,char(39))+' then '+@col_agg+
            ' else null end),0) as ['+lower(Replace(Replace(@opp_agg+'_'+convert(varchar(100),@pivot_col)+'_'+replace(replace(@col_agg,'[',''),']',''),' ',''),'&',''))+
                (case when @add_to_col_name is null then space(0) else '_'+isnull(ltrim(rtrim(@add_to_col_name)),'') end)+']'
            print @query_col_pvt
            Select @full_query_pivot=@full_query_pivot+@query_col_pvt+', '

            --print @full_query_pivot

            Fetch Next From cur_col
            Into @pivot_col;        

        End     

        Close cur_col;
        Deallocate cur_col;

        Fetch Next From cur_agg
        Into @opp_agg,@col_agg; 
    End

    Close cur_agg;
    Deallocate cur_agg;

    Select @full_query_pivot=substring(@full_query_pivot,1,len(@full_query_pivot)-1);

    Select @query='Select '+@sel_cols+','+@full_query_pivot+' into '+@new_table+' From '+(case when @ind_tmpTbl=1 then 'tempdb.' else '' end)+
    @schema+'.'+@table+' Group by '+@sel_cols+';';

    print @query;
    Exec (@query);

End;
GO

Це приклад виконання:

Exec dbo.rs_pivot_table @schema=dbo,@table=##TEMPORAL1,@column=tip_liq,@agg='sum([val_liq]),avg([can_liq]),',@sel_cols='cod_emp,cod_con,tip_liq',@new_table=##TEMPORAL1PVT;

то Select * From ##TEMPORAL1PVTповернеться:

введіть тут опис зображення



2

Ось перегляд відповіді @Tayrn вище, яка може допомогти вам зрозуміти перетворення трохи легше:

Це може бути не найкращим способом зробити це, але саме це допомогло мені обернути голову навколо того, як обертати таблиці.

ID = рядки, які потрібно перевести

MY_KEY = стовпець, який ви вибираєте зі своєї оригінальної таблиці, що містить назви стовпців, які ви хочете зібрати.

VAL = значення, яке потрібно повернути під кожен стовпець.

MAX (VAL) => Можна замінити іншими агрегатними функціями. SUM (VAL), MIN (VAL), ETC ...

DECLARE @cols AS NVARCHAR(MAX),
@query  AS NVARCHAR(MAX)
select @cols = STUFF((SELECT ',' + QUOTENAME(MY_KEY) 
                from yt
                group by MY_KEY
                order by MY_KEY ASC
        FOR XML PATH(''), TYPE
        ).value('.', 'NVARCHAR(MAX)') 
    ,1,1,'')
set @query = 'SELECT ID,' + @cols + ' from 
         (
            select ID, MY_KEY, VAL 
            from yt
        ) x
        pivot 
        (
            sum(VAL)
            for MY_KEY in (' + @cols + ')
        ) p '

        execute(@query);

2

Просто дайте вам уявлення про те, як інші бази даних вирішують цю проблему. DolphinDBтакож має вбудовану підтримку повороту, і sql виглядає набагато більш інтуїтивно та акуратно. Це так само просто, як вказати стовпчик ключів ( Store), поворотний стовпчик ( Week) та обчислений показник ( sum(xCount)).

//prepare a 10-million-row table
n=10000000
t=table(rand(100, n) + 1 as Store, rand(54, n) + 1 as Week, rand(100, n) + 1 as xCount)

//use pivot clause to generate a pivoted table pivot_t
pivot_t = select sum(xCount) from t pivot by Store, Week

DolphinDB - це стовпчаста база даних з високою продуктивністю. Розрахунок у демо-версії коштує всього 546 мс на ноутбуці Dell xps (i7 процесор). Щоб отримати детальнішу інформацію, зверніться до онлайн-посібника DolphinDB https://www.dolphindb.com/help/index.html?pivotby.html

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.