You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将插值对象数组转换为PostgreSQL复合类型数组?

问题描述

我通过SQL函数执行批量插入操作,但该函数无法直接接收记录集作为参数,必须先将数据转换为数组。基本类型数组可以通过CAST (${value} as primitive_type[])直接转换,效果正常。但批量插入需要传入复合类型数组,而CAST()无法完成这种转换——它仅支持单列输入。

目前我的实现流程是:将数据以${account_inits:json}形式插值到SQL中,通过CTE先转成记录集,再聚合为复合类型数组后传入函数。这套步骤过于繁琐,尝试跳过JSON转换时,又会触发array[]或malformed object literal语法错误,希望找到更简洁的转换方式。

表与自定义类型

CREATE TABLE accounts (
  id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
  login text NOT NULL,
  password text NOT NULL,
  email text
);

CREATE TYPE account_init AS (
  login text,
  password text,
  email text
);

函数定义

CREATE FUNCTION get_accounts(
  pagination_limit bigint DEFAULT 25,
  pagination_offset bigint DEFAULT 0,
  account_ids bigint[] DEFAULT NULL
)
RETURNS TABLE (
  id bigint,
  created_at timestamptz,
  login text,
  password text,
  email text
)
LANGUAGE SQL
AS $BODY$
  WITH input_accounts AS (
    SELECT
      id,
      created_at,
      login,
      password,
      email
    FROM
      accounts
    WHERE
      account_ids IS NULL OR id = ANY (account_ids)
    ORDER BY
      id
    LIMIT pagination_limit
    OFFSET pagination_offset
  )
  SELECT
    id,
    created_at,
    login,
    password,
    email
  FROM
    input_accounts
  ORDER BY
    id
$BODY$;

CREATE FUNCTION create_accounts(
  account_inits account_init[]
)
RETURNS TABLE (
  id bigint,
  created_at timestamptz,
  login text,
  password text,
  email text
)
LANGUAGE SQL
AS $BODY$
  WITH new_accounts AS (
    INSERT INTO accounts ( 
      login, 
      password, 
      email 
    )
    SELECT 
      login, 
      password, 
      email
    FROM 
      unnest(account_inits)
    RETURNING
      id
  )
  SELECT
    id,
    created_at,
    login,
    password,
    email
  FROM
    get_accounts(
      NULL,
      NULL,
      ARRAY(
        SELECT
          id
        FROM
          new_accounts
      )
    )
  ORDER BY
    id
$BODY$;

待插入数据

const account_inits = [
  {
    login:"EC4A42323F", 
    password: "3DF1542F23A29B73281EEC5EBB55FFE18C253A7E800E7A541B"
  },
  {
    login:"1D771C1E52", 
    password: "2817029563CC722FBC3D53F9F29F0000898F9843518D882E4A", 
    email: "a@b"
  },
  {
    login:"FB66381D3A", 
    password: "C8F865AC1D54CFFA56DEBDEEB671C8EF110991BBB3B9EE57D2", 
    email: null
  }
]

当前实现方式

--- 插入数据
WITH input_inits AS (
  SELECT
    login,
    password,
    email
  FROM
    json_to_recordset(${account_inits:json}) AS input_init(
      login text,
      password text,
      email text
    )
),
input_data AS (
  SELECT
    array_agg(
      CAST (
        (
          login,
          password,
          email
        ) AS account_init
      )
    ) AS account_inits
  FROM
    input_inits
)
SELECT
  new_accounts.id,
  new_accounts.created_at,
  new_accounts.login,
  new_accounts.password,
  new_accounts.email
FROM
  input_data
  CROSS JOIN
  create_accounts(input_data.account_inits) AS new_accounts
ORDER BY
  new_accounts.id ASC
;

优化方案

可以直接构造复合类型数组,跳过JSON转换步骤,核心是为每个待插入对象生成(login, password, email)::account_init格式的复合类型,再组合成数组传入函数。

示例代码

假设你的数据插值后能生成如下SQL结构:

SELECT
  new_accounts.id,
  new_accounts.created_at,
  new_accounts.login,
  new_accounts.password,
  new_accounts.email
FROM
  create_accounts(
    ARRAY[
      ('EC4A42323F', '3DF1542F23A29B73281EEC5EBB55FFE18C253A7E800E7A541B', NULL)::account_init,
      ('1D771C1E52', '2817029563CC722FBC3D53F9F29F0000898F9843518D882E4A', 'a@b')::account_init,
      ('FB66381D3A', 'C8F865AC1D54CFFA56DEBDEEB671C8EF110991BBB3B9EE57D2', NULL)::account_init
    ]
  ) AS new_accounts
ORDER BY
  new_accounts.id ASC;

关键点说明

  • 每个元组必须严格匹配account_init类型的字段顺序和类型
  • 空值用NULL显式表示,避免语法错误
  • 数组中的每个元素都要通过::account_init显式转换为自定义复合类型,PostgreSQL才能正确识别数组类型

这种方式省去了JSON解析和CTE聚合的步骤,直接构造目标数组传入函数,效率更高也更简洁。

内容的提问来源于stack exchange,提问作者Biller Builder

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 18:15:50