0

我有一堆聚合的事件表(订单就是其中之一),我可以像这样查询

SELECT * FROM events WHERE aggregate_id = :order_id AND aggregate_type = :order_type

这适用于简单的情况,但它更多地涉及重复创建的事件。业务需求之一是可以复制订单(从概念上讲,认为是move,不是copy)。这将关闭原始订单,然后员工可以继续处理新订单。为了显示订单的完整历史记录,不仅要显示当前订单的事件,还要显示任何原始订单的事件。

这是一个简化的示例:

订单 3

  • 5 月 10 日 - 发货
  • 5 月 9 日 - 包装
  • 5 月 9 日 - 与订单 2重复

订单 2

  • 5 月 9 日 - 关闭
  • 5 月 8 日 - 联系客户
  • 5 月 8 日 - 收到货
  • 5 月 6 日 - 从订单 1复制

订单 1

  • 5 月 6 日 - 关闭
  • 5 月 5 日 - 创建

我提出了一个相当简单的查询,适用于这种情况:

WITH RECURSIVE original_orders(order_id) AS (
  select aggregate_id
  from events
  where aggregate_type = 'order'
    and aggregate_id = :order_id
    and event_name = 'duplicate created'
  UNION ALL
  select body->'$.duplicatedFromOrderId'
  from original_orders
    inner join events on (aggregate_type = 'order' and aggregate_id = order_id and event_name = 'duplicate created')
)
SELECT events.*
FROM events
INNER JOIN original_orders ON aggregate_id = order_id;
-- extra context from the real (not simplified) use-case:
-- I actually use the CTE twice, because "notes" are stored separately
-- INNER JOIN users ON event.user_id - to get more info
-- UNION ALL SELECT * FROM notes JOIN original_orders ON order_id = notes.object_id

递归 CTE 最终从最新订单(基本案例部分)开始选择订单 ID,然后通过从事件主体中获取原始订单 ID 进行递归。order_id该示例的结果将是具有 3 行的单列: (3, 2, 1).

问题是,如果我们查看原始订单(在本例中为Order 1 ),根本不会显示任何事件,因为它没有重复的 created事件。因此,任何不重复的订单在基本情况下都不会选择任何内容,因此 CTE 将返回一个空结果集。

我觉得我的逻辑有些缺陷,我错过了 [明显] 简单的方法来获得我需要的东西。我认为在基本情况下,我可以改为搜索 ANY 事件名称和 GROUP BY id,因此order_id即使订单不是重复的,也可以获得基本情况下的查询(以类似的方式,我可以使用SELECT DISTINCT并放弃GROUP BY但这真的是一回事)。像这样:

WITH RECURSIVE original_orders(order_id) AS (
  select aggregate_id
  from events
  where aggregate_type = 'order'
    and aggregate_id = :order_id
  group by aggregate_id
  UNION ALL
  -- ...
)

这感觉像是一种解决方法,所以我很想修正我的逻辑。我错过了什么?


可重现的例子:

create table events
(
    id             int auto_increment,
    aggregate_type varchar(100) not null,
    aggregate_id   varchar(255) not null,
    event_name     varchar(255) not null,
    occurred_on    datetime     not null,
    body           json         not null,
    constraint events_pk
        primary key (id)
);

insert into events (aggregate_type, aggregate_id, event_name, occurred_on, body)
values ('order', 1, 'created', '2020-05-05 09:00:00', json_object()),
       ('order', 1, 'closed', '2020-05-06 09:00:00', json_object()),
       ('order', 2, 'duplicate created', '2020-05-06 09:00:00', json_object('duplicatedFromOrderId', 1)),
       ('order', 2, 'received', '2020-05-07 09:00:00', json_object()),
       ('order', 2, 'closed', '2020-05-08 09:00:00', json_object()),
       ('order', 3, 'duplicate created', '2020-05-08 09:00:00', json_object('duplicatedFromOrderId', 2)),
       ('order', 3, 'shipped', '2020-05-09 09:00:00', json_object());

运行查询:order_id = 3应该返回所有 7 个事件。

运行查询:order_id = 2应该返回 5 个事件(应该忽略订单 3 的事件)。

运行查询:order_id = 1应该返回 2 个事件。

4

1 回答 1

2
WITH RECURSIVE cte as (
    select * from events
    where aggregate_id = :order_id
    and aggregate_type = 'order'
    union all
    select ev.* from cte 
    inner join events ev on ev.aggregate_id = cast(cte.body->'$.duplicatedFromOrderId' as UNSIGNED)
    where ev.aggregate_type = 'order'
)
select * from cte;
于 2021-05-08T22:02:55.397 回答