SQL:从2个表创建完整记录

时间:2014-08-07 15:33:56

标签: sql database postgresql database-design entity-attribute-value

我有一个DB结构(为了解问题而简化为最大值):

Table "entry" ("id" integer primary key)
Table "fields" ("name" varchar primary key, and others)
Table "entry_fields" ("entryid" integer primary key, "name" varchar primary key, "value")

对于给定的“entry.id”,我想获得此条目的详细信息,即。在单个SQL查询中链接到此条目的所有“entry_fields”。

一个例子可能会更好:

“fields”:

"result"
"output"
"code"
"command"

“条目”包含:

id : 842
id : 850

“entry_fields”包含:

entryid : 842, name : "result", value : "ok"
entryid : 842, name : "output", value : "this is an output"
entryid : 842, name : "code", value : "42"
entryid : 850, name : "result", value : "ko"
entryid : 850, name : "command", value : "print ko"

想要的输出是:

| id  | command    | output               | code | result |
| 842 | NULL       | "this is an output"  | 42   | ok     |
| 850 | "print ko" | NULL                 | NULL | ko     |

目的是能够在不改变“条目”表结构的情况下添加“字段”

我尝试过类似的事情:

SELECT e.*, (SELECT name FROM fields) FROM entry AS e

但是Postgres抱怨道:

  

错误:用作表达式

的子查询返回多行

希望有人可以帮助我!

3 个答案:

答案 0 :(得分:2)

按要求提供的解决方案

虽然坚持这种不幸的设计,但最快的查询将是crosstab(),由附加模块tablefunc提供。这个相关答案的详细信息如下:

对于提出的问题:

SELECT * FROM crosstab(
      $$SELECT e.id, ef.name, ef.value
       FROM   entry             e
       LEFT   JOIN entry_fields ef
              ON ef.entryid = e.id
             AND ef.name = ANY ('{result,output,code,command}'::text[])
       ORDER  BY 1, 2$$

     ,$$SELECT unnest('{result,output,code,command}'::text[])$$
   ) AS ct (id int, result text, output text, code text, command text);

数据库设计

如果您没有巨大数量的不同字段,那么将所有三个表合并为一个简单的表将更简单,更高效

CREATE TABLE entry (
   entry_id serial PRIMARY KEY
  ,field1   text
  ,field2   text
  , ... more fields
);

没有值的字段可以是NULLNULL存储非常便宜(在NULL位图中每列基本上为1位):

即使您有数百个不同的列,并且每个条目只填充少量列,这仍然会占用更少的磁盘空间。

您的查询变得微不足道:

SELECT entry_id, result, output, code, command
FROM   enty;

如果列 1 太多,并且这不仅仅是误导设计(通常可以将其折叠成更少的列),请考虑数据类型hstore或{{对于json存储,3}} / jsonb(在Postgres 9.4中)。

1 EAV

Maximum Columns per Table   250 - 1600 depending on column types

考虑这个相关答案的替代方案:

关于dba.SE上EAV结构的典型用例/问题的这个问题:

答案 1 :(得分:1)

动态SQL:

CREATE TABLE fields (name varchar(100) PRIMARY KEY)
INSERT INTO FIELDS VALUES ('RESULT')
INSERT INTO FIELDS VALUES ('OUTPUT')
INSERT INTO FIELDS VALUES ('CODE')
INSERT INTO FIELDS VALUES ('COMMAND')

CREATE TABLE ENTRY_fields (ENTRYID INT, name varchar(100), VALUE VARCHAR(100) CONSTRAINT PK PRIMARY KEY(ENTRYID, name))
INSERT INTO ENTRY_fields VALUES(842, 'RESULT', 'OK')
INSERT INTO ENTRY_fields VALUES(842, 'OUTPUT', 'THIS IS AN OUTPUT')
INSERT INTO ENTRY_fields VALUES(842, 'CODE', '42')
INSERT INTO ENTRY_fields VALUES(850, 'RESULT', 'KO')
INSERT INTO ENTRY_fields VALUES(850, 'COMMAND', 'PRINT KO')

CREATE TABLE ENTRY (ID INT PRIMARY KEY)
INSERT INTO ENTRY VALUES(842)
INSERT INTO ENTRY VALUES(850)

DECLARE @COLS NVARCHAR(MAX), @SQL NVARCHAR(MAX)

select @Cols = stuff((select ', ' + quotename(dt)
from (select DISTINCT name as dt 
from fields) X
FOR XML PATH('')),1,2,'')

PRINT @COLS
SET @SQL = 'SELECT * FROM (SELECT id, f.name, value
from fields F CROSS join ENTRY LEFT JOIN entry_fields ef on ef.name =  f.name AND ID = ef.ENTRYID
) Y PIVOT (max(value) for name in ('+ @Cols +'))PVT '

--print @SQL

exec (@SQL)

如果您认为您的值将在字段表中保持不变:

SELECT * FROM (SELECT id, f.name ,value
from fields F CROSS join ENTRY LEFT JOIN entry_fields ef on ef.name =  f.name AND ID = ef.ENTRYID
) Y PIVOT (max(value) for name in ([CODE], [COMMAND], [OUTPUT], [RESULT]))PVT

可能与postgresql一起使用的查询:

    SELECT ID, MAX(CODE) as CODE, MAX(COMMAND) as COMMAND, MAX(OUTPUT) as OUTPUT, MAX(RESULT) as RESULT
FROM (SELECT ID, 
      CASE WHEN f.name = 'CODE' THEN VALUE END AS CODE, 
      CASE WHEN f.name = 'COMMAND' THEN VALUE END AS COMMAND,
      CASE WHEN f.name = 'OUTPUT' THEN VALUE END AS OUTPUT,
      CASE WHEN f.name = 'RESULT' THEN VALUE END AS RESULT
from fields F CROSS join ENTRY LEFT JOIN entry_fields ef on ef.name =  f.name AND ID = ENTRYID
) Y
GROUP BY ID

答案 2 :(得分:0)

子查询(SELECT name FROM fields)将返回4行。你不能在SQL中将4行填充为1。您可以使用我不熟悉的crosstab来回答。或者您可以使用这样的粗略查询:

SELECT   e.*,
         (SELECT value FROM entry_fields AS ef WHERE name = 'command' AND ef.entryid = f.entryid) AS command,
         (SELECT value FROM entry_fields AS ef WHERE name = 'output' AND ef.entryid = f.entryid) AS output,
         (SELECT value FROM entry_fields AS ef WHERE name = 'code' AND ef.entryid = f.entryid) AS code,
         (SELECT value FROM entry_fields AS ef WHERE name = 'result' AND ef.entryid = f.entryid) AS result
FROM     entry AS e