【问题标题】:Query to match foreign key relationships查询以匹配外键关系
【发布时间】:2019-05-21 14:43:12
【问题描述】:

我在 Postgres 数据库中有两个表,表示来自市场的简单订单。一个包含订单信息的主表和一个包含购买细节的详细表,外键返回到主表。很简单。

来自市场的数千个订单,我想根据购买的内容和数量找到一些特定的订单。

我还有两张桌子,以类似的方式,一个主人和一个孩子,我在其中创建一个“包装”并详细说明市场上的物品。

对于示例:包装 A 包含 2 个苹果和 3 个橙子。我在表格中定义。现在我想知道有多少订单,以及来自市场的哪些订单与该特定组合完全匹配。

精确匹配很重要。包含额外产品或任何不同数量的订单不匹配。

在 SQL Fiddle 中,我使用数据设置了简单示例。原始 DDL 如下。表中的两个订单应与 Pack A 匹配。

http://sqlfiddle.com/#!17/b4f55

CREATE TABLE customer_order(
 order_id serial PRIMARY KEY NOT NULL,
 customer_name VARCHAR(100) NOT NULL
);

CREATE TABLE order_detail(
    id serial PRIMARY KEY,
    order_id INTEGER,
    item_sku VARCHAR(50),
    item_quantity INTEGER,
    FOREIGN KEY(order_id) REFERENCES customer_order(order_id)
);

INSERT INTO customer_order (customer_name) VALUES ('John');
INSERT INTO customer_order (customer_name) VALUES ('Mary');
INSERT INTO customer_order (customer_name) VALUES ('Bill');

INSERT INTO order_detail (order_id, item_sku, item_quantity) VALUES (1, 'APPLE', 2);
INSERT INTO order_detail (order_id, item_sku, item_quantity) VALUES (1, 'ORANGE', 3);
INSERT INTO order_detail (order_id, item_sku, item_quantity) VALUES (2, 'ORANGE', 5);
INSERT INTO order_detail (order_id, item_sku, item_quantity) VALUES (3, 'APPLE', 2);
INSERT INTO order_detail (order_id, item_sku, item_quantity) VALUES (3, 'ORANGE', 3);

CREATE TABLE pack_master(
 pack_id serial PRIMARY KEY NOT NULL,
 name VARCHAR(100) NOT NULL
);

CREATE TABLE pack_child(
    id serial PRIMARY KEY,
    pack_id INTEGER,
    item_sku VARCHAR(50),
    item_quantity INTEGER,
    FOREIGN KEY(pack_id) REFERENCES pack_master(pack_id)
);

INSERT INTO pack_master (name) VALUES ('Pack A');
INSERT INTO pack_master (name) VALUES ('Pack B');

INSERT INTO pack_child (pack_id, item_sku, item_quantity) VALUES (1, 'APPLE', 2);
INSERT INTO pack_child (pack_id, item_sku, item_quantity) VALUES (1, 'ORANGE', 3);
INSERT INTO pack_child (pack_id, item_sku, item_quantity) VALUES (2, 'GRAPES', 5);

【问题讨论】:

  • 能否请您以表格格式添加您的预期输出?
  • 您的问题到底是什么?您想查找与 one 给定包装匹配的订单数量吗?还是为所有人?另请声明您的 Postgres 版本。
  • 另外:UNIQUE 没有 (pack_id, item_sku) 约束,所以“Apples”可以在同一个包中多次出现?订单方面也一样?如果有,如何处理?
  • 我冒昧地做了一个小改动:pack_master.pack_id 而不是 pack_master.id 以使其保持一致。

标签: sql postgresql relational-division sql-match-all


【解决方案1】:

假设 pack_child (pack_id, item_sku) 和 order_detail (order_id, item_sku) 被定义为 UNIQUE,这将起作用:

