你遇到什么场景?
SDTM的EG/LB/V S数据是长格式(每个参数一行),Table需要宽格式(每个参数一列)。SUPPQUAL的连接动不动就要TRANSPOSE+MERGE好几轮。用对SQL可以一步到位。
决策树
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 单表长→宽(EG参数展开) | MAX(CASE WHEN) | 一步完成,无需中间数据集 |
| 单表宽→长(分析集Listing) | Array + OUTPUT | 灵活控制每行输出 |
| SUPPQUAL连接主域 | LEFT JOIN SUPP × N次 | 比TRANSPOSE+MERGE少50%步骤 |
| 两表拼接(如DA+SUPPDA) | LEFT JOIN | TRANSPOSE也可,按需求选 |
代码模板
模板一:单表长→宽(EG参数展开)
ADEG中HR/PR/RR/QRS/QT/QTCF/INTP各占一行 → 变成一行七个变量。推荐MAX(CASE WHEN)一步到位。
SAS
/* 长→宽:一步完成,无需中间数据集 */
proc sql;
create table final as
select
USUBJID,
VISITNUM,
EGTPTNUM,
max(case when PARAMCD = "HR" then AVALC else "" end) as HR,
max(case when PARAMCD = "PR" then AVALC else "" end) as PR,
max(case when PARAMCD = "RR" then AVALC else "" end) as RR,
max(case when PARAMCD = "QRS" then AVALC else "" end) as QRS,
max(case when PARAMCD = "QT" then AVALC else "" end) as QT,
max(case when PARAMCD = "QTCF" then AVALC else "" end) as QTCF,
max(case when PARAMCD = "INTP" then AVALC else "" end) as INTP,
max(case when PARAMCD = "INTP" then EGDESC else "" end) as EGDESC1
from ADAM.ADEG
group by USUBJID, VISITNUM, EGTPTNUM;
quit;
要点:MAX在这里不是求最大值——因为每个(paramcd, usubjid, visitnum)组合只有一行,MAX只是把唯一值从CASE WHEN里"捞"出来。这是一种透视技巧,不是聚合计算。
模板二:SUPPQUAL连接——SQL替代TRANSPOSE+MERGE
DA域有SUPPDA存补充信息(如DAIPBSA、IPNUM)。传统做法先TRANSPOSE再MERGE;用SQL的LEFT JOIN直接拿到各QNAM的值。
SAS
/* SUPPDA → 主域DA:SQL替代TRANSPOSE+MERGE */
data SUPPDA;
set SDTM.SUPPDA;
DASEQ = input(IDVARVAL, best.); /* IDVARVAL转数值用于join */
run;
proc sql;
create table DA1 as
select A.*,
B.QVAL as DAIPBSA,
C.QVAL as IPNUM,
D.QVAL as DACOM,
E.QVAL as IPCNCOM
from SDTM.DA as A
left join SUPPDA(where=(QNAM="DAIPBSA")) B
on A.USUBJID = B.USUBJID and A.DASEQ = B.DASEQ
left join SUPPDA(where=(QNAM="IPNUM")) C
on A.USUBJID = C.USUBJID and A.DASEQ = C.DASEQ
left join SUPPDA(where=(QNAM="DACOM")) D
on A.USUBJID = D.USUBJID and A.DASEQ = D.DASEQ
left join SUPPDA(where=(QNAM="IPCNCOM")) E
on A.USUBJID = E.USUBJID and A.DASEQ = E.DASEQ;
quit;
模板三:宽→长(Array + OUTPUT)
多个分析集flag变量(SAFFL/EFFFL/DLTSFL等)每个是一条记录 → 展开成多行Listing。
SAS
/* 宽→长:分析集flag展开为Listing行 */
data FINAL;
length COL1-COL5 $200;
set ADSL;
COL1 = strip(substr(SPART,1,2)) || '/' || strip(compress(TRT01P));
COL2 = SUBJID;
array _FL{5} $ SAFFL EFFFL DLTSFL PKSFL PDSFL;
array _REASONS{5} $ SAFREAS EFFREAS DLTSREAS PKSREAS PDSREAS;
array _SETS{5} $200 _temporary_ (
"安全分析集" "疗效分析集" "DLT可评价分析集"
"PK分析集" "PD分析集"
);
do I = 1 to dim(_FL);
COL3 = _SETS{I};
if _FL{I} = "N" then do;
COL4 = "是";
COL5 = _REASONS{I};
output; /* 每条flag生成一行 */
end;
end;
drop I;
run;
常见坑
⚠️ 两表拼接仍用MAX(CASE WHEN)?
MAX(CASE WHEN)仅适用单表长转宽。如果数据来自两个不同数据集(如DA + 另一来源),必须用LEFT JOIN。
MAX(CASE WHEN)仅适用单表长转宽。如果数据来自两个不同数据集(如DA + 另一来源),必须用LEFT JOIN。
⚠️ SQL长转宽的效率
前期只保留必要变量(keep需要的列),减少SQL查询的冗余空间。参数越多LEFT JOIN越重,但比TRANSPOSE还是快。
前期只保留必要变量(keep需要的列),减少SQL查询的冗余空间。参数越多LEFT JOIN越重,但比TRANSPOSE还是快。
⚠️ IDVARVAL转数值
SUPPQUAL的IDVARVAL是字符型,JOIN前需
SUPPQUAL的IDVARVAL是字符型,JOIN前需
DASEQ = input(IDVARVAL, best.)转数值匹配主域的SEQ变量。