7

我有sqlfiddle中定义的这些表和查询。

首先,我的问题是将显示 LEFT JOINed 访问行与最新年份的人分组。我使用子查询解决了。

现在我的问题是该子查询没有使用visits表上定义的 INDEX。这导致我的查询几乎无限期地在每个大约有 15000 行的表上运行。

这是查询。目标是在访问表中列出每个人的最新(按年份)记录。

不幸的是,在大表上它变得非常缓慢,因为它没有在子查询中使用 INDEX。

SELECT *
FROM people
LEFT JOIN (
  SELECT *
  FROM visits
  ORDER BY visits.year DESC
) AS visits
ON people.id = visits.id_people
GROUP BY people.id

有谁知道如何强制 MySQL 使用visits表上已经定义的 INDEX?

4

2 回答 2

4

您的查询:

SELECT *
FROM people
LEFT JOIN (
  SELECT *
  FROM visits
  ORDER BY visits.year DESC
) AS visits
ON people.id = visits.id_people
GROUP BY people.id;
  • 首先,使用非标准 SQL 语法(出现在SELECT列表中的项目不是GROUP BY子句的一部分,不是聚合函数并且不附加在分组项目上)。这可能会给出不确定(半随机)的结果。

  • 其次,(为了避免不确定的结果)您ORDER BY在子查询中添加了一个子查询(非标准或非标准),MySQL 文档中的任何地方都没有记录它应该按预期工作。因此,它可能现在可以工作,但在不久的将来可能无法ORDER BY工作,当您升级到 MySQL 版本 X 时(优化器将足够聪明地理解派生表内部是多余的并且可以被消除)。

尝试使用此查询:

SELECT 
    p.*, v.*
FROM 
    people AS p
  LEFT JOIN 
        ( SELECT 
              id_people
            , MAX(year) AS year
          FROM
              visits
          GROUP BY
              id_people
         ) AS vm
      JOIN
          visits AS v
        ON  v.id_people = vm.id_people
        AND v.year = vm.year 
    ON  v.id_people = p.id;

SQL小提琴

一个复合索引(id_people, year)将有助于提高效率。


一种不同的方法。如果您先将人员限制在合理的限制(例如 30 人)然后加入visits表格,则效果很好:

SELECT 
    p.*, v.*
FROM 
    ( SELECT *
      FROM people
      ORDER BY name
        LIMIT 30
    ) AS p
  LEFT JOIN 
    visits AS v
      ON  v.id_people = p.id
      AND v.year =
    ( SELECT 
          year
      FROM
          visits
      WHERE
          id_people = p.id
      ORDER BY
          year DESC
        LIMIT 1
     )  
ORDER BY name ;
于 2012-09-03T13:39:32.917 回答
2

当您只需要一个用于加入的表名时,为什么还要有一个子查询?

我也不清楚为什么您的查询中有一个GROUP BY子句。GROUP BY通常与MAXor之类的聚合函数一起使用COUNT,但您没有这些。

这个怎么样?它可能会解决您的问题。

    SELECT people.id, people.name, MAX(visits.year) year
      FROM people
      JOIN visits ON people.id = visits.id_people
  GROUP BY people.id, people.name

如果您需要显示此人、最近一次访问以及最近一次访问的注释,您将不得不再次将访问表显式连接到摘要查询(虚拟表),就像这样。

SELECT a.id, a.name, a.year, v.note
  FROM (
         SELECT people.id, people.name, MAX(visits.year) year
          FROM people
          JOIN visits ON people.id = visits.id_people
      GROUP BY people.id, people.name
  )a
  JOIN visits v ON (a.id = v.id_people and a.year = v.year)

去小提琴:http ://www.sqlfiddle.com/#!2/d67fc/20/0

如果您需要向从未访问过的人展示一些东西,您应该尝试JOIN在我的声明中使用 切换项目LEFT JOIN

正如其他人所写,ORDER BY子查询中的子句不是标准的,并且会产生不可预测的结果。在您的情况下,它使优化器感到困惑。

编辑GROUP BY是一把大锤子。除非你需要,否则不要使用它。而且,除非您在查询中使用聚合函数,否则不要使用它。

请注意,如果您在最近一年的某个人的访问中拥有多行,则此查询将为该人生成多行,为该年的每次访问生成一行。如果您只需要每人一行,并且您不需要访问记录,那么第一个查询就可以解决问题。如果您在一年内对一个人进行了多次访问,而您只需要最近的一次,则必须确定哪一行是最新的。通常它将是具有最高 ID 号的那个,但只有您自己知道这一点。在这种情况下,我在您的小提琴中添加了另一个人。http://www.sqlfiddle.com/#!2/4f644/2/0

这是复杂的。但是:如果您的visits.id 编号是自动分配的并且它们始终按时间顺序排列,您可以简单地报告最高的访问id,并保证您将获得最近的一年。这将是一个非常有效的查询。

SELECT p.id, p.name, v.year, v.note
  FROM (
         SELECT id_people, max(id) id
          FROM visits
      GROUP BY id_people
  )m
  JOIN people p ON (p.id = m.id_people)
  JOIN visits v ON (m.id = v.id)

http://www.sqlfiddle.com/#!2/4f644/1/0 但这不是您的示例设置方式。因此,您需要另一种方法来消除您最近访问的歧义,这样您每人只需获得一行。我们可以使用的唯一技巧是使用最大的 id 号。

因此,根据这个定义,我们需要从您的表中获取最新的 visit.id 编号列表。该查询使用 MAX(year)...GROUP BY(id_people) 嵌套在 MAX(id)...GROUP BY(id_people) 查询中来做到这一点。

  SELECT v.id_people,
         MAX(v.id) id
    FROM (
         SELECT id_people, 
                MAX(year) year
           FROM visits
          GROUP BY id_people
         )p
    JOIN visits v ON (p.id_people = v.id_people AND p.year = v.year)
   GROUP BY v.id_people

整体查询(http://www.sqlfiddle.com/#!2/c2da2/1/0)是这样的。

SELECT p.id, p.name, v.year, v.note
  FROM (
      SELECT v.id_people,
             MAX(v.id) id
        FROM (
             SELECT id_people, 
                    MAX(year) year
               FROM visits
              GROUP BY id_people
             )p
        JOIN visits v ON (     p.id_people = v.id_people 
                           AND p.year = v.year)
       GROUP BY v.id_people
      )m
   JOIN people p ON (m.id_people = p.id)
   JOIN visits v ON (m.id = v.id)

SQL 中的消歧是一项很难学习的事情,因为您需要一些时间来理解 DBMS 中的行没有固有顺序的想法。

于 2012-09-03T13:41:18.547 回答