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

如何重构被破坏的复杂SQL查询,sql,database,oracle,plsql,Sql,Database,Oracle,Plsql,这是该领域的简化模型 简而言之,unit将文档授予客户。有两种类型的单位:主单位和它们的子单位。两者都属于同一个省,一个省可能属于多个城市。文档有许多事件(处理历史记录)。客户属于一个省市 我必须编写查询,它返回随机的文档集,给定目标主单元代码。标准如下: 返回10个文档,其中最新事件代码=10 每份文件必须属于居住在该单位所在地区任何城市的不同客户(首选不同城市) 返回符合条件的客户最新文档 结果中必须同时存在两种文档类型 每次查询的结果(选择的客户)应为随机 但是 如果没有足够的客户

这是该领域的简化模型

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

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

  • 返回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)

如何在考虑效率和随机性的情况下正确地进行此查询?我不是要求精确的解决方案,而是至少要有指导原则。

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

下面我从一个
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开始,可以使用“先提取”。在早期版本中,您将使用另一个子查询和另一个
行编号

至于速度,我建议查询使用以下索引:

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);

最后两个中的一个将使用,另一个不使用。用
解释计划检查哪一个
并删除未使用的计划。

文档和事件之间的关系是什么?我没有看到公共字段名。实际上我没有看到很多对应的字段名。IIRC,mysql没有CTE。@jbrahy我只是忘了在图片中包含外键。正如你所看到的,它们之间存在一对多的关系,所以区域实际上只是两个层次;各省及其城市?我可以通过观察一个城市的类型或注意到它有一个父区域来检测它。我可以通过查看一个省的类型或注意到它没有父区来检测它。对的因此,只有两个级别具有冗余的类型信息。我会为每个城市找到一个省。所有这些都正确吗?这是正确的