【问题标题】:Left join combined with full outer join左连接与完全外连接相结合
【发布时间】:2016-12-19 19:30:44
【问题描述】:

有一个主表,它引用了另外两个表。这两个表每个引用可能有多个条目。

主要:

| id | mname | sid | lid |
|----|-------|-----|-----|
|  1 | a1    |   1 |   2 |
|  2 | a2    |   2 |   3 |
|  3 | a3    |   1 |   1 |

短:

| id | lang | sdesc |
|----|------|-------|
|  1 | de   | S1    |
|  1 | en   | S2    |
|  2 | de   | S3    |
|  3 | en   | S4    |

(id, lang) 是唯一的。

长:

| id | lang | ldesc |
|----|------|-------|
|  1 | de   | L1    |
|  1 | en   | L2    |
|  2 | de   | L3    |
|  3 | en   | L4    |

(id, lang) 是唯一的。

我想将这三个表连接起来得到以下结果:

| mname | lang | sdesc  | ldesc  |
|-------|------|--------|--------|
| a1    | de   | S1     | L3     |
| a1    | en   | S2     | (null) |
| a2    | de   | S3     | (null) |
| a2    | en   | (null) | L4     |
| a3    | de   | S1     | L1     |
| a3    | en   | S2     | L2     |

我的第一次尝试是 (sqlfiddle)

select m.mname, s.lang, s.sdesc, l.lang, l.ldesc
from main m
left join short_desc s on s.id = m.sid
left join long_desc l on l.id = m.lid

这给了太多的条目。

下一个是 (sqlfiddle)

select m.mname, s.lang, s.sdesc, l.lang, l.ldesc
from main m
left join short_desc s on s.id = m.sid
left join long_desc l on l.id = m.lid
where s.lang = l.lang

这会遗漏有效的条目。

经过上述的许多变化和大量的玩弄,我想出了(sqlfiddle

with x1 as (select m.id, m.mname, s.lang, s.sdesc
            from main m
            join short_desc s on s.id = m.sid),
     x2 as (select m.id, m.mname, l.lang, l.ldesc
            from main m
            join long_desc l on l.id = m.lid)
select coalesce(x1.mname, x2.mname) mname,
       coalesce(x1.lang, x2.lang) lang,
       x1.sdesc,
       x2.ldesc
from x1
full outer join x2 on x2.id = x1.id and x2.lang = x1.lang

这给出了预期的结果,但对我来说,这对于如此简单的 (?) 要求来说似乎太过分了。

所以这是我的问题,有没有更简单的方法?

【问题讨论】:

  • ... from main m cross join (select * from unnest(array['de','en'])) lang(code) left join short_desc s on s.id = m.sid and lang.code = s.lang ...
  • 您的数据和想要的结果不匹配。您的结果要求 a2、en、null、L4 行,但您的数据针对 id 3 具有 L4。
  • @RelativePHPNewbie 是的,m.lid = l.id,即m = (2, a2, 2, *3*)l = (*3*, en, L4)
  • @Abelisto 哇,想添加这个作为答案吗?我还不明白这个,但它似乎工作(sqlfiddle

标签: sql postgresql left-join full-outer-join


【解决方案1】:

IMO 您在数据架构中遗漏的是可用语言的字典。如果引入它,所有其他都变得简单。

所需的字典是:

  • array['en','de','fr']这样的简单常量
  • 真正的桌子
  • short_desclong_desc 等表中收集语言代码的视图

由您决定哪种情况更适合您的目的。

在下面的示例中,我们将语言字典视为 CTE:

with lang(code) as (values('en'),('de'),('fr'))
select m.*, lang.code
from main m cross join lang;

╔════╤═══════╤═════╤═════╤══════╗
║ id │ mname │ sid │ lid │ code ║
╠════╪═══════╪═════╪═════╪══════╣
║  1 │ a1    │   1 │   2 │ en   ║
║  2 │ a2    │   2 │   3 │ en   ║
║  3 │ a3    │   1 │   1 │ en   ║
║  1 │ a1    │   1 │   2 │ de   ║
║  2 │ a2    │   2 │   3 │ de   ║
║  3 │ a3    │   1 │   1 │ de   ║
║  1 │ a1    │   1 │   2 │ fr   ║
║  2 │ a2    │   2 │   3 │ fr   ║
║  3 │ a3    │   1 │   1 │ fr   ║
╚════╧═══════╧═════╧═════╧══════╝
(9 rows)

如您所见,现在我们为每种语言提供了单独的行,下一步和最后一步是使用 idlang 字段将其他两个表连接到上述查询:

with lang(code) as (values('en'),('de'),('fr'))
select m.*, lang.code, s.sdesc, l.ldesc
from main m cross join lang
  left join short_desc s on s.id = m.sid and s.lang = lang.code
  left join long_desc l on l.id = m.lid and l.lang = lang.code;

╔════╤═══════╤═════╤═════╤══════╤═══════╤═══════╗
║ id │ mname │ sid │ lid │ code │ sdesc │ ldesc ║
╠════╪═══════╪═════╪═════╪══════╪═══════╪═══════╣
║  3 │ a3    │   1 │   1 │ de   │ S1    │ L1    ║
║  3 │ a3    │   1 │   1 │ en   │ S2    │ L2    ║
║  3 │ a3    │   1 │   1 │ fr   │ ░░░░  │ ░░░░  ║
║  1 │ a1    │   1 │   2 │ de   │ S1    │ L3    ║
║  1 │ a1    │   1 │   2 │ en   │ S2    │ ░░░░  ║
║  1 │ a1    │   1 │   2 │ fr   │ ░░░░  │ ░░░░  ║
║  2 │ a2    │   2 │   3 │ de   │ S3    │ ░░░░  ║
║  2 │ a2    │   2 │   3 │ en   │ ░░░░  │ L4    ║
║  2 │ a2    │   2 │   3 │ fr   │ ░░░░  │ ░░░░  ║
╚════╧═══════╧═════╧═════╧══════╧═══════╧═══════╝

【讨论】:

    【解决方案2】:

    您希望left join 具有lang 上的条件 on 子句:

    select m.mname, s.lang, s.sdesc, l.lang, l.ldesc
    from main m left join
         short_desc s
         on s.id = m.sid left join
         long_desc l
         on l.id = m.lid and s.lang = l.lang;
    

    这应该是 Goldilock 的解决方案,行数恰到好处。

    甚至:

    select m.mname, s.lang, s.sdesc, l.lang, l.ldesc
    from main m left join
         short_desc s
         on s.id = m.sid left join
         long_desc l
         on l.id = m.lid and (s.lang = l.lang or s.lang is null);
    

    【讨论】:

    • 这适用于ldesc 为空的条目,但它会忽略sdesc 为空的条目,如“a2 (null) L4”行 (sqlfiddle)。
    猜你喜欢
    • 2014-09-23
    • 1970-01-01
    • 2011-10-01
    • 2017-03-25
    • 1970-01-01
    • 2021-09-02
    • 1970-01-01
    • 2012-07-27
    • 2019-10-03
    相关资源
    最近更新 更多