【问题标题】:MySQL limit on 2nd join in queryMySQL 对第二次加入查询的限制
【发布时间】:2019-04-22 18:07:47
【问题描述】:

我不确定如何限制查询中的第二次加入。在我准备的fiddle 中,我需要限制结果中每个用户的行数。

表a:

id | word
----------
1  | xyz
2  | zzz

表b:

name  | word
------------
Peter | xyz
John  | xyz
Jane  | xyz

表c:

name  | product
------------
Peter | blah
Peter | blah2
Peter | blah3
Peter | blah4
Peter | blah5
Peter | blah6
John | hello
John | world
John | blah

期望的结果:表 b 的前两个条目(用户 Peter 和 John)和每个用户在表 c 上加入的前两个条目。

name  | word | product
----------------------
Peter | xyz | blah
Peter | xyz | blah2
John  | xyz | hello
John  | xyz | world

我当前的查询是

select c.name, first_join.word, c.product from (
select b.* from a
left join b on b.word=a.word
where a.id=1
limit 2
) first_join
left join c on c.name=first_join.name

它产生结果:

name  | word | product
------------------------------
Peter | xyz  | blah
Peter | xyz  | blah2
Peter | xyz  | blah3
Peter | xyz  | blah4
Peter | xyz  | blah5
Peter | xyz  | blah6
John  | xyz  | hello
John  | xyz  | world
John  | xyz  | blah

你有什么想法吗?我找到了另一个讨论这个问题的线程,但我无法将其映射到我的案例。我知道结构并不完美,但我不允许更改表 c 的设计。

【问题讨论】:

  • 请同时包含有问题的代码。
  • 如果我有同样的任务,我会像你一样使用子查询。出于所有意图和目的,子查询与联接相同,只是您可以限制子查询中的记录数量。如果您的查询有效并且您对它没问题 - 不要再犹豫了。即使你要重写它,你最终也会得到相同的性能。
  • 你可以尝试使用union运算符

标签: mysql join limit


【解决方案1】:

您可以使用存储过程,选择不同的名称,遍历结果并为每个名称选择 2 行。

以下存储过程为您提供所需的结果(这适用于 MySQL 5.6 服务器,但不适用于 sqlfiddle):

drop procedure if exists fetch_names;

DELIMITER $$

CREATE PROCEDURE fetch_names()

    BEGIN 
    DECLARE v_ready INTEGER DEFAULT 0;
    DECLARE v_name varchar(100) DEFAULT "";
    -- first fetch all distinct names
    DECLARE names_cursor CURSOR FOR select distinct c.name from c;
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_ready = 1;

    -- use a temporary table to store results
    CREATE TEMPORARY TABLE names_temp ( `name` varchar(255),  `word` varchar(255),  `product` varchar(255));

    OPEN names_cursor;

    -- loop through all names
    get_names: LOOP
    FETCH names_cursor INTO v_name;

    IF v_ready = 1 THEN 
     LEAVE get_names;
     END IF;

    -- get 2 results for each name (you may add some order parameter here)
    INSERT INTO names_temp SELECT c.name, b.word, c.product from a
    LEFT JOIN b on a.word = b.word
    LEFT JOIN c on b.name = c.name
    WHERE c.name = v_name
    LIMIT 2;

    END LOOP get_names;
    CLOSE names_cursor;

    -- output the collected result and drop the temporary table
    SELECT * FROM names_temp;
    DROP TEMPORARY TABLE IF EXISTS names_temp;

END$$
DELIMITER ;

-- call the procedure and get the results
call fetch_names();

【讨论】:

  • 非常感谢。这按预期工作。我不知道编写这样的行为需要付出这么大的努力。认为它可以用另一个子查询或其他一些单行来完成。我从未使用过程序,非常感谢代码中的 cmets。
【解决方案2】:

让我们剖析您的查询:

SELECT b.* FROM a
LEFT JOIN b ON b.word=a.word
WHERE a.id=1
LIMIT 2
-- This sub-query returns the following result:
+-------+------+
| name  | word |
+-------+------+
| Peter | xyz  |
| John  | xyz  |
+-------+------+

如果没有来自子查询的完整语法,您的主要语法将如下所示:

SELECT c.name, first_join.word, c.product FROM first_join
LEFT JOIN c ON c.name=first_join.name;

让我们将c.name, first_join.word, c.product 替换为*。然后你会得到这样的结果:

