【问题标题】:SQL query that returns records with certain NULL values when WHERE does not match当 WHERE 不匹配时返回具有某些 NULL 值的记录的 SQL 查询
【发布时间】:2018-04-17 03:36:04
【问题描述】:

我在 Postgres 中运行以下查询:

SELECT  raw_times.*, efforts.id as effort_id, efforts.event_id as event_id, splits.id as split_id 
FROM raw_times 
INNER JOIN event_groups ON event_groups.id = raw_times.event_group_id 
INNER JOIN events ON events.event_group_id = event_groups.id 
INNER JOIN efforts ON efforts.event_id = events.id 
INNER JOIN aid_stations ON aid_stations.event_id = events.id 
INNER JOIN splits ON splits.id = aid_stations.split_id 
WHERE efforts.bib_number::text = raw_times.bib_number
    AND splits.parameterized_base_name = raw_times.parameterized_split_name

这个想法是找到匹配的号码布号码和拆分名称,并返回填充了各种关系 id 的 raw_time 记录。

用简单的英语来说,逻辑是这样工作的:对于每个 raw_time,检查 event_group_id。一个 event_group 有很多事件,一个事件有很多努力,努力表有一个 bib_number 列。参赛号码在 event_group 中是唯一的,但在整个努力表中不是唯一的。

所以对于每个 raw_time,因为我们知道 event_group_id 和 bib_number,我们可以确定它与哪个工作量相关。知道努力可以让我们也知道事件(因为努力有一个 event_id)。

一个事件通过aid_stations 连接表有许多拆分。拆分名称在事件中是唯一的。因为我们知道事件(如上所述确定)并且我们知道拆分名称(它是 raw_times 表上的一列),所以我们可以确定 split_id。

对于有匹配的书目号和拆分名称的记录,查询按预期工作。但是对于 bib 编号或拆分名称不匹配的记录,不满足 WHERE 子句,因此根本不返回 raw_time 记录。

我已经尝试使用 LEFT JOIN 代替每个 INNER JOIN 进行查询,但我得到了相同的结果。

我想要的是返回所有 raw_time 记录,但是如果没有匹配的拆分名称,则返回 split_id 为 NULL 的记录,如果没有匹配的 bib 号码,则返回 NULL 的记录effort_id、event_id 和 split_id。

raw_times 表如下所示:

id  event_group_id  parameterized_split_name  bib_number
3        53         finish                    11
4        53         finish                    603
5        53         finish                    9999
6        53         nonexistent               603

event_groups 表如下所示:

id
53
51

事件表如下所示:

id  event_group_id
26  53
28  53
18  51

努力表如下所示:

id      event_id  bib_number
22183   26        11
22400   28        603
5747    18        11

aid_stations 表如下所示:

id   event_id  split_id
236  26        30
237  26        31
238  26        106
239  26        111
240  26        112
241  26        109
242  26        113
254  28        119
255  28        118
138  18        1
150  18        16

拆分表如下所示:

id  parameterized_base_name
30  finish
31  start
106 aid-1
109 aid-4
111 aid-2
112 aid-3
113 aid-5
118 start
119 finish
1   start
16  finish

查询应该返回:

id  event_group_id parameterized_split_name bib_number effort_id event_id  split_id
3   53             finish                   11         22183     26        30
4   53             finish                   603        22400     28        119
6   53             nonexistent              603        22400     28        NULL
5   53             finish                   9999       NULL      NULL      NULL

这里是 ERD 的链接:https://github.com/SplitTime/OpenSplitTime/blob/master/erd.pdf

【问题讨论】:

  • 要解决您的直接问题(可选连接),解决方案肯定是使用外连接(即LEFT JOIN)和ON 部分内的连接。
  • 为了帮助它有一个关系图真的很有帮助 - 所有表及其关系的图片 - 最重要的是关系的一对多方面。当您加入实际上是多对多的表时,您会得到“重复计算”
  • PS 我查看了您的网站 - 看起来非常棒。
  • @Nick.McDermaid 感谢您的 cmets 和客气话。我在问题中添加了指向 ERD 的链接。

标签: sql postgresql


【解决方案1】:

通过拥有样本数据和想要的结果的优势,“缺失元素”似乎是需要通过 split_id 的相关子查询获得有限的结果。

请参阅rextester.comrextester.com在 PostgreSQL 9.6 中的工作

CREATE TABLE raw_times
    (id int, event_group_id int, parameterized_split_name varchar(11), bib_number int)
;

INSERT INTO raw_times
    (id, event_group_id, parameterized_split_name, bib_number)
VALUES
    (3, 53, 'finish', 11),
    (4, 53, 'finish', 603),
    (5, 53, 'finish', 9999),
    (6, 53, 'nonexistent', 603)
;


CREATE TABLE event_groups
    (id int)
;

INSERT INTO event_groups
    (id)
VALUES
    (53)
;


CREATE TABLE efforts
    (id int, event_id int, bib_number int)
;

INSERT INTO efforts
    (id, event_id, bib_number)
VALUES
    (22183, 26, 11),
    (22400, 28, 603)
;


CREATE TABLE aid_stations
    (id int, event_id int, split_id int)
;

INSERT INTO aid_stations
    (id, event_id, split_id)
VALUES
    (236, 26, 30),
    (237, 26, 31),
    (238, 26, 106),
    (239, 26, 111),
    (240, 26, 112),
    (241, 26, 109),
    (242, 26, 113),
    (254, 28, 119),
    (255, 28, 118)
