MySQL 限制第二次加入查询
MySQL limit on 2nd join in query
我不确定如何限制查询中的第二次连接。在我准备的 fiddle 中,我需要限制结果中每个用户的行数。
table一个:
id | word
----------
1 | xyz
2 | zzz
table b:
name | word
------------
Peter | xyz
John | xyz
Jane | xyz
table c:
name | product
------------
Peter | blah
Peter | blah2
Peter | blah3
Peter | blah4
Peter | blah5
Peter | blah6
John | hello
John | world
John | blah
期望的结果:table b 的前两个条目(用户 Peter 和 John)和每个用户在 table 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
你有什么想法吗?我找到了另一个讨论这个问题的线程,但我无法将其映射到我的案例中。我知道结构并不完美,但我不允许更改 table c.
的设计
让我们剖析您的查询:
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
子查询 returns 两行,但是当您 LEFT JOIN
它 returns 来自 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;
另外两个例子:
-- 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);
但是如果你有一个至少有自动增量值的列会容易得多。
您可以使用存储过程,select 不同的名称,遍历结果和 select 每个名称 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();
我不确定如何限制查询中的第二次连接。在我准备的 fiddle 中,我需要限制结果中每个用户的行数。
table一个:
id | word
----------
1 | xyz
2 | zzz
table b:
name | word
------------
Peter | xyz
John | xyz
Jane | xyz
table c:
name | product
------------
Peter | blah
Peter | blah2
Peter | blah3
Peter | blah4
Peter | blah5
Peter | blah6
John | hello
John | world
John | blah
期望的结果:table b 的前两个条目(用户 Peter 和 John)和每个用户在 table 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
你有什么想法吗?我找到了另一个讨论这个问题的线程,但我无法将其映射到我的案例中。我知道结构并不完美,但我不允许更改 table c.
的设计让我们剖析您的查询:
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
子查询 returns 两行,但是当您 LEFT JOIN
它 returns 来自 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;
另外两个例子:
-- 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);
但是如果你有一个至少有自动增量值的列会容易得多。
您可以使用存储过程,select 不同的名称,遍历结果和 select 每个名称 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();