根据 Oracle 中的顺序分离结果 SQL

Segregate results based on order in Oracle SQL

我正在尝试从员工列表中获取职位编号。如果他们多年来没有调动过部门,那应该算是一份工作。一旦他们搬到一个新部门,那应该被视为一份新工作。我面临的问题是,如果他们搬回一个部门,我的要求也应该被视为一份新工作。所以流程会是这样的:

我在下面做了一个超级简化的查询来演示我正在努力实现的目标:

SELECT RANK() OVER(ORDER BY last_year_in_role) JOB_NO, name, Role
FROM
    (
    SELECT MAX(years_at_company) last_year_in_role, Name, Role
    FROM (
        SELECT 'Bob' name, 1 years_at_company, 'Sales' role FROM DUAL
        UNION
        SELECT 'Bob', 2, 'Sales' FROM DUAL
        UNION
        SELECT 'Bob', 3, 'Sales' FROM DUAL
        UNION 
        SELECT 'Bob', 4, 'IT' FROM DUAL
        UNION
        SELECT 'Bob', 5, 'Sales' FROM DUAL
        UNION
        SELECT 'Bob', 6, 'Marketing' FROM DUAL
        )
    GROUP BY Name, Role
    )
;

这会产生以下结果:

这是错误的,因为它将 SALES 的所有时间分组在一起,即使数据中有中断。我觉得这个问题可以通过 WINDOW 函数以某种方式对 ROLE 进行分区来解决,但我没有成功。

因为这是一个 Gaps & Island 问题,其中“海滩”被定义为部门变更。您可以使用 LAG() 分析函数检测它们。例如,您可以这样做:

with data as (
        SELECT 'Bob' name, 1 years_at_company, 'Sales' role FROM DUAL
        UNION
        SELECT 'Bob', 2, 'Sales' FROM DUAL
        UNION
        SELECT 'Bob', 3, 'Sales' FROM DUAL
        UNION 
        SELECT 'Bob', 4, 'IT' FROM DUAL
        UNION
        SELECT 'Bob', 5, 'Sales' FROM DUAL
        UNION
        SELECT 'Bob', 6, 'Marketing' FROM DUAL
)
select *
from (
  select d.*,
    case when role = lag(role) over(partition by name order by years_at_company)
         then 0 else 1 end as beach
  from data d
) x
order by name, years_at_company

结果:

 NAME  YEARS_AT_COMPANY  ROLE       BEACH 
 ----- ----------------- ---------- ----- 
 Bob   1                 Sales      1     
 Bob   2                 Sales      0     
 Bob   3                 Sales      0     
 Bob   4                 IT         1     
 Bob   5                 Sales      1     
 Bob   6                 Marketing  1     

请参阅 db<>fiddle 中的 运行 示例。

上面的查询预处理数据以生成 beach 列。有了它,您可以清楚地区分哪些行实际代表新工作,哪些行不代表。

您可以继续处理数据以聚合数据或根据需要过滤数据,但这应该会为您提供所需的所有信息。

在 12.1 及更高版本中,最佳(最简单、最高效和最优雅)的解决方案使用 match_recognize 子句:

select name, job_no, role
from   (
         SELECT 'Bob' name, 1 years_at_company, 'Sales' role
                                      FROM DUAL UNION ALL
         SELECT 'Bob', 2, 'Sales'     FROM DUAL UNION ALL
         SELECT 'Bob', 3, 'Sales'     FROM DUAL UNION ALL
         SELECT 'Bob', 4, 'IT'        FROM DUAL UNION ALL
         SELECT 'Bob', 5, 'Sales'     FROM DUAL UNION ALL
         SELECT 'Bob', 6, 'Marketing' FROM DUAL
       )
match_recognize(
  partition by name
  order     by years_at_company
  measures  match_number() as job_no, a.role as role
  pattern   (a b*)
  define    b as role = a.role
);

NAME JOB_NO ROLE     
---- ------ ---------
Bob       1 Sales    
Bob       2 IT       
Bob       3 Sales    
Bob       4 Marketing