MySQL列名称作为与联结表的多对多关系的字段名称

时间:2013-03-21 21:11:44

标签: mysql sql many-to-many pivot pivot-table

这里有点复杂的一点。我一直在寻找将结果用作列名的方法,虽然看起来可能,但我发现的所有示例都使用了非常简单的表格设计。

我得到的是3张桌子。

我们有一个'订单'表,然后可以有'order_extras'。 'extras'表存储附加组件的名称和价格,'order_extras'基本上包含主键,订单ID和额外ID。

粗略的图形表示如下:enter image description here

以此为例,我们假设'extras'表中填充了3个额外的项目,在此阶段名称和价格无关紧要。

我想要做的是获取所有订单,但每个额外项目的名称都有额外的列。如果该商品已被购买(也在order_extras表中链接),它将显示价格,否则它将为空/ null。

这甚至可能吗?我一直在寻找数据透视表,但有关这类事情的信息似乎有些不稳定。任何信息或建议将不胜感激!

示例数据

附加功能:

+----+------------------+--------+
| id |       name       | price  |
+----+------------------+--------+
|  1 | Insurance        | 59.95  |
|  2 | Lifetime Updates | 79.95  |
|  3 | Phone Support    | 124.95 |
+----+------------------+--------+

订单:

+----+------------+
| id |  customer  |
+----+------------+
|  1 | John Smith |
|  2 | Bob Newbie |
|  3 | Bill Jobs  |
|  4 | Ray Stantz |
+----+------------+

order_extras:

+----+----------+----------+
| id | order_id | extra_id |
+----+----------+----------+
|  1 |        4 |        2 |
|  2 |        3 |        1 |
|  3 |        3 |        3 |
|  4 |        1 |        1 |
+----+----------+----------+

期望的输出:

+----------+----------------+-----------+------------------+---------------+
| order.id | order.customer | Insurance | Lifetime Updates | Phone Support |
+----------+----------------+-----------+------------------+---------------+
|        1 | John Smith     | 59.95     | 0                | 0             |
|        2 | Bob Newbie     | 0         | 0                | 0             |
|        3 | Bill Jobs      | 59.95     | 0                | 124.95        |
|        4 | Ray Stantz     | 0         | 79.95            | 0             |
+----------+----------------+-----------+------------------+---------------+

2 个答案:

答案 0 :(得分:2)

不幸的是,MySQL没有 pivot 函数,但可以使用带有CASE表达式的聚合函数进行复制。

如果您有一个已知数量的extras,那么您可以对查询进行硬编码:

select o.id,
  o.customer,
  max(case when e.name = 'Insurance' then e.price else 0 end) Insurance,
  max(case when e.name = 'Lifetime Updates' then e.price else 0 end) `Lifetime Updates`,
  max(case when e.name = 'Phone Support' then e.price else 0 end) `Phone Support`
from orders o
left join order_extras oe
  on o.id = oe.order_id
left join extras e
  on oe.extra_id = e.id
group by o.id, o.customer

请参阅SQL Fiddle with Demo

对于您的情况,您似乎将拥有未知数量的值。如果是这种情况,那么您将需要使用预准备语句来生成动态sql:

SET @sql = NULL;
SELECT
  GROUP_CONCAT(DISTINCT
    CONCAT(
      'max(CASE WHEN e.name = ''',
      name,
      ''' THEN e.price else 0 END) AS `',
      name, '`'
    )
  ) INTO @sql
FROM extras;

SET @sql 
  = CONCAT('SELECT o.id,
                o.customer, ', @sql, ' 
            from orders o
            left join order_extras oe
              on o.id = oe.order_id
            left join extras e
              on oe.extra_id = e.id
            group by o.id, o.customer');

PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

请参阅SQL Fiddle with Demo

这两个版本都会给出结果:

| ID |   CUSTOMER | INSURANCE | LIFETIME UPDATES | PHONE SUPPORT |
------------------------------------------------------------------
|  1 | John Smith |     59.95 |                0 |             0 |
|  2 | Bob Newbie |         0 |                0 |             0 |
|  3 |  Bill Jobs |     59.95 |                0 |        124.95 |
|  4 | Ray Stantz |         0 |            79.95 |             0 |

答案 1 :(得分:1)

试试这个。

    select o.id , o.customer ,
    max(if(e.name = 'Insurance' , round(e.price,2), 0)) as Insurance,
    max(if(e.name = 'Lifetime Updates' , round(e.price,2), 0)) as Lifetime_Updates,
    max(if(e.name = 'Phone Support' , round(e.price,2), 0)) as Phone_Support

     from orders o
     left join order_extras oe
     on o.id = oe.order_id
    left join Extras e
    on oe.extra_id = e.id

    group by o.id, o.customer

DEMO HERE

输出是:

   ID   CUSTOMER    INSURANCE   LIFETIME_UPDATES    PHONE_SUPPORT
    1   John Smith   59.95              0                0
    2   Bob Newbie     0                0                0
    3   Bill Jobs    59.95              0              124.95
    4   Ray Stantz     0              79.95              0