【问题标题】:Is there a more efficient way than doing several JOINS (MySQL)?有没有比做几个 JOINS (MySQL) 更有效的方法?
【发布时间】:2009-10-14 17:02:41
【问题描述】:

我有三张桌子:

  1. USER: user_id (pk); username
  2. FIELD: field_id (pk); name
  3. METADATA: metadata_id (pk); field_id (indx); user_id (indx); value

这样做的原因是应用程序允许为每个用户创建自定义字段。为了显示用户的所有信息,我正在构建一个动态查询(通过 PHP),最终看起来像这样:

SELECT
  u.username, m1.value AS m1value, m2.value AS m2value
FROM user AS u
LEFT JOIN metadata AS m1
  ON (u.user_id=m1.user_id AND m1.field_id=1)
LEFT JOIN metadata AS m2
  ON (u.user_id=m2.user_id AND m2.field_id=2)

此示例只有 2 个用户元数据字段,但您会知道如果有十几个字段会是什么样子。

还有其他更好的方法来编写这个查询吗?随着用户和元数据字段的增长,我担心此查询的性能。

编辑: 我希望在返回的结果中每行有一个用户。

【问题讨论】:

    标签: sql mysql query-optimization


    【解决方案1】:

    为什么不一次抓住它们?

    SELECT u.user_id,u.username, m.field_id,m.value FROM user u
    LEFT JOIN metadata m
    ON u.user_id=m.user_id 
    WHERE 1 ORDER BY user_id
    

    或者对于特定用户:

    SELECT u.user_id,u.username, m.field_id,m.value FROM user u
    LEFT JOIN metadata m
    ON u.user_id=m.user_id 
    WHERE user_id = ? ORDER BY user_id
    

    除了被索引之外,请确保两个表之间的 user_id 的类型和长度完全相同,否则您最终仍会进行表扫描。

    您的服务器代码是什么语言?

    每个用户获取一行的简单方法(有点)是在您的循环中返回行,检查每个 user_id 是否与最后一个相同。如果没有,则新行。

    while ( $row = $sth->fetch_object() ) {
      $previous_user_id = '';
      if ( $row->user_id != $previous_user_id ) {
        # new row
      } else {
        # not new row
      }
      $previous_user_id = $row->user_id;
    }
    

    【讨论】:

    • 这是可行的,但理想情况下我希望每个用户返回一行。
    • Bart 以这种方式返回它们然后让应用程序旋转它们可能会更快。您也不再需要动态构建查询。您也不必事先发送代码来确定要添加到动态查询中的自定义字段数量。
    • 在对两种方式进行了一些测试之后,我发现它们实际上在速度方面的表现几乎相同。不过,这个使用 PHP 执行枢轴的示例要快一点。
    【解决方案2】:

    您必须有两个查询。一个用于用户检索(SELECT * FROM users),另一个用于提取用户的自定义字段(SELECT * FROM fields WHERE users_id = user_id)。

    在某些情况下,您可以通过一个查询来完成它...告诉我们更多关于您所追求的结果的确切信息。

    【讨论】:

      【解决方案3】:

      通常每行返回一个元数据元素,如下所示:

      SELECT u.username, mi.field_id, m1.value
      FROM user AS u
      LEFT JOIN metadata AS m1 ON u.user_id = m1.user_id
      

      这应该对成千上万的用户执行良好。

      【讨论】:

      • 我计划合作的目标用户数是 50,000。我猜会有 6 个左右的元数据字段。
      • 如果您知道有六个,为什么不将这些元数据字段移到主用户表中呢?这会表现得更好,而且 6 个字段的磁盘空间并不多,尤其是在它们为空的情况下。带有空字符串的 varchar(4000) 在磁盘上占用 2 个字节。
      【解决方案4】:

      你可以试试...

      SELECT
          u.username,
         (SELECT TOP 1 m.value
          FROM metadata m
          WHERE u.user_id=m.user_id AND m.field_id=1),
         (SELECT TOP 1 m.value
          FROM metadata m
          WHERE u.user_id=m.user_id AND m.field_id=2)
      FROM user AS u
      

      ...但性能可能与您所拥有的相似(并且可能更差)。如果您遇到性能问题,请检查以确保 user_id 和 field_id 都已编入索引。

      【讨论】:

      • 性能说明很好。谢谢。我确实有一个唯一的 user_id-field_id 复合键。
      【解决方案5】:

      (正如评论者指出的那样,这在 MySQL 中无效,所以,抱歉。但如果您有兴趣:)

      按照上面的建议对元数据表执行一次 JOIN,然后使用 PIVOT 将每个用户的多行更改为一行多列,每个字段一列。我认为这在 SQL Server 2005 及更高版本中有效。

      【讨论】:

      • 不幸的是,他正在寻找 MySQL 而不是 MS SQL
      猜你喜欢
      • 1970-01-01
      • 2013-06-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-04-13
      • 1970-01-01
      相关资源
      最近更新 更多