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

Posted

技术标签:

【中文标题】如何与第一个匹配行左连接并用空填充其余部分?【英文标题】:How to left join with first matching row and fill the rest with null? 【发布时间】:2021-12-13 08:12:56 【问题描述】:

我的核心案例有点复杂,所以我会用一个例子来说明它。假设我有这样的表格:

动物

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

我可以在我的查询中进行哪些更改以仅使用第一个匹配行加入最后一个表?我尝试对LIMIT 1OUTER JOINMIN 进行一些操作,因为我在类似问题中找到了一些建议,但无论如何我都无法实现我的目标。

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

【问题讨论】:

我有这样的表格提供它们作为 CREATE TABLE + INSERT INTO 脚本或在线小提琴的链接。在文本表格格式的视图中提供所需的输出(它必须与示例数据精确匹配!)。指定精确的 mysql 版本。 为什么in_sale 的行'cat1' 连接到'cage1' 但没有连接到'room1'?什么定义了这种关系? PS。 fiddle 我想加入第一个匹配的行,无论是cage1 还是room1。所以我的第一个想法是在name 上加入animalsin_sale,如果每个name 组的位置是MINNULL 我想加入第一个匹配的行 (1)什么是“第一”?要使该术语存在,您必须定义一些唯一的行排序。 (2) 想象places 中的amount 值对于name = 'cat1' 的两行都等于2 - 如何定义这些行中的哪些匹配,哪些不匹配? (3) 想象在in_sale 中有5 行name = 'cat1' 有5 个不同的price 值——如何定义什么地方必须属于什么价格? 看看this fiddle 中的查询对您的任务安全吗? 【参考方案1】:

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

【讨论】:

在同一个 select 语句中读取和分配相同的变量在 MySQL 中是未定义的行为,请参阅文档重新分配和变量。 Why the order of evaluation for expressions involving user variables is undefined? 在MySQL - Define a variable within select and use it within the same select 上查看我的cmets(重新回答错误)。 在链接答案的评论中存档死链接:web.archive.org/web/20210303125758/https://mysqlserverteam.com/… @philipxy 哇,这是我第一次在 SQL 中使用自定义变量,但我并不知道。我的解决方案基于我在 *** 上找到的一些 LAG() OVER(PARTITION BY... ORDER BY...) 替代方案,但如果您能够分享更安全的解决方案,请随时这样做:) 使用存储过程。很可能这是一个常见问题解答。它通常是问题中的答案,重新完成人们通过问题和答案中的变量错误地尝试做的事情。 (合理的搜索涉及您的问题/问题/目标的许多清晰、简洁和精确的措辞,有和没有您的特定名称/字符串/数字、“site:***.com”和标签,以及阅读许多答案。)PS“工作正常每次”(就像“其他人都这样做”)没有理由相信或使用(不清楚)不合理的幼稚/民间理论。 PS 另外,我怀疑您“每次”都检查了所有 20000 行。 我还没有使用存储过程和php multi_query函数的技能,但也许将来我会学习并更新答案。 PS。我明白了,这不是数学证据。就我而言,我可以处理它,即使某些事情会以某种方式出错(我认为可能性很小),世界也不会结束。无论如何,我想看看任何例子,当使用这个出错时......在链接中我看到的只是理论,但没有没有按预期工作的例子。 PS2。 “每次”是指在测试期间的每次,当然,我不必手动检查每一行。

以上是关于如何与第一个匹配行左连接并用空填充其余部分?的主要内容,如果未能解决你的问题,请参考以下文章

mysql左连接没有数据还会查出来吗

MySQL连接5种方式

MySQL中常见的连接查询方式都有哪些?

LINQ:如何使用动态键连接两个数据表

左连接后缺少导航属性

填充行的其余部分/避免违反空约束