(합격)!!
Website
시험 기본
시험 구조
Performance Based Exam (즉, 문제는 이론 + 코드 기반 실습)
출제 영역
- SQL (35%)
- Macro (35%)
- Advanced Techniques (30%)
1. Accessing Data Using SQL (35%)
- 기본 SELECT: SELECT, FROM, WHERE, ORDER BY
- 새 변수 생성: AS, 계산열, CASE WHEN
- 조인: INNER, LEFT, RIGHT, FULL (COALESCE 함수 포함)
- Set operators: UNION, OUTER UNION, EXCEPT, INTERSECT
- 집계: AVG, COUNT, MAX, MIN, SUM, GROUP BY, HAVING
- 중복 제거: DISTINCT
- 서브쿼리, 인라인 뷰 활용
- SAS 확장 기능:
- Dataset options (KEEP=, DROP=, RENAME=, OBS=)
- Invocation options (INOBS=, OUTOBS=, NOPRINT, NUMBER)
- Functions (SCAN, SUBSTR, LENGTH)
- Dictionary tables (DICTIONARY.COLUMNS 등)
- CALCULATED 키워드
2. Macro Processing (35%)
- 매크로 변수
- 생성: %LET, INTO:, CALL SYMPUTX
- Scope 제어: %GLOBAL, %LOCAL, SYMPUTX 옵션
- 이름 구분자(.) 활용
- 매크로 프로그램
- 정의/호출: %MACRO … %MEND
- 파라미터 유무 호출
- 조건문: %IF-%THEN-%ELSE
- 반복문: %DO-%END
- AUTOCALL facility (재사용 가능한 매크로 저장/호출)
- 매크로 함수
- 기본: %SCAN, %SUBSTR, %UPCASE
- Quoting: %STR, %NRSTR
- Evaluation: %SYSEVALF
- %SYSFUNC → DATA step 함수 호출
- 디버깅
- MLOGIC, MPRINT, SYMBOLGEN
- %PUT 로그 확인
- 데이터 기반 매크로
- && 간접 참조
- Dictionary table 기반 매크로
- 반복적 매크로 호출 생성
3. Advanced Techniques (30%)
- 배열 (Array)
- 문자형/숫자형 배열
- DIM 함수
- Temporary array
- Dataset 값으로 초기화
- Hash Object
- 선언: DECLARE HASH, DECLARE HITER
- 주요 메서드: DEFINEKEY, DEFINEDATA, DEFINEDONE, FIND, ADD, OUTPUT
- Iterator: FIRST, NEXT, LAST, PREV
- 활용: Lookup table, 정렬, 순회
- Utility Procedures
- PROC FORMAT – PICTURE 문 (숫자/날짜 포맷 정의, 옵션: round, default, prefix 등)
- PROC FCMP – 사용자 정의 함수 (단일/다중 인자, 조건문 처리, CMPLIB= 옵션으로 호출)
- 고급 함수
- FINDC, FINDW, COUNT, COUNTC, COUNTW
- LAG
- Regex 함수 (PRX)
- 메타문자: () [] {} * + ? . | ^ $
- 함수: PRXMATCH, PRXPARSE, PRXCHANGE
공식 문제
Question 1
array
Open a new programming window to create ACT01.sas in c:.
Write a SAS program that will:
- Create output data set work.ACT01 using sashelp.pricedata as input.
- Use an array to increase the values of the price1 through price17 variables by 10%.
Run your program and troubleshoot as necessary. When you are finished with the project:
- Ensure that you have saved your program as ACT01.sas in c:.
- From the score.sas program, call the scoreit macro using ACT01 as the parameter: %scoreit(ACT01).
What is the value for Response in the SAS log? ___
Correct Solution: All price values for all price1-through price17 will be increased by 10%. For example, price2 in observation 5 will now be 126.50. Arrays and do loops would be used in the program.
data work.atc01;
set sashelp.pricedata;
array prices {*} price1-price17;
do i = 1 to dim(prices);
prices[i] = prices[i] * 1.10;
end;
drop i;
run;
Question 2
symputx
Open a new programming window to create MAC01.sas in c:.
Write a DATA step that reads only the first observation of the sashelp.cars data set and stores the value of the Make variable in a macro variable named CarMaker.
The macro variable must be defined from within the DATA Step.
Run your program and troubleshoot as necessary.
When you are finished with the project:
- Ensure that you have saved your program as MAC01.sas in c:.
- From the score.sas program, call the scoreit macro using MAC01 as the parameter: %scoreit(MAC01).
What is the value for Response in the SAS log? __
Correct Solution: The CarMaker macro variable will have a value of Acura.
The program will include a symputx routine.
data _null_;
set sashelp.cars;
if _n_ = 1 then call symputx('Carmaker'.Make);
run;
Question 3
proc sql, mean(), group by
Open a new programming window to create SQL01.sas in c:.
Write an SQL query that will:
- Create output data set work.SQL01 using sashelp.cars as input.
- Compute the average MPG_City for each group of Make.
Name the calculated variable AvgCityMPG.
- The output data should have 2 columns, Make and AvgCityMPG.
Run your program and troubleshoot as necessary.
When you are finished with the project:
- Ensure that you have saved your program as SQL01.sas in c:.
- From the score.sas program, call the scoreit macro using SQL01 as the parameter: %scoreit(SQL01).
What is the value for Response in the SAS log? __
Correct Solution: An SQL query with a group by clause will be written.
The AvgCityMPG for MAKE=MINI will be 26.5.
proc sql;
create table work.SQL01 as
select Make, mean(MPG_City) as AvgCityMPG
from sashelp.cars
group by Make;
quit;
Accessing Data Using SQL
기본 SELECT: SELECT, FROM, WHERE, ORDER BY
proc sql;
select id,
name,
salary * 12 as Annual_Salary
from employees
where department = 'HR' and salary > 40000
order by Annual_Salary desc;
quit;
id, name, Annual_Salary만 출력, employees 테이블에서 HR 부서 & 급여 > 40000인 직원만 뽑음, 연봉 높은 사람 순으로 정렬,
FROM employees→ employees 테이블에서 데이터를 불러옴.SELECT id, name salary * 12 as Annual_Salary→ 계산 열(연봉) 생성, AS로 새 이름 부여WHERE department = 'HR' and salary > 40000→ 부서가 HR이고, 월급이 40,000 초과인 직원만 필터링.ORDER BY Annual_Salary desc→ 연봉(Annual_Salary) 기준 내림차순 정렬.
새 변수 생성: AS, 계산열, CASE WHEN
AS(Alias, 별칭); 새 변수를 만들거나, 기존 변수에 별칭(alias)을 부여할 때 사용.
- AS 키워드는 출력 시 컬럼명 변경 + 새 변수 생성 역할.
proc sql;
select id,
salary * 12 as Annual_Salary
from employees;
quit;
- salary * 12 계산 열을 만들고, 새 변수 이름은 Annual_Salary.
- 계산 열 (
Computed Column); 기존 컬럼을 이용해 계산된 새로운 컬럼을 만듦.
- AS와 함께 쓰임.
proc sql;
select id,
weight / (height*height) as BMI
from health;
quit;
weight와 height를 활용해서 BMI라는 계산 열 생성.
특징
- SELECT 문에서만 존재 (원본 데이터셋에는 추가되지 않음).
- 다른 계산열에 재사용 가능 (단, 순서상 앞에서 정의한 계산 열은 뒤에서 사용 가능).
proc sql;
select id,
salary * 12 as Annual_Salary,
calculated Annual_Salary * 0.1 as Tax
from employees;
quit;
- CALCULATED 키워드: 이미 SELECT 안에서 만든 계산 열 재사용.
CASE WHEN; 조건문을 사용하여 새로운 변수를 생성.
- IF THEN ELSE와 비슷한 역할을 SQL에서 수행.
proc sql;
select id,
case
when score >= 90 then 'A'
when score >= 80 then 'B'
when score >= 70 then 'C'
else 'F'
end as Grade
from exam;
quit;
점수를 등급으로 변환하여 새 변수 Grade 생성.
특징:
- case로 시작하고 END로 끝남.
- 반드시 end AS 후 새변수명으로 이름 부여.
- 문자형/숫자형 혼합 불가 (모두 같은 타입이어야 함).
조인: INNER, LEFT, RIGHT, FULL (COALESCE 함수 포함)
INNER JOIN; 두 테이블에 모두 존재하는 공통 키 값만 결과에 포함.
- 매칭되지 않는 행은 제외됨.
proc sql;
select a.id, a.name, b.score
from tableA as a
inner join tableB as b
on a.id = b.id;
quit;
- tableA와 tableB에 동시에 존재하는 id 값만 결과에 출력됨.
LEFT JOIN; 왼쪽 테이블(A)의 모든 행을 결과에 포함.
- 오른쪽 테이블(B)에 매칭되는 값이 없으면 NULL로 표시.
proc sql;
select a.id, a.name, b.score
from tableA as a
left join tableB as b
on a.id = b.id;
quit;
- tableA의 모든 id가 나오고, 매칭 안 되는 경우 score는 NULL.
- 문자형은 공백 ’’
- 숫자형은 결측 .
RIGHT JOIN; 오른쪽 테이블(B)의 모든 행을 결과에 포함.
- 왼쪽(A)에 매칭되는 값이 없으면 NULL.
proc sql;
select a.id, a.name, b.score
from tableA as a
right join tableB as b
on a.id = b.id;
quit;
- tableB의 모든 id가 나오고, 매칭 안 되는 경우 name은 NULL.
- 문자형은 공백 ’’
- 숫자형은 결측 .
FULL JOIN; 두 테이블(A, B) 모두의 모든 행을 결과에 포함.
- 매칭이 없는 경우 NULL 값 채워짐.
proc sql;
select a.id, a.name, b.score
from tableA as a
full join tableB as b
on a.id = b.id;
quit;
- tableA와 tableB의 전체 id가 포함되며, 없는 부분은 NULL.
- 문자형은 공백 ’’
- 숫자형은 결측 .
COALESCE함수; NULL 값을 처리하기 위해 사용. 여러 인수 중 처음으로 NULL이 아닌 값을 반환.
- FULL JOIN 시 특히 자주 사용됨.
proc sql;
select coalesce(a.id, b.id) as id,
a.name,
b.score
from tableA as a
full join tableB as b
on a.id = b.id;
quit;
- a.id가 없으면 b.id를 대신 사용. 즉, 두 테이블을 FULL JOIN 할 때 키를 깔끔하게 하나로 합칠 수 있음.
- a.id, b.id 모두 나타내지 않고 id로만
Set operators: UNION, OUTER UNION, EXCEPT, INTERSECT
| 연산자 | 중복 처리 | 컬럼 매칭 | 특징 |
|---|---|---|---|
| UNION | 중복 제거 | 위치 기반 | 두 SELECT 결과 합침 (DISTINCT 효과, 자동 정렬) |
| UNION ALL | 중복 유지 | 위치 기반 | 두 SELECT 결과 합침 (중복 포함, 빠름) |
| OUTER UNION | 중복 유지 | 위치 기반 | 컬럼 개수가 달라도 합칠 수 있음, 없는 값은 NULL/결측 |
| OUTER UNION CORR | 중복 유지 | 이름 기반 | 컬럼 이름 기준으로 매칭, 없는 값은 NULL/결측 |
| EXCEPT | 중복 제거 | 위치 기반 | 첫 번째 SELECT 결과에서 두 번째 SELECT 결과를 제외 |
| INTERSECT | 중복 제거 | 위치 기반 | 두 SELECT 결과의 공통 행만 반환 |
UNION; 두 쿼리 결과를 합침. (중복은 자동 제거)
- UNION ALL을 쓰면 중복까지 포함.
- 각 SELECT 절의 컬럼 개수, 타입, 순서가 같아야 함.
proc sql;
select id, name from tableA
union
select id, name from tableB;
quit;
- tableA와 tableB의 행을 합치되, 중복은 제거.
OUTER UNION; 두 테이블을 세로 + 가로 방향으로 합치는 개념.
- 공통 변수가 있으면 맞춰서 붙이고, 없는 변수는 NULL로 채움.
- 즉, 변수 구조가 달라도 가능.
proc sql;
select id, name from tableA
outer union corr
select id, score from tableB;
quit;
- CORR 옵션: 같은 이름의 컬럼만 정렬해서 붙이고, 없는 값은 NULL.
EXCEPT; 첫 번째 SELECT 결과에서 두 번째 SELECT 결과를 뺀 것.
- 중복은 제거됨.
proc sql;
select id from tableA
except
select id from tableB;
quit;
- tableA에는 있지만 tableB에는 없는 id만 출력.
INTERSECT; 두 SELECT 결과에서 공통으로 존재하는 행만 반환.
- 중복은 제거됨.
proc sql;
select id from tableA
intersect
select id from tableB;
quit;
- 두 테이블에 모두 존재하는 id만 출력.
집계: AVG, COUNT, MAX, MIN, SUM, GROUP BY, HAVING
- 집계 함수 (Aggregate Functions)
| 함수 | 설명 | 예시 |
|---|---|---|
| AVG( ) | 평균 | avg(salary) |
| COUNT( ) | 개수 (NULL 제외) | count(*) → 전체 행 개수count(var) → var이 NULL 아닌 행 개수 |
| MAX( ) | 최댓값 | max(salary) |
| MIN( ) | 최솟값 | min(salary) |
| SUM( ) | 합계 | sum(salary) |
GROUP BY
- 특정 컬럼을 기준으로 데이터를 그룹화해서 집계 수행.
- SELECT 절에는 그룹 변수 + 집계 함수만 올 수 있음.
proc sql;
select department, avg(salary) as Avg_Salary
from employees
group by department;
quit;
- 부서별 평균 급여 계산.
HAVING
- WHERE와 비슷하지만 집계된 결과에 조건을 걸 때 사용.
- WHERE은 개별 행(row)에 조건, HAVING은 그룹화된 결과에 조건.
proc sql;
select department, avg(salary) as Avg_Salary
from employees
group by department
having avg(salary) > 50000;
quit;
- 평균 급여가 50,000 초과인 부서만 출력.
- WHERE vs HAVING 차이
WHERE: 집계하기 전에 데이터 필터링HAVING: 집계한 후 그룹 결과 필터링
proc sql;
/* WHERE 사용 */
select department, avg(salary) as Avg_Salary
from employees
where salary > 30000
group by department;
/* HAVING 사용 */
select department, avg(salary) as Avg_Salary
from employees
group by department
having avg(salary) > 50000;
quit;
group by 사용 하고 안 하고의 차이
- GROUP BY 없음 → 각 행(row) 그대로 출력
- GROUP BY 있음 → 지정한 그룹별로 요약(평균, 합계 등 집계 함수 결과)만 출력
- GROUP BY 사용하지 않은 경우 (원본 그대로)
| Make | MPG_City |
|---|---|
| Toyota | 20 |
| Toyota | 22 |
| Toyota | 25 |
| Ford | 15 |
| Ford | 18 |
| Honda | 30 |
- GROUP BY 사용한 경우 (Make별 평균)
| Make | AvgCityMPG |
|---|---|
| Ford | 16.5 |
| Honda | 30.0 |
| Toyota | 22.3 |
중복 제거: DISTINCT
- DISTINCT 기본
- SELECT 절에서 사용.
- 결과에서 중복된 행(row)을 제거.
- 모든 컬럼 조합이 동일하면 한 번만 남음.
proc sql;
select distinct department
from employees;
quit;
- 부서 이름(department)이 중복 없이 한 번씩만 출력.
- 여러 컬럼과 함께 사용
- 여러 변수 조합이 동일하면 중복 제거.
proc sql;
select distinct department, job_title
from employees;
quit;
- (department, job_title) 조합이 같은 경우 한 줄만 출력.
- COUNT(DISTINCT …)
- 특정 변수의 고유값 개수 세기.
- SAS PROC SQL은 COUNT(DISTINCT col) 지원.
proc sql;
select count(distinct department) as Dept_N
from employees;
quit;
- department의 고유 개수 출력.
- DISTINCT와 GROUP BY 차이
DISTINCT: 단순히 중복 제거 → 전체 결과를 unique set으로 만듦.GROUP BY: 그룹별 집계 수행 → 집계함수와 함께 사용.
/* DISTINCT */
proc sql;
select distinct department
from employees;
quit;
/* GROUP BY */
proc sql;
select department, avg(salary) as Avg_Sal
from employees
group by department;
quit;
- DISTINCT: 그냥 고유 부서 목록, GROUP BY: 부서별 평균 급여
서브쿼리, 인라인 뷰 활용
서브쿼리 (Subquery)
- 다른 SQL문 안에 포함된 하위 SELECT문. 보통 WHERE, HAVING, SELECT 절에서 많이 사용됨.
단일 값 서브쿼리; 서브쿼리가 하나의 값을 반환.
proc sql;
select id, name, salary
from employees
where salary > (select avg(salary) from employees);
quit;
- 직원 급여가 전체 평균 급여보다 큰 행만 선택.
다중 값 서브쿼리 (IN, EXISTS, NOT EXISTS); 서브쿼리가 여러 행을 반환.
- IN
proc sql;
select id, name
from employees
where department in (select department from departments where region = 'EU');
quit;
유럽(EU) 지역 부서에 속한 직원만 선택.
EXIST
proc sql;
select *
from tableA as a
where exists (
select 1
from tableB as b
where a.id = b.id
);
quit;
- a.id=b.id 조건을 만족하는 행이 tableB에 존재하면 True
상관 서브쿼리 (Correlated Subquery); 서브쿼리가 바깥 쿼리의 컬럼과 연결됨.
proc sql;
select e1.id, e1.salary
from employees e1
where salary > (select avg(e2.salary)
from employees e2
where e1.department = e2.department);
quit;
- 각 부서 평균 급여보다 급여가 높은 직원 선택.
인라인 뷰 (Inline View); FROM 절 안에 들어가는 서브쿼리 (일시적 테이블처럼 사용).
proc sql;
select department, Avg_Sal
from (select department, avg(salary) as Avg_Sal
from employees
group by department) as dept_avg
where Avg_Sal > 50000;
quit;
- 먼저 부서별 평균 급여(dept_avg)를 만든 후, 평균이 50,000 초과인 부서만 선택.
SAS 확장 기능:
Dataset options (KEEP=, DROP=, RENAME=, OBS=)
| 옵션 | 역할 | 예시 |
|---|---|---|
| KEEP= | 지정한 변수만 불러오기/저장 | (keep=id name salary) |
| DROP= | 지정한 변수 제외 | (drop=phone) |
| RENAME= | 변수명 변경 | (rename=(dept=Department)) |
| OBS= | 행 개수 제한 | (obs=10) |
KEEP=; 데이터셋에서 필요한 변수만 선택
- 입력 시: 읽을 때 해당 변수만 불러옴
- 출력 시: 결과 데이터셋에 해당 변수만 남김
data emp_keep;
set employees(keep=id name salary);
run;
- employees에서 id, name, salary만 가져와서 새 데이터셋 생성
DROP=; 데이터셋에서 제외할 변수 지정
- 입력 시: 불필요한 변수는 불러오지 않음
- 출력 시: 결과 데이터셋에서 제외됨
data emp_drop;
set employees(drop=address phone);
run;
- employees에서 address, phone 제외하고 나머지 변수 저장
RENAME=; 변수 이름 변경
data emp_rename;
set employees(rename=(salary=Monthly_Salary dept=Department));
run;
- salary → Monthly_Salary, dept → Department로 변경
OBS=; 읽어올 관측치 개수 제한
- 보통 테스트나 샘플링할 때 사용
data emp_obs;
set employees(obs=10);
run;
- employees에서 앞의 10개 행만 불러옴
- 함께 사용 예시
data emp_final;
set employees(keep=id name salary dept
drop=phone
rename=(dept=Department)
obs=5);
run;
obs=5→ 처음 5개 행만 읽음keep=id name salary dept→ 필요한 변수만 선택drop=phone→ phone 제외 (이미 keep에 없으므로 영향 없음)rename=dept→Department 적용
Invocation options (INOBS=, OUTOBS=, NOPRINT, NUMBER)
| 옵션 | 역할 | 예시 | 특징 |
|---|---|---|---|
| INOBS= | 입력 데이터 행 개수 제한 | proc sql inobs=10; |
데이터 읽을 때 앞에서 10행만 사용 |
| OUTOBS= | 출력 행 개수 제한 | proc sql outobs=5; |
실행은 전체 대상으로 하지만 결과는 5행만 표시 |
| NOPRINT | 결과 출력 생략 | proc sql noprint; |
로그/결과창에 출력 안 됨, 테이블 생성용에 자주 사용 |
| NUMBER | 결과에 행 번호 표시 | proc sql number; |
결과창에서 행 번호 확인 가능 |
INOBS=; 입력 행 제한 (읽을 때 앞에서부터 지정한 행 개수만 사용)
proc sql inobs=10;
select * from employees;
quit;
- employees에서 앞의 10개 행만 읽어서 SQL 실행.
OUTOBS=; 출력 행 제한 (결과로 보여주는 행 개수 제한)
proc sql outobs=5;
select * from employees;
quit;
- employees 전체를 읽지만, 결과는 앞의 5개 행만 출력.
NOPRINT; SQL 실행은 하지만 결과 테이블은 출력하지 않음.
- 보통 CREATE TABLE이나 INSERT INTO 같은 결과 저장용 쿼리에서 사용.
proc sql noprint;
create table emp_summary as
select department, avg(salary) as Avg_Sal
from employees
group by department;
quit;
- 테이블은 생성되지만 로그/출력 창에는 결과 안 뜸.
NUMBER; 결과 출력 시, 각 행 왼쪽에 행 번호를 붙여줌.
proc sql number;
select id, name, salary
from employees;
quit;
- 결과창에 1, 2, 3… 번호가 붙어서 출력됨.
Functions (SCAN, SUBSTR, LENGTH)
| 함수 | 역할 | 예시 | 결과 |
|---|---|---|---|
| SCAN | 구분자로 나눈 n번째 단어 추출 | scan("a,b,c",2,",") |
"b" |
| SUBSTR | 문자열에서 특정 위치 부분 추출/수정 | substr("ABCDEF",2,3) |
"BCD" |
| LENGTH | 문자열 길이 반환 | length("Hello") |
5 |
SCAN; 문자열에서 단어 단위로 추출
scan(string, n <, delimiters>)
- string: 문자 변수
- n: 몇 번째 단어를 추출할지
- delimiters: 구분자 (기본은 공백, , 등 지정 가능)
data _null_;
x = "SAS Advanced Certification";
word1 = scan(x,1); /* SAS */
word2 = scan(x,2); /* Advanced */
word3 = scan(x,3); /* Certification */
put word1= word2= word3=;
run;
시험에서는 이메일 주소에서 @ 앞부분 추출 같은 문제가 자주 나옴.
scan("user@test.com", 1, "@")→ “user”scan("user@test.com", 2, "@")→ “test.com”
SUBSTR; 문자열에서 일정 위치부터 지정 길이만큼 잘라내기
substr(string, start <, length>)
- start: 시작 위치 (1부터 시작)
- length: 잘라낼 문자 수 (생략하면 끝까지)
data _null_;
x = "ABCDEFG";
y1 = substr(x,2,3); /* BCD */
y2 = substr(x,5); /* EFG */
put y1= y2=;
run;
- 주의: SUBSTR 함수는 할당문 좌변에 올 수 있음 → 문자열 일부 수정 가능!
data _null_;
x = "12345";
substr(x,2,2) = "AB"; /* x = 1AB45 */
put x=;
run;
LENGTH; 문자열의 길이(문자 개수) 반환
length(string)
data _null_;
x = "Hello";
len = length(x); /* 5 */
put len=;
run;
- LENGTH는 공백도 포함
- LENGTHN = 문자열 길이, NULL → 0
- LENGTHC = 문자 변수의 메모리 길이 반환 (실제 저장 공간)
Dictionary tables (DICTIONARY.COLUMNS 등)
| Dictionary Table | SASHELP 뷰 | 주요 정보 |
|---|---|---|
| COLUMNS | VCOLUMN | 변수명, 타입, 길이 등 |
| TABLES | VTABLE | 데이터셋 행 수, 생성·수정일 |
| LIBNAMES | VLIBNAM | 라이브러리 경로, 엔진 |
| MEMBERS | VMEMBER | 라이브러리 내 멤버 |
| OPTIONS | VOPTION | 시스템 옵션 |
| MACROS | VMACRO | 매크로 변수 |
| INDEXES | VINDEX | 인덱스 정보 |
- Dictionary Tables
SAS 세션에서 사용할 수 있는 메타데이터 테이블
- 데이터 구조를 미리 확인하기 좋음
라이브러리, 데이터셋, 변수, 인덱스, 포맷, 옵션 등에 대한 정보를 담고 있음
조회할 때는 PROC SQL 사용
특징
- DICTIONARY. 라이브러리로 직접 접근 가능
- 같은 내용을 SASHELP. 뷰로도 조회 가능 (SASHELP.VCOLUMN 등)
- 주요 Dictionary Tables
| 테이블 이름 | 내용 |
|---|---|
| DICTIONARY.COLUMNS | 데이터셋의 변수 정보 (이름, 타입, 길이 등) |
| DICTIONARY.TABLES | 라이브러리 내 테이블 정보 (행 수, 수정일 등) |
| DICTIONARY.LIBNAMES | 현재 할당된 라이브러리 정보 |
| DICTIONARY.MEMBERS | 모든 SAS 라이브러리의 멤버(데이터셋, 뷰 등) |
| DICTIONARY.INDEXES | 인덱스 정보 |
| DICTIONARY.OPTIONS | 현재 세션의 시스템 옵션 값 |
| DICTIONARY.MACROS | 매크로 변수 정보 |
| DICTIONARY.ENGINES | 라이브러리 엔진 정보 |
- 활용 예시
- 컬럼(변수) 정보 조회
proc sql;
select libname, memname, name, type, length
from dictionary.columns
where libname='WORK' and memname='EMPLOYEES';
quit;
- WORK 라이브러리의 EMPLOYEES 데이터셋 변수 정보 조회
| 변수명 | 의미 |
|---|---|
libname |
라이브러리 이름 (예: WORK, SASHELP) |
memname |
데이터셋 이름 |
name |
변수 이름 |
type |
변수 유형 (char 또는 num) |
length |
변수 길이 (문자는 바이트 단위, 숫자는 8이 기본) |
label |
변수 라벨 (있을 경우) |
format |
변수 서식 |
informat |
입력 서식 |
varnum |
변수의 순서 (데이터셋 내 위치) |
- 데이터셋(테이블) 정보 조회
proc sql;
select libname, memname, nobs, crdate, modate
from dictionary.tables
where libname='WORK';
quit;
- WORK 라이브러리에 있는 모든 데이터셋의 행 수, 생성일(create date), 수정일(moddify date) 확인
- 라이브러리 정보 확인
proc sql;
select libname, path, engine
from dictionary.libnames;
quit;
- 라이브러리 위치 및 엔진 확인
Macro Processing
생성: %LET, INTO:, CALL SYMPUTX
| 방법 | 사용 위치 | 생성 범위 | 용도 |
|---|---|---|---|
| %LET | 매크로 언어 직접 | 전역 | 고정 상수/문자 할당 |
| INTO: | PROC SQL | 전역 | SQL 결과 → 매크로 변수 |
| CALL SYMPUTX | DATA step | 기본 전역 (지역도 가능) | 데이터 값 → 매크로 변수 |
%LET; 전역 매크로 변수를 생성
- 간단히 상수 값이나 문자열을 할당할 때 사용
%let today = 2025-09-06;
%put &=today;
- 항상 전역 변수(global)
- 문자/숫자 상관없이 문자열처럼 저장
- 공백 포함 시 따옴표 사용 필요 (%let name=“My Name”;)
INTO:; PROC SQL 안에서 결과를 매크로 변수로 저장
- 단일 값 저장
proc sql noprint;
select avg(salary)
into :avg_sal
from employees;
quit;
%put &=avg_sal;
- 평균 급여를 &avg_sal 매크로 변수에 저장
- 만약 AVG_SAL이 5라면,
- AVG_SAL=5
- 이렇게 결과가 나옴.
- 여러 값 저장
공백 구분:
proc sql noprint;
select name
into :namelist separated by ' '
from employees;
quit;
%put &=namelist;
만약 NAMELIST=Alice Bob Carol라면, Alice Bob Carol 이런 식으로 저장
그래서 결과는 NAMELIST=Alice Bob Carol 이렇게 나옴
여러 개 매크로 변수에 순서대로 할당:
proc sql noprint;
select name
into :name1-:name3
from employees;
quit;
%put &name1 &name2 &name3;
CALL SYMPUTX; DATA step에서 매크로 변수 생성
data _null_;
set employees;
call symputx('empname', name);
run;
%put &=empname;
마지막 관측치의 name 값이 &empname에 저장
행 단위 반복 가능 → 관측치별 매크로 변수 만들 수 있음
SYMPUTX는 공백 제거하고, 자동으로 문자/숫자 처리
SYMPUT보다 SYMPUTX를 쓰는 게 권장됨
다음 코드에서 maxage라는 매크로 변수를 생성하시오.
data _null_;
set sashelp.class end=last;
retain maxage 0;
if Age > maxage then maxage=Age;
if last then call symputx('maxage', maxage);
run;
%put &=maxage;
Scope 제어: %GLOBAL, %LOCAL, SYMPUTX 옵션
| 방법 | 생성 범위 | 특징 |
|---|---|---|
| %GLOBAL | 전역 | 항상 전역 매크로 변수 생성 (세션 전체에서 유효) |
| %LOCAL | 지역 | 매크로 안에서만 사용, 매크로 종료 시 삭제 |
| CALL SYMPUTX | 기본 전역, 옵션으로 제어 | 'G' 전역, 'L' 지역 / 기본은 실행 환경 따라 자동 결정 |
- 매크로 변수 스코프 개념
전역(Global): SAS 세션 내내 유지, 모든 매크로에서 접근 가능지역(Local): 특정 매크로 실행 중에만 존재, 매크로 종료 시 자동 삭제
%GLOBAL
- 전역 매크로 변수를 명시적으로 선언
- 매크로 외부·내부 어디서든 사용 가능
%global gvar;
%let gvar = 100;
%put &=gvar; /* 100 */
- 이미 존재하는 전역 변수면 값은 유지, 없으면 새로 생성
%LOCAL
- 지역 매크로 변수 선언 (해당 매크로 안에서만 유효)
%macro test;
%local lvar;
%let lvar = 200;
%put &=lvar;
%mend;
%test;
%put &=lvar;
- 매크로 종료 시 자동 소멸
CALL SYMPUTX옵션
- DATA step에서 매크로 변수 생성 시 스코프 제어 가능
%macro demo;
data _null_;
x = 300;
call symputx('mvar', x, 'L'); /* Local */
run;
%put Local var = &mvar;
%mend;
%demo;
- 세 번째 인수(스코프 지정):
- ‘G’ → 전역(Global) 매크로 변수 생성
- ‘L’ → 지역(Local) 매크로 변수 생성
- 생략 시:
- 매크로 실행 중이면 지역(local)
- 아니면 전역(global)
이름 구분자(.) 활용
- 기본 예시
%let var = name;
%put &var1; /* 매크로 변수 var1 찾음 → 정의 안 되어 있으면 경고 */
%put &var.1; /* 매크로 변수 var 찾고, 뒤에 "1" 붙임 → name1 */
- &var1 은 var1이라는 매크로 변수를 찾음.
- &var.1 은 var 매크로 변수의 값(name) + 1 = name1
- 문자열 연결할 때
%let month = Jan;
%put &month.Sales; /* 매크로 변수 monthSalses 찾으려 함 → 에러 */
%put &month..Sales; /* month → Jan + Sales → JanSales */
- &month.Sales 처럼 구분자 붙여야 원하는 결과 나옴.
- 루프에서 자주 쓰임
%macro loop;
%do i=1 %to 3;
%let var&i = value&i;
%put &&var&i; /* && → &var1, &var2 ... */
%end;
%mend;
%loop;
- 이때도 &var.&i 혹은 &var&i 로 처리해야 함.
- .을 붙이면 매크로 변수명과 루프 인덱스 구분이 확실해짐.
매크로 프로그램
정의/호출: %MACRO … %MEND
%macro 매크로이름(매개변수);
/* 매크로 코드 */
%mend 매크로이름;
- %macro ~ %mend 사이에 매크로 프로그램 코드 작성
- 호출할 때는 %매크로이름(인수) 형태로 실행
- 매크로 프로그램 생성과 호출
- 매개변수 없는 매크로
%macro hello;
%put Hello, SAS Macro!;
%mend hello;
%hello;
- %hello; 실행 시 로그에 Hello, SAS Macro! 출력
- 매개변수 있는 매크로
%macro greet(name);
%put Hello, &name!;
%mend greet;
%greet(Name);
- %greet(Name); 실행 시 Hello, Name! 출력
파라미터 유무 호출
- 매개변수의 종류
- 위치 매개변수(Positional parameter)
%macro report(dept, year);
%put Department=&dept, Year=&year;
%mend;
%report(HR, 2025);
- 키워드 매개변수(Keyword parameter); 기본값 지정 가능
%macro report(dept=Sales, year=2025);
%put Department=&dept, Year=&year;
%mend;
%report(); /* Sales, 2025 */
%report(dept=HR, year=2024); /* HR, 2024 */
- 조건문과 반복문
조건문: %IF-%THEN-%ELSE
- 조건문 %IF … %THEN … %ELSE
%macro check(num);
%if &num > 0 %then %put Positive;
%else %put Non-positive;
%mend;
%check(10);
minoperator
- minoperator는 in을 쓸 수 있게 해줌
%macro avgfuel(loc) / minoperator;
%else %if &loc in ASIA EUROPE %then %do;
반복문: %DO-%END
- 반복문 %DO ~ %END
%macro loop(n);
%do i=1 %to &n;
%put Iteration &i;
%end;
%mend;
%loop(3);
- 매크로 프로그램 활용 예시
%macro summary(dataset, var);
proc sql;
select mean(&var), max(&var), min(&var)
from &dataset;
quit;
%mend;
%summary(sashelp.class, height);
- sashelp.class 데이터에서 height 변수의 평균, 최댓값, 최솟값 출력
AUTOCALL facility (재사용 가능한 매크로 저장/호출)
/* AUTOCALL 세팅 */
options mautosource
sasautos = ("c:\cert\programs" "!sasroot\core\sasmacro");
/* c:\cert\programs\greet.sas 파일 내용:
%macro greet(name);
%put Hello, &name!;
%mend;
*/
/* 호출 */
%greet(Max);
Hello, Max!
매크로 함수
기본: %SCAN, %SUBSTR, %UPCASE
| 함수 | 역할 | 예시 | 결과 |
|---|---|---|---|
| %SCAN | 구분자로 n번째 단어 추출 | %scan(red,blue,green,2,%str(,)) |
blue |
| %SUBSTR | 문자열 부분 추출 | %substr(Advanced,1,3) |
Adv |
| %UPCASE | 대문자 변환 | %upcase(London) |
LONDON |
%SCAN; 문자열에서 단어 단위 추출 (매크로 함수 버전)
%let str = red,blue,green;
%put %scan(&str,1,%str(,)); /* red */
%put %scan(&str,2,%str(,)); /* blue */
%put %scan(&str,3,%str(,)); /* green */
- 데이터 함수 SCAN과 유사하지만 매크로 처리 단계에서 실행
- 구분자(delimiters) 지정 가능 (%str(,) 처럼 특수문자 처리 필요)
%SUBSTR; 문자열의 특정 위치 부분 추출
%let word = Advanced;
%put %substr(&word,1,3); /* Adv */
%put %substr(&word,5); /* nced (길이 생략 시 끝까지) */
데이터 함수 SUBSTR와 달리 매크로 함수는 수정 불가, 추출 전용
시작 위치는 1부터 시작
매크로 변수 name=Alexander에서 앞 4글자만 추출하여 매크로 변수 shortname을 만드시오.
%SUBSTR(string, position, length)- 결과: Alex
%let name=Alexander;
%let shortname=%substr(&name,1,4);
%put &=shortname;
%UPCASE; 문자열을 모두 대문자로 변환
%let city = London;
%put %upcase(&city); /* LONDON */
- 데이터 함수 UPCASE와 동일한 기능을 매크로 환경에서 수행
- 시험에서는 주로 비교 시 대소문자 무시 처리 용도로 등장
%LOWCASE; 문자열을 모두 소문자로 변환
%let city = LONDON;
%put %lowcase(&city); /* london */
%CMPCASE; 문자열의 앞글자만 대문자로 변환
%let city = LONDON;
%put %cmpcase(&city); /* London */
Quoting: %STR, %NRSTR
- 매크로 언어에서 특수문자, 공백, 예약어를 “그대로 문자로 취급”하게 해줌.
- SAS는 기본적으로 , ; ’ ” ( ) 같은 문자를 코드 구분자로 해석하려고 하는데, quoting을 쓰면 문자 그대로 저장 가능.
| 함수 | 역할 | 처리 가능한 문자 | 예시 | 결과 |
|---|---|---|---|---|
| %STR | 특수문자/공백을 문자열로 저장 | 공백, , ; ( ) |
%str(New York) |
New York |
| %NRSTR | %, & 같은 매크로 trigger까지 문자로 저장 |
위 + %, & |
%nrstr(%put Hello;) |
%put Hello; (실행 안 됨) |
%STR; 특수문자나 공백을 매크로 상수로 저장할 때 사용.
%let city = %str(New York);
%put &=city; /* New York */
- 공백, 세미콜론(;), 괄호, 쉼표(,) 같은 구분 문자를 문자 그대로 저장
- ) & % 같은 매크로 trigger 문자는 처리 못함
%NRSTR; %STR과 동일하지만, 매크로 trigger 문자(&, %)도 문자 그대로 저장
%let code = %nrstr(%put Hello;);
%put &=code; /* %put Hello; */
- % %put, ¯ovar 같은 것까지 문자 그대로 저장 → 실행 안 됨
- 보통 매크로 디버깅, 코드 조각 저장 등에 쓰임
/* %STR */
%let msg1 = %str(Hello; World);
%put &=msg1; /* Hello; World */
/* %NRSTR */
%let msg2 = %nrstr(%put Hello;);
%put &=msg2; /* %put Hello; (실행 안 됨, 문자열로 저장됨) */
Evaluation: %SYSEVALF
- 매크로 환경에서 산술 계산을 할 때 사용
- 특히 소수점(부동소수점) 계산이 필요할 때 반드시 필요
| 함수 | 특징 | 예시 | 결과 |
|---|---|---|---|
%EVAL |
정수 계산만 가능 | %eval(5/2) |
2 |
%SYSEVALF |
실수 계산 가능 | %sysevalf(5/2) |
2.5 |
%SYSEVALF(...,BOOLEAN) |
조건식 판정 | %sysevalf(5>3,boolean) |
1 |
%put %sysevalf(3.2 + 4.8); /* 8.0 */
%put %sysevalf(10/3); /* 3.33333 */
- 매크로 변수 계산도 가능
%let a = 5;
%let b = 2;
%let result = %sysevalf(&a / &b);
%put &=result; /* 2.5 */
- 옵션
%SYSEVALF(expression <,conversion-type>) - conversion-type: 계산 결과를 어떻게 반환할지 결정
| 옵션 | 설명 | 예시 |
|---|---|---|
| BOOLEAN | 0/1 반환 (조건식 판정) | %sysevalf(5>3,boolean) → 1 |
| CEIL | 올림 | %sysevalf(3.2,ceil) → 4 |
| FLOOR | 내림 | %sysevalf(3.8,floor) → 3 |
| INTEGER | 정수 변환 (소수 버림) | %sysevalf(3.8,integer) → 3 |
%EVAL→ 정수 연산만 가능 (소수점 불가)%SYSEVALF→ 부동소수점 연산 가능
%put %eval(5/2); /* 2 */
%put %sysevalf(5/2); /* 2.5 */
관련 문제
- 모두 매크로 코드로 x초기값1.25로 지정하고 do until이나 do while로 2보다 작은 값에 댜해서만 x log기록 후 0.25씩 늘려가는 매크로
%sysevalf입력!
%macro loopx;
%global x;
%let x = 1.25;
%do %while(&x < 2);
&x = %sysevalf(&x + 0.25);
%end;
%mend loopx;
%loopx
| 구문 | 조건 검사 시점 | 최소 반복 | 반복 조건 | 주요 용도 |
|---|---|---|---|---|
DO WHILE(condition); |
루프 시작 전 | 0회 | 조건이 참이면 계속 | 사전 조건 확인 |
DO UNTIL(condition); |
루프 끝난 후 | 1회 | 조건이 참이 되면 종료 | 최소 1회 실행 보장 |
%SYSFUNC → DATA step 함수 호출
- 매크로 환경에서 DATA step 함수를 호출할 수 있게 해줌
- 즉, 매크로 코드에서도 숫자, 문자 처리 함수를 직접 활용 가능
| 구분 | 예시 | 결과 |
|---|---|---|
| 숫자 함수 | %sysfunc(mean(5,10,15)) |
10 |
| 날짜 함수 | %sysfunc(today(),date9.) |
06SEP2025 |
| 문자 함수 | %sysfunc(upcase(text)) |
TEXT |
| 중첩 사용 | %sysfunc(upcase(%sysfunc(reverse(abc)))) |
CBA |
%sysfunc(function-name <(arguments)> <,format>)
- function-name : DATA step 함수 이름
- arguments : 함수 인자
- format : 결과를 출력할 때 적용할 SAS 포맷(선택 사항)
- 숫자 함수 호출
%put %sysfunc(mean(5,10,15)); /* 10 */
%put %sysfunc(round(3.14159,0.01)); /* 3.14 */
- 날짜 함수 호출
%put %sysfunc(today(),date9.); /* 06SEP2025 */
%put %sysfunc(time(),time8.); /* 14:35:12 같은 형식 */
- 포맷을 뒤에 붙이면 사람이 읽기 쉽게 출력 가능
- 문자 함수 호출
%let text = python;
%put %sysfunc(upcase(&text)); /* PYTHON */
%put %sysfunc(reverse(&text)); /* nohtpyp */
- 고급 사용 – 매크로 변수에 저장
%let curdate = %sysfunc(today(),yymmddn8.);
%put &=curdate; /* 20250906 */
- 오늘 날짜를 YYYYMMDD 형태로 매크로 변수에 저장
macro 변수 저장
- sashelp.class에서 나이가 16인 학생들의 이름을 매크로 변수 student_list에 공백으로 구분해 저장하시오.
proc sql noprint;
select Name into :student_list separated by ' '
from sashelp.class
where Age=16;
quit;
%put &=student_list;
매크로 지우는 코드
%symdel
%symdel &vars;
디버깅
MLOGIC, MPRINT, SYMBOLGEN
| 옵션 | 역할 | 로그에 출력되는 내용 | 예시 |
|---|---|---|---|
| MLOGIC | 매크로 실행 흐름 표시 | 매크로 시작/끝, %IF 조건 평가 |
%if가 TRUE/FALSE로 나오는지 확인 |
| MPRINT | 매크로가 생성한 SAS 코드 표시 | 실제 실행된 DATA/PROC 구문 | 확장된 코드 확인 |
| SYMBOLGEN | 매크로 변수 치환 과정 표시 | &var → 값 |
매크로 변수 값 확인 |
MLOGIC
- 매크로 실행 시, 매크로의 논리 흐름(logic)을 로그에 출력
- 어떤 매크로가 호출됐는지, %IF 조건이 어떻게 평가됐는지 보여줌
options mlogic;
%macro test(x);
%if &x > 10 %then %put Greater than 10;
%else %put 10 or less;
%mend;
%test(5);
- log
MLOGIC(TEST): Beginning execution.
MLOGIC(TEST): %IF condition &x > 10 is FALSE
MLOGIC(TEST): Ending execution.
MPRINT
- 매크로 실행 시, 실제로 실행된 SAS 코드를 로그에 출력
- 매크로가 어떤 DATA/PROC 구문으로 확장(expand)되는지 확인 가능
options mprint;
%macro step;
proc print data=sashelp.class(obs=5);
run;
%mend;
%step;
- log
MPRINT(STEP): proc print data=sashelp.class(obs=5);
MPRINT(STEP): run;
SYMBOLGEN
- 매크로 변수 치환 과정을 로그에 출력
- 어떤 값으로 변환되는지 확인 가능
options symbolgen;
%let name = Name;
%put Hello, &name;
- log
SYMBOLGEN: Macro variable NAME resolves to Name
%PUT 로그 확인
- 매크로 코드에서 메시지를 SAS 로그에 출력
- 주로 디버깅, 매크로 변수 값 확인에 사용
| 사용법 | 설명 | 예시 | 결과 |
|---|---|---|---|
%put &var; |
매크로 변수 값 출력 | %put &city; |
London |
%put &=var; |
변수명=값 출력 | %put &=city; |
CITY=London |
%put NOTE: 등 |
로그 메시지 레벨 지정 | %put NOTE: Hello; |
NOTE: Hello |
%put _all_; |
모든 매크로 변수 표시 | %put _all_; |
전역/자동 변수 목록 |
%let name = Name;
%put Hello, &name;
- log
Hello, Name
- 매크로 변수 값 확인
%let city = London;
%put &=city;
- log
CITY=London
- &=변수명 형태로 쓰면 변수명=값 형태로 깔끔하게 출력됨
- 사용자 정의 메시지
%put NOTE: Macro started!;
%put WARNING: Check input dataset;
%put ERROR: Something went wrong;
- log
NOTE: Macro started!
WARNING: Check input dataset
ERROR: Something went wrong
- 실제 SAS 로그의 NOTE / WARNING / ERROR 스타일로 표시됨
- 시스템 매크로 변수 출력
%put _automatic_; /* 자동 생성된 매크로 변수 */
%put _user_; /* 사용자가 만든 매크로 변수 */
%put _all_; /* 모든 매크로 변수 */
GLOBAL CITY London
AUTOMATIC SYSDATE9 06SEP2025
%put
- % put error:쓰고 log에 print 안 되게 하는 법
%let a=0;
%macro test;
%if &a %then %put ERROR: Something wrong.;
%mend;
%test;
a가 0이면 에러 log안 나오게
USER 키워드는 사용자가 만든
모든 매크로 변수 + global + local를 로그에 보여줌
%let global1=AAA;
%macro demo;
%let local1=BBB;
%global global2;
%let global2=CCC;
%put ---- Inside macro ----;
%put _USER_;
%mend;
%demo
%put ---- After macro ----;
%put _USER_;
- log 결과
GLOBAL GLOBAL1 AAA
GLOBAL GLOBAL2 CCC
LOCAL LOCAL1 BBB
---- After macro ----
GLOBAL GLOBAL1 AAA
GLOBAL GLOBAL2 CCC
%put _ALL_; → 자동변수 + 사용자 변수 + 시스템 변수 전부 다%put _AUTOMATIC_; → 자동으로 제공되는 시스템 매크로 변수만%put _GLOBAL_; → 글로벌 변수만%put _LOCAL_; → 매크로 실행 중일 때만 로컬 변수만
proc sql noprint;
select name
into :namelist separated by ','
into :n1 - :n5
into :avg_h, :avg_w
from sashelp.class
where sex='F';
quit;
- 디버깅 활용
- 보통 MLOGIC, MPRINT, SYMBOLGEN 옵션과 같이 %PUT을 넣어 매크로 변수 값이 제대로 넘어오는지 확인
%macro demo(x);
%put NOTE: Parameter X = &x;
%mend;
%demo(100);
- log
NOTE: Parameter X = 100
put vs putlog
- put
- %LET → 매크로 변수 name 생성 (Name)
- %PUT → 매크로 실행 시점에 로그에 문자열 출력
- 결과 (로그):
%let name=Mike;
%put Hello, &name.;
결과: Hello, Mike
- put log
- DATA step 실행 중, putlog 문이 SAS 로그에 메시지 기록
- “현재 값은” → 문자열 그대로 출력
- x= y= → 변수 이름과 값 함께 출력
- 결과 (로그):
data _null_;
x = 10;
y = 20;
putlog "현재 값은 " x= y=;
run;
결과: 현재 값은 x=10 y=20
데이터 기반 매크로
간접참조&&
매크로 변수 이름을 다른 매크로 변수 값으로부터 동적으로 만들어서 참조하는 것.
&& 조합은 치환 과정에서 단계적으로 풀리면서 최종 매크로 변수로 변환됨.
아래와 같은 매크로 변수들이 있을 때,
var1=Height, var2=Weight, var3=Age. 루프를 이용해 차례대로 변수명을 출력하시오.&&var&i → &var1 → Height, 두 번 풀려서 최종적으로 변수명이 나옴
%let var1=Height;
%let var2=Weight;
%let var3=Age;
%macro show;
%do i=1 %to 3;
%put &&var&i;
%end;
%mend;
%show
Dictionary table 기반 매크로
반복적 매크로 호출 생성
- 간단한 반복문으로 매크로 호출
%macro call_loop;
%do i=1 %to 3;
%put This is loop number &i;
%end;
%mend;
%call_loop;
- log
This is loop number 1
This is loop number 2
This is loop number 3
- 매크로 변수 리스트 기반 반복 호출
%let city1=Seoul;
%let city2=London;
%let city3=Paris;
%macro city_loop;
%do i=1 %to 3;
%put City: &&city&i;
%end;
%mend;
%city_loop;
City: Seoul
City: London
City: Paris
- 여기서 간접참조(&&) 활용됨 → &&city&i → &city1, &city2, &city3
- SQL INTO: 이용해서 매크로 호출 생성
- 데이터셋 값으로 반복 호출할 수도 있음.
proc sql noprint;
select name
into :namelist separated by ' '
from sashelp.class(obs=3);
quit;
%macro run_names;
%let n=%sysfunc(countw(&namelist));
%do i=1 %to &n;
%let one=%scan(&namelist,&i);
%put Student: &one;
%end;
%mend;
%run_names;
Student: Alfred
Student: Alice
Student: Barbara
- 매크로 호출 자체를 반복적으로 생성
- 예를 들어 proc print를 여러 데이터셋에 대해 자동 실행:
%macro print_multi(dsnlist);
%let n=%sysfunc(countw(&dsnlist));
%do i=1 %to &n;
%let one=%scan(&dsnlist,&i);
proc print data=&one(obs=3);
run;
%end;
%mend;
%print_multi(sashelp.class sashelp.cars sashelp.iris);
- 3개 데이터셋 각각에 대해 proc print 실행
오늘 날짜 표현 방법
- 방법 1:
%SYSFUNC + date()- date() 함수 → 현재 날짜 (SAS 일련번호, 1960/01/01 기준)
- date9. 포맷 → 26AUG2025 형태
%let today=%sysfunc(date(), date9.);
%put &=today;
- 방법 2:
%SYSFUNC + today()- today() 함수도 현재 날짜 반환 (date()와 동일)
- 다른 포맷 사용 가능 (yymmdd10. → 2025-08-26)
%let today=%sysfunc(today(), yymmdd10.);
%put &=today;
- 방법 3:
datetime()- 현재 날짜+시간 출력 (26AUG2025:15:30:45)
%let now=%sysfunc(datetime(), datetime20.);
%put &=now;
- 방법 4:
%sysfunc(today(), worddate.)- 결과: 26 August 2025 (가독성 좋은 문자열)
%let today=%sysfunc(today(), worddate.);
%put &=today;
- 방법 5:
%sysfunc(today(), weekdate.)- 결과: Tuesday, August 26, 2025
%let today=%sysfunc(today(), weekdate.);
%put &=today;
- 방법 6: 시스템 매크로 변수 활용
(&sysdate9, &sysdatetime)- SAS 자동 제공 시스템 매크로 변수 사용
- 형식은 고정되어 있음
%put &=sysdate9; /* SYSDATE9=16OCT2025 */
%put &=sysdate; /* SYSDATE=26AUG25 */
%put &=sysdatetime; /* SYSDATETIME=26AUG25:15:30:45 */
| 구문 | 의미 | 결과 예시 |
|---|---|---|
%put &var; |
변수 값만 출력 | London |
%put &=var; |
변수 이름과 값을 함께 출력 | VAR=London |
Advanced Techniques
배열 (Array)
| 항목 | 설명 | 예시 |
|---|---|---|
| 문자형/숫자형 배열 | $ 기호 여부로 문자/숫자형 구분 |
array names[3] $ name1-name3; |
| DIM 함수 | 배열 길이 반환 | dim(scores) |
| Temporary array | 변수와 연결 안 됨, 메모리에만 존재 | array temp[3] _temporary_; |
| Dataset 값 초기화 | 데이터셋 값이나 직접 값으로 초기화 | array nums[5] (10 20 30 40 50); |
문자형/숫자형 배열
숫자형 배열 (기본)
data example;
array scores[3] score1-score3;
input score1-score3;
datalines;
10 20 30
;
run;
- score1, score2, score3가 배열 scores[1], scores[2], scores[3]로 참조 가능
문자형 배열
- array 선언 시 $ 기호로 문자형 선언
data example;
array names[3] $ name1-name3;
input name1 $ name2 $ name3 $;
datalines;
Kim Lee Park
;
run;
- names[1] = Kim, names[2] = Lee, names[3] = Park
DIM 함수
배열의 크기(요소 개수) 반환
data example;
array nums[5] (1 2 3 4 5);
do i=1 to dim(nums);
put nums[i]=;
end;
run;
- DIM(arrayname) → 배열 길이
- LBOUND / HBOUND도 있음 (배열 시작/끝 인덱스)
Temporary array
데이터셋 변수와 연결되지 않고, 프로그램 실행 중에만 존재하는 배열 (메모리에서만 사용)
data temp_array;
array temp[3] _temporary_;
do i=1 to 3;
temp[i] = i*10;
put temp[i]=;
end;
run;
- 실제 데이터셋에는 저장되지 않음
- 반복 계산용, lookup table 용도로 활용
Dataset 값으로 초기화
배열을 데이터셋 값으로 채움 (array + set)
data init_array;
set scores_dataset; /* dataset의 값 읽어오기 */
array scores[3] score1-score3;
do i=1 to dim(scores);
put scores[i]=;
end;
run;
또는 DATA step 안에서 직접 초기화
data init_array;
array nums[5] (10 20 30 40 50);
do i=1 to dim(nums);
put nums[i]=;
end;
run;
시험에서
- 이렇게 하면 7,8,9 이렇게 3개 가져옴
array Farenht [7:9] Temp7-Temp9;
array Celsius [7:9] TempC7-TempC9;
- 또는 이렇게 해서 한 번에 4개 나열
array Status[4] $ 5 StatusQ1-StatusQ4;
- 2행 3열로 나타남
array Avg [2013:2014,3] (40.9, 40.7, 38.6, 42.5, 42.6, 45.4):
N = 1일 때만 작동 → 즉, 한 번의 데이터 스텝 실행 중에 array_data의 두 줄 모두 처리함.
set array_data는 두 번 호출되며 각 줄을 Row=1, Row=2로 저장
배열의 메모리 배치 순서: TwoD[1,1] → TwoD[1,2] → TwoD[1,3] → TwoD[2,1] → TwoD[2,2] → TwoD[2,3]
Row=1 → OneD = Jack, Mary, Sally
- TwoD[1,1] = Jack
- TwoD[1,2] = Mary
- TwoD[1,3] = Sally
Row=2 → OneD = Christy, John, Marty
- TwoD[2,1] = Christy
- TwoD[2,2] = John
- TwoD[2,3] = Marty
data new_data(drop=Row Column);
array TwoD[2,3] $; /* 2행 3열 문자형 배열 선언 */
retain TwoD1-TwoD6; /* 배열 값을 유지 */
if _N_ = 1 then do Row = 1 to 2; /* 최초 실행 시 2개의 행 처리 */
set array_data; /* 한 줄씩 읽음 */
array OneD[3] Name1-Name3; /* Name1~Name3을 1차원 배열로 묶음 */
do Column = 1 to 3;
TwoD[Row, Column] = OneD[Column]; /* 값을 2차원 배열에 저장 */
end;
end;
run;
Hash object
| 구분 | 키워드 / 메서드 | 설명 |
|---|---|---|
| 선언 | declare hash h, declare hiter hi("h") |
해시 객체, 반복자 생성 |
| 주요 메서드 | defineKey, defineData, defineDone, find, add, output |
key–data 구조 정의, 검색/추가/출력 |
| Iterator | first, next, last, prev |
key 순서로 순회 |
| 활용 | Lookup table, 정렬, 순회 | merge 대체, 빠른 검색, 정렬 처리 |
선언: DECLARE HASH, DECLARE HITER
- Hash 객체 생성
declare hash h(dataset:"mydata");
- Iterator 생성 (순회용)
declare hiter hi("h");
- h는 해시 객체 이름, hi는 반복자(iterator)
주요 메서드: DEFINEKEY, DEFINEDATA, DEFINEDONE, FIND, ADD, OUTPUT
| 메서드 | 설명 |
|---|---|
| defineKey | 해시의 key 지정 (중복 불가) |
| defineData | 저장할 데이터 변수 지정 |
| defineDone | 선언 종료 (필수) |
| find | key 값으로 검색 |
| add | 새로운 key–data 쌍 추가 |
| output | 해시 객체 내용을 데이터셋으로 출력 |
if _N_=1 then do;
declare hash h();
h.defineKey("id");
h.defineData("name","age");
h.defineDone();
end;
rc = h.add(); /* 현재 PDV 값으로 추가 */
rc = h.find(); /* key 값으로 검색 */
Iterator: FIRST, NEXT, LAST, PREV
| 메서드 | 설명 |
|---|---|
| first() | 첫 번째 key로 이동 |
| next() | 다음 key로 이동 |
| last() | 마지막 key로 이동 |
| prev() | 이전 key로 이동 |
if _N_=1 then do;
declare hiter hi("h");
end;
rc = hi.first();
do while (rc=0);
put id= name= age=;
rc = hi.next();
end;
hiter는 전체순환이라서 hi.first()처럼 지정해줘야 한다.
활용: Lookup table, 정렬, 순회
- Lookup Table
- 작은 데이터셋을 메모리에 로딩 후 key 기반 검색 (데이터 step merge 대신 고속 검색)
- 정렬(Sort)
- key 순서로 정렬된 순회 가능 (hiter.first(), hiter.next())
- 순회(Iteration)
- 전체 key–data 쌍을 순회하며 처리
hash 데이터셋 output하는 법
-
data example1;
if _n_=1 then do;
declare hash h(dataset:"sashelp.class");
h.defineKey("name"); /* 키 */
h.defineData("age","height","weight"); /* 데이터 */
h.defineDone();
end;
set sashelp.class(keep=name); /* 왼쪽 데이터 */
rc = h.find(); /* 키로 찾기 */
if rc=0 then output; /* 매칭된 경우 출력 */
drop rc;
run;
sashelp.class에서 name이sashelp.class데이터에서 만들어진 hash와 일치하면 output
-
data work.StateCityPopulation;
if _N_=1 then do;
if 0 then set pg3.population.usstates;
declare hash States(dataset:'pg3.population_usstates');
States.definekey('StateName');
States.definedata('Capital', 'StatePop2017');
States.definedone();
end;
set pg3.population uscities;
StateName=stnamel(StateCode):
RC=States. find(key:StateName);
*(해시 객체 States에서 주 이름(StateName)을 키로 검색해서 해당 Capital과 StatePop2017 값을 찾아서 자동으로 변수에 바인딩합니다 성공 시 RC = 0);
if RC ne 0 then call missing(Capital, StatePop2017);
*(해시에서 값을 찾지 못했을 경우, Capital과 StatePop2017 값을 명시적으로 결측 처리합니다.)
PctPop«CityPop2017/StatePop2017;
format StatePop2017 comma14. PctPop percent8.1;
(해당하는 거만 데이터셋 만들어지게 하려면)
if RC = 0 then do; /* 🔍 lookup 성공한 경우에만 */
PctPop = CityPop2017 / StatePop2017;
format StatePop2017 comma14. PctPop percent8.1;
output; /* ✅ 해당 행만 데이터셋에 저장 */
end;
()
run;
hiter
data test;
input id name $ age;
datalines;
1 Alice 14
2 Bob 15
3 Carol 13
;
run;
data _null_;
if _N_ = 1 then do;
declare hash h(dataset:"test");
h.definekey("id");
h.definedata("id","name","age");
h.definedone();
declare hiter hi("h");
end;
rc = hi.first();
do while (rc = 0);
putlog id= name= age=;
rc = hi.next();
end;
run;
hiter는 iterator로, 순회하면서 데이터 검색한다.hi.first()로 첫번째 정해놓고,do while로 rc가 0이 나올때까지 rc 검색
| 구분 | find |
hiter |
|---|---|---|
| 방식 | key 값 직접 지정 | 모든 key 순회 |
| 반환 범위 | 단일 key | 전체 key |
| 사용 목적 | lookup / join | 전체 탐색 / 조건 필터링 |
| output 가능? | 가능 (if rc=0 then output;) |
가능 (output; inside loop) |
Utility Procedures
| 기능 | 문법/옵션 | 설명 | 예시 |
|---|---|---|---|
| PROC FORMAT – PICTURE | round, default=, prefix=, datatype=date |
숫자/날짜 포맷 정의 | $000,000,009, %Y-%0m-%0d |
| PROC FCMP – 함수 | function … endsub; |
사용자 정의 함수 | function myadd(x,y) return(x+y); |
| PROC FCMP 호출 | options cmplib=… |
함수 등록 후 호출 | x=myadd(10,20) |
PROC FORMAT – PICTURE 문 (숫자/날짜 포맷 정의, 옵션: round, default, prefix 등)
- PICTURE 문은 숫자/날짜를 원하는 출력 형태로 사용자 정의 포맷 가능.
- VALUE와 달리 범주(label) 지정이 아니라, 숫자·날짜 표시 스타일을 직접 정의.
| 코드 | 설명 | 예시 값 |
|---|---|---|
| %A | 요일 이름 (전체) | Wednesday |
| %a | 요일 이름 (앞 3글자) | Wed |
| %d | 월 중 일자 (1~31, 한 자리 가능) | 2 |
| %0d | 월 중 일자 (2자리, 0 채움) | 02 |
| %B | 월 이름 (전체) | January |
| %3B | 월 이름 (앞 3글자) | Jan |
| %m | 월 번호 (1~12, 한 자리 가능) | 1 |
| %0m | 월 번호 (2자리, 0 채움) | 01 |
| %Y | 연도 (4자리) | 2019 |
| %y | 연도 (2자리) | 19 |
| %H/%0H | 시 (24시간제, 한 자리 또는 두 자리) | 21 |
| %I/%0I | 시 (12시간제, 한 자리 또는 두 자리) | 9 또는 09 |
| %M/%0M | 분 (한 자리 또는 두 자리) | 13 |
| %S/%0S | 초 (한 자리 또는 두 자리) | 5 또는 05 |
| %p | 오전/오후 표시 (AM/PM) | PM |
- 기본
proc format;
picture format-name
low-high = 'template'
(옵션들);
run;
- template : 출력할 형태 정의 (9은 숫자 자리 표시, 0은 강제로 0 채움)
- round → 반올림
- default=n → 출력 자릿수 기본값 지정
- prefix=‘문자’ → 출력값 앞에 문자 붙임
- 숫자 포맷
proc format;
picture myfmt
low-high = '000,000,009' (prefix='$');
run;
data _null_;
x=12345;
put x=myfmt.;
run;
- 결과
$000,012,345
- 날짜 포맷 예시
proc format;
picture mydate
low-high = '%Y-%0m-%0d' (datatype=date);
run;
data _null_;
x = '06SEP2025'd;
put x=mydate.;
run;
- 결과
2025-09-06
시험에서
proc format;
picture MyDate (default=15)
low-high = '%a-%d-%3B-¿y'
(datatype=date) ;
picture MyTime (default=14)
low-high
'H:%OH M:%OM S:%OS'
(datatype=time) ;
run;
PROC FCMP – 사용자 정의 함수 (단일/다중 인자, 조건문 처리, CMPLIB= 옵션으로 호출)
사용자 정의 함수(Function) 또는 서브루틴 작성 가능.
만든 함수는 CMPLIB= 시스템 옵션으로 등록 후 호출 가능.
기본
proc fcmp outlib=work.funcs.mycat;
function myadd(x,y);
return(x+y);
endsub;
run;
outlib= : 라이브러리.저장데이터셋.카테고리
function : 사용자 정의 함수 선언
endsub; : 함수 종료
다중 인자 + 조건문 처리
proc fcmp outlib=work.funcs.mycat;
function mygrade(score);
if score >= 90 then return('A');
else if score >= 80 then return('B');
else return('F');
endsub;
run;
- CMPLIB= 옵션으로 호출
options cmplib=work.funcs;
data _null_;
x = myadd(10,20);
g = mygrade(85);
put x= g=;
run;
- 결과
x=30 g=B
시험에서
PROC FCMP OUTLIB=libref.table.package;
FUNCTION function-name(arguments) <$> <length>;
...programming statements...
RETURN(expression);
ENDSUB;
RUN;
proc fcmp outlib-pg3. funcs.weather;
function FtoC (TempF) ;
TempC=round ( (TempF-32) *5/9,.01) ;
return (TempC) ;
endsub;
run;
*function불러와야 쓸 수 있음;
options cmplib=pg3.funcs;
- 예시
- 두 수 중 큰 값을 반환하는 함수 mymax를 정의하고 호출하시오.
proc fcmp outlib=work.funcs.math;
function mymax(x,y);
if x>y then return(x);
else return(y);
endsub;
run;
options cmplib=work.funcs;
data ex8;
result=mymax(10,20);
put result=;
run;
고급 함수
FINDC, FINDW, COUNT, COUNTC, COUNTW
| 함수 | 설명 | 예시 | 결과 |
|---|---|---|---|
| FINDC | 문자열에서 특정 문자(집합)의 위치 반환 | findc("abc123","123") |
4 |
| FINDW | 문자열에서 단어 위치 반환 (구분자 기반) | findw("cat dog pig","dog"," ") |
2 |
| COUNT | 특정 문자열이 포함된 횟수 (중첩 포함 가능) | count("banana","ana") |
2 |
| COUNTC | 특정 문자(집합)가 나타나는 횟수 | countc("banana","a") |
3 |
| COUNTW | 단어 개수 (구분자 기준) | countw("cat dog pig") |
3 |
LAG
시계열 데이터에서 이전 관측값 가져오기
lag1, lag2 있는 데이터 두 번 돌리면 아래처럼 이상하게 나타남(원래 데이터 10 20 30 40)
큐를 초기화하거나 lag 대신 retain+by 등으로 대체 고려
data test;
input x;
lag1_x = lag1(x);
lag2_x = lag2(x);
datalines;
10
20
30
40
;
run;
proc print data=test; run;
- 결과
| x | lag1_x | lag2_x |
|---|---|---|
| 10 | . | . |
| 20 | 10 | . |
| 30 | 20 | 10 |
| 40 | 30 | 20 |
Regex 함수 (PRX)
메타문자: () [] {} * + ? . | ^ $
| 패턴 | 설명 |
|---|---|
. |
아무 문자 하나 (줄바꿈 제외) |
\d |
숫자 (digit, 0-9) |
\w |
알파벳/숫자/언더스코어 |
\s |
공백 문자 (space, tab 등) |
^ |
문자열의 시작 |
$ |
문자열의 끝 |
+ |
1개 이상 반복 |
* |
0개 이상 반복 |
? |
0개 또는 1개 |
[abc] |
a, b, 또는 c 중 하나 |
[^abc] |
a, b, c 이외의 문자 |
(abc) |
그룹 |
함수: PRXMATCH, PRXPARSE, PRXCHANGE
| 함수 | 설명 | 예시 | 결과 |
|---|---|---|---|
| PRXPARSE | 정규식 패턴을 컴파일 (패턴 ID 반환) | pattern=prxparse('/cat/'); |
패턴 ID |
| PRXMATCH | 문자열에서 정규식 매칭 위치 반환 | prxmatch('/dog/',"my dog") |
4 |
| PRXCHANGE | 정규식 기반 치환 | prxchange('s/dog/cat/',-1,"dog dog") |
cat cat |
시험에서
prxparse→ 정규식 패턴 컴파일
- prxparse(‘//’) → “숫자 1개 이상” 정규식 생성
- “abc123xyz”에서 숫자는 4번째 위치 → pos=4
data _null_;
retain regex;
if _n_ = 1 then regex = prxparse('/\d+/'); /* 숫자 찾기 */
str = "abc123xyz";
pos = prxmatch(regex, str);
put pos=;
run;
=같이 입력하면 pos = 값으로 출력
prxmatch→ 문자열에 패턴 매칭 (첫 번째 위치)
- “cat”은 “my cat is here”에서 4번째 → pos1=4
- “dog only”에는 없음 → pos2=0
data _null_;
regex = prxparse('/cat/');
str1 = "my cat is here";
str2 = "dog only";
pos1 = prxmatch(regex, str1);
pos2 = prxmatch(regex, str2);
put pos1= pos2=;
run;
prxchange→ 정규식 치환
- 정규식 ()-()-() → 연도-월-일 캡처
- $3/$2/$1 → 일/월/연도로 재배치
- 결과: 17/08/2025
data _null_;
str = "2025-08-17";
/* yyyy-mm-dd → dd/mm/yyyy 변환 */
new = prxchange('s/(\d{4})-(\d{2})-(\d{2})/$3\/$2\/$1/', -1, str);
put new=;
run;
prxsubstr→ 매치 위치와 길이 반환
- 첫 숫자 시작 = 14번째 (pos=14)
- 길이 = 5 (len=5)
- matched=12345
data _null_;
regex = prxparse('/\d+/'); /* 숫자 찾기 */
str = "Order number 12345 confirmed";
call prxsubstr(regex, str, pos, len);
matched = substr(str, pos, len);
put pos= len= matched=;
run;
prxnext→ 여러 매치 순회
- prxnext는 여러 개의 매치를 순차적으로 반환할 때 사용.
data _null_;
regex = prxparse('/\d+/'); /* 숫자 찾기 */
str = "a1 b22 c333";
start = 1;
stop = length(str);
do while (prxnext(regex, start, stop, str, pos, len) > 0);
match = substr(str, pos, len);
put match=;
end;
run;