SQL Server 2008 PIVOT - Как управлять содержимым столбца - PullRequest
0 голосов
/ 26 ноября 2010

Я пытаюсь сделать PIVOT-запрос из рабочей таблицы со структурой:

DECLARE @workingData TABLE
(
    location VARCHAR(20),
    name VARCHAR(50),
    sales_type VARCHAR(20),
    local_id VARCHAR(15),
    house_description VARCHAR(40),
    sales_order VARCHAR(10),
    order_year INT,
    amount NUMERIC(14,0)
)

Столбец имени содержит одно из двух значений: «имя1» или «имя2». Они должны использоваться в группировке, как описано ниже.

Я бы хотел, чтобы он стал таблицей со столбцами:

location
sales_type
local_id
house_description
sales_order
name1_2007
name2_2007
name1_2008
name2_2008
name1_2009
name2_2009
name1_2010
name2_2010

Я пробовал это:

SELECT 
    location, sales_type, local_id, house_description, sales_order,
    MAX([1]) AS [name1_2007], MAX([2]) AS [name2_2007],
    MAX([3]) AS [name1_2008], MAX([4]) AS [name2_2008],
    MAX([5]) AS [name1_2009], MAX([6]) AS [name2_2009],
    MAX([7]) AS [name1_2010], MAX([8]) AS [name2_2010],
    '2010' As [Base Year]
FROM (
    SELECT location, sales_type, local_id, house_description, sales_order, order_year, name, amount
    ,ROW_NUMBER() OVER ( PARTITION BY location, sales_type, local_id, sales_order 
    ORDER BY order_year, name) AS seq
    FROM @workingData
    ) AS SourceTable
PIVOT
(
    MAX(amount)
    FOR seq IN ([1], [2], [3], [4], [5], [6], [7], [8], [9])
) AS PivotTable
GROUP BY 
location, sales_type, local_id, house_description, sales_order

И это ПОЧТИ работает! ;) Но мои значения не помещены в правильные столбцы. Если определенное значение существует для определенного местоположения, sales_type, local_id, house_description и sales_order, оно всегда выводится в столбце [1]. Но это должно быть определено, если name1 или name2 и его order_year!

Я понимаю, что то, что я вижу, является прямым результатом моей операции row_number, которая вычисляет столбец seq как 1, если существует только одна запись. Так, может быть, я атакую ​​это неправильно?

Кто-нибудь может решить эту проблему?

Ответы [ 2 ]

3 голосов
/ 26 ноября 2010

Хотя у вас есть только банкоматы Name1 и Name2, это типичный пример динамического разворота.

Создайте следующий SP:

CREATE PROC [dbo].[pivotsp]
  @query    AS NVARCHAR(MAX),                   -- The query, can also be the name of a table/view.
  @on_rows  AS NVARCHAR(MAX),                   -- The columns that will be regular rows.
  @on_cols  AS NVARCHAR(MAX),                   -- The columns that are to be pivoted.
  @agg_func AS NVARCHAR(257) = N'SUM',          -- Aggregate function.
  @agg_col  AS NVARCHAR(MAX),                   -- Column to aggregate.
  @output   AS NVARCHAR(257) = N'',             -- Table for results
  @debug    AS bit = 0                          -- 1 for debugging
AS

-- Example usage:
--    exec pivotsp
--          'select * from vsaleshistory',
--          'market,marketid,family,familyid,Forecaster,Forecasterid,product,productid',
--          'month',
--          'sum',
--          'ku',
--          '##sales'

-- Input validation
IF @query IS NULL OR @on_rows IS NULL OR @on_cols IS NULL
   OR @agg_func IS NULL OR @agg_col IS NULL
BEGIN
  RAISERROR('Invalid input parameters.', 16, 1);
  RETURN;
END

-- Additional input validation goes here (SQL Injection attempts, etc.)

