SAS 升级到 9.4M7 后,Oracle 数据库上的 MIN 函数行为发生了变化

MIN function behavior changed on Oracle databases after SAS Upgrade to 9.4M7

我有一个已经运行多年的程序。今天,我们从 SAS 9.4M3 升级到 9.4M7。

proc setinit
Current version: 9.04.01M7P080520

从那以后,我无法获得与升级前相同的结果。

请注意,我是直接在 Oracle 数据库上查询。

尝试用最小的、可重现的 SAS table 示例复制问题,我发现在 SAS table 而不是 Oracle 数据库上查询时问题消失了。

假设我有以下数据集:

data have;
infile datalines delimiter="|";
input name :. id . value :. t1 :.;
datalines;
Joe|A|TLO
Joe|B|IKSK
Joe|C|Yes
;

使用临时 table:

proc sql;
    create table want as
    select name,
    min(case when id = "A" then value else "" end) as A length 8
    from have
    group by name;
quit; 

Results:

name   A
Joe   TLO

但是,当 运行 直接在 oracle 数据库上进行完全相同的查询时,我得到了一个缺失值:

proc sql;
    create table want as
      select name, 
      min(case when id = "A" then value else "" end) as A length 8
      from have_oracle
      group by name;
quit;

name     A
Joe       

根据文档,min() 函数在 SAS table

上使用时运行正常

The MIN function returns a missing value (.) only if all arguments are missing.

我相信当 Oracle 不理解 SAS 传递给它的函数时会发生这种情况 - SAS 和 Oracle 中的最小函数非常不同,SAS 中的等价函数是 LEAST().

所以我的猜测是升级弄乱了 t运行如何将 SAS min 函数转换为 Oracle,但这仍然是一个猜测。有人 运行 喜欢这种行为吗?


编辑:@Richard 的评论

options sastrace=',,,d' sastraceloc=saslog nostsuffix;

proc sql;
    create table want as
    select t1.name,
    min(case when id = 'A' then value else "" end) as A length 8
    from oracle_db.names t1 inner join oracle_db.ids t2 on (t1.tid = t2.tid)
    group by t1.name;
    
ORACLE_26: Prepared: on connection 0
SELECT * FROM NAMES
 
ORACLE_27: Prepared: on connection 1
SELECT  UI.INDEX_NAME, UIC.COLUMN_NAME FROM  USER_INDEXES UI,USER_IND_COLUMNS UIC WHERE  UI.TABLE_NAME='NAMES' AND 
UIC.TABLE_NAME='NAMES' AND  UI.INDEX_NAME=UIC.INDEX_NAME
 
ORACLE_28: Executed: on connection 1
SELECT statement  ORACLE_27
 
ORACLE_29: Prepared: on connection 0
SELECT * FROM IDS
 
ORACLE_30: Prepared: on connection 1
SELECT  UI.INDEX_NAME, UIC.COLUMN_NAME FROM  USER_INDEXES UI,USER_IND_COLUMNS UIC WHERE  UI.TABLE_NAME='IDS' AND 
UIC.TABLE_NAME='IDS' AND  UI.INDEX_NAME=UIC.INDEX_NAME
 
ORACLE_31: Executed: on connection 1
SELECT statement  ORACLE_30
 
ORACLE_32: Prepared: on connection 0
select t1."NAME", MIN(case  when t2."ID" = 'A' then t1."VALUE" else ' ' end) as A from 
NAMES t1 inner join IDS t2 on t1."TID" = t2."TID" group by t1."NAME"
 
ORACLE_33: Executed: on connection 0
SELECT statement  ORACLE_32
 
ACCESS ENGINE:  SQL statement was passed to the DBMS for fetching data. 
NOTE: Table WORK.SELECTED_ATTR created, with 1 row and 2 columns.

!                              quit;
NOTE: PROCEDURE SQL used (Total process time):
      real time           0.34 seconds
      cpu time            0.09 seconds

当我尝试在 Oracle 中重现它时,我得到了您正在寻找的结果,所以我怀疑它与 SAS(我不熟悉)有关。

with t as (
  select 'Joe' name, 'A' id, 'TLO' value from dual union all
  select 'Joe' name, 'B' id, 'IKSK' value from dual union all
  select 'Joe' name, 'C' id, 'Yes' value from dual
)
select name
, min(case when id = 'A' then value else '' end) as a
from t
group by name;


NAME A   
---- ---- 
Joe  TLO

不相关,如果您只对 id = 'A' 感兴趣,那么更好的查询是:

select name
, min(value) as a
from t
where id = 'A'
group by name;

使用 SASTRACE= 系统选项记录发送到 DBMS 的 SQL 语句。

options SASTRACE=',,,d';

将提供最详细的日志记录。

从准备好的语句中,您可以看到为什么从 Oracle 查询中得到空白。

select 
  t1."NAME"
, MIN ( case
          when t2."ID" = 'A' then t1."VALUE" 
          else ' '
        end
      ) as A 
from 
  NAMES t1 inner join IDS t2 on t1."TID" = t2."TID" 
group by
  t1."NAME"

SQL MIN () 聚合函数将不考虑空值。

在 SAS SQL 中,空白值也被解释为 null。

在 SAS 中,您的 SQL 查询 returns 最小非空值 TLO

在 Oracle 转换查询中,SAS 空白 '' 被转换为 ' ' 一个非空的空白字符,因此 ' ' < 'TLO' 得到空白结果.

您要在 Oracle 中强制执行的实际 MIN 是 min(case when id = "A" then value else null end)@Tom 已经表明可以通过省略 else 子句来实现。

查看实际差异的唯一方法是 运行 先前 SAS 版本中带有跟踪的查询,或者如果幸运的话,请参阅(被许多人忽略的)“新功能”文档中的解释。

为什么使用 ' ''' 作为 ELSE 值?也许 Oracle 以不同于空字符串的方式处理其中包含空格的字符串。

为什么不在 ELSE 子句中使用 null
或者只是离开 ELSE 子句并让它默认为 null?

libname mylib oracle .... ;
proc sql;
  create table want as
    select name
         , min(case when id = "A" then value else null end) as A length 8
    from mylib.have_oracle
    group by name
  ;
quit;

也可以自己尝试 运行 Oracle 代码,而不是使用隐式传递。

proc sql;
  connect to oracle ..... ;
  create table want as
    select * from connection to oracle
     (
      select name, 
      min(case when id = "A" then value else null end) as A length 8
      from have_oracle
      group by name
     )
  ;
quit;