如何在 LEFT JOIN 上使用 LIMIT?
Posted
技术标签:
【中文标题】如何在 LEFT JOIN 上使用 LIMIT?【英文标题】:How to use LIMIT on LEFT JOIN? 【发布时间】:2018-12-29 12:51:10 【问题描述】:我有 2 个名为 categories
和 products
的表具有这种关系:
categories.category_id
= products.product_category_id
我想显示包含 2 个产品的所有类别!
[
[category 1] =>
[
'category_id' => 1,
'category_title' => 'category_title 1',
[products] => [
'0' => [
'product_category_id' => 1,
'product_id' => 51,
'product_title' => 'product_title 1',
],
'1' => [
'product_category_id' => 1,
'product_id' => 55,
'product_title' => 'product_title 2',
]
]
],
[category 2] =>
[
'category_id' => 2,
'category_title' => 'category_title 2',
[products] => [
'0' => [
'product_category_id' => 2,
'product_id' => 32,
'product_title' => 'product_title 3',
],
'1' => [
'product_category_id' => 2,
'product_id' => 33,
'product_title' => 'product_title 4',
]
]
],
...
]
我 Laravel 雄辩,我可以使用这样的东西:
$categories = Category::with(['products' => function($q)
$q->limit(2)
])->get();
但是我没有使用 Laravel,我需要纯 SQL 代码! 我试过这段代码:
SELECT
CT.category_id,
PT.product_category_id,
CT.category_title,
PT.product_id,
PT.product_title
FROM
categories CT
LEFT JOIN(
SELECT
*
FROM
products
LIMIT 2
) AS PT
ON
CT.category_id = PT.product_category_id
WHERE
CT.category_lang = 'en'
但是这段代码有问题!似乎 mysql 首先在 products
表中获得 2 行,然后尝试将 LEFT JOIN
从 categories
到那 2 行!这导致返回产品的null
值(如果我删除LIMIT
它效果很好,但我需要LIMIT
)
我也测试过这段代码:(UPDATED)
SELECT
CT.category_id,
PT.product_category_id,
CT.category_title,
PT.product_id,
PT.product_title
FROM
categories CT
LEFT JOIN(
SELECT
*
FROM
products
WHERE
product_category_id = CT.category_id
LIMIT 5
) AS PT
ON
CT.category_id = PT.product_category_id
WHERE
CT.category_lang = 'en'
但是我收到了这个错误:
1054 - “where 子句”中的未知列“CT.category_id”
我无法在子查询中访问CT.category_id
。
什么是最好的解决方案?
【问题讨论】:
你使用的是什么版本的 MariaDB? @wchiquito 服务器类型:MariaDB 服务器版本:10.1.29-MariaDB - mariadb.org 二进制分发协议版本:10 是的,所以问题是您不能从 JOIN 子查询的基本查询中引用列。我会删除我上面所有的 cmets 来清理东西,随意做同样的事情。 【参考方案1】:在 MySQL 中,您需要使用变量。基本思路是:
select p.*
from (select p.*,
(@rn := if(@c = product_category_id, @rn + 1,
if(@c := product_category_id, 1, 1)
)
) as rn
from (select p.*
from products p
order by p.product_category_id
) p cross join
(select @c := -1, @rn := 0) params
) p
where rn <= 2;
您可以将其用作子查询,然后加入其余的类别信息。
Here 是一个数据库如果此代码工作正常。
在 MySQL 8+/MariaDB 10.2+ 中,这更容易写成:
select p.*
from (select p.*,
row_number() over (partition by product_category_id order by product_category_id) as seqnum
from products p
) p
where seqnum <= 2;
注意:您可以通过调整两个查询中的 order by
子句来指定您想要的 2 个产品(例如,按字母顺序排列的前两个)。
编辑:
在 MariaDB 10.2 中,您显然不能使用子查询进行排序,因此适当的查询是:
select p.*
from (select p.*,
(@rn := if(@c = product_category_id, @rn + 1,
if(@c := product_category_id, 1, 1)
)
) as rn
from products p cross join
(select @c := -1, @rn := 0) params
order by p.product_category_id
) p
where rn <= 2;
Here 是 dbfiddle。
你也可以用这个方法:
select p.*
from products p
where (select count(*)
from products p2
where p2.product_category_id = p.product_category_id and p2.id <= p.id
) <= 2;
我真的不推荐这样做,因为相关子查询的性能通常比order by
差得多。但是,如果每个类别没有太多产品,那么这会很好(使用正确的索引)。
【讨论】:
当我使用第一个时,我遇到了这个错误:#1064 - 你的 SQL 语法有错误;检查与您的 MariaDB 服务器版本相对应的手册,以了解在第 14 行的 '' 附近使用的正确语法 当我使用第二个代码时,我遇到了这个错误: select p.* from (select p.*, row_number() over (partition by category_id order by category_id) as seqnum from products p) p其中 seqnum @soheilyo 第二个查询需要 MariaDB 10.2+。 @JonasStaudenmeir 我的是:服务器版本:10.1.29-MariaDB - mariadb.org 二进制分发,你对第一个查询有什么想法? @soheilyo。 . .第一个查询中有一条无关的行。我已将其删除。以上是关于如何在 LEFT JOIN 上使用 LIMIT?的主要内容,如果未能解决你的问题,请参考以下文章
在 sql 查询中使用 LIMIT 和 ORDER BY 和 LEFT JOIN
使用 LEFT JOIN 和 ORDER BY...LIMIT 查询慢,使用 Filesort
MySQL left join with 'b's limit
一起去大厂系列针对left join以及limit的两条优化小技巧
一起去大厂系列针对left join以及limit的两条优化小技巧
MySQL的几种连接 join/inner join/cross join/逗号/left join/right join/natural join