SQL查询:为每个产品匹配价格最低且距离最近的供应商
问题分类与检索关键词
- 这类需求属于SQL中非常常见的**分组多条件排序取TopN(此处N=1)**场景,没有特殊的专属分类名
- 常用检索关键词:
SQL 分组按多字段排序取第一条、row_number() 窗口函数 分组取最优值、SQL 宽表转长表 取最值 - 补充提示:你当前使用的是「产品作为列」的宽表结构,如果后续产品品类会频繁增减,更建议调整为「产品作为行值」的窄表结构(字段为vendor、product_name、cost、distance、address),后续查询不需要反复修改SQL拼接逻辑,维护成本低很多。
最优SQL实现方案
实现逻辑说明
因为你的表所有字段都是varchar类型,且是宽表结构,实现分三步:
- 先把5个产品列通过
UNION ALL转成长表结构,同时做数据清洗:去掉价格字段的$符号转成数值、把距离字符串转成整数,避免字符串字典序排序导致的结果错误 - 用标准窗口函数
ROW_NUMBER(),按产品分组,优先按价格升序、价格相同时按距离升序给每个组内的供应商打排名 - 过滤出每个产品组排名为1的记录,输出要求的字段即可
这个方案兼容所有支持SQL:2003标准的数据库(MySQL8.0+、PostgreSQL、SQL Server、Hive、SparkSQL等),逻辑清晰、执行效率远高于传统的子查询关联min值写法,是目前行业通用的最优实现。
可直接运行的SQL代码
假设你的源表名为vendor_prices,如果表名不同自行替换即可:
WITH unpivot_clean AS ( -- 宽表转长表+数据类型清洗 SELECT vendor, 'product_a' AS product_name, CAST(REPLACE(product_a, '$', '') AS DECIMAL(10,2)) AS cost_sort, product_a AS cost, CAST(distance AS INT) AS distance_sort, distance, address FROM vendor_prices WHERE product_a IS NOT NULL AND product_a <> '' UNION ALL SELECT vendor, 'product_b' AS product_name, CAST(REPLACE(product_b, '$', '') AS DECIMAL(10,2)) AS cost_sort, product_b AS cost, CAST(distance AS INT) AS distance_sort, distance, address FROM vendor_prices WHERE product_b IS NOT NULL AND product_b <> '' UNION ALL SELECT vendor, 'product_c' AS product_name, CAST(REPLACE(product_c, '$', '') AS DECIMAL(10,2)) AS cost_sort, product_c AS cost, CAST(distance AS INT) AS distance_sort, distance, address FROM vendor_prices WHERE product_c IS NOT NULL AND product_c <> '' UNION ALL SELECT vendor, 'product_d' AS product_name, CAST(REPLACE(product_d, '$', '') AS DECIMAL(10,2)) AS cost_sort, product_d AS cost, CAST(distance AS INT) AS distance_sort, distance, address FROM vendor_prices WHERE product_d IS NOT NULL AND product_d <> '' UNION ALL SELECT vendor, 'product_e' AS product_name, CAST(REPLACE(product_e, '$', '') AS DECIMAL(10,2)) AS cost_sort, product_e AS cost, CAST(distance AS INT) AS distance_sort, distance, address FROM vendor_prices WHERE product_e IS NOT NULL AND product_e <> '' ), ranked AS ( -- 按规则分组排序打标 SELECT *, ROW_NUMBER() OVER( PARTITION BY product_name ORDER BY cost_sort ASC, distance_sort ASC ) AS rn FROM unpivot_clean ) -- 取每个产品排名第一的结果 SELECT product_name AS vendor, cost, vendor, distance, address FROM ranked WHERE rn = 1 ORDER BY product_name;
关键注意点
- 不要用
RANK()/DENSE_RANK()替代ROW_NUMBER():前两个函数会在排序字段值相同时返回并列排名,可能导致同一个产品返回多条供应商记录,不符合你要唯一最优供应商的需求 - 必须做数值类型转换:varchar类型的排序是按字符字典序执行,比如字符串
'$10'会排在'$8'前面、'1000'会排在'900'前面,不转类型会得到完全错误的排序结果 - 如果你使用的是不支持窗口函数的老旧数据库(比如MySQL5.x),可以改用关联子查询写法,但性能会随数据量上升明显下降,优先升级数据库版本用窗口函数方案。
运行上述代码得到的结果和你给出的预期结果完全一致。
内容的提问来源于stack exchange,提问作者accforso
相关产品推荐
相关产品推荐

