【问题标题】:SQL : Create a full record from 2 tablesSQL:从 2 个表创建完整记录
【发布时间】:2014-09-30 22:22:06
【问题描述】:

我有一个数据库结构(为了理解问题而最大限度地简化):

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”。

举个例子可能会更好:

“字段”:

"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 抱怨:

错误:用作表达式的子查询返回多行

希望有人可以帮助我!

【问题讨论】:

  • 能否请您提供此表的脚本
  • 听起来你需要了解 JOINing 表......
  • @Hellion:或者更确切地说:数据库设计和/或交叉制表。

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


【解决方案1】:

按要求解决

虽然坚持这种不幸的设计,但最快的查询是使用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,这不仅仅是一种错误的设计(通常,这可以折叠成更少的列),请考虑数据类型hstorejson / jsonb(在 Postgres 9.4 中)用于 EAV 存储。

1Per Postgres "About" page:

Maximum Columns per Table   250 - 1600 depending on column types

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

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

【讨论】:

  • 我虽然先考虑这个更简单的设计,但后来我失去了能够在不更改表结构的情况下添加字段的能力,并且必须将“ALTER TABLE”权限授予简单用户?
  • ...但是,在阅读了您关于 EAV 的链接后,我将重新考虑要使用的正确数据库设计。谢谢!!!
【解决方案2】:

动态 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

【讨论】:

  • 这不是 Postgres 的有效语法。
  • +1 很好地展示了如果您选择 EAV,您最终会遇到的复杂性。
【解决方案3】:

子查询 (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

【讨论】:

    猜你喜欢
    • 2015-12-11
    • 1970-01-01
    • 1970-01-01
    • 2018-03-18
    • 1970-01-01
    • 1970-01-01
    • 2015-07-24
    • 1970-01-01
    相关资源
    最近更新 更多