mcpField
S
sas

sas

github.com/sasjs数据与数据库31.1k 次安装2 天前更新
代码仓库

关于 sas

面向 SAS 编程者的静态语言技能,用于编写和审查符合惯例的 SAS 代码,涵盖 DATA 步骤、PROC SQL、宏和常见过程。它提供日志检查建议,但不执行代码或访问外部系统。

主要功能

  1. 01DATA 步骤与 PROC SQL 指引
  2. 02宏语言编写模式
  3. 03常见 PROC 与 ODS 工作流
  4. 04文件 I/O 与格式
  5. 05风格和可移植性规则
  6. 06日志与警告审查

适用场景

  1. 01编写 SAS 数据转换程序
  2. 02审查或调试 SAS 程序
  3. 03构建 PROC SQL 查询和报告
  4. 04检查宏中的常见问题

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/sasjs/skills --skill sas

安装后在 Claude Code 中输入/sas确认已加载。

详细介绍

SAS 语言

编写符合惯例、达到生产质量的 SAS 代码。本技能涵盖 SAS 语言本身,不依赖任何框架。

范围

  • DATA 步编程(SET/MERGE/BY、RETAIN、数组、DO 循环、哈希对象、first./last. 处理)
  • PROC SQL(联接、子查询、视图、使用 CONNECT TO 直通)
  • 宏语言(%macro/%mend、宏变量、%local/%global、条件 %IF 逻辑、引用函数 %STR/%NRSTR/%BQUOTE、CALL SYMPUTX)
  • 常用 PROC:SORT、MEANS/SUMMARY、FREQ、TRANSPOSE、REPORT、PRINT、CONTENTS、DATASETS、FORMAT、IMPORT/EXPORT
  • 格式和输入格式(通过 PROC FORMAT 定义的用户格式、picture 格式)
  • ODS 输出(HTML、PDF、RTF、Excel、OUTPUT 目标)
  • 文件 I/O:LIBNAME、FILENAME、INFILE/INPUT、FILE/PUT

工作流

对每个会生成 SAS 代码的请求(新建程序、编辑或调试),都遵循以下流程:

  1. 分析请求。 确认输入项:数据集、列、宏变量、平台(SAS 9 还是 Viya)。注意所需输出:数据集、报告(ODS)、宏变量或文件。如果请求含糊不清,请说明假设并继续。
  2. 起草代码。 遵循下方的风格规则,编写满足需求的最精简程序。使用两级数据集名称,为字符变量显式指定 length,在宏中使用 %local,并在 proc sql 后使用 quit;。去重时优先使用 proc sort nodupkey,而不是手动实现。
  3. 运行(或说明运行情况)。 如果有可用的 SAS 会话,请提交代码并获取日志。如果没有,请对照常见陷阱列表在脑中逐步检查代码,并指出哪些部分无法验证。
  4. 检查日志。 将任何 WARNING: 或 ERROR: 视为必须修复的问题,而不是可以忽略的提示。常见问题包括:未初始化变量、数值到字符的隐式转换、隐式 length=8 导致截断、“no observations”、未解析的宏引用、只读自动宏变量。合理情况下,也应删减多余的 NOTE。
  5. 对照陷阱复查。 返回代码前,再次根据常见陷阱列表检查最终代码。修复或注明所有标出的问题。

输入: 请求(自然语言或 .sas 文件),可选的示例数据或日志摘录。输出: 符合惯例的 SAS 代码(缩进 2 个空格,以 run;/quit; 结束),并在有帮助时简要说明已处理的陷阱或作出的假设。验证: 返回的代码经过日志扫描后,在思考检查中没有 WARNING/ERROR;若条件允许,实际运行也应生成干净的日志。

不可妥协:不得出现 WARNING

生成的 SAS 代码必须运行干净——日志中不得有 WARNING(也不得有 ERROR)。将每个 WARNING: 都视为问题:未初始化变量、隐式类型转换、应产生警告的截断提示、“no observations”、未解析的宏引用等。如果确实无法避免某个警告,请有意抑制它(例如使用显式选项),并说明原因。同样,在合理情况下避免多余的 NOTE。

样式规则

