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

Posted

技术标签:

【中文标题】SAS 升级到 9.4M7 后,Oracle 数据库上的 MIN 函数行为发生了变化【英文标题】:MIN function behavior changed on Oracle databases after SAS Upgrade to 9.4M7 【发布时间】:2022-01-23 13:11:10 【问题描述】:

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

proc setinit
Current version: 9.04.01M7P080520

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

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

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

假设我有以下数据集:

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

使用临时表:

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 表上使用时表现正常

MIN 函数返回缺失值 (.)仅当所有参数都缺失时

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

所以我的猜测是升级搞砸了如何将 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

【问题讨论】:

您是使用 ODBC 连接还是访问 Oracle?检查这些组件是否已升级? 请注意,在 SAS 中键入 ""'' 不会创建空字符串。这与输入" " 相同。如果你真的想在 SAS 中生成一个空字符串,你需要使用像 trimn(' ') 这样的函数调用。 【参考方案1】:

使用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 查询返回最小非空值 TLO

在 Oracle 转换查询中,SAS 空白 '' 转换为 ' ' 单个空白字符,它不为空,因此 ' ' < 'TLO' 并且您得到空白结果。

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

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

【讨论】:

清除,非常感谢。我将不得不深入研究 What's New 文档,因为代码中没有任何变化。【参考方案2】:

您为什么使用' ''' 作为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;

【讨论】:

完全删除 else 语句使其工作。但是我仍然不知道升级 SAS 时出了什么问题。 您是否更改了 SAS 代码?也许在您没有引号之间的空格之前。【参考方案3】:

当我尝试在 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;

【讨论】:

以上是关于SAS 升级到 9.4M7 后,Oracle 数据库上的 MIN 函数行为发生了变化的主要内容,如果未能解决你的问题,请参考以下文章

RDS postgres 从 9.4 升级到 9.5,CPU 卡在 100% 几个小时

用RunASDate解决SAS 9.4许可证过期的问题

Centos7升级内核后无法启动解决办法:mpt[23]sas驱动问题

sas spawner

Flux 错误:配置无效:升级到 Flux 9.4 和 Typo3 9.5 后未设置“vendorName”

sas share