+-------+------+-------+---------+
| name  | word | name  | product |
+-------+------+-------+---------+
| Peter | xyz  | Peter | blah    |
| Peter | xyz  | Peter | blah2   |
| Peter | xyz  | Peter | blah3   |
| Peter | xyz  | Peter | blah4   |
| Peter | xyz  | Peter | blah5   |
| Peter | xyz  | Peter | blah6   |
| John  | xyz  | John  | hello   |
| John  | xyz  | John  | world   |
| John  | xyz  | John  | blah    |
+-------+------+-------+---------+

现在,您可以在这里问自己这是否是您想要的?如果不是,您需要确保您了解您在此处尝试执行的操作的逻辑。例如,为什么您的first_join 子查询返回两行但当您LEFT JOIN 它返回来自table c 的所有行? 嗯,答案是因为你的ON c.name=first_join.name。它匹配 table c 中出现的所有 peter 和 john ,无论您在子查询中施加的限制如何。如果你对外部查询设置限制,它也不会起作用。

tl;博士 如果您使用的是 MySQL 8.0,您可以尝试以下查询:

SELECT name,word,product FROM
(SELECT 
    b.name, b.word,c.product,
    ROW_NUMBER() OVER (PARTITION BY c.name ORDER BY c.name DESC) AS wRow
FROM 
a JOIN
b ON a.word=b.word JOIN
c ON c.name=b.name
WHERE a.id=1) r
WHERE wRow IN (1,2)
ORDER BY name DESC;

Fiddle

另外两个例子:

-- First: by using INNER JOIN and UNION (as suggested in the comment).
SELECT b.name,b.word,r.product FROM a JOIN b ON a.word=b.word JOIN
((SELECT * FROM c WHERE c.name='peter' LIMIT 2) UNION
(SELECT * FROM c WHERE c.name='john' LIMIT 2)) r
ON b.name=r.name 
WHERE a.id=1;

-- Second: by using LEFT JOIN.    
SELECT b.Name,b.Word,IF(p.product IS NULL,j.product,p.product) AS 'Product' 
FROM a JOIN b ON a.word=b.word LEFT JOIN
(SELECT * FROM c WHERE NAME='peter' LIMIT 2) p ON b.name=p.name LEFT JOIN
(SELECT * FROM c WHERE NAME='john' LIMIT 2) j ON b.name=j.name
WHERE a.id=1 AND b.name IN (p.name,j.name);

更新:好的。不漂亮,但这个查询可以工作。我已经在 MySQL 5.7 上进行了测试。 ;)

SELECT b.Name,b.Word,c.Product FROM a 
JOIN b ON a.word=b.word 
JOIN c ON b.name=c.name 
JOIN (SELECT c.name,
REPLACE(SUBSTRING_INDEX(GROUP_CONCAT(product SEPARATOR ' '),' ',2),' ',',') prod 
FROM c GROUP BY NAME) r
ON b.name=r.name WHERE FIND_IN_SET(c.product,prod);

但如果你有一个至少具有自动增量值的列会容易得多。

【讨论】:

  • 谢谢。第一个查询在你的小提琴中工作,但不幸的是我的 MySQL 版本是 5.7,所以第一个查询对我不起作用。我会检查我是否可以毫无问题地更新 MySQL。我认为其他两个示例不合适,因为那里有硬编码的字符串。我在考虑更多的广义形式,例如。 G。使用 a.id=2 及以上。
  • 是的,我认为它不是动态的,因此在现实环境中使用起来并不容易。我还有一个建议。我会更新我的答案,看看你是否可以使用它。
  • 我不知道有这么多不同的方法可以达到目标,而且我从未使用过 wRow 和 FIND_IN_SET。但是,我会接受 HelgeB 的回答,因为我认为这是一个非常好的解决方案。尽管如此,还是非常感谢您采用不同的方法!
  • 是的@GioWay。由您决定接受哪个答案..只要它是您环境的最佳实践。我正在学习自己,这是我可以改进的一种方式,所以老实说,这是我第一次自己使用 FIND_IN_SET。就 wRow 而言,它只是一个别名,所以我认为您的意思是这个函数 ROW_NUMBER() OVER (PARTITION BY c.name ORDER BY c.name DESC) .. 也是我第一次使用它。 ;)
猜你喜欢
  • 1970-01-01
  • 2013-04-07
  • 1970-01-01
  • 2012-04-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-08-02
  • 2013-09-17
相关资源
最近更新 更多