BEGIN TRY
  DECLARE
    @sql     AS NVARCHAR(MAX),
    @cols    AS NVARCHAR(MAX),
    @newline AS NVARCHAR(2);

  SET @newline = NCHAR(13) + NCHAR(10);

  -- If input is a valid table or view
  -- construct a SELECT statement against it
  IF COALESCE(OBJECT_ID(@query, N'U'),
              OBJECT_ID(@query, N'V')) IS NOT NULL
    SET @query = N'SELECT * FROM ' + @query;

  -- Make the query a derived table
  SET @query = N'(' + @query + N') AS Query';

  -- Handle * input in @agg_col
  IF @agg_col = N'*'
    SET @agg_col = N'1';

  -- Construct column list
  SET @sql =
      N'SET @result = '                                    + @newline +
      N'  STUFF('                                          + @newline +
      N'    (SELECT N'','' +  quotename( '
                   + 'CAST(pivot_col AS sysname)' +
                   + ')  AS [text()]'                          + @newline +
      N'     FROM (SELECT DISTINCT('
                   + @on_cols + N') AS pivot_col'              + @newline +
      N'           FROM' + @query + N') AS DistinctCols'   + @newline +
      N'     ORDER BY pivot_col'                           + @newline +
      N'     FOR XML PATH(''''))'                          + @newline +
      N'    ,1, 1, N'''');'

  IF @debug = 1
     PRINT @sql

  EXEC sp_executesql
    @stmt   = @sql,
    @params = N'@result AS NVARCHAR(MAX) OUTPUT',
    @result = @cols OUTPUT;

  IF @debug = 1
     PRINT @cols

  -- Create the PIVOT query
  IF @output = N''
      begin
        SET @sql =
            N'SELECT *'                                          + @newline +
            N'FROM (SELECT '
                          + @on_rows
                          + N', ' + @on_cols + N' AS pivot_col'
                          + N', ' + @agg_col + N' AS agg_col'        + @newline +
            N'      FROM ' + @query + N')' +
                          + N' AS PivotInput'                        + @newline +
            N'  PIVOT(' + @agg_func + N'(agg_col)'               + @newline +
            N'    FOR pivot_col IN(' + @cols + N')) AS PivotOutput;'
      end
  ELSE
      begin
        set @sql = 'IF  EXISTS (SELECT * FROM tempdb.sys.objects WHERE  ' +
            'name = ''' + @output + ''' AND type = N''U'') DROP TABLE tempdb.' + @output
        EXEC sp_executesql @sql;

        SET @sql =
            N'SELECT * INTO ' + @output                          + @newline +
            N'FROM (SELECT '
                          + @on_rows
                          + N', ' + @on_cols + N' AS pivot_col'
                          + N', ' + @agg_col + N' AS agg_col'        + @newline +
            N'      FROM ' + @query + N')' +
                          + N' AS PivotInput'                        + @newline +
            N'  PIVOT(' + @agg_func + N'(agg_col)'               + @newline +
            N'    FOR pivot_col IN(' + @cols + N')) AS PivotOutput;'
      end

    IF @debug = 1
       PRINT @sql

    EXEC sp_executesql @sql;
END TRY
BEGIN CATCH
  DECLARE
    @error_message  AS NVARCHAR(2047),
    @error_severity AS INT,
    @error_state    AS INT;

  SET @error_message  = ERROR_MESSAGE();
  SET @error_severity = ERROR_SEVERITY();
  SET @error_state    = ERROR_STATE();

  RAISERROR(@error_message, @error_severity, @error_state);

  RETURN;
END CATCH

Теперь все стало проще. С входа

1   loca    namea   st1 1   house1  2   2007    1234
2   loca    namea   st1 1   house1  2   2007    2345
3   loca    namea   st1 1   house1  2   2007    3456
4   loca    namea   st1 1   house1  2   2008    6789
5   loca    namea   st1 1   house1  2   2008    7890
6   loca    nameb   st1 1   house1  2   2007    1234
7   locc    nameb   st1 1   house1  2   2007    2345
8   loca    nameb   st1 1   house1  2   2007    3456
9   loca    nameb   st1 1   house1  2   2008    6789
10  locc    nameb   st1 1   house1  2   2008    7890

мы агрегируем по имени и году

SELECT
    location, sales_type, local_id, house_description, sales_order,
    [name] + '_' + cast(order_year AS varchar(20)) as nameyear, 
    max(amount) as amount
INTO
    ##crosstab
FROM
    working
GROUP BY
    location, sales_type, local_id, house_description, sales_order,
    [name] + '_' + cast(order_year AS varchar(20))

1012 * дает *

loca    st1 1   house1  2   namea_2007  3456
loca    st1 1   house1  2   namea_2008  7890
loca    st1 1   house1  2   nameb_2007  3456
loca    st1 1   house1  2   nameb_2008  6789
locc    st1 1   house1  2   nameb_2007  2345
locc    st1 1   house1  2   nameb_2008  7890

затем, используя pivot_sp

EXEC pivotsp
      'select * from ##crosstab',
      'location, sales_type, local_id, house_description, sales_order',
      'nameyear',
      'max',
      'amount',
      '##answer'

SELECT 
    *
FROM
    ##answer

получаем

locat   st   local      house  so  namea_2007 namea_2008 nameb_2007 nameb_2008
loca    st1 1   house1  2   3456    7890    3456    6789
locc    st1 1   house1  2   NULL    NULL    2345    7890

НТН

0 голосов
/ 26 ноября 2010

Спасибо.Ваш ответ заставил меня понять, что использование ROW_NUMBER было неправильным подходом к созданию динамического PIVOT.Я изменил имена сводных столбцов с [1], [2] и т. Д., Поэтому теперь я могу точно сопоставить их по совокупности имени и года.Как хорошо показывает пример с ухмылкой!

Мне также нравится процедура ухмылки.Я могу пригодиться.:)

Чтобы прояснить это для других пользователей, сталкивающихся с той же проблемой, я перечислю здесь фактический SQL, который делает мой запрос PIVOT (В реальной жизни я генерирую это динамически):

SELECT 
        location,
        sales_type,
        local_id,
        house_description,
        sales_order,
            MAX([name12007]), 
            MAX([name22007]), 
            MAX([name12008]), 
            MAX([name22008]), 
            MAX([name12009]), 
            MAX([name22009]), 
            MAX([name12010]), 
            MAX([name22010]), 
        '2010' As [BaseYear]
    FROM (
        SELECT location, sales_type, local_id, house_description, sales_order, order_year, name, 
            ISNULL(amount,0) AS [amount],
            (name + CONVERT(VARCHAR(MAX),order_year)) AS ColumnIdentifier
        FROM @workingData
        ) AS SourceTable
    PIVOT
    (
        MAX(amount)
        FOR ColumnIdentifier IN ( [name12007],[name22007],[name12008],[name22008],
                                      [name12009],[name22009],[name12010],[name22010])
    ) AS PivotTable
    GROUP BY 
        location, sales_type, local_id, house_description, sales_order

С уважениемAlex

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...