SELECT pc.pack_id, od.order_id
FROM   pack_child pc
LEFT   JOIN order_detail od USING (item_sku, item_quantity)
GROUP  BY 1, 2
HAVING count(*) = count(od.id)  -- every item of the pack has a match
AND    NOT EXISTS (
   SELECT
   FROM   order_detail od1
   LEFT   JOIN pack_child pc1 ON pc1.item_sku = od1.item_sku
                             AND pc1.item_quantity = od1.item_quantity
                             AND pc1.pack_id = pc.pack_id
   WHERE  od1.order_id = od.order_id
   AND    pc1.id IS NULL       -- and order has no additional item
   );

返回完全匹配的所有 pack_id 和 order_id 对。

db小提琴here

有一百零一种编写查询的替代方法。哪个最快取决于基数、数据分布、约束以及最重要的可用索引。

这是 的特殊应用。这是一个技术库:

一个替代方法,可能更快:创建父表的视图或materialized views,包括项目计数:

CREATE MATERIALIZED VIEW v_pack_master AS
SELECT *
FROM   pack_master
JOIN  (
   SELECT pack_id, count(*) AS items
   FROM   pack_child
   GROUP  BY 1
   ) c USING (pack_id);

CREATE MATERIALIZED VIEW v_customer_order AS
SELECT *
FROM   customer_order
JOIN  (
   SELECT order_id, count(*) AS items
   FROM   order_detail
   GROUP  BY 1
   ) c USING (order_id);

(订单通常以后不会更改,因此可能是物化视图的可行候选者。)

只有当可以有许多个订单项目时,一个索引才可能支付(这个顺序的索引表达式):

CREATE INDEX foo ON v_customer_order (items, order_id);

查询现在只考虑具有匹配项目计数的订单:

SELECT * -- pack_id, order_id
FROM   v_pack_master pm
LEFT   JOIN v_customer_order co USING (items)
JOIN   LATERAL (
   SELECT count(*) AS items
   FROM   pack_child pc
   JOIN   order_detail od USING (item_sku, item_quantity)
   WHERE  pc.pack_id  = pm.pack_id
   AND    od.order_id = co.order_id
   ) x USING (items);

.. 那么,如果所有项目都匹配,我们不必再排除其他项目。而且我们可以立即使用父表中的所有列,以返回您想要返回的任何内容...

【讨论】:

  • 这真是太棒了!我现在正在我的真实数据库上尝试它,看看它的外观,但到目前为止,这似乎是我一直在寻找的答案。不过,我很好奇,如果最终有一种方法可以从现有订单数据中动态“检测”包装组合。因此,不必提前定义包装,如果我可以说“如果订单重复超过 10 次,请继续考虑该包装并找到它匹配的所有订单”。这超出了这个问题或课程的范围,但这是我的下一个想法。
  • @G.Ball:可以用类似的方式设计一个查询来识别 > 9 个相同订单的所有“包”。可以手动调用或使用 crontab 或类似方法进行调度,或在插入/更新/删除时触发,但最后一个选项有很多开销和可能的复杂性。如果您想详细探讨这一点,请提出一个具有精确要求的新问题。
【解决方案2】:

我想知道有多少订单,以及来自市场的哪些订单 匹配该特定组合。

据此,我假设由于您有 2 个数量为 2 的苹果订单和 2 个数量为 2 的橙色订单,您的结果应该类似于下表,因为它们存在于具有相同 item_sku 和数量的包中.

 item_sku  | Count
   --------+------
    Apple  | 2
    Orange | 2

SQL:

SELECT OD.item_sku, count(OD.item_sku)
FROM order_detail as OD
JOIN pack_child as PC
ON OD.item_sku = PC.item_sku
WHERE OD.item_sku = PC.item_sku AND (OD.item_quantity = PC.item_quantity)
GROUP BY OD.item_sku

【讨论】:

  • countcolumn 表示如果存在该组合,则需要从包中获取的数量。
猜你喜欢
  • 2020-01-02
  • 1970-01-01
  • 2010-09-10
  • 1970-01-01
  • 2018-10-09
  • 2014-07-22
  • 2012-10-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多