我的数据库结构:
CREATE TABLE categories (
name VARCHAR(30) PRIMARY KEY
);
CREATE TABLE additives (
name VARCHAR(30) PRIMARY KEY
);
CREATE TABLE beverages (
name VARCHAR(30) PRIMARY KEY,
description VARCHAR(200),
price NUMERIC(5, 2) NOT NULL CHECK (price >= 0),
category VARCHAR(30) NOT NULL REFERENCES categories(name) ON DELETE CASCADE ON UPDATE CASCADE
);
CREATE TABLE b_additives_xref (
bname VARCHAR(30) REFERENCES beverages(name) ON DELETE CASCADE ON UPDATE CASCADE,
aname VARCHAR(30) REFERENCES additives(name) ON DELETE CASCADE ON UPDATE CASCADE,
PRIMARY KEY(bname, aname)
);
INSERT INTO categories VALUES
('Cocktails'), ('Biere'), ('Alkoholfreies');
INSERT INTO additives VALUES
('Kaliumphosphat (E 340)'), ('Pektin (E 440)'), ('Citronensäure (E 330)');
INSERT INTO beverages VALUES
('Mojito Speciale', 'Cocktail mit Rum, Rohrzucker und Minze', 8, 'Cocktails'),
('Franziskaner Weißbier', 'Köstlich mildes Hefeweizen', 6, 'Biere'),
('Augustiner Hell', 'Frisch gekühlt vom Fass', 5, 'Biere'),
('Coca Cola', 'Coffeeinhaltiges Erfrischungsgetränk', 2.75, 'Alkoholfreies'),
('Sprite', 'Erfrischende Zitronenlimonade', 2.50, 'Alkoholfreies'),
('Karaffe Wasser', 'Kaltes, gashaltiges Wasser', 6.50, 'Alkoholfreies');
INSERT INTO b_additives_xref VALUES
('Coca Cola', 'Kaliumphosphat (E 340)'),
('Coca Cola', 'Pektin (E 440)'),
('Coca Cola', 'Citronensäure (E 330)');
我正在尝试列出所有饮料及其属性(
price
、description
等),并从 additives
表中添加另一列 b_additives_xref
,该表包含一个串联字符串,其中包含每种饮料中包含的所有添加剂:
SELECT
beverages.name AS name,
beverages.description AS description,
beverages.price AS price,
beverages.category AS category,
string_agg(additives.name, ', ') AS additives
FROM beverages, additives
LEFT JOIN b_additives_xref ON b_additives_xref.aname = additives.name
GROUP BY beverages.name
ORDER BY beverages.category;
输出:
Coca Cola | Coffeeinhaltiges Erfrischungsgetränk | 2.75 | Alkoholfreies | Kaliumphosphat (E 340), Pektin (E 440), Citronensäure (E 330)
Karaffe Wasser | Kaltes, gashaltiges Wasser | 6.50 | Alkoholfreies | Kaliumphosphat (E 340), Pektin (E 440), Citronensäure (E 330)
Sprite | Erfrischende Zitronenlimonade | 2.50 | Alkoholfreies | Kaliumphosphat (E 340), Pektin (E 440), Citronensäure (E 330)
Augustiner Hell | Frisch gekühlt vom Fass | 5.00 | Biere | Kaliumphosphat (E 340)[...]
这是错误的,因为只有“Coca Cola”在
b_additives_xref
表中具有现有行。除了“Coca Cola”行之外,所有其他行在列 additives
中都应具有“null”或“空字段”值。我做错了什么?
我相信你正在寻找这个
SELECT
B.name AS name,
B.description AS description,
B.price AS price,
B.category AS category,
string_agg(A.name, ', ') AS additives
FROM Beverages B
LEFT JOIN b_additives_xref xref ON xref.bname = B.name
Left join additives A on A.name = xref.aname
GROUP BY B.name
ORDER BY B.category;
输出
NAME DESCRIPTION PRICE CATEGORY ADDITIVES
Coca Cola Coffeeinhaltiges Erfrischungsgetränk 2.75 Alkoholfreies Kaliumphosphat (E 340), Pektin (E 440), Citronensäure (E 330)
问题是您的
beverages
和 additives
表之间存在笛卡尔积
FROM beverages, additives
每条记录都与其他记录放在一起。它们都需要显式连接到外部参照表。
给你的一些建议
CREATE TABLE category (
category_id int PRIMARY KEY
,category text UNIQUE NOT NULL
);
CREATE TABLE beverage (
beverage_id serial PRIMARY KEY
,beverage text UNIQUE NOT NULL -- maybe not unique?
,description text
,price int NOT NULL CHECK (price >= 0) -- in Cent
,category_id int NOT NULL REFERENCES category ON UPDATE CASCADE
-- not: ON DELETE CASCADE
);
CREATE TABLE additive (
additive_id serial PRIMARY KEY
,additive text UNIQUE
);
CREATE TABLE bev_add (
beverage_id int REFERENCES beverage ON DELETE CASCADE ON UPDATE CASCADE
,additive_id int REFERENCES additive ON DELETE CASCADE ON UPDATE CASCADE
,PRIMARY KEY(beverage_id, additive_id)
);
serial
列,对于小表使用简单的 integer
。饮料和添加剂的名称很可能并不是严格唯一的,您希望不时更改它们,这使得它们不适合作为主键。 integer
色谱柱也更小,处理速度更快。enum
。text
代替 character varying (n)
。ON DELETE CASCADE
integer
列而不是 NUMERIC(5, 2)
(用 Cent 数字代替 € / $)。更小、更快、更简单。
需要时格式化输出。这个密切相关的答案中的更多建议和链接:
如何在PostgreSQL中实现多对多关系?
适应新架构和一些一般建议。
SELECT b.*, string_agg(a.additive, ', ' ORDER BY a.additive) AS additives
-- order by optional for sorted list
FROM beverage b
JOIN category c USING (category_id)
LEFT JOIN bev_add ba USING (beverage_id) -- simpler now
LEFT JOIN additive a USING (additive_id)
GROUP BY b.beverage_id, c.category_id
ORDER BY c.category;
USING
。
category
和
GROUP BY category_id
或
category
(建议模式的缺点)。
SELECT
B.name AS name,
B.description AS description,
B.price AS price,
B.category AS category,
string_agg(A.name, ', ') AS additives
FROM beverages B
LEFT JOIN b_additives_xref xref ON xref.bname = B.name
LEFT JOIN additives A on A.name = xref.aname
GROUP BY B.name
ORDER BY B.category;
感谢布拉德在他的回答和评论中给了我解决方案。