【问题标题】:Prevent duplicate values in LEFT JOIN防止 LEFT JOIN 中的重复值
【发布时间】:2015-08-05 07:09:40
【问题描述】:

我遇到了从LEFT JOIN 获得重复值的情况。我认为这可能是一种理想的行为,但与我想要的不同。

我有三个表:persondepartmentcontact

人:

id bigint,
person_name character varying(255)

部门:

person_id bigint,
department_name character varying(255)

联系方式:

person_id bigint,
phone_number character varying(255)

Sql 查询:

SELECT p.id, p.person_name, d.department_name, c.phone_number 
FROM person p
  LEFT JOIN department d 
    ON p.id = d.person_id
  LEFT JOIN contact c 
    ON p.id = c.person_id;

结果:

id|person_name|department_name|phone_number
--+-----------+---------------+------------
1 |"John"     |"Finance"      |"023451"
1 |"John"     |"Finance"      |"99478"
1 |"John"     |"Finance"      |"67890"
1 |"John"     |"Marketing"    |"023451"
1 |"John"     |"Marketing"    |"99478"
1 |"John"     |"Marketing"    |"67890"
2 |"Barbara"  |"Finance"      |""
3 |"Michelle" |""             |"005634"

我知道这是连接的作用,保持与选定行相乘。但它给人的感觉就像电话号码023451,99478,67890 是针对两个部门的,而它们仅与人员 john 相关,具有不必要的重复值,这会随着更大的数据集而升级问题。
所以,这就是我想要的:

id|person_name|department_name|phone_number
--+-----------+---------------+------------
1 |"John"     |"Finance"      |"023451"
1 |"John"     |"Marketing"    |"99478"
1 |"John"     |""             |"67890"
2 |"Barbara"  |"Finance"      |""
3 |"Michelle" |""             |"005634"

这是我的情况示例,我正在使用大量表和查询。所以,需要一个通用的解决方案。

【问题讨论】:

  • 防止任何内部或左连接的重复是通过正确连接多重性来处理的(并在开始时让它们正确!)。当前显示的结果是“正确的” - 否则信息将丢失。
  • 您想要的结果绝对是任意的,因为架构中的联系人和部门之间没有关系
  • @Beginner-postgres 数据库。有关系吗 ?它的基本sql对吗?
  • @ᴳᵁᴵᴰᴼ 我知道这有点武断,但结果会产生很多冗余。任何有助于达到我想要的结果。我需要为此更改架构吗?
  • 你怎么知道Finance 的电话号码是023451 我看不出dept 和contact 之间有什么关系。

标签: sql join


【解决方案1】:

虽然这些表格显然是为了讨论而简化的,但它们似乎在结构上存在缺陷。表的结构应该显示实体之间的关系,而不仅仅是实体和/或属性的列表。在这种情况下,我会认为电话号码是(个人或部门实体的)属性。

第一步是创建具有关系的表,每个表都有一个主键,可能还有一个外键。在此示例中,让 person 表使用 person_id 作为主键,让 department 表使用 department_id 作为其主键会很有帮助。接下来寻找一对多或多对多的关系,并相应地设置你的外键:

  • 如果一个人一次只能在一个部门,那么您就有一个(部门)对多(人)。部门表中没有外键,但部门 ID 将是人员表中的外键。
  • 如果一个人可以在多个部门中,则他们是多对多的,并且您需要一个额外的联结表,其中 person_id 和 department_id 作为外键。

总而言之,您的方案中应该只有两张表:一张用于人员,另一张用于部门。即使允许个人电话号码(persons 表中的一列)和 department 表中的部门号码,这也是一种更好的方法。

唯一需要注意的是,一个部门有多个号码(或多个部门共用一个电话号码),但这超出了原始问题的范围。

【讨论】:

  • 感谢重播。就像你说的,我和他们之间有初级和外国的关系。它就像人员->联系人(一对多),人员->部门(一对多)。如果用户想要查看人员以及他的联系人和部门信息,那么完美的方式是什么?
  • 您已经将 person_id 作为部门表中的外键,这是一个很好的开始。接下来,我会将联系人表转换为人员表中的一列。联系信息不适合独立表格。
  • 我怀疑您的电话号码可能是部门号码和个人号码的组合。如果它是两种类型的混合,您将不得不以某种方式区分差异,或者使用临时“标志”列,或者使用带有逗号分隔的部门编号列表的 IN(...) 进行查询。
  • 联系表在我的情况下相当大。它不仅存储电话号码,还存储血型、婚姻状况、地址等所有详细信息。所以,它是一个独立的表
