【问题标题】:In one to many relationship, return distinct rows based on MIN value在一对多关系中,根据 MIN 值返回不同的行
【发布时间】:2014-08-28 14:28:53
【问题描述】:

假设一位患者多次就诊。我想编写一个查询,根据他们最早的访问返回不同的患者行。例如,考虑以下行。

patients
-------------
id    name
1     Bob
2     Jim
3     Mary

visits
-------------
id    patient_id    visit_date    reference_number
1     1             6/29/14       09f3be26
2     1             7/8/14        34c23a9e
3     2             7/10/14       448dd90a

我想看到查询返回的是:

id    name    first_visit_date    reference_number
1     Bob     6/29/14             09f3be26
2     Jim     7/10/14             448dd90a

我尝试过的类似于:

SELECT
  patients.id, 
  patients.name, 
  visits.visit_date AS first_visit_date, 
  visits.reference_number
FROM
  patients
INNER JOIN (
  SELECT
    *
  FROM
    visits
  ORDER BY
    visit_date
  LIMIT
    1
) visits ON
  visits.patient_id = patients.id

添加 LIMIT 会使查询返回 0 行,但删除它会导致查询返回重复项。这里有什么诀窍?我也尝试在 INNER JOIN 中选择 MIN(visit_date) 但这也返回了重复。

更新

有人建议这个问题是重复的,但对我来说它似乎不同,因为我是在两个单独的表上做这个的。另一个问题的公认答案建议加入y.max_total = x.total,如果要加入的表是从中选择的同一个表,则该方法有效。此外,我需要从具有 MIN 日期的行中返回其他列,而不仅仅是日期本身。

不过,我接受的答案效果很好。

【问题讨论】:

标签: sql postgresql one-to-many greatest-n-per-group


【解决方案1】:

避免使用DISTINCT ON(p.id),而是使用普通的旧NOT EXISTS(...)

SELECT p.id, p.name
     , v.first_visit_date, v.reference_number
FROM patients p
JOIN visits v ON p.id = v.patient_id
    -- exclude all join-products that are not the first for a patient.
WHERE NOT EXISTS (
   SELECT *
   FROM visits nx
   WHERE nx.patient_id = v.patient_id
   AND ( nx.visit_date < v.visit_date
       OR (nx.visit_date = v.visit_date AND nx.id < v.id) -- tie-breaker condition
       )
   );  

【讨论】:

    【解决方案2】:

    使用distinct on

    select distinct on (p.id)
        p.id, 
        p.name, 
        v.visit_date as first_visit_date, 
        v.reference_number
    from
        patients p
        inner join
        visits v on p.id = v.patient_id
    order by p.id, v.visit_date
    

    http://www.postgresql.org/docs/current/static/sql-select.html#SQL-DISTINCT

    【讨论】:

    • 我选择了 postgresql 作为我的示例,但我意识到 distinct on 在其他方言中不可用。例如,您能否展示一下这在 Oracle 中是如何实现的?
    • 或者如果你愿意,我可以发布一个不同的问题并链接到这个问题以供参考。
    • @Samo 是的,这是一个完全不同的答案。所以请提出一个不同的问题。
    【解决方案3】:

    您希望通过聚合查询将 visits 表的多行减少为每个患者 ID 一行。 LIMIT 有点混乱,因为它不是严格相关的,但如果你有足够的决心,你可能会让它工作。如果查询优化器足够好,那么使用 LIMIT (适当地)的版本和这个版本之间应该没有性能差异:

    SELECT
      patients.id, 
      patients.name, 
      visits.first_visit_date AS first_visit_date, 
      visits.reference_number
    FROM
      patients
    INNER JOIN (
      SELECT
        patient_id,
        MIN(visit_date) as first_visit_date
      FROM
        visits
      GROUP BY
        patient_id
    ) visits ON
      visits.patient_id = patients.id
    

    我更喜欢带有 MIN() 的版本,因为它对我来说更清晰。此外,如果它对我来说更清楚,那么查询优化器也更有可能更清楚。

    【讨论】:

    • 我也更喜欢distinct on,即使它更长,因为distinct on 解决方案假定指定顺序中的第一行将是保留的行。也许这行得通,但如果不阅读文档我无法确定,这是一个维护问题。 YMMV。
    • 这有几个问题。首先,visits.reference 号码在您的查询中不存在,因为您没有在 INNER JOIN 中选择它。其次,当我添加它时,我仍然会得到重复的行。
    • 也许可行,但如果不阅读文档我无法确定 是的,请阅读文档以确保它在说 也许之前可以正常工作。第二点是有效的。对于来自其他 DBMS 的人来说,这是令人困惑的。对于 Postgresql 用户来说,这很清楚。
    • @JohnBollinger:distinct on 将比使用聚合的自联接快得多。使用窗口函数也会比自连接更快
    【解决方案4】:

    虽然您使用的是 PostgreSQL,但以防万一它可能有用或“鼓舞人心”,这里是 T-SQL 版本。

    SELECT p.id, name, first_visit.visit_date as first_visit_date, v.reference_number as first_visit_reference_number
    FROM patients p
    INNER JOIN 
    (
        SELECT patient_id, MIN(visit_date) AS visit_date
        FROM visits
        GROUP BY patient_id
    ) first_visit ON first_visit.patient_id = p.id
    INNER JOIN visits v ON v.patient_id = p.id AND v.visit_date = first_visit.visit_date
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-01-24
      • 2018-11-20
      • 2012-09-29
      • 2016-10-26
      • 2021-01-16
      • 1970-01-01
      • 2017-01-29
      相关资源
      最近更新 更多