3

我正在尝试将视图从 Oracle RDBMS 转换为 SQL Server。视图如下所示:

create or replace view user_part_v
as
  select part_region.part_id, users.id as users_id
    from part_region, users
   where part_region.region_id in(select     region_id
                                        from region_relation
                                  start with region_id = users.region_id
                                  connect by parent_region_id = prior region_id)

在阅读了递归 CTE以及它们在子查询中的使用之后,我最好的猜测是将上述内容转换为 SQL Server 语法:

create view user_part_v
as
  with region_structure(region_id, parent_region_id) as (
    select region_id
         , parent_region_id
      from region_relation
     where parent_region_id = users.region_id
    union all
    select r.region_id
         , r.parent_region_id
      from region_relation r
      join region_structure rs on rs.parent_region_id = r.region_id
  )
  select part_region.part_id, users.id as users_id
    from part_region, users
   where part_region.region_id in(select region_id from region_structure)

显然,这给了我一个关于 CTE 定义中对 users.region_id 的引用的错误。

如何在 SQL Server 中获得与从 Oracle 视图中获得的结果相同的结果?

背景

我正在将一个系统从运行在 Oracle 11g RDMS 上的系统转换为 SQL Server 2008。这个系统是一个相对较大的基于 Java EE 的系统,使用 JPA(Hibernate)从数据库中查询。

许多查询使用上述视图将返回的结果限制为适合当前用户的结果。如果我不能直接转换视图,那么转换将更加困难,因为我需要更改我们查询数据库的所有位置以获得相同的结果。

此视图引用的表的结构类似于:

USERS
  ID 
  REGION_ID

REGION
  ID
  NAME

REGION_RELATIONSHIP
  PARENT_REGION_ID
  REGION_ID

PART
  ID
  PARTNO
  DESCRIPTION

PART_REGION
  PART_ID
  REGION_ID

所以,我们有区域,排列成层次结构。用户可以被分配到一个区域。一个部件可以分配到多个区域。用户可能只能看到分配给其区域的部件。这些区域引用了不同的地理区域:

World
  Europe
    Germany
    France
    ...
  North America
    Canada
    USA
      New York
      ...

如果零件#123 被分配到美国地区,而用户被分配到纽约地区,那么用户应该能够看到该零件。

更新:我能够通过创建一个包含必要数据的单独视图来解决该错误,然后让我的主视图加入该视图。这使系统正常工作,但我还没有进行彻底的正确性或性能测试。我仍然愿意接受有关更好解决方案的建议。

4

1 回答 1

2

我重新格式化了您的原始查询,以便我更容易阅读。

create or replace view user_part_v
as
select part_region.part_id, users.id as users_id
from part_region, users
where part_region.region_id in(
    select region_id
    from region_relation
    start with region_id = users.region_id
    connect by parent_region_id = prior region_id
);

让我们检查一下这个查询中发生了什么。

select part_region.part_id, users.id as users_id
from part_region, users

这是一种旧式连接,其中表是笛卡尔连接的,然后结果被后续的 where 子句减少。

where part_region.region_id in(
    select region_id
    from region_relation
    start with region_id = users.region_id
    connect by parent_region_id = prior region_id
);

使用 connect by 语句的子查询使用region_id外部查询中的用户表来定义递归的起点。然后该in子句检查是否在递归查询的结果中找到了region_idfor 。part_region此递归遵循表中给出的父子链接region_relation

因此,将 in 子句与引用父级和旧式联接的子查询相结合意味着您必须考虑查询的目的是要完成什么并从那个方向接近它(而不仅仅是一个调整过的重新旧查询的排列)以便能够将其转换为单个递归 CTE。

如果将部件分配给沿区域层次结构的同一分支的多个区域,则此查询也将返回多行。例如,如果该部件同时分配给北美美国,分配给纽约的用户将得到两行users_id相同part_id编号的返回。


鉴于 Oracle 视图和您给出的视图应该做什么的背景,我认为您正在寻找的是更像这样的东西:

create view user_part_v
as
with user_regions(users_id, region_id, parent_region_id) as (
    select u.users_id, u.region_id, rr.parent_region_id
    from users u 
    left join region_relation rr on u.region_id = rr.region_id
    union all
    select ur.users_id, rr.region_id, rr.parent_region_id
    from user_regions ur
    inner join region_relation rr on ur.parent_region_id = rr.region_id
)
select pr.part_id, ur.users_id
from part_region pr
inner join user_regions ur on pr.region_id = ur.region_id;

请注意,我已经users_id在递归 CTE 的输出中添加了 ,然后只是对part_region表和 CTE 结果进行了简单的内部连接。

让我为你分解查询。

select u.users_id, u.region_id, rr.parent_region_id
from users u 
left join region_relation rr on u.region_id = rr.region_id

这是我们递归的起始集。我们正在获取region_relation表格并将其与表格连接起来users,以便为每个用户获取递归的起点。该起点是用户被分配到的区域以及parent_region_id该区域的区域。left join在这里完成A并从user表中提取 region_id,以防用户被分配到最顶层的区域(这意味着该区域的表中不会有条目region_relation)。

select ur.users_id, rr.region_id, rr.parent_region_id
from user_regions ur
inner join region_relation rr on ur.parent_region_id = rr.region_id

这是 CTE 的递归部分。我们获取每个用户的现有结果,然后为现有集合的父区域为每个用户添加行。这种递归一直发生,直到我们用完父母。region_id(即我们点击了表中没有条目的行region_relationship。)

select pr.part_id, ur.users_id
from part_region pr
inner join user_regions ur on pr.region_id = ur.region_id;

这是我们获取最终结果集的部分。假设(正如我从您的描述中所做的那样)每个区域只有一个父区域(这意味着每个区域只有一行region_relationshipregion_id,一个简单的连接将返回所有应该能够根据部件查看部件的用户region_id. 这是因为对于用户的分配区域,每个用户恰好返回一行,对于每个父区域,直到层次结构根,每个用户都返回一行。

笔记:

原始查询和这个查询都有一个限制,我想确保您知道。如果该部分被分配到层次结构中低于用户的区域(即,作为用户区域的后代的区域,例如被分配到纽约和用户到美国而不是相反的部分),用户不会看到该部分。该部分必须分配给用户分配的区域,或者区域层次结构中的更高区域。

users_id另一件事是,这个查询仍然表现出我上面提到的关于原始查询的情况,如果一个部分被分配到沿层次结构的同一分支的多个区域,那么对于和的相同组合将返回多行part_id。我这样做是因为我不确定您是否希望改变这种行为。

如果这实际上是一个问题并且您想消除重复项,那么您可以将 CTE 下面的查询替换为以下查询:

select p.part_id, u.users_id
from part p
cross join users u
where exists (
    select 1
    from part_region pr
    inner join user_regions ur on pr.region_id = ur.region_id;
    where pr.part_id = p.part_id
    and ur.users_id = u.users_id
);

这会在part表和users表之间进行笛卡尔连接,然后只返回两者的组合在子查询的结果中至少有一行的行,这是我们试图去重的结果。

于 2017-06-26T07:23:07.900 回答