列出所有 Postgres 枚举数组值与应用于记录的值相结合,显示应用与否的状态

List all Postgres enum array values combined with values applied on the record showing a status if applied or not

最近我重新发现了 Postgres 用户定义的类型和数组,这让我觉得 Postgres 在这个术语流行之前就已经不是SQL(不仅是SQL)了。

需要选择一个枚举值列的数组以显示所有枚举值和一个标志,如果枚举数组列包含枚举值或没有正确排序(首先按列应用枚举值排序如果枚举值未包含在列中,则按枚举值排序)

示例 DDL 是:

-- available website settings enum:
CREATE TYPE website_menu_extras_type AS ENUM ( 'logo', 'emails', 'locations', 'phones', 'search' );

-- this type represents if website setting is enabled or not (the setting is treated as enabled if it's included to a website's column `menu_extras` of type `website_menu_extras_type[]`):
CREATE TYPE website_menu_extras_with_defaults AS ( menu_extra website_menu_extras_type, is_enabled BOOLEAN );

-- website table which contains applied `website_menu_extras_type` values as array:
CREATE TABLE website ( ID serial PRIMARY KEY, menu_extras website_menu_extras_type [] );

-- insert some values:
INSERT INTO website ( menu_extras )
VALUES
    ( ARRAY [ 'logo', 'emails' ]:: website_menu_extras_type [] );

-- select `menu_extras` as applied values 
-- and `menu_extras_with_defaults` which is an array 
-- of website_menu_extras_with_defaults having 
-- all values of `website_menu_extras_type` enum
-- and `is_enabled` set to true if `menu_extras` includes enum value
-- and `is_enabled` set to false if `menu_extras` does not include enum value
-- `menu_extras_with_defaults` should be sorted by `menu_extras` order 
-- and then by `website_menu_extras_type` enum order if `menu_extras` didn't include the enum value
SELECT
    id, menu_extras, menu_extras as menu_extras_with_defaults
FROM
    website;

可能会有大量的网站记录,主要是读取和设置枚举将在未来扩展,所以将设置 'included' 放入网站记录看起来比使用许多更好的解决方案-对多 table.

我已经开始使用 UDF(例如,它不会像预期的那样工作),例如:

  CREATE FUNCTION website_menu_extras_with_defaults ( website website ) RETURNS website_menu_extras_with_defaults[] AS $$ 
  WITH
    all_enum_values
    AS
    (
      SELECT UNNEST
        (
        enum_range ( NULL
     :: website_menu_extras_type )) AS val 
    ),
    all_enum_values1 AS
  (
    SELECT UNNEST
        (
        enum_range ( NULL
  :: website_menu_extras_type )) AS val 
    )
  -- select * from x1
  SELECT

    array[( val, FALSE ), (val, TRUE)]
  :: website_menu_extras_with_defaults []
FROM
    all_enum_values
-- where val in (website).menu_extras
    $$ LANGUAGE SQL STABLE;

我没有按预期完成工作。

在这种情况下,您如何获得正确的 menu_extras_with_defaults 值?

unnest(...) WITH ORDINALITY 是你的朋友:

CREATE OR REPLACE FUNCTION website_menu_extras_with_defaults(website website)
   RETURNS website_menu_extras_with_defaults[]
   LANGUAGE sql AS
$$SELECT array_agg(
            ROW(et.e, me.num IS NOT NULL)::website_menu_extras_with_defaults
               ORDER BY me.num, et.num
         )
FROM unnest(enum_range(NULL::website_menu_extras_type)) WITH ORDINALITY AS et(e, num)
   LEFT JOIN unnest(website.menu_extras) WITH ORDINALITY AS me(e, num)
      USING (e)$$;

让我在这里补充一两句建议。

尽管 PostgreSQL 支持复合数据类型和数组,但您不应该在所有地方开始使用它们。

这里不需要使用数组和复合类型。 website 也可能只有 id,而 menu_extras 应该是第二个 table 引用 website.

此外,除非您确定值的范围永远不会改变,否则不要使用 enum 类型。例如,您不能从 enum 类型中删除值。最好使用常规查找 table.

我预测,如果您像那样更改数据模型,编写您想要的函数会容易得多。

非原子数据类型在您需要它们的极少数情况下可能是一个强大的工具,但如果不加选择地使用它们会造成很多痛苦。