MySQL中相邻列的值查询

时间:2012-11-20 20:35:49

标签: mysql select join union entity-attribute-value

我相信这种性质的所有问题都是这样开始的...... 我有两个表fieldsdataFields描述了(不存在的)表的列名,data包含该(不存在的)表的数据。像这样......

字段:

    +----+---------+-------------+---------------+-------+----------+
    | ID | F_ORDER | NAME        | LABEL         | VALUE | TYPE     |
    +----+---------+-------------+---------------+-------+----------+
    |  1 |       1 | IS_EMPLOYEE | Region        |       | checkbox |
    |  2 |       3 | EM_AVATAR   | Avatar        |       | avatar   |
    |  3 |       4 | EM_JOBTITLE | Job Title     |       | text     |
    |  4 |       5 | EM_COMPANY  | Company       |       | text     |
    |  5 |       6 | EM_PHONE    | Phone         |       | text     |
    |  6 |       2 | EM_ORDER    | Display Order | 5     | text     |
    +----+---------+-------------+---------------+-------+----------+

数据:

    +-----+----------+---------+--------------------------------------+
    | ID  | FIELD_ID | USER_ID | VALUE                                |
    +-----+----------+---------+--------------------------------------+
    |   5 |        1 |       1 | YES                                  |
    |   6 |        2 |       1 |                                      |
    |   7 |        3 |       1 | Owner                                |
    |   8 |        4 |       1 | Acme, Inc.                           |
    |   9 |        5 |       1 | 123-456-7987                         |
    | 150 |        5 |      31 | 123-623-5555                         |
    | 149 |        4 |      31 | Acme, Inc.                           |
    | 148 |        3 |      31 | Sales and Customer Support           |
    | 147 |        2 |      31 |                                      |
    | 146 |        1 |      31 | YES                                  |
    |  26 |        1 |       6 | NO                                   |
    |  27 |        2 |       6 | http://example.com/avi/avi.jpeg      |
    |  28 |        3 |       6 | CEO                                  |
    |  29 |        4 |       6 | Acme                                 |
    |  30 |        5 |       6 | (123) 734-5555                       |
    |  31 |        1 |       7 | NO                                   |
    |  32 |        2 |       7 | http://example.com/avi/avi.jpeg      |
    |  33 |        3 |       7 | VP, Services                         |
    |  34 |        4 |       7 | Acme                                 |
    |  35 |        5 |       7 | (913) 963-5555                       |
    |  36 |        1 |      14 | NO                                   |
    |  37 |        2 |      14 | http://example.com/avi/avi.jpeg      |
    |  38 |        3 |      14 | Senior Accountant                    |
    |  39 |        4 |      14 | Acme                                 |
    |  40 |        5 |      14 | (123) 213-5555                       |
    |  41 |        1 |      10 | NO                                   |
    |  42 |        2 |      10 | http://example.com/avi/avi.jpeg      |
    |  43 |        3 |      10 | President                            |
    |  44 |        4 |      10 | Acme                                 |
    |  45 |        5 |      10 | (123) 734-5555                       |
    |  46 |        1 |      12 | NO                                   |
    |  47 |        2 |      12 | http://example.com/avi/avi.jpeg      |
    |  48 |        3 |      12 | Services Supervisor                  |
    |  49 |        4 |      12 | Acme                                 |
    |  50 |        5 |      12 | (123) 573-5555                       |
    |  51 |        1 |      11 | NO                                   |
    |  52 |        2 |      11 | http://example.com/avi/avi.jpeg      |
    |  53 |        3 |      11 | Operations Supervisor                |
    |  54 |        4 |      11 | Acme                                 |
    |  55 |        5 |      11 | (123) 259-5555                       |
    |  56 |        1 |       8 | NO                                   |
    |  57 |        2 |       8 | http://example.com/avi/avi.jpeg      |
    |  58 |        3 |       8 | General Information                  |
    |  59 |        4 |       8 | Acme                                 |
    |  60 |        5 |       8 | (123) 213-5555                       |
    |  61 |        1 |       9 | NO                                   |
    |  62 |        2 |       9 | http://example.com/avi/avi.jpeg      |
    |  63 |        3 |       9 | VP, Sales                            |
    |  64 |        4 |       9 | Acme                                 |
    |  65 |        5 |       9 | (123) 210-5555                       |
    +-----+----------+---------+--------------------------------------+

我正在寻找的查询的基本措辞是:我希望所有员工的所有信息(IS_EMPLOYEE = "YES")并按其显示顺序列(EM_ORDER)排序。

到目前为止,我的查询无处可寻。我得到的结果有点像这样:

    +--------+------------+------------+-------+---------+-------+
     ID        FIELD_ID    NAME         LABEL   TYPE       VALUE
    +--------+------------+------------+-------+---------+-------+
     7         1           IS_EMPLOYEE  Region  checkbox   YES
    +--------+------------+------------+-------+---------+-------+

