一尘不染

如何基于其他行返回结果集

sql

我有2张桌子-包装和物品。物料表包含所有属于包装的物料以及位置信息。像下面的示例表:

Packages table
id, type(enum{general,special})
1, general
2, special

Items table
id, package_id, location
1, 1, America
2, 1, Europe
3, 2, Europe

问题:我想查找属于某个位置的所有“特殊”软件包,如果未找到特殊软件包,则它应返回属于同一位置的“一般”软件包。

所以,

  1. 对于“欧洲”:包裹2应该是特殊包裹,因此应返回(包裹1也属于欧洲,但由于是普通包裹而不是必需的)

  2. 对于“美国”:由于没有特殊包装,应返回包装1


阅读 130

收藏
2021-05-16

共1个答案

一尘不染

这是两种不同的解决方案:(注意:我将枚举字段称为“ package_type”)

第一个解决方案(通过IF()函数):

select 
  i.location, 
  if(ps.id is not null, ps.id, pg.id) as package_id
from 
  (select distinct location from Items) i
  inner join 
    (select i.location, p.id
     from Items i
       inner join Packages p on (i.package_id = p.id and p.package_type = 'general')
    ) pg on (i.location = pg.location)
  left join 
    (select i.location, p.id
     from Items i
       inner join Packages p on (i.package_id = p.id and p.package_type = 'special')
    ) ps on (i.location = ps.location)

此解决方案本质上采用这些位置,并将其与常规(假定为存在;因此inner join)和特殊包(为可选;因此left join)一起添加到包中。它创建如下记录:

location | general-package | [special-package]

然后,它使用MySQLIF函数首先尝试选择特殊软件包的ID,然后回退到常规软件包的ID。

第二个解决方案(通过将枚举转换为整数):

select i.location, p.id
from
  (select i.location, max(cast(package_type as unsigned)) as package_type
   from Items i
     left join Packages p on (i.package_id = p.id)
   group by location
  ) i
  inner join 
    (select i.location, p.id, p.package_type
     from Items i
       inner join Packages p on (i.package_id = p.id)
    ) p on (i.location = p.location and i.package_type = p.package_type)

该解决方案利用了枚举存储为整数的事实。它将枚举转换为整数。special在这种情况下将返回2并且general将返回1。因为在这种情况下保证这些特殊值高于一般值(即2>
1),所以我们可以使用MAX聚合函数。现在,我们基本上有了一张位置及其“推荐包装”的表格(即特殊的(如果存在的话,否则为普通的))。我们只需将其与期望的包类型一起加入到普通查询中,即可返回正确的结果。

免责声明:我不确定这两种方法的效率,因此您可能需要自己进行测试。


如果您想重新设计表格或对表格进行归一化以提高效率,我认为这种设计可能更合适:

GeneralPackages table
id, name
1, General Package 1

SpecialPackages table
id, name
1, Special Package 1
2, Special Package 2

Items table
id, general_package_id, special_package_id, location
1, 1, NULL, America
2, 1, 2, Europe

这样做的好处是,可以更轻松地在数据库级别强制执行多个规则:

  • 位置必须始终具有常规包(Items.general_package_id可以定义为NOT NULL)
  • 一个位置必须只有一个通用程序包(将其添加到字段中而不是联接中可确保仅指定了一个)
  • 一个位置最多只能有一个特殊的程序包(将其添加到字段而不是联接中可确保仅指定了一个)
  • Items.general_package_id = GeneralPackages.id上的外键将保证该列仅包含“常规”有效软件包。
  • 可以对special_package_id做同样的事情。

缺点是,每次使用一个旧查询时,您可能都需要使用UNION ALL。

2021-05-16