我有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?
答案 0 :(得分: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文档中的任何地方都没有记录(非标准或非标准)它应该按预期工作。所以,它现在可能正在工作,但是当你升级到MySQL版本X(优化器能够聪明地理解里面的ORDER BY
时,它可能不在不太遥远的未来工作派生表是多余的,可以删除。)
尝试使用此查询:
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;
(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 ;
答案 1 :(得分:2)
为什么你需要一个子查询才能加入表名?
对我来说,为什么你的查询中有一个GROUP BY
子句也不明显。 GROUP BY
通常与MAX
或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
如果您需要显示此人,最近一次访问以及最近一次访问中的注释,您将不得不再次将访问表明确地加入到摘要查询(虚拟表)中。 / p>
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(年)... 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中没有行固有顺序的想法。