如何转动?如何将多行转换为多列的一行?

时间:2013-12-19 08:45:31

标签: mysql sql sql-server pivot-table

我想要合并两张桌子。第一个表是客户端,另一个是产品。目前我有22个产品,但我希望有一个灵活的数据库设计,所以在产品数据库中没有22列,每个客户每个产品有1行,所以如果我添加或删除1个产品,我不会必须改变数据库结构。

我想要一个select语句,我为每个客户选择所有产品,输出应该在一行中,每个产品都有一列。

我已经看到了一些类似的其他问题,但目标是将所有行连接在一列中 - 这是我不想要的。

假设有2个客户和3个产品。

表客户端:

ClientId | ClientName
---------------------
 1       | Name1
 2       | Name2

表产品

ProductId | ClientId | Product
-------------------------------------
 1        |   1      |  SomeproductA
 2        |   1      |  SomeproductB
 3        |   1      |  SomeproductA
 4        |   2      |  SomeproductC
 5        |   2      |  SomeproductD
 6        |   2      |  SomeproductA

输出应该是这样的:

表格输出:

 ClientId | ClientName | Product1     | Product 2    | Product 3
 -------------------------------------------------------------------
     1    | Name1      | SomeproductA | SomeproductB | SomeproductA
     2    | Name2      | SomeproductC | SomeproductD | SomeproductA

完美的解决方案也是灵活的,因为select语句应该计算每个客户端的不同产品的数量(对于所有客户端它们将始终是相同的),这样如果我为所有客户添加或删除1个产品客户端,我不应该更改select语句。

2 个答案:

答案 0 :(得分:8)

MYSQL版

这是查询。联接查询使用User Defined Variables MySQL feature为每个客户端组内的每个产品生成RowNumber(1,2,3,...)。外部查询使用GROUP BY形成PIVOT表,使用内部表中的行号形成CASE。如果您需要变量产品列数,请考虑创建此查询动态添加MAX(CASE WHEN p.RowNum=X THEN p.Product END) as ProductX到选择列表。

select Clients.ClientName,
       MAX(CASE WHEN p.RowNum=1 THEN p.Product END) as Product1,
       MAX(CASE WHEN p.RowNum=2 THEN p.Product END) as Product2,
       MAX(CASE WHEN p.RowNum=3 THEN p.Product END) as Product3,
       MAX(CASE WHEN p.RowNum=4 THEN p.Product END) as Product4


FROM Clients
JOIN
(
  SELECT Products.*,
       if(@ClientId<>ClientId,@rn:=0,@rn),
       @ClientId:=ClientId,
       @rn:=@rn+1 as RowNum

  FROM Products, (Select @rn:=0,@ClientId:=0) as t
  ORDER BY ClientId,ProductID
 ) as P 
   ON Clients.ClientId=p.ClientId

GROUP BY Clients.ClientId

SQLFiddle demo

SQL Server Edition:

select Clients.ClientId,
       MAX(Clients.ClientName),
       MAX(CASE WHEN p.RowNum=1 THEN p.Product END) as Product1,
       MAX(CASE WHEN p.RowNum=2 THEN p.Product END) as Product2,
       MAX(CASE WHEN p.RowNum=3 THEN p.Product END) as Product3,
       MAX(CASE WHEN p.RowNum=4 THEN p.Product END) as Product4


FROM Clients
JOIN
(
  SELECT Products.*,
       ROW_NUMBER() OVER (PARTITION BY ClientID ORDER BY ProductID) 
         as RowNum

  FROM Products
 ) as P 
   ON Clients.ClientId=p.ClientId
GROUP BY Clients.ClientId

SQLFiddle demo

答案 1 :(得分:0)

答案似乎同时解决了 MySQL 和 SQL Server,所以我在这里添加了一个进一步的 SQL Server 答案,该逻辑也可能适用于 MySQL。

以下是用于 MS SQL Server 的 Transact SQL 中的动态 SQL 版本。

这使您能够获得相同的结果,而不必像 CASE WHEN 解决方案那样在结果表中明确写出您需要的每一列。 CASE WHEN 对几列来说既简单又好用,但我最近有一个类似的场景,它可以旋转到大约 200 列。

对于动态 SQL,您本质上是使用生成的变量将所需的查询编译为字符串,然后执行它。

-- variable tables to store data
DECLARE @Clients TABLE(ClientID int, 
                ClientName nvarchar(10))

DECLARE @Products TABLE(ProductID int, 
                    ClientID int, 
                    Product nvarchar(15))

-- populate the variable tables with sample data
INSERT INTO @Clients 
VALUES (1, 'Name1'),
    (2, 'Name2')

INSERT INTO @Products 
VALUES (1, 1, 'SomeproductA'),
    (2, 1, 'SomeproductB'),
    (3, 1, 'SomeproductA'),
    (4, 2, 'SomeproductC'),
    (5, 2, 'SomeproductD'),
    (6, 2, 'SomeproductA')

-- display the tables to check
SELECT * FROM @Clients
SELECT * FROM @Products

-- join the two tables and generate a column with rows which will become the new 
-- column names (Product_col) which gives a number to each product per client
SELECT c.ClientID, 
    c.ClientName, 
    p.ProductID, 
    p.Product,
    CONCAT('Product', ROW_NUMBER() 
        OVER(PARTITION BY c.ClientID ORDER BY p.Product ASC))  AS Product_col
INTO #Client_Products
FROM @Products p 
LEFT JOIN @Clients c ON c.ClientID = p.ClientID

-- view the joined data and future column headings
SELECT * FROM #Client_Products

-- setup for the pivot, declare the variables to contain the column names for pivoted 
-- rows and the query string
DECLARE @cols1 AS NVARCHAR(MAX),
    @query  AS NVARCHAR(MAX);

-- column name list for products
SET @cols1 = STUFF((SELECT distinct ',' + QUOTENAME(Product_col) 
        FROM #Client_Products
        FOR XML PATH(''), TYPE
        ).value('.', 'NVARCHAR(MAX)') 
    ,1,1,'')

SELECT @cols1  -- view the future column names

-- generate query variable string

-- the top select is all the columns you want to actually see as the result
-- The inner query needs the columns you want to see in the result, and the columns 
-- you are pivoting with. The pivot needs to select the value you want to go into the 
-- new columns (MAX()) and the values that will become the column names (FOR x IN())
SET @query = 'SELECT ClientID, 
            ClientName,'
                + @cols1 +' 
            FROM
            (
                SELECT ClientID,
                    ClientName,
                    Product_col,
                    Product
                FROM #Client_Products
           ) x
         PIVOT 
        (
            MAX(Product)
            FOR Product_col IN (' + @cols1 + ')
        ) p'


EXECUTE(@query) -- execute the dynamic sql

DROP TABLE #Client_Products