如何左连接第一个匹配行并用空值填充其余行?
How to left join with first matching row and fill the rest with null?
我的核心案例有点复杂,所以我会用一个例子来说明。假设我有 table 个像这样的:
动物
name (PK)
color
cat1
white
cat2
red
dog1
black
地方
place (PK)
name (FK)
amount
cage1
cat1
2
room1
cat1
3
cage2
dog1
5
in_sale
name (FK)
amount
price
cat1
1
50.00
dog1
3
600.00
cat2
2
1.00
这是创建它们的代码:
CREATE TABLE `animals` (
`name` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`color` varchar(100) COLLATE utf8_unicode_ci NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE `in_sale` (
`name` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`amount` int(11) NOT NULL,
`price` varchar(100) COLLATE utf8_unicode_ci NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE `places` (
`place` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`name` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`amount` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
INSERT INTO `animals` (`name`, `color`) VALUES
('cat1', 'white'),
('cat2', 'red'),
('dog1', 'black');
INSERT INTO `in_sale` (`name`, `amount`, `price`) VALUES
('cat1', 1, '25.00'),
('cat1', 1, '50.00'),
('cat2', 2, '1.00'),
('dog1', 3, '600.00');
INSERT INTO `places` (`place`, `name`, `amount`) VALUES
('cage1', 'cat1', 2),
('cage2', 'dog1', 5),
('room1', 'cat1', 3);
现在我想运行查询:
SELECT a.*, p.place, p.amount AS amount_in_place, s.sales
FROM animals AS a
LEFT JOIN places AS p ON a.name=p.name
LEFT JOIN (SELECT GROUP_CONCAT("Amount: ",amount, " and price: ",price separator ", ") AS sales, name FROM in_sale GROUP BY name) AS s ON s.name=a.name
ORDER BY a.name;
但不幸的是,我意识到结果不是我所期望的。
结果:
name
color
place
amount_in_place
sales
cat1
white
cage1
2
Amount: 1 and price: 25.00, Amount: 1 and price: 5...
cat1
white
room1
3
Amount: 1 and price: 25.00, Amount: 1 and price: 50.00
cat2
red
NULL
NULL
Amount: 2 and price: 1.00
dog1
black
cage2
5
Amount: 3 and price: 600.00
预期:
name
color
place
amount_in_place
sales
cat1
white
cage1
2
Amount: 1 and price: 25.00, Amount: 1 and price: 50.00
cat1
white
room1
3
NULL
cat2
red
NULL
NULL
Amount: 2 and price: 1.00
dog1
black
cage2
5
Amount: 3 and price: 600.00
我可以在查询中更改什么以将最后一个 table 与第一个匹配行连接起来?我尝试用 LIMIT 1
、OUTER JOIN
和 MIN
进行一些操作,因为我在类似问题中找到了一些建议,但无论如何我都无法实现我的目标。
重要提示!请注意动物可以出售,即使它们没有分配位置。
感谢 @Akina 我可以为我的示例提供代码的最终版本:
SELECT name,
animals.color,
places.place,
places.amount amount_in_place,
CASE WHEN name = LAG(name) OVER (PARTITION BY name ORDER BY place)
THEN
null
ELSE
(SELECT GROUP_CONCAT("Amount: ",amount, " and price: ",price
SEPARATOR ", ") AS sales
FROM in_sale
WHERE in_sale.name=animals.name GROUP BY name)
END sales
FROM animals
LEFT JOIN places USING (name)
LEFT JOIN in_sale USING (name)
GROUP BY 1,2,3,4;
请注意,它仅适用于 MySQL 版本 8 或更高版本。
对于旧版本我们可以使用自定义变量:
SELECT x.*,
@rowname,
CASE WHEN name = @rowname
THEN
null
ELSE
(SELECT GROUP_CONCAT('Amount: ',amount, ' and price: ',price
SEPARATOR ', ') AS sales
FROM in_sale
WHERE in_sale.name=x.name GROUP BY name)
END sales,
@rowname := name
from
(SELECT name,
animals.color,
places.place,
places.amount amount_in_place
FROM animals
LEFT JOIN places USING (name)
LEFT JOIN in_sale USING (name)
GROUP BY 1,2,3,4) as x
join (SELECT @rowname := 0) as r;
警告! 正如 @philipxy 在评论中指出的那样,它可能会产生截然不同的意外结果。对我来说,比较 @rowname
和 @rowname := name
列中的结果并检查 sales
列,每次都能正常工作。 (本地 10.4.11-MariaDB
和外部服务器 MySQL 5.7.34-37-log - Percona Server
- 我加入了十几个表。它返回了 20000 多行)
我的核心案例有点复杂,所以我会用一个例子来说明。假设我有 table 个像这样的:
动物
name (PK) | color |
---|---|
cat1 | white |
cat2 | red |
dog1 | black |
地方
place (PK) | name (FK) | amount |
---|---|---|
cage1 | cat1 | 2 |
room1 | cat1 | 3 |
cage2 | dog1 | 5 |
in_sale
name (FK) | amount | price |
---|---|---|
cat1 | 1 | 50.00 |
dog1 | 3 | 600.00 |
cat2 | 2 | 1.00 |
这是创建它们的代码:
CREATE TABLE `animals` (
`name` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`color` varchar(100) COLLATE utf8_unicode_ci NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE `in_sale` (
`name` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`amount` int(11) NOT NULL,
`price` varchar(100) COLLATE utf8_unicode_ci NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE `places` (
`place` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`name` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`amount` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
INSERT INTO `animals` (`name`, `color`) VALUES
('cat1', 'white'),
('cat2', 'red'),
('dog1', 'black');
INSERT INTO `in_sale` (`name`, `amount`, `price`) VALUES
('cat1', 1, '25.00'),
('cat1', 1, '50.00'),
('cat2', 2, '1.00'),
('dog1', 3, '600.00');
INSERT INTO `places` (`place`, `name`, `amount`) VALUES
('cage1', 'cat1', 2),
('cage2', 'dog1', 5),
('room1', 'cat1', 3);
现在我想运行查询:
SELECT a.*, p.place, p.amount AS amount_in_place, s.sales
FROM animals AS a
LEFT JOIN places AS p ON a.name=p.name
LEFT JOIN (SELECT GROUP_CONCAT("Amount: ",amount, " and price: ",price separator ", ") AS sales, name FROM in_sale GROUP BY name) AS s ON s.name=a.name
ORDER BY a.name;
但不幸的是,我意识到结果不是我所期望的。
结果:
name | color | place | amount_in_place | sales |
---|---|---|---|---|
cat1 | white | cage1 | 2 | Amount: 1 and price: 25.00, Amount: 1 and price: 5... |
cat1 | white | room1 | 3 | Amount: 1 and price: 25.00, Amount: 1 and price: 50.00 |
cat2 | red | NULL | NULL | Amount: 2 and price: 1.00 |
dog1 | black | cage2 | 5 | Amount: 3 and price: 600.00 |
预期:
name | color | place | amount_in_place | sales |
---|---|---|---|---|
cat1 | white | cage1 | 2 | Amount: 1 and price: 25.00, Amount: 1 and price: 50.00 |
cat1 | white | room1 | 3 | NULL |
cat2 | red | NULL | NULL | Amount: 2 and price: 1.00 |
dog1 | black | cage2 | 5 | Amount: 3 and price: 600.00 |
我可以在查询中更改什么以将最后一个 table 与第一个匹配行连接起来?我尝试用 LIMIT 1
、OUTER JOIN
和 MIN
进行一些操作,因为我在类似问题中找到了一些建议,但无论如何我都无法实现我的目标。
重要提示!请注意动物可以出售,即使它们没有分配位置。
感谢 @Akina 我可以为我的示例提供代码的最终版本:
SELECT name,
animals.color,
places.place,
places.amount amount_in_place,
CASE WHEN name = LAG(name) OVER (PARTITION BY name ORDER BY place)
THEN
null
ELSE
(SELECT GROUP_CONCAT("Amount: ",amount, " and price: ",price
SEPARATOR ", ") AS sales
FROM in_sale
WHERE in_sale.name=animals.name GROUP BY name)
END sales
FROM animals
LEFT JOIN places USING (name)
LEFT JOIN in_sale USING (name)
GROUP BY 1,2,3,4;
请注意,它仅适用于 MySQL 版本 8 或更高版本。
对于旧版本我们可以使用自定义变量:
SELECT x.*,
@rowname,
CASE WHEN name = @rowname
THEN
null
ELSE
(SELECT GROUP_CONCAT('Amount: ',amount, ' and price: ',price
SEPARATOR ', ') AS sales
FROM in_sale
WHERE in_sale.name=x.name GROUP BY name)
END sales,
@rowname := name
from
(SELECT name,
animals.color,
places.place,
places.amount amount_in_place
FROM animals
LEFT JOIN places USING (name)
LEFT JOIN in_sale USING (name)
GROUP BY 1,2,3,4) as x
join (SELECT @rowname := 0) as r;
警告! 正如 @philipxy 在评论中指出的那样,它可能会产生截然不同的意外结果。对我来说,比较 @rowname
和 @rowname := name
列中的结果并检查 sales
列,每次都能正常工作。 (本地 10.4.11-MariaDB
和外部服务器 MySQL 5.7.34-37-log - Percona Server
- 我加入了十几个表。它返回了 20000 多行)