如何左连接第一个匹配行并用空值填充其余行?

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 1OUTER JOINMIN 进行一些操作,因为我在类似问题中找到了一些建议,但无论如何我都无法实现我的目标。

重要提示!请注意动物可以出售,即使它们没有分配位置。

感谢 @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 多行)