在 PostgreSQL 中实现多对多关系的最佳方式是什么?
What is the best way to implement a many-to-many relationship in PostgreSQL?
我正在尝试在 PostgreSQL 中实现多对多关系。
这是我数据库中的 tables:
CREATE TABLE products (
product_id serial PRIMARY KEY
, product_name varchar NOT NULL
);
CREATE TABLE orders (
order_id serial PRIMARY KEY
, order_name varchar NOT NULL
);
CREATE TABLE product_order (
product_id int REFERENCES products
, order_id int REFERENCES orders
, PRIMARY KEY (product_id, order_id)
);
产品和订单 table 中不会有任何 UPDATE 或 DELETE,因此不需要 ON DELETE 和 ON UPDATE 语句。
我还创建了两个哈希索引,因此我可以搜索订单和产品名称并将它们的 ID 存储在 table:
CREATE INDEX product_index ON products USING hash(product_name);
CREATE INDEX order_index ON orders USING hash(order_name);
这是我正在尝试做的事情:
- 我想插入产品 table 和 return 插入行的 ID。
- 我想在订单 table 和 return 中插入插入行的 ID。
- 我想将 product_id 和 order_id 都插入 product_order table。
有一个边缘情况:
如果我要插入的产品已经在产品 table 中,那么我不想创建具有不同 ID 的另一行。在这种情况下,我想检索已经在 table.
中的 product_id
这种边缘情况与订单相同。
为了完成所有这些,我创建了一个 SQL 函数:
CREATE OR REPLACE FUNCTION add_product_order(myproduct varchar, myorder varchar)
RETURNS VOID
LANGUAGE sql
AS
$$
WITH pro AS (
WITH p as (
SELECT product_id FROM products WHERE product_name = myproduct -- check if product is already in the table
)
INSERT into products (product_name) -- insert into products and get the product_id only if myproduct was not found
SELECT (myproduct)
WHERE NOT EXISTS (
SELECT product_id FROM p
)
RETURNING product_id
),
ord AS (
WITH o as(
SELECT order_id FROM orders WHERE order_name = myorder -- check if order is already in the table
)
INSERT into orders (order_name) -- insert into orders and get the order_id only if myorder was not found
SELECT (myorder)
WHERE NOT EXISTS (
SELECT order_id FROM o
)
RETURNING order_id
)
INSERT INTO product_order (product_id, order_id) -- insert both FK ids into the product_order table
SELECT pro.product_id, ord.order_id FROM pro, ord;
$$;
创建函数后,我对 运行 执行以下 SQL 查询:
select add_product_order('product1','order1');
一切似乎都很好,但是 只有当我尝试插入的产品不在 table.
中时它才有效
如果产品已经在table,第一个SELECTreturns在product_id临时ptable。但是我不知道如何在最后的 INSERT INTO product_order.
中获取 p.product_id
看到我不能做得太过分,我也尝试了 plpgsql FUNCTION:
CREATE OR REPLACE FUNCTION add_product_order(myproduct varchar, myorder varchar)
RETURNS VOID
LANGUAGE plpgsql
AS
$$
DECLARE
id_product integer;
id_order integer;
BEGIN
SELECT product_id INTO id_product FROM products WHERE product_name = myproduct; -- check if product is already in the table
IF NOT FOUND THEN
RAISE INFO 'product % not found', myproduct;
INSERT INTO products (product_name) VALUES (myproduct) RETURNING product_id; -- product not found, so insert it and get the id
id_product := product_id; -- Tried also with SELECT product_id INTO id_product;
END IF;
SELECT order_id INTO id_order FROM orders WHERE order_name = myorder; -- check if order is already in the table
IF NOT FOUND THEN
RAISE INFO 'order % not found', myorder;
INSERT INTO orders (order_name) VALUES (myorder) RETURNING order_id; -- order not found, so insert it and get the id
id_order := order_id;
END IF;
INSERT INTO product_order (product_id, order_id) VALUES (id_product, id_order); -- insert both ids into the product_order table
END;
$$;
这个 plpgsql FUNCTION 应该可以解决我在上面的 SQL 函数中遇到的问题。
但它给我一个错误:查询没有结果数据的目的地
完成此任务的正确方法是什么?
PS。在发布这个问题之前,我搜索并阅读了多个答案:
How to implement a many-to-many relationship in PostgreSQL?
What is the best way to store one-to-many or many-to-many relationships in PostgreSQL?
您只需插入 Product Name
和 Order name
让 Postgres 处理重复即可得到您想要的。您将它们的索引提升到 unique constraints
(不需要散列),然后让 ON CONFLICT 子句处理重复。这就是 select, if not found insert
逻辑无论如何都在尝试的。唯一的问题是 Postgres 不返回重复的 ID。为了克服 Product_Orders 的插入从适当的名称中检索 ID。这可以在带有几个 CTE 的单个语句中完成,但是返回子句是必需的,并且返回的任何内容在名称已经存在的情况下都是空的。所以无论如何它都会被忽略。但是 SQL function/procedure 可以有多个语句,所以(恕我直言)3 个语句更清楚。 (参见 example here)
create or replace
procedure gen_product_order(
myproduct varchar
, myorder varchar
)
language sql
as $$
insert into products(product_name)
values (myproduct)
on conflict (product_name) do nothing;
insert into orders (order_name)
values (myorder)
on conflict (order_name) do nothing;
insert into product_orders (product_id, order_id) -- insert both fk ids into the product_order table
select product_id, order_id
from (select product_id
from products
where product_name = myproduct
) prd
, (select order_id
from orders
where order_name = myorder
) ord
on conflict (product_id, order_id) do nothing;
$$;
注意:我使用了一个过程而不是一个返回 void 的函数,但是如果你的 Postgres 版本不支持一个函数也可以。
我正在尝试在 PostgreSQL 中实现多对多关系。 这是我数据库中的 tables:
CREATE TABLE products (
product_id serial PRIMARY KEY
, product_name varchar NOT NULL
);
CREATE TABLE orders (
order_id serial PRIMARY KEY
, order_name varchar NOT NULL
);
CREATE TABLE product_order (
product_id int REFERENCES products
, order_id int REFERENCES orders
, PRIMARY KEY (product_id, order_id)
);
产品和订单 table 中不会有任何 UPDATE 或 DELETE,因此不需要 ON DELETE 和 ON UPDATE 语句。
我还创建了两个哈希索引,因此我可以搜索订单和产品名称并将它们的 ID 存储在 table:
CREATE INDEX product_index ON products USING hash(product_name);
CREATE INDEX order_index ON orders USING hash(order_name);
这是我正在尝试做的事情:
- 我想插入产品 table 和 return 插入行的 ID。
- 我想在订单 table 和 return 中插入插入行的 ID。
- 我想将 product_id 和 order_id 都插入 product_order table。
有一个边缘情况:
如果我要插入的产品已经在产品 table 中,那么我不想创建具有不同 ID 的另一行。在这种情况下,我想检索已经在 table.
中的 product_id这种边缘情况与订单相同。
为了完成所有这些,我创建了一个 SQL 函数:
CREATE OR REPLACE FUNCTION add_product_order(myproduct varchar, myorder varchar)
RETURNS VOID
LANGUAGE sql
AS
$$
WITH pro AS (
WITH p as (
SELECT product_id FROM products WHERE product_name = myproduct -- check if product is already in the table
)
INSERT into products (product_name) -- insert into products and get the product_id only if myproduct was not found
SELECT (myproduct)
WHERE NOT EXISTS (
SELECT product_id FROM p
)
RETURNING product_id
),
ord AS (
WITH o as(
SELECT order_id FROM orders WHERE order_name = myorder -- check if order is already in the table
)
INSERT into orders (order_name) -- insert into orders and get the order_id only if myorder was not found
SELECT (myorder)
WHERE NOT EXISTS (
SELECT order_id FROM o
)
RETURNING order_id
)
INSERT INTO product_order (product_id, order_id) -- insert both FK ids into the product_order table
SELECT pro.product_id, ord.order_id FROM pro, ord;
$$;
创建函数后,我对 运行 执行以下 SQL 查询:
select add_product_order('product1','order1');
一切似乎都很好,但是 只有当我尝试插入的产品不在 table.
中时它才有效如果产品已经在table,第一个SELECTreturns在product_id临时ptable。但是我不知道如何在最后的 INSERT INTO product_order.
中获取 p.product_id看到我不能做得太过分,我也尝试了 plpgsql FUNCTION:
CREATE OR REPLACE FUNCTION add_product_order(myproduct varchar, myorder varchar)
RETURNS VOID
LANGUAGE plpgsql
AS
$$
DECLARE
id_product integer;
id_order integer;
BEGIN
SELECT product_id INTO id_product FROM products WHERE product_name = myproduct; -- check if product is already in the table
IF NOT FOUND THEN
RAISE INFO 'product % not found', myproduct;
INSERT INTO products (product_name) VALUES (myproduct) RETURNING product_id; -- product not found, so insert it and get the id
id_product := product_id; -- Tried also with SELECT product_id INTO id_product;
END IF;
SELECT order_id INTO id_order FROM orders WHERE order_name = myorder; -- check if order is already in the table
IF NOT FOUND THEN
RAISE INFO 'order % not found', myorder;
INSERT INTO orders (order_name) VALUES (myorder) RETURNING order_id; -- order not found, so insert it and get the id
id_order := order_id;
END IF;
INSERT INTO product_order (product_id, order_id) VALUES (id_product, id_order); -- insert both ids into the product_order table
END;
$$;
这个 plpgsql FUNCTION 应该可以解决我在上面的 SQL 函数中遇到的问题。
但它给我一个错误:查询没有结果数据的目的地
完成此任务的正确方法是什么?
PS。在发布这个问题之前,我搜索并阅读了多个答案:
How to implement a many-to-many relationship in PostgreSQL?
What is the best way to store one-to-many or many-to-many relationships in PostgreSQL?
您只需插入 Product Name
和 Order name
让 Postgres 处理重复即可得到您想要的。您将它们的索引提升到 unique constraints
(不需要散列),然后让 ON CONFLICT 子句处理重复。这就是 select, if not found insert
逻辑无论如何都在尝试的。唯一的问题是 Postgres 不返回重复的 ID。为了克服 Product_Orders 的插入从适当的名称中检索 ID。这可以在带有几个 CTE 的单个语句中完成,但是返回子句是必需的,并且返回的任何内容在名称已经存在的情况下都是空的。所以无论如何它都会被忽略。但是 SQL function/procedure 可以有多个语句,所以(恕我直言)3 个语句更清楚。 (参见 example here)
create or replace
procedure gen_product_order(
myproduct varchar
, myorder varchar
)
language sql
as $$
insert into products(product_name)
values (myproduct)
on conflict (product_name) do nothing;
insert into orders (order_name)
values (myorder)
on conflict (order_name) do nothing;
insert into product_orders (product_id, order_id) -- insert both fk ids into the product_order table
select product_id, order_id
from (select product_id
from products
where product_name = myproduct
) prd
, (select order_id
from orders
where order_name = myorder
) ord
on conflict (product_id, order_id) do nothing;
$$;
注意:我使用了一个过程而不是一个返回 void 的函数,但是如果你的 Postgres 版本不支持一个函数也可以。