;


CREATE TABLE splits
    (id int, parameterized_base_name varchar(6))
;

INSERT INTO splits
    (id, parameterized_base_name)
VALUES
    (30, 'finish'),
    (31, 'start'),
    (106, 'aid-1'),
    (109, 'aid-4'),
    (111, 'aid-2'),
    (112, 'aid-3'),
    (113, 'aid-5'),
    (118, 'start'),
    (119, 'finish')
;

查询 1

select 
     r.id, r.event_group_id, r.parameterized_split_name, r.bib_number
   , e.id as effort_id
   , e.event_id
   , s.split_id
from raw_times r
left join (
            select ef.id, ef.event_id, ef.bib_number, ev.event_group_id
            from efforts ef
            inner join events ev on ef.event_id = ev.id
           ) e on r.bib_number = e.bib_number
               and e.event_group_id = r.event_group_id
left join lateral (
            select a.split_id from aid_stations a
            inner join splits s on a.split_id = s.id
            where a.event_id = e.event_id
            and s.parameterized_base_name = r.parameterized_split_name
            limit 1) s on true
order by r.bib_number, r.id
;

结果

| id | event_group_id | parameterized_split_name | bib_number | effort_id | event_id | split_id |
|----|----------------|--------------------------|------------|-----------|----------|----------|
|  3 |             53 |                   finish |         11 |     22183 |       26 |       30 |
|  4 |             53 |                   finish |        603 |     22400 |       28 |      119 |
|  6 |             53 |              nonexistent |        603 |     22400 |       28 |   (null) |
|  5 |             53 |                   finish |       9999 |    (null) |   (null) |   (null) |

注意。如果使用旧版本的 Postgres,则可以在 select 子句中使用相关子查询来代替上面看到的 left join lateral

【讨论】:

  • 好的,这很有趣。但是我得到了不同的结果,因为 bib_number 在努力表中不是唯一的。它仅在 event_group 中的事件的努力中是唯一的。在我的实际(未编辑)数据库中,有很多重复 bib_numbers 的工作,并且这些重复项在使用此查询时显示为不需要的。有办法限制吗?
  • 另外,我没有在此查询中看到 raw_time.parameterized_split_name 与 split.parameterized_base_name 匹配的位置。我们不能只采用按 id 排序的第一个拆分。感谢您的帮助,并希望看到您进一步的想法。
  • 查询已修改
  • 这是一项重大改进。但是,我可以看到,我需要添加一些额外的数据来更好地代表真实的数据库。我已经编辑了问题,您会看到结果返回了一些不需要的数据。您能否根据额外的数据再看一下?
  • 我还更新了SQL Fiddle 以显示更改。您将看到 ID 为 3 的 raw_time 现在在查询结果中重复出现,其中包含一组不需要/不相关的 split_id、effort_id 和 event_id。
【解决方案2】:

在这种情况下,请在执行 LEFT OUTER 联接时添加条件。

SELECT  raw_times.*, efforts.id as effort_id, efforts.event_id as event_id, splits.id as split_id 
FROM raw_times 
INNER JOIN event_groups ON event_groups.id = raw_times.event_group_id 
INNER JOIN events ON events.event_group_id = event_groups.id 
LEFT JOIN efforts ON efforts.event_id = events.id AND efforts.bib_number::text = raw_times.bib_number
INNER JOIN aid_stations ON aid_stations.event_id = events.id 
LEFT JOIN splits ON splits.id = aid_stations.split_id AND splits.parameterized_base_name = raw_times.parameterized_split_name

编辑:

SELECT  raw_times.*, efforts.id as effort_id, efforts.event_id as event_id, splits.id as split_id 
FROM raw_times 
INNER JOIN event_groups ON event_groups.id = raw_times.event_group_id 
INNER JOIN events ON events.event_group_id = event_groups.id 
LEFT JOIN efforts ON efforts.event_id = events.id 
INNER JOIN aid_stations ON aid_stations.event_id = events.id 
LEFT JOIN splits ON splits.id = aid_stations.split_id 
WHERE (efforts.bib_number::text = raw_times.bib_number OR efforts.event_id IS NULL)
    AND (splits.parameterized_base_name = raw_times.parameterized_split_name OR splits.id IS NULL)

【讨论】:

  • 抱歉格式化,因为我是从手机而不是系统评论的。
  • 在我的数据库中,我目前有 6 个 raw_time 记录。此查询导致每个 raw_time 记录被返回 9 次(总共 54 条记录),并且在 force_id、event_id 和 split_id 列中有各种结果。我尝试使用SELECT DISTINCT ON (raw_times.id),但这会返回随机结果,其中大部分是错误的。
  • 您添加的连接条件有问题。暂时可以在上面加一个“Distinct”。顺便说一句,这是否解决了性能问题?
  • 根据我之前的评论,添加 DISTINCT 并不能解决问题。不确定您指的是哪个性能问题?
  • 没有样本数据我们怎么能“看到”任何东西?批评那些没有获得数据或预期结果的帮助的人是不合理的。我建议您阅读以下内容:Provide a Minimal Complete Verifiable Example (MCVE)Why should I provide a MCVE
猜你喜欢
  • 1970-01-01
  • 2011-06-09
  • 2020-08-01
  • 2022-11-19
  • 1970-01-01
  • 2021-01-07
  • 1970-01-01
  • 2017-11-26
  • 2012-11-05
相关资源
最近更新 更多