如何重构破坏的复杂SQL查询

时间:2017-07-03 20:26:06

标签: sql database oracle plsql

以下是域

的简化模型

enter image description here

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

我必须编写查询,在给定目标主单元代码的情况下返回随机文档集。以下是标准:

  • 返回最新event_code = 10
  • 的10个文件
  • 每个文档必须属于生活在该地区任何城市的不同客户(更喜欢不同的城市)
  • 返回符合条件的客户最新文档
  • 必须是结果中存在的两种文档类型
  • 每个查询的结果(所选客户)应随机

但是...

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

可能有数百万行,查询必须尽可能快,经常执行。

我不确定如何以理智的方式构建这种复杂的查询。我正在使用 Oracle PL / 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)

如何以高效,随意的方式正确地进行此查询?我不是要求确切的解决方案,而是至少要求指导。

1 个答案:

答案 0 :(得分:0)

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

下面我开始使用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;

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

从Oracle 12c开始,

FETCH FIRST可用。在早期版本中,您将使用另一个子查询,而使用另一个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,然后删除未使用的。