加入和过滤一对多关系

Posted

技术标签:

【中文标题】加入和过滤一对多关系【英文标题】:Joining and filtering one-to-many relationship 【发布时间】:2015-10-02 18:43:50 【问题描述】:

我需要一些关于优化 SQL 查询结构的帮助。我有这样的模型:

我正在尝试在表 NON_NATURAL_PERSONNNP_NAME 之间进行某种连接。因为我在表NNP_NAME 中有很多人的名字,所以我不能一对一的SELECT * from NON_NATURAL_PERSON inner join NNP_NAME 等。这样我就会为一个人的每个名字获得额外的行。

表格中的数据: 如何扩展此查询以获取下图中标记为红色的行?我想要的查询条件是:只有在存在时才加入 typeA 的名称。如果不是,则加入typeB的名称。如果都不存在 typeC 的连接名称。

SELECT nnp.ID, name.NAME, name.TYPE 
FROM NON_NATURAL_PERSON nnp
INNER JOIN NNP_NAME name ON (name.NON_NATURAL_PERSON = nnp.ID)

【问题讨论】:

您标记mysqlh2的任何特殊原因? @sstan 不,我的目标是通用 SQL。我的测试平台是 MySQL 和 H2 【参考方案1】:

如果 type 的拼写与写的完全一致(typeA、typeB、typeC),那么您可以使用 MIN() 函数:

SELECT NON_NATURAL_PERSON, MIN(type) AS min_type
FROM NNP_NAME
GROUP BY NON_NATURAL_PERSON

如果您还想要用户名,可以使用此查询:

SELECT
  n1.NON_NATURAL_PERSON AS ID,
  n1.Name,
  n1.Type
FROM
  NNP_NAME n1 LEFT JOIN NNP_NAME n2
  ON n1.NON_NATURAL_PERSON = n2.NON_NATURAL_PERSON
     AND n1.Type > n2.type
WHERE
  n2.type IS NULL

请看这个fiddle。如果 Types 没有按字面意思排序,请更改此行:

     AND n1.Type > n2.type

用这个:

     AND FIELD(n1.Type, 'TypeA', 'TypeB', 'TypeC') >
         FIELD(n2.type, 'TypeA', 'TypeB', 'TypeC')

MySQL FIELD(str, str1, str2, ...) 函数返回 str 在 str1, str2, ... 列表中的索引(位置),如果未找到 str,则返回 0。您想为每个 NON_NATURAL_PERSON 获取按类型排序的“第一条”记录。获取此信息的方式有多种,我选择了自我加入:

ON n1.NON_NATURAL_PERSON = n2.NON_NATURAL_PERSON
   AND n1.Type > n2.type -- or filed function

使用 WHERE 条件:

WHERE n2.type IS NULL

这将返回连接未成功的所有行 - 当没有小于 n1.type 的 n2.type 时连接不会成功 - 它将返回第一条记录。

编辑

如果您想要一个独立于平台的解决方案,避免创建新表,您可以使用 CASE WHEN,只需更改

AND n1.Type > n2.Type

AND
  CASE
    WHEN n1.Type='TypeA' THEN 1
    WHEN n1.Type='TypeB' THEN 2
    WHEN n1.Type='TypeC' THEN 3
  END
  >
  CASE
    WHEN n2.Type='TypeA' THEN 1
    WHEN n2.Type='TypeB' THEN 2
    WHEN n2.Type='TypeC' THEN 3
  END

【讨论】:

类型不是按字面排序的。出于测试目的,非常像那样 谢谢伙计。但是,除了使用 mysql 特定的 FIELD() 函数之外,还有其他方法吗?你能解释一下为什么你使用 2 个相同的 nnp_name 表的连接吗? @shx 我已经更新了我的答案,有多种方法可以解决您的问题,例如使用按类型排序的 GROUP_CONCAT,然后获取列表的第一个元素......但是自加入是我喜欢的那个。 你就是那个男人。谢谢。那个更大的 > 运算符仍然困扰着我。如果 typeA 变成 foo,typeB 变成 bar,typeC 变成 blahblah 怎么办?它们之间的优先级将保持相同的顺序。字典顺序不是必需的。 @shx 这就是我使用 FIELD 函数的原因,因此您可以指定任何自定义订单 FIELD(type, 'bar', 'foo', 'bla', 'zzz', 'aaa') , 所以 bar=1 foo=2 bla=3 zzz=4 aaa=5 - 更通用的 SQL 解决方案需要一个包含类型及其顺序的表【参考方案2】:

缺少一条信息。你说:

Always join name of typeA only if exists. If not, join name of typeB. If neither exists join name of typeC.

但你没有说明为什么你更喜欢 typeA 而不是 typeB。此信息不包含在您的数据中。 在@fthiella 的回答中,假设是按字典顺序排列的,或者使用FIELD 给出任意顺序。这也是为什么需要与表nnp_name 进行两次连接的原因。

您可以通过添加表name_type (id, name, order) 并更改类型列以包含id 来解决此问题。这将允许您以干净的方式添加缺少的信息。

通过与这个新表的额外连接,您将能够获得每一行的首选 nnp_name。

【讨论】:

你是对的。附加表将是最干净的方式,但我没有那个选项。我必须做一些 case/when/then 逻辑,因为 typeA 可能是 FOO,typeB 可能是 BAR 等。

以上是关于加入和过滤一对多关系的主要内容,如果未能解决你的问题,请参考以下文章

在一对多关系中使用 NSPredicate 进行过滤

如何过滤graphQL一对多关系中的查询?

Kafka Stream 和 KTable 一对多关系加入

是否有一个 NSPredicate 来过滤具有一对多关系的模型(过滤多个)?

CoreData:使用 NSPredicate 过滤一对多对多关系(此处不允许错误对多键)

Django模型加入一对多关系以在模板中显示