我有一个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抱怨道:
错误:用作表达式
的子查询返回多行
希望有人可以帮助我!
答案 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
);
没有值的字段可以是NULL
。 NULL
存储非常便宜(在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