【问题标题】:PostgreSQL: Select dynamic column in correlated subqueryPostgreSQL:在相关子查询中选择动态列
【发布时间】:2018-05-10 09:17:43
【问题描述】:

我正在使用实体-属性-值 (EAV) 模式来存储目标对象的“覆盖”。也就是三个表:

  • 实体,包含目标记录
  • 属性,包含实体表中“可覆盖”列的列名
  • 覆盖,包含 EAV 记录

我想做的是从实体表中选择覆盖以及“覆盖”列的值。因此,需要在 SQL 中动态使用属性名称。

我在 (PostgreSQL) SQL 中的幼稚尝试:

SELECT
  OV.entity_id        as entity,
  AT.name             as attribute,
  OV.value            as value,
  ENT.base_value      as base_value
FROM "override" AS OV
  LEFT JOIN "attribute" as AT
    ON (OV.attribute_id = AT.id)
  LEFT JOIN LATERAL (
            SELECT
              id,
              AT.name as base_value -- AT.name doesn't resolve to a SQL identifier
            FROM "entity"
            ) AS ENT
    ON ENT.id = OV.entity_id;

这不起作用,因为AT.name 不会解析为 SQL 标识符,而是简单地返回列名,例如“col1”、“col2”等,而不是使用列名查询 Entity。

我知道这是动态 SQL,但我对 PL/pgSQL 还是很陌生,因为它是相关/横向连接的,所以我无法弄清楚。另外,这是否可能,因为列类型不是同质类型的?请注意,覆盖表中的所有“值”都存储为字符串以解决此问题。

任何帮助将不胜感激!

【问题讨论】:

  • 样本数据和期望的结果会有所帮助。

标签: sql postgresql plpgsql dynamic-sql


【解决方案1】:

您可以使用 PL/pgSQL 动态请求列。我假设以下简化的数据库结构(在此示例中,所有原始值和覆盖值都是“字符变化”,因为我没有找到任何进一步的类型信息):

CREATE TABLE public.entity (
   id integer NOT NULL DEFAULT nextval('entity_id_seq'::regclass),   
   attr1 character varying,   
   attr2 character varying,   
   <...>
   CONSTRAINT entity_pkey PRIMARY KEY (id) 
)

CREATE TABLE public.attribute (   
   id integer NOT NULL DEFAULT nextval('attribute_id_seq'::regclass),
   name character varying,
   CONSTRAINT attribute_pkey PRIMARY KEY (id) 
)

CREATE TABLE public.override (   
   entity_id integer NOT NULL,
   attribute_id integer NOT NULL,
   value character varying,
   CONSTRAINT override_pkey PRIMARY KEY (entity_id, attribute_id),
   CONSTRAINT override_attribute_id_fkey FOREIGN KEY (attribute_id)
      REFERENCES public.attribute (id),
   CONSTRAINT override_entity_id_fkey FOREIGN KEY (entity_id)
      REFERENCES public.entity (id))

使用 PL/pgSQL 函数

create or replace function get_base_value(
  entity_id integer,
  column_identifier character varying
)
returns setof character varying
language plpgsql as $$
declare
begin
  return query execute 'SELECT "' || column_identifier || '" FROM "entity" WHERE "id" = ' || entity_id || ';';
end $$;

您几乎可以完全使用您的查询:

SELECT
      OV.entity_id        as entity,
      AT.name             as attribute,
      OV.value            as value,
      ENT.get_base_value  as base_value
    FROM "override" AS OV
      LEFT JOIN "attribute" as AT
        ON (OV.attribute_id = AT.id)
      LEFT JOIN LATERAL (
        SELECT id, get_base_value FROM get_base_value(OV.entity_id, AT.name)
      ) AS ENT
    ON ENT.id = OV.entity_id;

【讨论】:

  • 这看起来很棒,非常感谢!我有一个问题,我收到一个错误“列引用 id 不明确”,我认为是由于SELECT id, ... FROM get_base_value(...)。该函数是否还需要返回 entity_id,因为它目前只返回 setof character,或者 LEFT 连接被 CROSS 连接替换?
  • @GregBrown 如果只是通过将 select 语句更改为 SELECT OV.entity_id as id, get_base_value FROM get_base_value(OV.entity_id, AT.name) 来指定 id 是否有效?
  • 是的,现在可以选择OV.entity_id as id。谢谢,瓦斯科。这会比使用CROSS 加入方法更好吗?
  • @GregBrown 太好了!我看不出 CROSS JOIN 在哪里可以提高 LEFT JOIN 的性能。在任何情况下都需要 PL/pgSQL 函数来选择特定的原始列。
猜你喜欢
  • 2012-01-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多