【问题标题】:Efficient query for updating element tags更新元素标签的高效查询
【发布时间】:2022-11-09 02:05:10
【问题描述】:

我正在尝试一次性创建/添加/删除标签。我想获取一个标签名称数组和一个元素 ID,并有效地更新该元素以仅包含数组中的标签。

我有 3 个表,如下所示。

元素:

|id|stuff|
|--|-----|
|1 | ... |
|2 | ... |

标签:

|id|name|
|--|----|
|1 | pg |
|2 |node|

标签地图:

|id|element_id|tag_id|
|--|----------|------|
|1 |    2     |  1   |
|2 |    2     |  2   |

在操作开始时,可​​以为元素分配任意数量的标签。该操作将从一组标签名称和一个元素 ID 开始。在操作结束时,我希望将具有该元素 id 的元素仅分配给该数组中传入的标签。该数组可能具有尚未在标签表中创建的标签,因此需要插入。

这是我的愚蠢,未优化的解决方案。

BEGIN;
    INSERT INTO tags (name)
    VALUES (''),(''),('')...
    ON CONFLICT DO NOTHING;

    DELETE FROM tag_map
    WHERE element_id = 'myElemID';

    WITH tag_ids AS (
        SELECT id FROM tags
        WHERE name IN ('','',''...)
    )
    INSERT INTO tag_map (element_id, tag_id)
    SELECT ('myElemID', tag_ids);
COMMIT;

我确信有一种更有效的方法来完成同样的事情。也许甚至可以在一个查询中完成?任何帮助将不胜感激

【问题讨论】:

  • 您使用的是什么版本的 Postgres?你可以使用MERGE
  • 我正在使用 postgres 13.7,所以我无法使用 Merge
  • 不要认为只能在一个事务中在一个查询中做到这一点

标签: sql database postgresql performance


【解决方案1】:

假设一个示例结构:

create table elements (id serial primary key, stuff text);
create table tags     (id serial primary key, name text);
create table tag_map  (id serial primary key,
                       element_id integer references elements(id),
                       tag_id     integer references tags(id));
insert into elements (stuff) values ('e1'),('e2'),('e3');
insert into tags     (name)  values ('t1'),('t2'),('t3'),('t4');
insert into tag_map  (element_id, tag_id)  values (1,1),(1,2),(2,4);

您可以在一个操作中使用WITH (common table expressions) 做到这一点:

with 
 delete_tag_map_by_element as (  
    delete from tag_map tm 
    using elements e
    where tm.element_id=e.id 
    and   e.stuff='e2'
    returning 'deleted' as operation,tm.*)
,tag_ids_from_names as (
    select id
    from   tags
    where   name in ('t2','t3'))--tag list goes here
,insert_tag_map as (
    insert into tag_map (element_id, tag_id)
    select 3, --target element to be tagged goes here
         id
    from tag_ids_from_names
    returning 'inserted' as operation,*)
select * from delete_tag_map_by_element
  union
select * from insert_tag_map;

Online demo.


关于 cmets:一些 PostgreSQL 15 MERGE 功能在 PostgreSQL 12 中以 insert...on conflict do update 的形式提供给您,但这里没有意义,因为 tag_map.id 字段,我假设它是表主键。如果您放弃该列并将 pk 设置为

create table tag_map2 (
  element_id integer references elements(id),
  tag_id integer references tags(id),
  primary key (element_id,tag_id));

您可以使用MERGE 中的WHEN MATCHEDWHEN NOT MATCHED 处理来删除新集中缺少的标记元素映射。 insert...on conflict do update 可以保留现有映射并添加新映射,但如果没有 CTE,它将无法删除过时的映射 - 这就是为什么它通常被称为 UPSERT,而不是完全涵盖 @987654340 的所有用途@。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多