简体   繁体   English

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

[英]How to pivot? How to convert multiple rows into one row with multiple columns?

I have two tables which I want to combine.我有两个要合并的表。 The first table is with clients and the other with products.第一张桌子是客户,另一张桌子是产品。 Currently I have 22 products, but I want to have a flexible DB design so instead of having 22 columns in the product DB, I have 1 row for each product for each client so if I add or remove 1 product overall, I don't have to change the DB structure.目前我有 22 个产品,但我想要一个灵活的 DB 设计,所以不是在产品 DB 中有 22 列,而是每个客户的每个产品都有 1 行,所以如果我添加或删除一个产品,我不会必须改变数据库结构。

I want to have a select statement where I select all products for each client and the output should be in a single row with a column for each product.我想要一个 select 语句,我为每个客户选择所有产品,输出应该在一行中,每个产品都有一列。

I have seen some other questions which are similar, but there the aim is to have all the rows concatenated in 1 column- which I don't want.我已经看到了一些类似的其他问题,但目的是将所有行连接在 1 列中 - 我不想要。

Assuming 2 clients and 3 products.假设有 2 个客户和 3 个产品。

Table client:表客户端:

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

Table products餐桌产品

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

The output should be something like:输出应该是这样的:

Table output:表输出:

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

The perfect solution would also be flexible in the sense that the select statement should count the number of distinct products for each client (they will always be the same for all clients), such that if I add or remove 1 product for all clients, I should not change the select statement.完美的解决方案也是灵活的,因为 select 语句应该计算每个客户的不同产品的数量(它们对于所有客户总是相同的),这样如果我为所有客户添加或删除 1 个产品,我不应更改 select 语句。

MYSQL Edition MYSQL版

Here is the query.这是查询。 The joined query generates RowNumber (1,2,3,...) for each product inside each client group using User Defined Variables MySQL feature .连接查询使用用户定义的变量 MySQL 功能为每个客户端组内的每个产品生成 RowNumber (1,2,3,...)。 The outer query forms a PIVOT table using GROUP BY and CASE with Row Numbers from the inner table.外部查询使用GROUP BY和 CASE 与来自内部表的行号形成一个 PIVOT 表。 If you need to variable products column count then consider creating this query dynamic adding MAX(CASE WHEN p.RowNum=X THEN p.Product END) as ProductX to the select list.如果您需要更改产品列数,请考虑创建此查询动态添加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 SQLFiddle 演示

SQL Server Edition: SQL Server 版本:

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 SQLFiddle 演示

The answers seem to address both MySQL and SQL Server, so I am adding a further SQL Server Answer here and the logic might work in MySQL too.答案似乎同时解决了 MySQL 和 SQL Server,所以我在这里添加了一个进一步的 SQL Server 答案,该逻辑也可能适用于 MySQL。

Below is a dynamic SQL version in Transact SQL for MS SQL Server.下面是 MS SQL Server 的 Transact SQL 中的动态 SQL 版本。

This enables you to get the same result without having to explicitly write out every column you need in the resultant table as the CASE WHEN solution.这使您能够获得相同的结果,而不必像 CASE WHEN 解决方案那样在结果表中明确写出您需要的每一列。 The CASE WHEN is nice and simple for a few columns, but I recently had a similar scenario that pivoted to around 200 columns. CASE WHEN 对几列来说很好而且很简单,但我最近有一个类似的场景,它旋转到大约 200 列。

For dynamic SQL you essentially compile the query that you want as a string using generated variables and then execute it.对于动态 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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM