我相信标题是不言自明的。如何在PostgreSQL中创建表结构以建立多对多关系。
我的例子:
Product(name, price);
Bill(name, date, Products);
答案 0 :(得分:220)
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
);
我做了一些调整:
n:m关系通常由单独的表格实现 - 在这种情况下为bill_product
。
我将serial
列添加为代理主键。我强烈建议,因为产品的名称并不是唯一的。此外,使用4字节integer
而不是将字符串存储为text
或varchar
,强制执行唯一性并引用外键中的列要便宜得多。
在Postgres 10或更高版本中,请考虑使用IDENTITY
column。详细说明:
请勿将date
等基本数据类型的名称用作标识符。虽然这是可能的,但它是糟糕的风格,并导致混淆错误和错误消息。使用legal, lower case, unquoted identifiers。如果可以,请不要使用reserved words并避免使用双引号混合大小写标识符。
name
不是一个好名字。我将表格name
的{{1}}列重命名为product
。这是一个更好的命名约定。否则,当你在一个查询中加入几个表时 - 你在关系数据库中很多 - 你最终得到了多个名为product
的列,并且必须使用列别名来排序一团糟。那没用。另一种广泛的反模式只是name
作为列名
我不确定id
的名称是什么。在这种情况下,bill
可以是名称。
bill_id
属于数据类型 price
,可以按照输入(任意精度类型而不是浮点类型)存储小数 )。如果您专门处理整数,请设为numeric
。例如,您可以将价格保存为Cents 。
integer
(您问题中的amount
)会进入链接表"Products"
,同时也是bill_product
类型。如果您专门处理整数,请再次numeric
。
您在integer
中看到外键了吗?我创建了两个级联更改(bill_product
):如果ON UPDATE CASCADE
或product_id
应该更改,则更改会级联到bill_id
中的所有相关条目,并且不会中断。\ br />
我还使用bill_product
ON DELETE CASCADE
:如果您删除了帐单,则会删除详细信息
产品不是这样:您不想删除帐单中使用的产品。如果你尝试这个,Postgres会抛出一个错误。您可以向bill_id
添加另一列,以标记过时的行。
此基本示例中的所有列最终都是 product
,因此不允许NOT NULL
值。 (是的,所有列 - 主键中使用的列自动定义为NULL
。)这是因为UNIQUE NOT NULL
值在任何列中都没有意义。它让初学者的生活更轻松。但是你不会轻易逃脱,无论如何你需要理解NULL
handling。其他列可能允许NULL
值,函数和联接可以在查询等中引入NULL
值。
主键在键列上使用唯一的索引实现,这使得PK列上的条件快速查询。但是,键列的顺序与多列键相关。由于我的示例中的NULL
上的PK位于bill_product
,因此如果您有查询给定(bill_id, product_id)
,则可能只想在product_id
或(product_id, bill_id)
添加另一个索引1}}而不是product_id
。详细说明: