count(Distinct) 窗口函数替代方案

问题描述 投票:0回答:1

我编写了一个查询来比较oracle中2个数据库表的列/数据,它将打印基于关键列(分区子句中的字段)的2个表行中存在差异的记录,如果没有差异,则对于任何特定字段,它将打印 null,对于存在差异的字段,它将打印那里的值(其中 row_cnt = 2),如果任何表中有一些额外的记录,那么它也会打印它(其中 row_cnt =1) .

Sample script --

create table src_tab (id number,name varchar2(10), job varchar2(10), sal number);

create table tgt_tab (id number,name varchar2(10), job varchar2(10), sal number);

insert into src_tab values (1, 'john', 'not sure', 100);
insert into src_tab values (2, 'kevin', 'unemp', 200);
insert into src_tab values (3, 'chad', 'emplyd', 400);
insert into src_tab values (4, 'page', 'NA', 500);
insert into src_tab values (5, 'emmy', 'desk', 600);

insert into tgt_tab values (1, 'john', 'not sure', 150);
insert into tgt_tab values (2, 'kevin', 'unemp', 200);
insert into tgt_tab values (3, 'chad', 'empl', 400);
insert into tgt_tab values (4, 'page', 'NA', 500); 

Comparison query --
Select * from (select id,name,
  case NEW_CNT when 1 then 'TGT_TAB' else 'SRC_TAB' end tbl,
  ROW_CNT,
  case when count(job) over(partition by id,name)
    between 1 and count(distinct job) over(partition by id,name)
    then job end job,
  case when count(sal) over(partition by id,name)
    between 1 and count(distinct sal) over(partition by id,name)
    then sal end sal
FROM (
  select
    id,name, job, sal,
    sum(NEW_CNT) NEW_CNT, count(*) over(partition by id,name) ROW_CNT
  FROM (
    select 
    id,name, job, sal,
    -1 NEW_CNT
    from src_tab O
    union all
    select
    id,name, job, sal,
    1 NEW_CNT
    from tgt_tab N 
  )
  group by
    id,name, job, sal
  having sum(NEW_CNT) != 0
)
order by 1, 2, new_cnt)
;

由于这个查询是为oracle 19c数据库编写的,现在我需要在Sybase数据库上运行这个查询,但是当我在Sybase上尝试它时,我发现那里不允许COUNT(DISTINCT)窗口函数,所以现在寻找替代方法要打印与 Sybase 上相同的输出,我尝试使用领先/滞后函数,因为 Sybase 支持它,但输出不好。

Alternative Tried --
case when sal <>
    lead(sal,1,sal) over (partition by id,name order by id) then sal  end sal_lead

Expected Result --

ID  NAME    TBL     ROW_CNT JOB      SAL
1   john    SRC_TAB   2     null     100
1   john    TGT_TAB   2     null     150
3   chad    SRC_TAB   2     emplyd   null
3   chad    TGT_TAB   2     empl     null
5   emmy    SRC_TAB   1     desk     600

我们同时使用Sybase DB ASE和IQ,如果查询可以与Sybase-IQ兼容那么也很好。 如果我需要更多信息来理解我在 orcale 中编写的查询,请告诉我。

编辑: 我发现一种适用于 Sybase-IQ 的替代方案如下,如果有人有更好的方法,请发表评论!

with t1 as (
             select  'SRC_TAB' tbl,
                     s.*
               from  src_tab s
            union all
             select  'TGT_TAB' tbl,
                     t.*
               from  tgt_tab t
           ),
     t2 as (
            select  t1.*,
                    case max(job) over(partition by id,name)
                      when min(job) over(partition by id,name) then 1
                      else 0
                    end same_job_flag,
                    case max(sal) over(partition by id,name)
                      when min(sal) over(partition by id,name) then 1
                      else 0
                    end same_sal_flag,
                    count(*) over(partition by id,name) row_cnt
              from  t1
            )            
select  id,
        name,
        tbl,
        row_cnt,
        case
          when row_cnt = 1 then job 
          when same_job_flag = 0 then job
        end job,
        case
          when row_cnt = 1 then sal 
          when same_sal_flag = 0 then sal
        end sal
  from  t2
  where same_job_flag = 0
     or same_sal_flag = 0
     or row_cnt = 1
  order by id,
           name,
           tbl;
sql sybase sap-ase sap-iq
1个回答
1
投票

背景:

  • ASE 的 SQL 方言相当古老(由于 SAP 让它半途而废),功能有限(例如,不支持窗口函数,不支持通用表表达式 - CTE)
  • IQ 的 SQL 方言更加“现代”(尽管我不能说出它对窗口函数和 CTE 支持的完整性)

如果 OP 的 IQ 查询(带有 CTE 和窗口函数)生成预期结果,那么 OP 可能需要考虑一个选项:

  • 设置代理/远程表(从 IQ 到 ASE)
  • 针对代理/远程表运行 IQ 查询
  • IQ 的卖点之一是能够使用这种方法对 ASE 数据运行以 IQ 为中心的查询
  • (当然)的一个缺点是 IQ 需要通过网络提取所有 ASE 数据,以便 IQ 查询引擎可以处理这些数据

我能够将在 ASE 中运行的几个查询拼凑在一起,生成预期的结果......

向数据集中添加更多行:

insert into tgt_tab values ( 6, 'bob',   'unemp',    350)      -- TGT_TAB only row

insert into src_tab values (10, 'sara',  'desk',     450)      -- common key but with differences
insert into tgt_tab values (10, 'sara',  'umenp',    300)      -- in both non-key columns

一个4路联合查询:

-- only in src_tab

