如何检查Postgres表中是否存在列表元素

时间:2018-09-24 09:40:03

标签: arrays postgresql loops foreach

我的应用程序中有一个列表;

tags = ["nature", "science", "funny", "politics", "news"]

,我想检查所有这些元素是否存在于我的Tag.name表字段中。想法是用户无法添加系统中尚未存在的任何新标签。

当前,我正在尝试以以下方式在查询中运行.foreach循环:

DO $$
BEGIN
  FOREACH itag IN ARRAY {'nature', 'politics'} LOOP
    IF EXISTS (SELECT 1 FROM tags WHERE name = itag) THEN
      INSERT INTO TAGS (name, post_id) values itag, 'post01' ;
    ELSE
      RAISE EXCEPTION 'Tag % doesnt exists in table', itag;
    END IF;
  END LOOP;
END; $$

这给出了错误;

ERROR:  syntax error at or near "{"
LINE 3:   FOREACH itag SLICE 1 IN ARRAY {'nature', 'politics'} LOOP

我不确定如何在postgres中查询列表。我不想通过应用程序代码执行此操作并触发多个查询,因为列表中可能有很多元素。我想在单个查询中对照表值检查列表。

如果可以的话,还有没有办法优化我的查询?


编辑: 我已经使用@a_horse_with_no_name的答案来提出类似于我所寻找的流程。如果我的表中存在所有标签,则将添加这些条目,否则将引发异常。

DO $$
BEGIN
 IF (with to_check (itag) as (
   values ('nature'),('science'),('politics'),('scary')
  )
  select bool_and(exists (select * from tags t where t.name = tc.itag)) as all_tags_present
  from to_check tc) THEN
  RAISE INFO 'ALL GOOD. Here I will add the insert statement in my app';
ELSE
  RAISE EXCEPTION 'One or more tags are not present';
END IF;

END; $$

1 个答案:

答案 0 :(得分:1)

不需要PL / pgSQL或循环。您可以使用标记列表,并在一个语句中检查每个标记的存在:

with to_check (itag) as (
   values ('nature'),('science'),('funny'),('politics'),('news')
)
select tc.itag, 
       exists (select * from tags t where t.name = tc.itag) as tag_exists
from to_check tc;

如果您只想一个标志告诉您是否至少缺少一个标签,则可以使用以下命令:

with to_check (itag) as (
   values ('nature'),('science'),('funny'),('politics'),('news')
)
select bool_and(exists (select * from tags t where t.name = tc.itag)) as all_tags_present
from to_check tc;

bool_and仅在所有值均为true时才返回true。


您收到的错误是因为{'nature', 'politics'}是无效的数组文字。您要么需要使用array构造函数

array['nature', 'politics'] 

或可以转换为数组的字符串文字

'{nature, politics}'::text[]

(请注意,大括号位于字符串的内部)。

我更喜欢数组构造函数,因为我不必担心嵌套字符串文字。


  

想法是用户无法添加系统中尚未存在的任何新标签

此问题的正确解决方案是,使一个表包含标签定义,并确保每个标签名称仅使用一次:

create table tag_definition
(
   name   varchar(50) primary key
);

然后在您的标签表中引用tag_definition:

create table tags
(
   name     varchar(50) not null references tag_definition, 
   post_id  integer  not null references posts
);

现在无法在tags表中插入不存在的标记。

现在您要做的就是在插入行时捕获异常。 插入之前,无需检查标签。

通过为tags表(例如tag_definition)使用生成的主键,并将serial表用作参考,您可以节省空间并使tags表变小{1}}表。


鉴于您的问题中的插入语句,您可以使用单个插入语句来实现相同的目的:

with to_check (itag) as (
   values ('nature'),('politics')
)
insert into tags (tag, post_id)
select tc.itag, 'post01'
from to_check tc 
where not exists (select itag
                  from to_check
                  except
                  select t.name
                  from tags t);

但是,如果标签表增加,那么伸缩性将不会很好。如果来自to_check的所有标记都存在,则子选择将不返回任何内容,因此not exists条件将使INSERT返回所有内容。如果至少不存在一个标签,则不会插入任何内容。

要使(某种程度上)有效,您将需要在`tags(name)上建立索引。