我需要的是这样的结果:

    +-------+------------+---------+-----------+----------+-------------+--------+
     USER_ID IS_EMPLOYEE  EM_AVATAR EM_JOBTITLE EM_COMPANY EM_PHONE      EM_ORDER   
    +-------+------------+---------+-----------+----------+-------------+--------+
     6       YES           http://  CEO         Acme       123-123-555   5 
    +-------+------------+---------+-----------+----------+-------------+--------+ 

当然,我正试图将它作为一个可用的数组($results['user_id']['jobtitle']等)重新用于PHP。我可以得到一切并使用PHP完成它,但我正在尝试学习MySQL,我认为这是一个比做几个foreach块更快的方法...虽然我可能是错的。

提前感谢您的帮助。

2 个答案:

答案 0 :(得分:2)

您的数据表以Entity-Attribute-Value方式组织,这不是存储关系数据的有效方式。毫无疑问,使用SQL将其检索到每个属性只有一列的传统行中是很尴尬的。

要使用SQL执行此操作,您基本上需要执行数据透视查询。这很难写,而且执行效率非常低。

最好为员工提取所有数据:

SELECT e.*, f.label
FROM Data AS is_employee
JOIN Data AS e USING (user_id)
JOIN Fields AS f ON e.field_id = f.id
WHERE (is_employee.field_id, is_employee.value) = (1, 'Yes');

然后将其重组为PHP代码中的关联数组集合:

while ($row = $stmt->fetch()) {
  $data[ $row['user_id'] ][ $row['label'] ] = $row['value'];
}

答案 1 :(得分:1)

这基本上是PIVOT,但MySQL没有PIVOT函数,所以你可以使用聚合函数和CASE语句来复制它。

静态版本是指您想知道要转换为列的所有值(这些是您的字段名称):

select 
  d.user_id,
  max(case when f.name = 'IS_EMPLOYEE' then d.value else null end) IS_EMPLOYEE,
  max(case when f.name = 'EM_AVATAR' then d.value else null end) EM_AVATAR,
  max(case when f.name = 'EM_JOBTITLE' then d.value else null end) EM_JOBTITLE,
  max(case when f.name = 'EM_COMPANY' then d.value else null end) EM_COMPANY,
  max(case when f.name = 'EM_PHONE' then d.value else null end) EM_PHONE,
  max(case when f.name = 'EM_ORDER' then d.value else null end) EM_ORDER
from data d
left join fields f
  on f.id = d.FIELD_ID
group by d.user_id

请参阅SQL Fiddle with Demo

结果:

| USER_ID | IS_EMPLOYEE |                       EM_AVATAR |                EM_JOBTITLE | EM_COMPANY |       EM_PHONE | EM_ORDER |
---------------------------------------------------------------------------------------------------------------------------------
|       1 |         YES |                          (null) |                      Owner | Acme, Inc. |   123-456-7987 |   (null) |
|       6 |          NO | http://example.com/avi/avi.jpeg |                        CEO |       Acme | (123) 734-5555 |   (null) |
|       7 |          NO | http://example.com/avi/avi.jpeg |               VP, Services |       Acme | (913) 963-5555 |   (null) |
|       8 |          NO | http://example.com/avi/avi.jpeg |        General Information |       Acme | (123) 213-5555 |   (null) |
|       9 |          NO | http://example.com/avi/avi.jpeg |                  VP, Sales |       Acme | (123) 210-5555 |   (null) |
|      10 |          NO | http://example.com/avi/avi.jpeg |                  President |       Acme | (123) 734-5555 |   (null) |
|      11 |          NO | http://example.com/avi/avi.jpeg |      Operations Supervisor |       Acme | (123) 259-5555 |   (null) |
|      12 |          NO | http://example.com/avi/avi.jpeg |        Services Supervisor |       Acme | (123) 573-5555 |   (null) |
|      14 |          NO | http://example.com/avi/avi.jpeg |          Senior Accountant |       Acme | (123) 213-5555 |   (null) |
|      31 |         YES |                          (null) | Sales and Customer Support | Acme, Inc. |   123-623-5555 |   (null) |

如果您将未知数量的值转换为列,则可以使用预准备语句动态生成sql。

SET @sql = NULL;
SELECT
  GROUP_CONCAT(DISTINCT
    CONCAT(
      'max(case when f.name = ''',
      name,
      ''' then d.value end) AS ',
      name
    )
  ) INTO @sql
FROM fields;

SET @sql = CONCAT('SELECT d.user_id, ', @sql, ' 
                  from data d
                  left join fields f
                    on f.id = d.FIELD_ID
                   GROUP BY d.user_id');

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

请参阅SQL Fiddle with Demo

两者都会产生相同的结果。

然后,您可以添加WHERE子句以过滤掉任何不需要的行。