以下规则遵循 @sasjs/core 编码标准——适用于所有 SAS 代码:

  • 每行一条语句;缩进为 2 个空格,不使用制表符,不留行尾空格
  • 合并包含多个操作数的语句——%local/%global、length、attrib、retain、array、format/informat、label、keep/drop、var、rename——将所有操作数列在一条语句中。切勿仅为控制行长而拆成重复语句(例如两行 %local、两条 length 语句等);若行长超限,则换行续写——语句以分号结束,因此续行不受行长限制。
  • 每行不超过 80 个字符;使用 Unix(LF)换行符;编码为 UTF-8
  • 完全避免非 ASCII 字符和特殊字符,以确保跨 SAS 安装和编码的最大兼容性
  • 步骤始终以 run; 结束;对于 proc sql(以及连接 CAS 的过程步),必须使用 quit;,以避免在 Viya 中出现 WARNING: You cannot disconnect or terminate session ...
  • 所有数据集引用都必须采用两级名称(例如 work.blah,而不是 blah),以避免 DATASTMTCHK=ALLKEYWORDS 和活动的 USER 库带来的问题
  • 对字符变量显式使用 length / attrib,不要依赖默认值(以避免隐式长度为 8 导致截断)
  • 使用字面量后缀以提高清晰度('01JAN2020'd、'12:30't)
  • 优先使用带 nodupkey 的 proc sort,而不是手动去重逻辑
  • 宏:
    • 定义时使用括号,即使没有参数也要使用:%macro x();,而不是 %macro x;
    • 结束宏的 %mend; 必须重复宏名称
    • 宏调用不以分号结束:%my_macro(),而不是 %my_macro();
    • 必需参数使用位置参数;可选参数使用关键字形式(var=)
    • 宏名称使用小写,并采用“动词-名词”命名方式
    • 宏变量末尾不加句点(&var,而不是 &var.),除非需要句点来避免解析错误
    • 宏变量名称不区分大小写:&Foo、&FOO 和 &foo 都解析为同一符号(%symexist/%superq/symget 的参数也相同;它们接收的是名称,而非值)。不要将大小写不匹配当作 bug 追查——这绝不会是问题根源。
    • 所有宏变量都必须声明为 %local,除非有意设为全局变量(全局变量应使用应用程序前缀以避免冲突);在 DATA 步中使用 call symputx(而不是 symput)
    • 在宏中使用 /* */ 注释(星号注释会被编译进宏)
    • 使用 %length(&var)=0 检查来防护宏逻辑,而不是 &var=(空值比较不安全)
  • 避免命名冲突:使用基于 %sysfunc / &syslast 的 WORK 表(例如 data &output; set &syslast; run;),而不是硬编码名称
  • 会话终止时,WORK 库会自动清空:SAS 会在会话结束时删除 WORK 中的每个表,因此普通临时表无需显式清理(proc datasets lib=work nolist; delete ...; quit;)——不要添加此类清理,也不要在代码审查中将缺少清理标记为缺陷。仅当临时文件大到或数量多到可能在运行期间造成磁盘压力时,才显式清理;此时应在不再需要后立即删除,而不是留到会话结束时处理。
  • 不要编写开放式(非宏)条件代码:将平台分支或条件执行的代码块(例如 %if %mf_getplatform()=VIYA %then %do; ... %end;)封装在 %macro ... %mend 中,并调用该宏。在程序级别使用开放式 %if,在某些执行环境(作业/调度程序/测试框架)中会失败,并会掩盖作用域泄漏。

可移植性注意事项

  • 留意代码在 SAS 9.4 和 Viya 之间的差异(例如 CAS 操作与过程步、使用 proc casutil 加载 sashdat,以及受限服务器上无法使用 X 命令)
  • 除非另有要求,否则避免使用硬编码物理路径和特定引擎选项
  • 不要使用包含 if/else 逻辑的开放式宏代码:将分支代码块封装在 %macro ... %mend 中并调用它们——开放式 %if / %else 在所有 SAS 环境中的行为并不一致。

前后示例

每组示例都展示了一种常见的、容易产生 WARNING 或缺陷的写法,以及符合惯例的修正方式,并说明修正所针对的日志问题。

DATA 步骤:未初始化变量 + 隐式长度截断

之前 - 引用了 region,但从未为其赋值,也未通过 set 引入,因此日志会显示 WARNING: Variable region is uninitialized;而 name 继承了源数据中 first_name 的长度(通常为 $8),导致拼接后的值被静默截断:

data work.customers;
  set work.source;
  region = upcase(region);                 /* region not in work.source */
  name = catx(' ', first_name, last_name); /* truncated to source length of name */
