如何正确加入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”行之外,所有其他行在列
additives
中都应具有“null”或“空字段”值。我做错了什么?

sql postgresql join 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.