Select 使用左连接的一对多关系中的第一条记录

Select the first record in a one to many relation using left join

我正在尝试使用左联接来联接两个 table。并且结果集必须仅包含“正确”连接的第一条记录 table.

假设我有两个 tables 产品和 product_translations,如下所示;

Table 'products'

id price
1 100
2 150

Table 'product_translations'

id product_id lang column value
1 1 en title Product 1 Title
2 2 en title Product 2 Title
3 1 en description Product 1 Description
4 2 en description Product 2 Description
5 1 de title Produkt 1 Titel
6 2 de title Produkt 2 Titel
7 1 de description Produkt 1 Beschreibung
8 2 de description Produkt 2 Beschreibung

预期输出

id price title description
1 100 Product 1 Title Product 1 Description
2 150 Product 2 Title Product 2 Description

您似乎不需要“第一”行,而只需要两个基于语言的不同行。所以你需要加入翻译table:一个用于“标题”,一个用于“描述”

select p.*, t.description as title, d.description
from product p
  left join product_translations t 
         on t.product_id = p.id 
        and t.language = 'en'
        and t.column = 'title'
  left join product_translations d 
         on d.product_id = p.id 
        and d.language = 'en'
        and d.column = 'description'

如果你想用“主要”和“后备”语言检索它,你可以这样做:

with product_texts as (
  select t.product_id, t.value as title, d.value as description, t.lang
  from product_translations t
    join product_translations d 
      on d.product_id = t.product_id
     and d.lang = t.lang
     and d."column" = 'description'
  where t."column" = 'title'   
    and t.product_id = 3
    and t.lang in ('de', 'en')
) 
select t.*
from product_texts t
order by case 
           when t.lang = 'de' then 1
           else 2
         end
limit 1

由于总是加入反对它有点复杂,您可以创建一个函数来执行此操作:

create function get_translations(p_product_id int, p_lang text, p_fallback_lang text default 'en')
  returns table (product_id int, title text, description text, lang text)
as
$$ 
  with product_texts as (
    select t.product_id, t.value as title, d.value as description, t.lang
    from product_translations t
      join product_translations d 
        on d.product_id = t.product_id
       and d.lang = t.lang
       and d."column" = 'description'
    where t."column" = 'title'   
      and t.product_id = 3
      and t.lang in (p_lang, p_fallback_lang)
  ) 
  select t.*
  from product_texts t
  order by case 
             when t.lang = p_lang then 1
             else 2
           end
  limit 1
$$
language sql
rows 1
stable;

那么你可以这样做:

select *
from products p
  left join lateral get_translations(p.id, 'de', 'en') on true

调用函数的开销接近 none,因为这将被内联 - 就像您将函数的查询写入主查询一样。

为了获得良好的性能,您需要在 (product_id, lang, "column") 上建立索引,甚至可能需要两个过滤索引:

create index product_lang_title 
  on product_translations (product_id, lang)
where "column" = 'title';

create index product_lang_descr 
   on product_translations (product_id, lang)
where "column" = 'description';

Online example

使用row_number定义第一行。我假设您的意思是“第一条记录”是“id”值最低的记录。

with prod_t as
(
  select t1.*, row_number() over (partition by product_id, t1.column order by id) as rn
  from product_translations t1
)
select p1.*, t2.value as title, t3.value as desctiption
from products p1
left join prod_t t2
  on t2.product_id = p1.id
  and t2.column = 'title'
  and rn =1
left join prod_t t3
  on t3.product_id = p1.id
  and t3.column = 'description'
  and rn =1

此外,最好避免使用“column”作为列名