SQL 如何在 PostgreSQL 中实现多对多关系?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/9789736/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to implement a many-to-many relationship in PostgreSQL?
提问by Radu Gheorghiu
I believe the title is self-explanatory. How do you create the table structure in PostgreSQL to make a many-to-many relationship.
我相信标题是不言自明的。如何在PostgreSQL中创建表结构来建立多对多关系。
My example:
我的例子:
Product(name, price);
Bill(name, date, Products);
回答by Erwin Brandstetter
The SQL DDL (data definition language) statements could look like this:
SQL DDL(数据定义语言)语句可能如下所示:
CREATE TABLE product (
product_id serial PRIMARY KEY -- implicit primary key constraint
, product text NOT NULL
, price numeric NOT NULL DEFAULT 0
);
CREATE TABLE bill (
bill_id serial PRIMARY KEY
, bill text NOT NULL
, billdate date NOT NULL DEFAULT CURRENT_DATE
);
CREATE TABLE bill_product (
bill_id int REFERENCES bill (bill_id) ON UPDATE CASCADE ON DELETE CASCADE
, product_id int REFERENCES product (product_id) ON UPDATE CASCADE
, amount numeric NOT NULL DEFAULT 1
, CONSTRAINT bill_product_pkey PRIMARY KEY (bill_id, product_id) -- explicit pk
);
I made a few adjustments:
我做了一些调整:
The n:m relationshipis normally implemented by a separate table -
bill_product
in this case.I added
serial
columns as surrogate primary keys. I highly recommend that, because the name of a product is hardly unique. Also, enforcing uniqueness and referencing the column in foreign keys is much cheaper with a 4-byteinteger
than with a string stored astext
orvarchar
.
In Postgres 10 or later consider anIDENTITY
columninstead. Details:Don't use names of basic data types like
date
as identifiers. While this is possible, it is bad style and leads to confusing errors and error messages. Use legal, lower case, unquoted identifiers. Never use reserved wordsand avoid double-quoted mixed case identifiers if you can.name
is not a good name. I renamed thename
column of the tableproduct
to beproduct
. That is a better naming convention. Otherwise, when you join a couple of tables in a query - which you do a lotin a relational database - you end up with multiple columns namedname
and have to use column aliases to sort out the mess. That's not helpful. Another widespread anti-pattern would be justid
as column name.
I am not sure what the name of abill
would be. Maybebill_id
can be the namein this case.price
is of data typenumeric
to store fractional numbers precisely as entered(arbitrary precision type instead of floating point type). If you deal with whole numbers exclusively, make thatinteger
. For example, you could save prices as Cents.The
amount
("Products"
in your question) goes into the linking tablebill_product
and is of typenumeric
as well. Again,integer
if you deal with whole numbers exclusively.You see the foreign keysin
bill_product
? I created both to cascade changes (ON UPDATE CASCADE
): If aproduct_id
orbill_id
should change, the change is cascaded to all depending entries inbill_product
and nothing breaks.
I also usedON DELETE CASCADE
forbill_id
: If you delete a bill, the details are deleted with it.
Not so for products: You don't want to delete a product that's used in a bill. Postgres will throw an error if you attempt this. You would add another column toproduct
to mark obsolete rows instead.All columns in this basic example end up to be
NOT NULL
, soNULL
values are not allowed. (Yes, allcolumns - columns used in a primary key are definedUNIQUE NOT NULL
automatically.) That's becauseNULL
values wouldn't make sense in any of the columns. It makes a beginner's life easier. But you won't get away so easily, you need to understandNULL
handlinganyway. Additional columns might allowNULL
values, functions and joins can introduceNULL
values in queries etc.Read the chapter on
CREATE TABLE
in the manual.Primary keys are implemented with a unique indexon the key columns, that makes queries with conditions on the PK column(s) fast. However, the sequence of key columns is relevant in multicolumn keys. Since the PK on
bill_product
is on(bill_id, product_id)
in my example, you may want to add another index on justproduct_id
or(product_id, bill_id)
if you have queries looking for given aproduct_id
and nobill_id
. Details:Read the chapter on indexes in the manual.
在N:M关系通常由一个单独的表来实现-
bill_product
在这种情况下。我添加了
serial
列作为代理主键。我强烈建议这样做,因为产品的名称几乎不是唯一的。此外,integer
与存储为text
or的字符串相比,使用4 字节强制唯一性并引用外键中的列要便宜得多varchar
。
在 Postgres 10 或更高版本中,请考虑使用IDENTITY
列。细节:不要将基本数据类型的名称
date
用作标识符。虽然这是可能的,但这是一种糟糕的风格,会导致令人困惑的错误和错误消息。使用合法的、小写的、不带引号的标识符。如果可以,切勿使用保留字并避免使用双引号混合大小写标识符。name
不是个好名字。我name
将表的列重命名product
为product
. 这是一个更好的命名约定。否则,当你加入一对情侣在一个查询表的-你做了很多在关系型数据库-你最终命名多个列name
,并必须使用列别名理清混乱。那没有帮助。另一个广泛使用的反模式就是id
列名。
我不确定 a 的名称是什么bill
。在这种情况下,也许bill_id
可以是名称。price
是用于精确存储输入的小数的数据类型(任意精度类型而不是浮点类型)。如果您只处理整数,请使用. 例如,您可以将价格保存为 Cents。numeric
integer
在
amount
("Products"
你的问题)进入连结表bill_product
,是类型的numeric
为好。同样,integer
如果您只处理整数。你看到外键了
bill_product
吗?我创建了两个来级联更改 (ON UPDATE CASCADE
):如果 aproduct_id
或bill_id
应该更改,则更改将级联到所有依赖项,bill_product
并且没有任何中断。
我也用ON DELETE CASCADE
了bill_id
:如果你删除了一项法案,细节用它删除。
产品并非如此:您不想删除帐单中使用的产品。如果您尝试这样做,Postgres 将抛出错误。您可以添加另一列product
来标记过时的行。此基本示例中的所有列最终都是
NOT NULL
,因此NULL
不允许使用值。(是的,所有列 - 主键中使用的列都是UNIQUE NOT NULL
自动定义的。)这是因为NULL
值在任何列中都没有意义。它使初学者的生活更轻松。但是您不会那么容易逃脱,无论如何您都需要了解NULL
处理方式。附加列可能允许NULL
值、函数和连接可以NULL
在查询等中引入值。阅读
CREATE TABLE
手册中的章节。主键是通过键列上的唯一索引实现的,这使得在 PK 列上具有条件的查询更快。但是,键列的顺序与多列键相关。由于在我的示例中PK on
bill_product
是打开的(bill_id, product_id)
,您可能想要添加另一个索引 on justproduct_id
或者(product_id, bill_id)
如果您有查询寻找给定的 aproduct_id
和 nobill_id
。细节: