PostgreSQL - 如何以正确的方式 JOIN M:M 表?

问题描述 投票:0回答:3

我的数据库结构如下所示:

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)');

SqlFiddle

我想要实现的是列出所有饮料及其属性(

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”之外,所有其他行的“添加剂”列中都应具有“null”或“空字段”值。我做错了什么?

sql postgresql join database-design left-join
3个回答
2
投票

我相信你正在寻找这个

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

每条记录都与其他记录放在一起。它们都需要显式连接到外部参照表。


2
投票

给你的一些建议

架构

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

    定义查找表的 fk 约束之前请三思而行 通常,如果您(错误地)删除了某个类别,您不想想自动删除所有饮料。
  • 考虑使用普通的
    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
    (建议模式的缺点)。
  • 对于大表,查询仍然会更快,因为表更小,索引更小,速度更快,并且需要读取的页面更少。

1
投票

我正在寻找的查询看起来像:

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;

感谢布拉德在他的回答和评论中给了我解决方案。

© www.soinside.com 2019 - 2024. All rights reserved.