Community写作与编辑github.com

sasjs/skills

SASjs 生态系统和 SAS 编程语言的 Agent 技能

skills 是什么?

该技能为 AI 代理(如 Claude Code、Cursor、Codex)提供了编写地道、高质量 SAS 代码的全面指导。它涵盖了 SAS 语言的核心领域,包括 DATA 步编程、PROC SQL、宏语言以及各种常用过程,并提供了明确的编码风格规则和严格的质量要求。技能强调生成的 SAS 代码必须实现零警告(WARNING),并详细列出了常见的编程陷阱,如隐式类型转换、宏变量作用域问题和未初始化的变量。它还包含了可移植性注意事项,帮助用户编写在 SAS 9.4 和 Viya 平台上都能良好运行的代码,并通过大量“前后对比”示例,直观地展示了如何避免错误和编写更健壮的代码。总体而言,这是一套静态的语言参考指南,旨在帮助 AI 代理生成符合生产环境标准的 SAS 代码。

兼容平台~Claude Code~Codex CLI~Cursor
npx skills add sasjs/skills

Installed? Explore more 写作与编辑 skills: steipete/notion, langchain-ai/langchain, bytedance/podcast-generation · View all 6 →

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

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)
  • 常用过程:SORT、MEANS/SUMMARY、FREQ、TRANSPOSE、REPORT、PRINT、CONTENTS、DATASETS、FORMAT、IMPORT/EXPORT
  • 格式和输入格式(通过 PROC FORMAT 自定义,图片格式)
  • ODS 输出(HTML、PDF、RTF、Excel、OUTPUT 目标)
  • 文件 I/O:LIBNAME、FILENAME、INFILE/INPUT、FILE/PUT

工作流程

对每个生成 SAS 代码的请求(新程序、编辑或调试)应用此循环:

  1. 分析请求。 识别输入:数据集、列、宏变量、平台(SAS 9 vs Viya)。注意所需的输出:数据集、报告(ODS)、宏变量或文件。如果请求不明确,陈述您的假设并继续。
  2. 编写代码草案。 按照下面的样式规则编写最简洁的正确程序。使用两级数据集名称、为字符变量显式指定 length、在宏内部使用 %local,以及为 proc sql 使用 quit;。优先使用 proc sort nodupkey 而非手动去重。
  3. 运行它(或描述运行过程)。 如果有 SAS 会话可用,提交并捕获日志。如果没有,则根据常见陷阱列表在脑子里追踪代码——指出任何您无法验证的步骤。
  4. 检查日志。 将任何 WARNING: 或 ERROR: 视为需要修复的缺陷,而不是可以忽略的注释。常见问题包括:未初始化的变量、隐式的数值到字符转换、由于隐式 length=8 导致的截断、“无观测”、未解析的宏引用、只读的自动宏变量。在合理的情况下,也应删减多余的 NOTE。
  5. 对照陷阱进行复核。 在返回最终代码之前,根据常见陷阱列表重新通读一遍。修复或注释任何被标记的问题。

输入: 一个请求(自然语言或 .sas 文件),可选地附带样本数据或日志片段。输出: 地道的 SAS 代码(2 空格缩进,以 run;/quit; 结尾),外加一段简短说明,指出已处理的陷阱或已做的假设(如适用)。验证: 返回的代码需通过心智日志扫描,零 WARNING/ERROR,并且(当可用时)实际运行产生干净的日志。

不可协商:没有 WARNING

生成的 SAS 代码必须干净地运行——日志中零 WARNING(和零 ERROR)。将每个 WARNING: 视为缺陷:未初始化变量、隐式类型转换、本应是警告的截断注释、“无观测”、未解析的宏引用等。如果某个警告确实不可避免,请有意识地抑制它(例如使用显式选项)并注释原因。同样,在合理的情况下也应避免多余的 NOTE。

样式规则

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

  • 每行一条语句;缩进 = 2 个空格,无制表符,无尾部空白
  • 每行不超过 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,而不是依赖默认值(避免隐式 length=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 的参数也是如此,它们接受的是名称而非值)。不要将大小写不匹配视为错误——它从来不是原因。
    • 除非有意设为全局(全局变量应使用应用程序前缀以避免冲突),否则所有宏变量必须是 %local;在 DATA 步中使用 call symputx(而不是 symput)
    • 在宏内部使用 /* */ 进行注释(星号注释会被编译到宏中)
    • 使用 %length(&var)=0 检查而非 &var= 来守卫宏逻辑(空比较是不安全的)
  • 避免命名冲突:使用基于 %sysfunc-/&syslast 的工作表(例如 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 操作 vs 过程,用于加载 sashdat 的 proc casutil,锁定服务器上没有 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 不在 work.source 中 */
  name = catx(' ', first_name, last_name); /* 被截断为源数据中 name 的长度 */
run;

之后 —— region 通过 set 引入(或显式赋值),并且在 set 之前显式声明 length 以加宽 name,使得 catx 不会被截断:

data work.customers;
  length name $ 50;
  set work.source;                          /* region 必须存在于 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/进行类型转换以使类型匹配,使用显式内连接,并添加守卫以防止在键缺失时产生笛卡尔积:

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.) /* 显式类型转换;或修复上游长度 */
  ;
quit;

如果某个键列在两边都合法地存在重复值,请先聚合或去重——绝不依赖偶然的 1:1 连接。

宏:缺少 %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;

审查时要标记的常见陷阱

  • 意外的多对多 MERGE
  • 自动变量 _ERROR_ / 隐式 RETAIN 的意外行为
  • 宏时机问题:在 &macrovar 存在之前引用它,%if 评估 DATA 步变量(应使用 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 会话。“文件 I/O”、“PROC SQL”、“命令”和“执行”等术语指的是 SAS 语言概念(文件处理语句、SQL 过程、宏执行上下文),而不是此技能执行的操作系统级别的操作。

相关技能