PL / pgSQL-找出表中存在数组的项

问题描述

我正在尝试找到一种方法,通过将它们在表中的存在将文本数组分为两个数组。

让我们说users表具有以下三个ID:1,2,3,而我有一个input_array_ids,其值为1,5。表中存在ID 1,2,因此它进入另一个称为ids_valid的数组,ID 5进入ids_invalid。最后,我想对有效ID进行更新,并通知用户哪些ID已更新以及在表中找不到哪些ID。

CREATE FUNCTION update_status(p_id character varying DEFAULT NULL::character varying,p_status character varying DEFAULT NULL::character varying) RETURNS character varying
    LANGUAGE plpgsql
AS
$$
DECLARE

    v_valid_ids   users.id%type;
    v_invalid_ids users.id%type;
    v_result      varchar(255);
    v_ids         text[];
BEGIN
    v_ids = string_to_array(regexp_replace(p_id,'[\s+]','','g'),',');

    --Do some magic to split v_ids into v_valid_ids and v_invalid_ids

    UPDATE users
    SET row_status = p_status,updated_by = 'hxBiSP'
    WHERE id = ANY (v_valid_ids);

    v_result = 'Updated: ' || string_agg(v_valid_ids::character varying,') || 'Could not update: ' || string_agg(v_invalid_ids::character varying,');
    RETURN v_result;

END ;
$$;

ALTER FUNCTION update_status(varchar,varchar) OWNER TO postgres;

我该怎么办?

解决方法

我认为您不需要plgpsql即可完成所需的工作:CTE足够好。考虑以下sql函数:

create function update_status(p_id text,p_status text) 
returns text
as $$
    with 
        t_ids(id) as (select regexp_split_to_table('1,2,4',',\s*')::int),upd as (
            update users u
            set row_status = p_status,updated_by = 'hxbisp'
            from t_ids i 
            where i.id = u.id
        )
    select 'updated: ' || string_agg(u.id::text,') 
        || ' - could not update: ' || string_agg(i.id::text,') filter(where u.id is null)
    from t_ids i
    left join users u on u.id = i.id        
$$ 
language sql;

第一个CTE t_ids将输入字符串嵌套到行中。然后,upd执行联接。最后,外部查询使用left join和聚合(即函数的结果集)来生成表示匹配的ID和未匹配的ID的状态字符串。

Demo on DB Fiddle

-- initial table content
select * from users order by id;

id | name | row_status | updated_by
-: | :--- | :--------- | :---------
 1 | foo  |            |                    
 2 | bar  |            |                    
 3 | baz  |            |                    


-- call the function
select update_status('1,'zoo');

| update_status                       |
| :---------------------------------- |
| updated: 1,2 - could not update: 4 |

-- check the content of the table
select * from users order by id;

id | name | row_status | updated_by
-: | :--- | :--------- | :---------
 1 | foo  | zoo        | hxbisp    
 2 | bar  | zoo        | hxbisp    
 3 | baz  |            |
,

预先将ID分为两组现有的和不存在的(在代码中为v_valid_idsv_invalid_ids),这使您可以进入竞争状态。如果并发事务删除/更新/插入ID,该怎么办?

只需在原始ID组(列表/数组)上运行UPDATE,然后使用RETURNING子句返回实际上受 影响的ID。参见:

不仅通常便宜得多(仅对目标牌桌进行一次通行),而且针对比赛条件也很安全

假设使用integer 的ID,该ID可以实现更简单,更快速的代码,但是,使用附加模块intarray时-每个数据库需要使用一次安装

CREATE EXTENSION intarray;

请参阅:

(但是我们可以对任何数据类型使用此功能。)

CREATE OR REPLACE FUNCTION f_update_status(_ids_as_string text = NULL,_status text = NULL)
   RETURNS text
   LANGUAGE plpgsql AS
$func$
DECLARE
   _ids     int[] := string_to_array(_ids_as_string,')::int[];  -- ①
   _updated int[];
BEGIN
   WITH upd AS (
      UPDATE users
      SET    row_status = _status,updated_by = 'hxBiSP'
      WHERE  id = ANY (_ids)  -- just use input array
      RETURNING id            -- returns actually affected IDs
      )
   SELECT ARRAY(TABLE upd)
   INTO _updated;  -- ③ aggregate into array with ARRAY constructor

   RETURN format('Updated: %s. Not found: %s.',_updated::text,(_ids - _updated)::text);  -- ②
END
$func$;

db 提琴here

或者使用VARIADIC函数将实际整数值或实际整数数组作为单个参数传递,例如您上一个问题下建议的a_horse:

①可以在声明时间分配变量。并自动从整数中修剪前导或尾随空白。

②使用intarray提供的integer[] - integer[] → integer[] operator

③关于ARRAY构造函数:

如何将单个数组而不是值字典传递给VARIADIC函数: