MySQL分组与聚合
我有一个万智牌(Magic: The Gathering)卡牌价格的数据库。我想对每个卖家中的最低价进行查询,并把结果填充到另一张表中。然而,我从查询得到的结果相当怪异。 我觉得需要做某种子查询来实现。不过我还没搞清楚该怎么做。
下面是简化后的输出。希望看起来清晰。
我的数据库大致是这样的:
UUID | Seller | Price
1bfaaddc-9064-53f2-a55d-bfb972ea5aa9 cardkingdom 0.04
2559fbbd-08dd-54c7-b6ae-657e0689e605 cardkingdom 0.04
25d0bf7f-1a5d-579f-97f3-f4b18db45d25 cardkingdom 0.04
1c8ebaac-e166-54d6-9b78-29df71faa1c6 cardmarket 0.023368
892764f6-77b2-501b-bc48-c0d61954addc cardmarket 0.023368
a5272f48-77a9-5398-9b04-1db1bc7b0953 cardmarket 0.023368
00fc5c71-3bdb-5c7a-a79a-fc50a5b32040 manapool 0.15
0161f9f2-9d93-51b5-a5be-902ed5089e76 manapool 0.15
01b9cc89-6e90-5dbc-8daf-530d9bb23be0 manapool 0.15
dd838b36-2e58-502c-af14-db43abbea37a tcgplayer 0.07
1c8ebaac-e166-54d6-9b78-29df71faa1c6 tcgplayer 0.08
2a980176-aa7f-5adf-8dda-03381fc12295 tcgplayer 0.08
还有更多结果。只是为了让你理解思路。
所以当我执行查询时,所有结果都来自同一个UUID。请注意,cardkingdom的卖家并不是该卖家能够给出的最低价格。
SET sql_mode=(SELECT REPLACE(@@sql_mode,'ONLY_FULL_GROUP_BY',''));
SELECT uuid, seller, MIN(price)
FROM prices
WHERE sellertype = 'retail'
AND gametype = 'paper'
AND name = 'forest'
GROUP BY seller;
001b516c-09a3-5776-92d3-944fee7c18e6 cardkingdom 0.35
001b516c-09a3-5776-92d3-944fee7c18e6 manapool 0.15
001b516c-09a3-5776-92d3-944fee7c18e6 cardmarket 0.023368
001b516c-09a3-5776-92d3-944fee7c18e6 tcgplayer 0.07
这里有点不对劲。你能帮我吗?
解决方案
你使用的是 groupby 卖家,但每个卖家有多个 UUId。因为 UUId 不是 groupby 的一部分,因此MySQL无法确定性地知道应该选哪一行。所以MySQL会随机选取任意一行并显示结果。
你可以做的是,使用子查询计算每个卖家的最低价,然后把它再与原始表连接,以获取完整的行。
例如:
SELECT table1.uuid, table1.seller, table1.price
FROM prices table1
JOIN (
SELECT seller, MIN(price) AS min_price
FROM prices
GROUP BY seller
) table2
ON table1.seller = table2.seller
AND table1.price = table2.min_price;
备选方案
非标准的GROUP BY语法
REPLACE(@@sql_mode, 'ONLY_FULL_GROUP_BY', '')
是的,这样对你没有帮助。 直接删掉那一行。
有时人们会选择“偷懒”,偏离SQL-92标准,但通常只会惹上麻烦。这会妨碍你对手头问题的认真思考。由格式错误的SELECT语句产生的诊断信息实际上会把你引导到正确的方向,因此禁用它们可能事与愿违。
主键
SELECT uuid, seller, MIN(price)
FROM prices
WHERE ...
GROUP BY seller
;
好吧,现在我们回到标准语法,应该很明显,uuid 不属于那个查询。只有当我们说的是 GROUP BY uuid, seller 时才有意义,你的叙述解释也明确地表明你想要那样。自然可以再加上第三个 price 列,因为它有聚合在上面。
因此我们说,在WHERE过滤之后,大致是一个三元组关系。它是从复合键 (uuid, seller) 指向属性 price 的关系。经过GROUP BY之后,实际上成为了一个唯一的主键 (uuid, seller)。
简述:你想要 SELECT ... GROUP BY uuid, seller。
顺便说一句,你也可能想再加上一个ORDER BY,因为当前的查询可能会无意间产生你喜欢的排序,但还没有对排序作出任何特定的保证。如果你不留意这类细节,维护者在与你的代码库持续互动时,查询优化器可能会对排序做出一些奇怪的处理。
另外,在一个 cards 表中,uuid 列将是一个非常理想的主键。但你的示例行清楚地表明,在 prices 表中它根本不是唯一的——一个卡牌有多个卖家。因此在那张表中,确实应将该列重命名为 card,或许是 card_uuid。