【问题标题】:How to refactor complicated SQL query which is broken如何重构已损坏的复杂 SQL 查询
【发布时间】:2017-07-03 20:26:05
【问题描述】:

这是域的简化模型

简而言之,单位将文件授予客户。有两种类型的单元:主单元和它们的子单元。两者都属于同一个省,一个省可能属于多个城市。文档有许多事件(处理历史)。客户属于一市一省。

我必须编写查询,它返回 随机 组文档,给定目标主机代码。以下是标准:

  • 返回最新 event_code = 10 的 10 个文档
  • 每份文件必须属于居住在单位所在地区任何城市(首选不同城市)的不同客户
  • 返回客户符合条件的最新文档
  • 必须结果中同时存在这两种文档类型
  • 每个查询的结果(选择的客户)应该是随机的

但是……

  • 如果没有足够的客户,请尝试使用同一客户的多个文档作为最后的手段
  • 如果文档也不够多,尽量返回
  • 如果没有其他文档类型的单个实例,则全部返回

可能有几百万行,而且查询一定要快,要经常执行。

我不确定如何以理智的方式构建这种复杂的查询。我正在使用 OraclePL/SQL。这是我尝试过的东西,但它没有按预期工作(返回错误数据)。我应该如何重构这个查询并获得随机结果,并遵守所有这些边界规则?我也担心连接和位置的性能。

CURSOR c_documents IS
WITH documents_cte AS
    SELECT d.document_id AS document_id, d.create_dt AS create_dt,
      c.customer_id 
    FROM documents d
    JOIN customers c ON (c.customer_id = d.customer_id AND
      c.province_id = (SELECT region_id FROM unit WHERE unit_code = 1234))
    WHERE exists (
       SELECT 1 
       FROM event 
       where document_id = d.document_id AND
         event_code = 10 
         AND create_dt = 
            SELECT MAX(create_dt) 
            FROM event 
            WHERE document_id = d.document_id)
SELECT * FROM documents_cte d
WHERE create_dt = (SELECT MAX(create_dt) 
                   from documents_cte
                   WHERE customer_id = d.customer_id)

如何在考虑到随机性的情况下正确地进行此查询?我不是要求确切的解决方案,但至少是指导方针。

【问题讨论】:

  • 文档和事件有什么关系?我没有看到一个通用的字段名称。其实我没有看到很多对应的字段名。
  • IIRC,mysql 没有 CTE。
  • @jbrahy 我只是忘记在图片中包含外键。如您所见,它们之间存在一对多的关系
  • 所以区域实际上只是两个级别;省和他们的城市?我可以通过查看它的类型或注意到它有一个 parent_region 来检测一个城市。我可以通过查看它的类型或注意到它没有 parent_region 来检测一个省。正确的?所以只有两个级别具有冗余类型信息。我会为每个城市找到一个省。这一切都正确吗?
  • 这是正确的

标签: sql database oracle plsql


【解决方案1】:

我会尽可能避免使用分层表。在您的情况下,您使用层次表来允许无限深度,但最后您只存储两个级别:省和他们的城市。最好只有两张表:一张代表省,一张代表城市。没什么大不了的,但这会让你的数据模型更简单,更容易查询。

下面我从WITH 子句开始获取城市表,因为这样的表不存在。然后我一步一步去:获取属于该单位的客户,然后获取他们的文件并对其进行排名。最后,我选择了排名靠前的文档,并随机抽取了 10 个排名最高的文档。

with cities as
(
  select
    c.region_id as city_id,
    o.region_id as province_id
  from region c
  join region p on p.region_id = c.parent_region_id
)
, unit_customers as
(
  select customer_id
  from customer
  where city_id in
  (
    select city_id
    from cities
    where 
    (
      select region_id
      from unit
      where unit_code = 1234
    ) in (city_id, province_id)
  )
)
, ranked_documents as
(
  select
    document.*,
    row_number(partition by customer_id order by create_dt desc) as rn
  from document
  where customer_id in -- customers belonging to the unit
  (
    select customer_id 
    from unit_customers
  )
  and document_id in -- documents with latest event code = 10
  (
    select document_id
    from event 
    group by document_id
    having max(event_code) keep (dense_rank last order by create_dt) = 10
  )
)
select *
from ranked_documents
order by rn, dbms_random.value
fetch first 10 rows only;

这没有考虑获取两种文档类型,因为这与获取每个客户的最新文档的规则相矛盾。

FETCH FIRST 从 Oracle 12c 开始可用。在早期版本中,您将使用一个子查询和另一个 ROW_NUMBER

至于速度,我推荐这些索引用于查询:

create index idx_r1 on region(region_id); -- already exists for region_id = primary key
create index idx_r2 on region(parent_region_id, region_id);
create index idx_u1 on unit(unit_code, region_id);
create index idx_c1 on customer(city_id, customer_id);
create index idx_e1 on event(document_id, create_dt, event_code);
create index idx_d1 on document(document_id, customer_id, create_dt);
create index idx_d2 on document(customer_id, document_id, create_dt);

将使用最后两个中的一个,另一个不使用。用EXPLAIN PLAN检查哪个并丢弃未使用的。

【讨论】:

    猜你喜欢
    • 2020-03-20
    • 2020-03-09
    • 2022-11-21
    • 2012-02-17
    • 2019-08-05
    • 1970-01-01
    • 2021-12-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多