【解决方案2】:

我认为您只需要获取特定人员的部门和电话列表即可。所以只需使用array_agg(或string_aggjson_agg):

SELECT
    p.id,
    p.person_name,
    array_agg(d.department_name) as "department_names",
    array_agg(c.phone_number) as "phone_numbers"
FROM person AS p
LEFT JOIN department AS d ON p.id = d.person_id
LEFT JOIN contact AS c on p.id = c.person_id
GROUP BY p.id, p.person_name

【讨论】:

    【解决方案3】:

    我喜欢将此问题称为“代理交叉加入”。由于没有信息(WHEREJOIN 条件)表 departmentcontact 应该如何匹配,它们通过代理表 person 交叉连接 - 为您提供 Cartesian product .和这个非常相似:

    那里有更多解释。

    您的查询的解决方案:

    SELECT p.id, p.person_name, d.department_name, c.phone_number
    FROM   person p
    LEFT   JOIN (
       SELECT person_id, min(department_name) AS department_name
       FROM   department
       GROUP  BY person_id
       ) d ON d.person_id = p.id
    LEFT   JOIN (
       SELECT person_id, min(phone_number) AS phone_number
       FROM   contact
       GROUP  BY person_id
       ) c ON c.person_id = p.id;
    

    您没有定义选择哪个部门或电话号码,所以我随意选择了最低。你可以用其他任何方式...

    【讨论】:

    • 感谢您提供的信息。我在这里遇到了问题。但是根据您的查询,如果用户想要第一个部门,第一行的电话号码,然后第二行的第二个,依此类推。这听起来很荒谬,但有什么想法吗?
    • 喜欢可以在部门和联系人表上使用序列或其他东西来检索相应的记录而不重复。
    • 任何子查询都可以吗?
    • @GautamKumarSamal:你如何定义“第一”。与电子表格不同,RDBMS 的表中没有自然顺序。如果可以先定义,则可以在子查询的行号中运行row_number() OVER (ORDER BY <criteria>) AS rn,然后运行LEFT JOIN。如果还不清楚,请开始一个包含所有详细信息的新问题。相关:stackoverflow.com/questions/28034827/what-type-of-join-to-use/…
    • 你似乎选择了MIN而不是FIRST,不是吗?
    【解决方案4】:

    使用这种类型的查询:SQL Server
    (您可以将ORDER BY idid 更改为您想要的每一列)

    SELECT 
        p.id, 
        p.person_name, 
        d.department_name, 
        c.phone_number
    FROM
        person p
        LEFT JOIN 
        (SELECT *, ROW_NUMBER() OVER (PARTITION BY person_id ORDER BY id) AS seq
         FROM department) d 
        ON d.person_id = p.id And d.seq = 1
        LEFT JOIN 
        ( SELECT *, ROW_NUMBER() OVER (PARTITION BY person_id ORDER BY id) AS seq
         FROM contact) c 
        ON c.person_id = p.id And c.seq = 1;
    

    【讨论】:

      【解决方案5】:
      SELECT p.id, p.person_name, d.department_name, c.phone_number 
      FROM person p
        LEFT JOIN department d 
          ON p.id = d.person_id
        LEFT JOIN contact c 
          ON p.id = c.person_id 
      group by p.id, p.person_name, d.department_name, c.phone_number
      

      【讨论】:

      • 这不会给出相同的结果吗?由于在原始示例中没有两行具有所有相同的 [id, person, dept, phone] 值,因此按这些字段进行分组不会改变任何内容。
      猜你喜欢
      • 2021-07-17
      • 2021-11-08
      • 2021-12-17
      • 2021-09-12
      • 1970-01-01
      • 1970-01-01
      • 2019-08-13
      • 1970-01-01
      • 2019-01-18
      相关资源
      最近更新 更多