select  s.id,s.name,tbl='SRC_TAB',ROW_CNT=1,s.job,s.sal
from    src_tab s
where   not exists(select  1
                   from    tgt_tab t
                   where   t.id   = s.id
                   and     t.name = s.name)

union all

-- only in tgt_tab

select  t.id,t.name,tbl='TGT_TAB',ROW_CNT=1,t.job,t.sal
from    tgt_tab t
where   not exists(select  1
                   from    src_tab s
                   where   t.id   = s.id
                   and     t.name = s.name)

union all

-- common key but job and/or sal are different (src_tab)

select  s.id,s.name,tbl='SRC_TAB',ROW_CNT=2,
        case when s.job != t.job then s.job else NULL end as job,
        case when s.sal != t.sal then s.sal else NULL end as sal

from    src_tab s

join    tgt_tab t
on      s.id   = t.id
and     s.name = t.name

where   s.job != t.job
or      s.sal != t.sal

union all

-- common key but job and/or sal are different (tgt_tab)

select  s.id,s.name,tbl='TGT_TAB',ROW_CNT=2,
        case when s.job != t.job then t.job else NULL end as job,
        case when s.sal != t.sal then t.sal else NULL end as sal

from    src_tab s

join    tgt_tab t
on      s.id   = t.id
and     s.name = t.name

where   s.job != t.job
or      s.sal != t.sal

order by id,name,tbl

我们可以通过使用 2 行虚拟/派生表将公共键匹配拆分为 2 个单独的行,将其缩减为 3 路并集:

-- only in src_tab

select  s.id,s.name,tbl='SRC_TAB',ROW_CNT=1,s.job,s.sal
from    src_tab s
where   not exists(select  1
                   from    tgt_tab t
                   where   t.id   = s.id
                   and     t.name = s.name)

union all

-- only in tgt_tab

select  t.id,t.name,tbl='TGT_TAB',ROW_CNT=1,t.job,t.sal
from    tgt_tab t
where   not exists(select  1
                   from    src_tab s
                   where   t.id   = s.id
                   and     t.name = s.name)

union all

-- common key but job and/or sal are different

select  s.id,s.name,dt.tbl,ROW_CNT=2,
        case when dt.tbl = 'SRC_TAB' and s.job != t.job then s.job
             when dt.tbl = 'TGT_TAB' and s.job != t.job then t.job
             else NULL
        end as job,
        case when dt.tbl = 'SRC_TAB' and s.sal != t.sal then s.sal
             when dt.tbl = 'TGT_TAB' and s.sal != t.sal then t.sal
             else NULL
        end as sal

from    src_tab s

join    tgt_tab t
on      s.id   = t.id
and     s.name = t.name

join    (select 'SRC_TAB'
         union all
         select 'TGT_TAB'
        ) dt (tbl)
on      1=1

where   s.job != t.job
or      s.sal != t.sal

order by id,name,tbl

注意事项:

  • 因为每个独立查询都针对特定场景,所以我们的
    ROW_CNT
    值只是硬编码值(这些查询生成
    ROW_CNT
    的唯一原因是匹配 OP 的预期输出)
  • 由于这些查询不依赖于 CTE 或窗口函数,因此应该(相对)容易地调整语法以确保它们在大多数(所有?)其他 RDMBS 环境中运行,尽管所述查询的性能可能无法与 CTE/窗口函数查询

这两个查询都会生成以下内容:

 id          name       tbl     ROW_CNT     job        sal
 ----------- ---------- ------- ----------- ---------- -----------
           1 john       SRC_TAB           2 NULL               100
           1 john       TGT_TAB           2 NULL               150

           3 chad       SRC_TAB           2 emplyd            NULL
           3 chad       TGT_TAB           2 empl              NULL

           5 emmy       SRC_TAB           1 desk               600

           6 bob        TGT_TAB           1 unemp              350

          10 sara       SRC_TAB           2 desk               450
          10 sara       TGT_TAB           2 umenp              300

注意事项:

  • 插入空白行以提高可读性
  • ASE 16.0 SP04 PL04
    实例生成的结果

我猜OP的问题是一个更大项目的最小的、可重现的示例,需要针对一组表运行

diff

如果是这种情况,那么很快就会发现到目前为止提出的查询(OP 的查询,我的查询)都没有很好地“扩展”。所有这些查询都针对特定的表对、特定的列集和特定的数据类型集进行硬编码。

对于一个完整的

diff
流程OP需要考虑:

  • 可变数量的关键列
  • 可变数量的非关键列(即扩展数量的
    case
    语句)
  • 更广泛的数据类型分类(例如,比较 (b)lob 列?比较日期/时间数据类型与毫秒级别的舍入问题?比较不同字符集下存储的文本数据?比较浮点/实数,其中精度取决于硬件?)
  • 需要特殊对待 NULL(例如,在某些 SQL 方言中
    = NULL
    is NULL
    的行为不同)
  • 大型表的性能开销可能令人望而却步

构建一个强大的工具需要大量的时间和精力,特别是如果尝试用 SQL 构建整个工具。需要考虑的一些想法:

  • 使用 SQL 以外的其他方法来执行比较(例如,
    awk
    perl
    ruby
    c(++)
    java
  • 查看您的 RDBMS 供应商是否提供数据比较工具(例如,Sybase/SAP 的 Repserver 有
    rs_subcmp
    (操作系统级别,基于 Java 的实用程序)和
    Data Assurance
    (操作系统级别,基于 Java,许可证$$ $ed 实用程序))
  • 查看是否有任何(内部)ETL 工具具有“差异”功能
  • 找到具有“差异”功能的第 3 方产品(例如,在不同 RDBMS 产品和/或实例之间迁移数据的第 3 方工具)
© www.soinside.com 2019 - 2024. All rights reserved.