run;

之后 - 通过 set 引入 region(或显式赋值),并在 set 前显式声明 length,增大 name 的长度,避免 catx 的结果被截断:

data work.customers;
  length name $ 50;
  set work.source;                          /* region must exist in work.source */
  region = upcase(region);
  name = catx(' ', first_name, last_name);
run;
PROC SQL:隐式转换 + 笛卡尔积

之前 - 按字符型 id 和数值型 cust_id 连接会触发 NOTE: Character values have been converted to numeric(某些选项下会触发 WARNING),而连接键缺失可能导致笛卡尔积规模暴增:

proc sql;
  create table work.joined as
  select a.*, b.balance
  from work.orders a, work.accounts b
  where a.id = b.cust_id;
quit;

之后 - 显式使用 length/cast 确保类型一致,显式指定内连接,并在键缺失时防止产生笛卡尔积:

proc sql;
  create table work.joined as
  select a.id, a.amount, b.balance
  from work.orders as a
  inner join work.accounts as b
    on a.id = input(b.cust_id, 8.) /* explicit cast; or fix upstream length */
  ;
quit;

如果两侧的键列都可能合法地存在重复值,请先聚合或去重,切勿依赖偶然形成的一对一连接。

宏:缺少 %local + 不安全的空值检查

之前 - 宏内部的 %let 会在全局作用域中创建 dsid(没有 %local),而 %if &filter= 不安全:当 &filter 为空时,它会解析为 %if = %then,导致语法错误,而不是正常进入分支:

%macro apply_filter(filter=);
  %if &filter= %then %do;
    data work.out; set work.in; run;
  %end;
  %else %do;
    data work.out; set work.in; where &filter; run;
  %end;
  %let dsid = &syslast;
%mend apply_filter;

之后 - %local dsid 将临时变量限定在宏作用域内,%length(&filter)=0 可安全检查空值,宏定义使用括号,且 %mend 重复宏名称:

%macro apply_filter(filter=);
  %local dsid;
  %if %length(&filter)=0 %then %do;
    data work.out; set work.in; run;
  %end;
  %else %do;
    data work.out; set work.in; where &filter; run;
  %end;
  %let dsid = &syslast;
%mend apply_filter;
多操作数语句:合并为一条语句,而非重复声明

之前 - 两条 %local 语句和两条 length 语句分别声明相关操作数。这种重复没有必要;相关操作数应放在同一条声明语句中:

%local dbg libref1 libref2 loglocation fname1 fname2;
%local jobstate err_httpcode err_msg;

data work.staged;
  length customer_id 8;
  length customer_name $ 50;
  set work.source;
run;

之后 - 将每组声明合并为一条语句;如果单条语句超过 80 个字符,则换行续写(语句以分号结束,因此续写行不受长度限制):

%local dbg libref1 libref2 loglocation fname1 fname2
       jobstate err_httpcode err_msg;

data work.staged;
  length customer_id 8 customer_name $ 50;
  set work.source;
run;

这适用于所有接受操作数列表的语句——%local/%global、length、attrib、retain、array、format/informat、label、keep/drop、var、rename。

评审时需要指出的常见陷阱

  • 非预期的多对多 MERGE
  • 自动变量 _ERROR_ / 隐式 RETAIN 带来的意外行为
  • 宏时序问题:在 &macrovar 尚不存在时引用它,或 %if 对数据步骤变量求值(应改用 if/symget)
  • 自动宏变量(&syscc、&syswarningtext、&syserrortext、&sysdate、&sysuserid 等)为只读变量——尝试覆盖它们(例如 %let syswarningtext=; 或 call symput('syscc',...))会引发 ERROR: Unable to assign value to a macro variable that is read only(或类似错误)。切勿尝试“清除”它们。
  • 首次赋值时隐式采用 length=8 导致截断
  • proc sql 笛卡尔积警告

限制

此技能是一份静态语言参考,为编写和评审 SAS 代码提供指导。它不会执行 SAS 代码、运行 shell 命令、访问文件系统、连接数据库或发起网络请求。所有代码示例仅供参考,必须由用户提交到 SAS 会话中运行。“File I/O”、“PROC SQL”、“command”和“execution”等术语指的是 SAS 语言概念(文件处理语句、SQL 过程、宏执行上下文),而不是此技能执行的操作系统级操作。

评论

支持 Markdown