1

基本上,我有一个存储列名的表,但有一些限制:infos,以及另一个存储这些列的值的表:info_data。我想得到一个表,其中包含来自的列和来自的infos数据info_data。我已经尝试过使用交叉表功能,但它没有达到预期的效果。

我有 2 张桌子:

CREATE TABLE infos
(id serial PRIMARY KEY,
 name text NOT NULL,
 id_member integer NOT NULL,
 title text,
 min_length integer NOT NULL DEFAULT 0,
 max_length integer NOT NULL DEFAULT 30,
 required boolean NOT NULL DEFAULT false,
 type text NOT NULL DEFAULT 'text'::text
);

CREATE INDEX info_id_idx ON infos (id);

CREATE TABLE info_data
(id serial PRIMARY KEY,
 id_info integer,
 value text,
  CONSTRAINT info_data_id_info_fkey FOREIGN KEY (id_info)
      REFERENCES infos (id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION
);

CREATE INDEX info_data_id_idx ON info_data(id);

具有以下值:

信息:

COPY infos (id, name, id_member, title, min_length, max_length, required, type)     FROM     stdin;
1 nume 1 Nume 0 30 t text
2 prenume 1 Prenume 0 30 t text
3 cnp 1 C.N.P. 13 13 t number
4 nume anterior 1 Nume anterior 0 30 f text
5 stare civila 1 Starea civila 0 30 f text
6 cetatenie 1 Cetatenie 0 30 f text
7 rezidenta 1 Rezidenta 0 30 f text
9 tip act 1 C.I. / B.I. 0 10 t text
10 serie ci 1 Serie C.I. / B.I. 0 30 t text
11 numar ci 1 Numar C.I. / B.I. 0 30 t text
12 data eliberarii 1 Data eliberarii 0 30 t text
13 eliberat de 1 Eliberat de 0 30 t text
8 adresa 1 Adresa 0 50 f text
\.

信息数据:

COPY info_data (id, id_info, value) FROM stdin;
1 1 a
2 2 a
3 3 100
4 4
5 5
6 6
7 7
8 8
9 9 ci
10 10 sv
11 11 13
12 12 132
13 13 123
14 1 b
15 2 b
16 3 100
17 4
18 5
19 6
20 7
21 8
22 9 BI
23 10 XT
24 11 123
25 12 10
26 13 10
\.

问题:我怎样才能实现这个输出?(必须根据表中的唯一条目生成infos

nume, prenume, cnp, nume anterior, ... (as columns - built from infos)
a   , a, ...
b   , b, ... (as rows - built from info_data)
4

4 回答 4

1

这个问题比你想象的要难解决。您的尝试crosstab()瞄准了正确的方向。但是要分配动态列名,您还需要动态 SQL:EXECUTE在 plpgsql 函数中。

将列的数据类型infos.type从更改textregtype以防止 SQL 注入和其他错误。例如,您有 data type number,它不是有效的 PostgreSQL 数据类型。我用 替换了它numeric,所以它可以工作。

可以通过避免需要双引号的列名来简化任务。喜欢nume_anterior而不是"nume anterior".

您可能希望在表中添加一列row_idinfo_data标记一行的所有元素。该函数需要它crosstab(),它允许您忽略具有NULL值的列。有两个参数的crosstab()函数可以处理缺失的列。我用下面的表达式综合了缺失的列(d.id-1)/13- 这适用于您示例中的数据。

您需要安装附加模块 tablefunc(每个数据库一次):

CREATE EXTENSION tablefunc;

在此相关答案中查找其他解释和链接。

此功能将执行所需的操作:

CREATE OR REPLACE FUNCTION f_mytbl()
  RETURNS TABLE (id int
, nume text           , prenume text       , cnp numeric
, "nume anterior" text, "stare civila" text, cetatenie text
, rezidenta text      , adresa text        , "tip act" text
, "serie ci" text     , "numar ci" text    , "data eliberarii" text
, "eliberat de" text)
  LANGUAGE plpgsql AS
$BODY$
BEGIN

RETURN QUERY EXECUTE $f$
SELECT *
FROM   crosstab(
    'SELECT (d.id-1)/13 -- AS row_id
          , i.id, d.value
     FROM   infos i
     JOIN   info_data d ON d.id_info = i.id
     ORDER  BY 1, i.id',

    'SELECT id
     FROM   infos
     ORDER  BY id'
    )
AS tbl ($f$ || 'id int,
, nume text           , prenume text       , cnp numeric
, "nume anterior" text, "stare civila" text, cetatenie text
, rezidenta text      , adresa text        , "tip act" text
, "serie ci" text     , "numar ci" text    , "data eliberarii" text
, "eliberat de" text)';

END;
$BODY$;

称呼:

SELECT * FROM x.mytbl();

不要对嵌套的美元报价感到困惑。

顺便说一句:我用这个语句创建了列列表:

SELECT 'id int,' || string_agg(quote_ident(name) || ' ' || type
                              ,', ' ORDER BY i.id) 
FROM   infos i;
于 2012-05-27T21:29:22.030 回答
0

这就是为什么 EAV 对大多数事情都很糟糕的原因。如果您想检索列中的内容,您可能不想使用 EAV。我提出了我自己的 EAV-lite,它使用应用程序数据目录和 ALTER TABLE 命令根据自定义字段的类似内容创建实际的关系模型。然而,这并不是真的在这里或那里。

基本上你有两个选择。您不能“只使用存储过程”,因为您必须提前告诉 PostgreSQL 使用什么结果名称和类型。您可以使用返回引用和动态 SQL 的存储过程。这就是满足您严格要求的答案。

这看起来像

CREATE OR REPLACE FUNCTION eav_get(in_id int) RETURNS refcursor
LANGUAGE PLPGSQL AS 
 $$ DECLARE outval refcursor;
            t_row RECORD;
            t_query TEXT;
  BEGIN
      t_query := 'SELECT ';

      FOR t_row IN select distinct "name" FROM infos
      LOOP
         t_query := t_query 'max(CASE WHEN "name" = '|| quote_literal(t_row."name") || ' THEN value ELSE NULL END) ';
      END LOOP;
      t_query := t_query || 'FROM info_data WHERE id_key = || in_id || ' 
                 GROUP BY id_key ';
      OPEN outval FOR EXECUTE t_query;
      RETURN outval;
 END; $$;

我还没有测试过上面的代码,但它应该足够接近让你继续前进。

但是,您应该考虑另一种选择。这不会在列中返回,但它更干净并且在客户端解析起来也不太难。我们在实际需要 EAV 的 LedgerSMB 中使用它。

 SELECT id_key, array_agg("name"::text || '=' || "value"::text) 
   FROM infos_data 
  WHERE id_key = ?

假设您不允许等号(您可以使用任何其他分隔符),您会得到一个 PostgreSQL 数组,该数组很容易为您的应用程序解析。

于 2012-09-06T06:51:05.910 回答
0

如果您必须对 EAV 内容进行非规范化并且没有可用的类似枢轴的功能,那么这个丑陋的野兽会派上用场。

我必须在 info_data 中为实体添加一个键域。

ALTER TABLE info_data
        ADD column id_key INTEGER
        ;
UPDATE info_data
SET id_key= 1+ ((id-1)/13);

WITH reut AS (
   SELECT d.id_info
        , d.value
        , d.id_key
        , i.zname AS zname
   FROM info_data d
   JOIN infos i ON i.id = d.id_info
        )
, num AS (
        SELECT DISTINCT id_key AS id_key
        FROM info_data
        )
SELECT n.id_key AS id_key
        ,r1.value AS "nume"
        ,r2.value AS "prenume"
        ,r3.value AS "cnp"
        ,r4.value AS "nume anterior"
        ,r5.value AS "stare civila"
        ,r6.value AS "cetatenie"
        ,r7.value AS "rezidenta"
        ,r8.value AS "adresa"
        ,r9.value AS "tip act"
        ,r10.value AS "serie ci"
        ,r11.value AS "numar ci"
        ,r12.value AS "data eliberarii"
        ,r13.value AS "eliberat de"
FROM num n
LEFT JOIN reut r1 ON r1.id_key = n.id_key AND r1.zname = 'nume'
LEFT JOIN reut r2 ON r2.id_key = n.id_key AND r2.zname = 'prenume'
LEFT JOIN reut r3 ON r3.id_key = n.id_key AND r3.zname = 'cnp'
LEFT JOIN reut r4 ON r4.id_key = n.id_key AND r4.zname = 'nume anterior'
LEFT JOIN reut r5 ON r5.id_key = n.id_key AND r5.zname = 'stare civila'
LEFT JOIN reut r6 ON r6.id_key = n.id_key AND r6.zname = 'cetatenie'
LEFT JOIN reut r7 ON r7.id_key = n.id_key AND r7.zname = 'rezidenta'
LEFT JOIN reut r8 ON r8.id_key = n.id_key AND r8.zname = 'adresa'
LEFT JOIN reut r9 ON r9.id_key = n.id_key AND r9.zname = 'tip act'
LEFT JOIN reut r10 ON r10.id_key = n.id_key AND r10.zname = 'serie ci'
LEFT JOIN reut r11 ON r11.id_key = n.id_key AND r11.zname = 'numar ci'
LEFT JOIN reut r12 ON r12.id_key = n.id_key AND r12.zname = 'data eliberarii'
LEFT JOIN reut r13 ON r13.id_key = n.id_key AND r13.zname = 'eliberat de'

ORDER BY n.id_key;
于 2012-05-30T14:21:41.477 回答
0

我想你可以做这样的事情

SELECT 
    C1.VALUE AS (SELECT NAME FROM INFOS WHERE ID=1),  
    C2.VALUE AS (SELECT NAME FROM INFOS WHERE ID=2),  
    C3.VALUE AS (SELECT NAME FROM INFOS WHERE ID=3),  
    C4.VALUE AS (SELECT NAME FROM INFOS WHERE ID=4),  
    C5.VALUE AS (SELECT NAME FROM INFOS WHERE ID=5),  
    C6.VALUE AS (SELECT NAME FROM INFOS WHERE ID=6),  
    C7.VALUE AS (SELECT NAME FROM INFOS WHERE ID=7),  
    C8.VALUE AS (SELECT NAME FROM INFOS WHERE ID=8),  
    C9.VALUE AS (SELECT NAME FROM INFOS WHERE ID=9),  
    C10.VALUE AS (SELECT NAME FROM INFOS WHERE ID=10),  
    C11.VALUE AS (SELECT NAME FROM INFOS WHERE ID=11),  
    C12.VALUE AS (SELECT NAME FROM INFOS WHERE ID=12),  
    C13.VALUE AS (SELECT NAME FROM INFOS WHERE ID=13)  
    FROM (
    (SELECT VALUE FROM INFO_DATA WHERE ID_INFO=1) C1,
    (SELECT VALUE FROM INFO_DATA WHERE ID_INFO=2) C2,
    (SELECT VALUE FROM INFO_DATA WHERE ID_INFO=3) C3,
    (SELECT VALUE FROM INFO_DATA WHERE ID_INFO=4) C4,
    (SELECT VALUE FROM INFO_DATA WHERE ID_INFO=5) C5,
    (SELECT VALUE FROM INFO_DATA WHERE ID_INFO=6) C6,
    (SELECT VALUE FROM INFO_DATA WHERE ID_INFO=7) C7,
    (SELECT VALUE FROM INFO_DATA WHERE ID_INFO=8) C8,
    (SELECT VALUE FROM INFO_DATA WHERE ID_INFO=9) C9,
    (SELECT VALUE FROM INFO_DATA WHERE ID_INFO=10) C10,
    (SELECT VALUE FROM INFO_DATA WHERE ID_INFO=11) C11,
    (SELECT VALUE FROM INFO_DATA WHERE ID_INFO=12) C12,
    (SELECT VALUE FROM INFO_DATA WHERE ID_INFO=13) C13)

仅当 Infos 表中的行数为 13 时。

编辑

由于列名是动态的,您需要使用存储过程代替直接查询。在这种情况下,您必须使用动态查询。告诉我你是否可以使用存储过程。我可以说出确切的查询。

于 2012-05-25T10:47:44.853 回答