
第2章 SAS语言与数据管理
SAS系统强大的数据管理能力、计算能力、分析能力依赖于作为其基础的SAS语言。SAS语言是一个专用的数据管理与分析语言,它的数据管理功能类似于数据库语言(如FoxPro),但又添加了一般高级程序设计语言的许多成分(如分支、循环、数组),以及专用于数据管理、统计计算的函
数。SAS系统的数据管理、报表、图形、统计分析等功能都可以用SAS语言程序来调用,只要指定要完成的任务就可以由SAS系统按照预先设计好的程序去进行,所以SAS语言和FoxPro等一样是一种第四代语言。
本章简单介绍SAS语言的基本成分与规则,SAS语言如何用来管理数据,SAS语言作为一个统计计算语言的用法,以及SAS过程使用的初步知识。
§2.1 SAS语言构成
2.1.1 SAS语句
SAS语言程序由数据步和过程步组成。数据步用来生成数据集、计算、整理数据,过程步用来对数据进行分析、报告。SAS语言的基本单位是语句,每个SAS语句一般由一个关键字(如DATA,PROC,INPUT,CARDS,BY)开头,包含SAS名字、特殊字符、运算符等,以分号结束。
SAS关键字是用于SAS语句开头的特殊单词,SAS语句除了赋值、累加、注释、空语句以外都以关键字开头。SAS名字在SAS程序中标识各种SAS成分,如变量、数据集、数据库,等等。SAS名字由1到8个字母、数字、下划线组成,第一个字符必须是字母或下划线。SAS关键字和SAS名字都不分大
小写。
2.1.2 SAS表达式
SAS数据步程序中的计算用表达式完成。表达式把常量、变量、函数调用用运算符、括号连接起来得到一个计算结果。
SAS常量主要有数值型、字符型两种,并且还提供了用于表达日期、时间的数据类型。例如
l 数值型:12,-7.5,2.5E-10
l 字符型:'Beijing',"Li Ming","李明"
l 日期型:'13JUL1998'd
l 时间型:'14:20't
l 日期时间型:'13JUL1998:14:20:32'dt
数值型常数可以用整数、定点实数、科学计数法实数表示。字符型常数为两边用单撇号或两边用双撇号包围的若干字符。日期型常数是在表示日期的字符串后加一个字母d(大小写均可),中间没有空格。时间型常数是在表示时间的字符串后加一个字母t。日期时间型常数在表示日期时间的
字符串后加字母dt。
因为SAS是一种数据处理语言,而实际数据中经常会遇到缺失值,比如没有观测到数值,被访问人不肯回答,等等。SAS中用一个单独的小数点来表示缺失值常量。
SAS变量的基本类型有两种:数值型和字符型。日期、时间等变量存为数值型。SAS的数值型变量可以存储任意整数、定点实数、浮点实数,一般不关心其区别。数值型变量在数据集中的存贮一般使用8个字节。SAS的字符型变量缺省的长度是8个字符,但是如果在INPUT语句中输入字符型变量
时指定了长度则不受此限制。可以用LENGTH语句直接指定变量长度,LENGTH语句一般应出现在变量定义之前,格式为:
LENGTH 变量名 $ 长度;
例如
LENGTH name $ 20;
SAS运算符包括算术、比较、逻辑等运算符。
算术运算符为 + - * / **,运算优先级按通常的优先规则。
比较运算符用于比较常量、变量的值大小、相等,包括
= ^= > < >= <= IN
EQ NE GT LT GE LE
其中EQ等名字和=等特殊字符是同一运算符的等价写法。比较运算符得到"真"或"假"的结果,主要用于需要条件的分支、循环等语句中。运算符IN是一个SAS特有的比较运算符,用来检查某个变量的取值是否在一个给定列表中,比如
prov in ('Beijing', 'Tianjin', 'Shanghai', 'Chongqing')
可以判断变量prov的取值是否为四个直辖市之一。
逻肌?nbsp;
OTHERWISE 语句;
END;
这种SELECT语句没有选择表达式,而是在每一个WHEN语句指定一个条件(逻辑表达式),执行第一个满足条件的WHEN后的语句。如果所有条件都不满足则执行OTHERWISE后的语句。例如:
SELECT;
WHEN(age<=12) put '少年';
WHEN(age<35) put '青年';
OTHERWISE put '中老年';
END;
注意上例中第二个WHEN语句的条件等价于age>12 and age<35,因为如果年龄小于等于12的话则会执行第一个WHEN语句,然后退出SELECT结构,根本不会判断第二个条件。这与其它语言中的IF-ELSEIF-ELSE结构的用法是一致的。
2.2.4 循环结构
SAS数据步可以使用丰富的循环结构,主要的是两种:计数DO循环和当型、直到型循环。
计数DO循环的写法是:
DO 计数变量 = 起始值 TO 结束值 BY 步长;
循环体语句……
END;
在DO和END之间可以有多个语句。程序先把计数变量赋值为起始值,如果此值小于等于结束值则执行循环体语句,然后把计数变量加上步长,再判断它是否小于等于结束值,如果是则继续执行循环体,直到计数变量的值大于结束值为止。上述结构中"BY
步长"可以省略,这时步长为1。如果步长取负值,则继续循环的条件是计数变量大于等于结束值。例如:
data;
DO i = 1 TO 20 BY 2;
j = i**3;
put i 3. j 5.;
END;
run;
可以输出一个1,3,5,7,…,19的立方表。
在循环体中可以用LEAVE语句跳出循环,相当于C语言的break语句。例如在上例中的循环体最后加上这样一句可以在立方大于1000时停止循环:
if j>1000 then LEAVE;
在循环体内用CONTINUE语句可以立即结束本轮循环并转入下一轮循环的判断与执行。比如:
data;
do x=0 to 3.1415926 by 0.01;
y = sin(x);
if y<0 then CONTINUE;
z = cos(x);
put x 5.2 y 10.7 z 10.7;
end;
run;
这个程序对0到 之间的数每隔0.01计算正弦值,如果正弦值为负则考虑下一个值,正弦值非负时计算余弦值并显示。
当型循环的语法是:
DO WHILE(循环继续条件);
循环体语句……
END;
程序先判断循环继续条件是否成立,成立时执行循环体语句,再判断循环继续条件,如此重复,直到循环继续条件不再成立。例如,下面的程序判断1333333是不是素数:
data;
x=1333333;
i=3;
DO WHILE (mod(x,i) ^= 0);
i=i+2;
END;
if i<x then put x '不是素数';
else put x '是素数';
run;
其中mod(x,i)表示x除以i的余数。
直到型循环的写法是:
DO UNTIL (循环退出条件);
循环体语句……
END;
程序先执行循环体,然后判断循环退出条件是否成立,成立则结束循环,否则继续。注意每轮循环都是先执行循环体再判断是否退出。例如:
data;
n=0;
do until (n>=5);
n+1;
put n=;
end;
run;
可以依次输出n=1,2,3,4,5,当n=5时退出条件"n>=5"满足,循环结束。上例中语句n+1是一种特殊的写法,叫做累加语句,等价于n=n+1。
事实上,SAS的循环语句比上面所述还要灵活得多,它在DO语句中可以指定一个循环列表,比如:
data;
do i=3,7, 11 to 17 by 3 while (i**2<200);
j=i**2;
put i j;
end;
run;
循环变量i取5,7,11,14循环体被执行,当i取17时i的平方为289故循环体不被执行,循环结束。注意WHILE条件只作用于用逗号隔开的最后一项。
2.2.5 数组
SAS可以把一组同为数值型或同为字符型的变量合在一起,使用同一个名字称呼,用下标来区分。这与通常的程序设计语言中的数组略有区别,通常的程序设计语言中数组元素没有对应的变量名,而SAS数组每个元素都有自己的变量名。
一、数值型数组
定义数值型数组的格式为:
ARRAY 数组名(维数说明) 数组元素名列表 (初始值表);
例如:
ARRAY tests(3) math chinese english (0, 0, 0);
数组名是一个合法的SAS名字且不能与同一数据步中的变量重名。对一维数组,维数说明只要说明元素个数,这时下标从1开始。数组元素名列表列出这个数组的各个元素实际代表的变量名,各变量名以空格分隔。比如,上例中tests(1)代表数学成绩,tests(2)代表语文成绩,tests(3)代表
英语成绩。初始值表给各数组元素赋初值,按顺序对应。
数组说明中初始值表可以省略,这时其初始值为相应数组元素的值(如果其数组元素还没有值则初值为缺失值)。
数组说明中的数组元素名列表可以省略,这时其元素也有对应的变量名,变量名为数组名后附加序号,比如:
ARRAY x(3);
中数组x的各元素名为x1,x2,x3。
也可以在说明维数时用"下标下界:下标上界"来说明一个其它的下标下界,如
ARRAY sales(95:97) yr95-yr97 ;
这时sales(95)为yr95,sales(96)为yr96,sales(97)为yr97。上面的变量名列表是一种特殊的语法,在用到变量名列表时如果连续写几个前面字母相同,后面是连续的序号的变量,只要写出第一个和最后一个,中间用减号连接。
一维数组的维数说明还可以是一个星号,这时数组大小由提供的元素列表中的变量个数决定,如上面的数组tests可以等价地说明为:
ARRAY tests(*) math chinese english (0, 0, 0);
可以用函数DIM(数组名)来获得数组的长度。
可以定义二维数值型数组,只要在维数说明中指定用逗号分开的两个下标界说明,例如:
array table(2,2) x11 x12 x21 x22;
说明table(1,1)为x11,table(1,2)为x12,table(2,1)为x21,table(2,2)为x22。二维数组元素按行排列。
二、字符型数组
定义字符型数组的语法略复杂,它需要加一个$符来说明数组元素类型为字符型,并且要说明每一元素所能存储的字符串的最大长度。说明格式如下:
ARRAY 数组名(维数说明) $ 元素长度说明 数组元素名列表 (初始值表);
例如:
ARRAY names(3) $ 10 child father mother;
字符型数组其它方面用法与数值型相同。
三、临时数组
上面格式说明的数组都是把若干个变量集合在一起使用同一个数组名称呼,每个数组元素是一个独立的变量。SAS也提供了与其它程序设计语言相同的数组,即数组元素只由数组名和序号决定,没有对应的变量名。这种数组叫住?0。下一个INPUT语句从数据行中读入下一个观测,把变量X、
Y赋值100、200。读取位置由运行时设置的一个数据指针指示。然后计算变量Z的值得300。于是PUT语句输出的X、Y、Z值分别为100、200、300。然后,运行控制跳过CARDS语句到空语句,到数据步结尾,把第二号观测输出到数据集,再返回到数据步开头,把变量值赋初值为缺失值,所以第
一个PUT语句输出的三个变量值为缺失值。然后运行到INPUT语句,应该读入下一个观测,但是查询数据指针发现已经读完了所有数据,所以本数据步结束,并把两个观测写入数据集WORK.A中。提交PROC PRINT;RUN;就可以显示此数据集的内容如下:
OBS X Y Z
1 10 20 30
2 100 200 300
从这个例子可以看出SAS数据步程序和普通程序的一个重大区别:SAS数据步如果有数据输入,比如用INPUT、SET、MERGE、UPDATE、MODIFY等语句读入数据,则数据步中隐含了一个循环,即数据步程序执行到最后一个语句后,会返回到数据步内的第一个可执行语句开始继续执校钡蕉寥?nbsp;
数据语句(INPUT、SET、MERGE、UPDATE、MODIFY等)读入了数据结束标志为止才停止执行数据步,并把读入的各个观测写入在DATA语句中指定的数据集。如果没有数据输入而只是直接计算,则数据步程序不需要此隐含循环。数据步因为有这样一个隐含循环,所以也提供了用来查询某一步
是第几次循环的特殊变量 _N_,它的值为数据步循环计数值。
数据步流程见图 1。
图 1 数据步流程图
2.3.2 用INPUT语句输入数据
在数据步中输入数据可以从原始数据输入,也可以从已有数据集输入。从原始数据输入要使用INPUT语句来指定输入的变量和格式。数据行写在CARDS语句和一个只有一个分号的行之间。
最简单的INPUT语句使用自由格式:按顺序列出每个观测的各个变量名,中间用空格分开。变量如果是字符型的需要在变量名后面加一个$符号,$符与变量名可以直接相连也可以隔一个空格。例如:
data c9501;
input name $ sex $ math chinese;
cards;
李明 男 92 98
张红艺 女 89 106
王思明 男 86 90
张聪 男 98 109
刘颍 女 80 110
;
run;
注意这个例子的数据有五个观测,四个变量,每行数据的各变量之间用空格分隔。为输入这些数据,INPUT语句中依次列出了四个变量名,并在字符型变量NAME和SEX后加了$符。要生成一个数据集这是最简单的写法。
使用自由格式也有一些限制条件,如果不满足这些条件时需要改用其它输入格式:
l 数据每行为一个观测,各数据值之间用空格或制表符分隔
l 无论是字符型还是数值型缺失数据都必须用小数点表示
l 字符型数据长度不能超过8个字符,不允许完全是空白,中间不允许有空白,开头和结尾如果有空白将被忽略
l 在INPUT语句中必须列出观测中的每一项数据对应的变量名而不能省略中间的某一个
在满足以上条件时就可以使用自由格式,它也有明显的优点:使用简单;输入数据时不必上下对齐;不需要知道每个变量的具体列数而只需知道它的次序。
如果各数据行的各个数据项是上下对齐的,还可以使用INPUT语句的列方式。这时,除了在INPUT关键字后面列出变量名外,还需要在每个变量名(及$符)后面列出该变量在数据行中所占据的列起始位置与结束位置,比如上面的例子可以改写成:
data c9501;
input name $ 1-10 sex $ 11-13 math 14-16 chinese 18-20;
cards;
李明 男 92 98
张红艺 女 89 106
王思明 男 86 90
张聪 男 98 109
刘颍 女 80 110
;
run;
使用列方式时一定要正确数出每一项所占的位置。列方式有如下特点:
l 要求数据行各项上下对齐
l 各项之间可以没有任何分隔,连续写在一起
l 字符型数据长度可以超过8个字符,中间可以有空格,头尾的空格仍将被忽略。
l 不论字符型变量还是数值型变量如果指定列位置都是空白则输入值为缺失值。小数点仍表示数值型和字符型变量的缺失值。
l 可以只输入数据行中的某些项而忽略其它项。
列方式不要求数据项之间分开,所以经常用来输入紧缩格式的数据。比如,我们要输入一批身份证号码,但只输入其中的出生年、月、日信息,就可以用如下程序:
data pids;
input year 7-8 mon 9-10 day 11-12;
cards;
110103751209223
110101690215005
;
run;
列格式可以与自由格式混用,见1.1.3的例子。
如果需要完全原样地输入字符型数据(包括头尾空格、单独的小数点),可以用有格式输入,即在字符型变量名和$符后加上一个输入格式如CHAR10.表示读入10个字符。
有特殊格式的数据枰糜懈袷绞淙耄丛诒淞棵蠹痈袷矫F渲凶畛<氖怯美词淙肴掌凇J葜械娜掌谛捶ňJ嵌嘀侄嘌模热?998年10月9日可以写成"1998-10-9","19981009","9/10/98"等等,为读入这样的日期数据就需要为它指定特殊的日期输入格式。另外,日期数据在S
AS中是按数值存储的,所以如果要显示日期值,也需要为它指定特殊的日期输出格式。例如:
data a;
input date yymmdd8. sales;
format date yymmdd10.;
cards;
56-6-13 1100
67.12.15 1200
78 10 2 1300
891001 1400
19960101 1500
20020901 1600
;
run;
proc print;run;
其中日期数据占据8列位置,如果不满8列要用空格补充,不能让后面的数据进入这8列。这样可以输入没有世纪数,年、月、日之间用减号、小数点、空格分隔的日期,可以输入YYMMDD格式的六位数的日期(一位数的月、日前面补0),可以输入带世纪数的YYYYMMDD格式的日期(一位数的月
、日前面补0)。FORMAT语句规定输出日期变量时使用的显示格式。结果为:
1 1956-06-13 1100
2 1967-07-11 1200
3 1978-10-02 1300
4 1989-10-01 1400
5 1996-01-01 1500
程序语句对生成的数据集进行修改。比如,我们把超过100分的语文成绩都改为100分,就可以用如下程序:
data c9501a;
set c9501;
if chinese>100 then chinese=100;
run;
当然,这种修改也可以在读入原始数据的数据步中使用而不限于使用SET的数据步。也可以生成新的变量。
在数据步中可以用KEEP语句或DROP语句指定要保留的变量或要丢弃的变量。比如,
data c9501b;
set c9501;
keep name avg;
run;
生成的数据集C9501B只包含NAME和AVG两个变量。用KEEP语句指定要保留的变量。用DROP语句指定要丢弃的变量,比如上例中的KEEP语句可以换成:
drop sex math chinese;
用这种方法可以取出数据集的一部分列组成的子集。
也可以指定一个条件取出数据集的某些行组成的子集。比如,我们希望取出数学分数90分以上,语文分数100分以上的学生的观测,可以用如下的"子集IF语句":
data c9501c;
set c9501;
IF math>=90 and chinese>=100;
run;
注意子集IF语句不同于我们前面所讲的分支语句,它没有THEN部分,只有条件,用于取出满足条件的行子集。
2.3.5 用SET和OUTPUT语句拆分数据集
有时我们需要根据某一分类原则把数据行分别存放到不同的数据集。比如,我们希望把数据集C9501中的所有男生的观测放到数据集C9501M中,把所有女生的观测放到C9501F中,可以使用如下程序:
data c9501m c9501f;
set c9501;
select(sex);
when('男') output c9501m;
when('女') output c9501f;
otherwise put sex= '有错';
end;
drop sex;
run;
proc print data=c9501m;run;
proc print data=c9501f;run;
这个程序中有两个地方需要注意:在DATA语句中,我们指定了两个数据集名,这表示要生成两个数据集。程序中用SET语句引入了一个数据集,这个数据集的观测如何分配到两个结果数据集中呢?关键在于OUTPUT语句。OUTPUT语句是一个可执行语句,它命令把当前观测写到语句指定的数据
集中。这样,我们根据SELECT的结果把不同性别分别放到了两个不同数据集中。
OUTPUT语句还可以用来强行写入数据集而不必象我们在数据步流程图中说明的那样等到数据步最后一个语句完成。数据步中有了OUTPUT语句后数据步流程中不再有自动写入观测的操作,而只能由OUTPUT语句指定输出。不指定数据集名的OUTPUT语句输出到第一个结果数据集。比如下面的程序
生成一个包含1到10的及其平方的有10个观测的数据集:
data sq;
do i=1 to 10;
j=i*i;
output;
end;
run;
proc print;run;
如果删去上面的OUTPUT语句则结果数据集中只有i=11,j=100的一个观测。
2.3.6 数据集的纵向合并
Classes
几个结构相同的数据集可以上下地连接到一起。比如,我们有四个班的学生情况的数据集Class1-Class4,每个数据集包含一个班学生的学号、姓名、性别信息,我们希望把这些数据集合并为一个大数据集,可以用如下代码:
data classes;
set class1 class2 class3 class4;
run;
可见,要把若干个结构相同的数据集合并为一个数据集,只要在DATA语句中指定要生成的大数据集的名字,然后在数据步中使用SET语句并在SET语句中依次列出各小数据集。
有时我们需要在合并数据集时加入一个变量来指示每一个观测原来来自哪一个小数据集,这可以在SET语句的每一个数据集名后面加一个括号,里面写上IN=变量名,变量名所给的变量取1表示观测来自此数据集,取0表示观测非来自此数据集。例如,在2.3.5中我们把C9501数据集按男、女拆
分成了C9501M和C9501F两个数据集并抛弃了性别变量,就可以用如下程序连接两个数据集并恢复性别信息:
data new;
set c9501m(in=male) c9501f(in=female);
if male=1 then sex='男';
if female=1 then sex='女';
run;
在数据步中,如果观测来自C9501M,则变量MALE值为1,如果观测来自C9501F则变量FEMALE值为1,可以使用这两个变量的值定义新变量SEX。用数据集选项的IN=指定的变量不能直接进入结果数据集而只能用于数据步程序中。
2.3.7 数据集的横向合并
两个(或多个)数据集如果包含了同样的一些观测的不同属性(变量),比如,数据集C9501U包含学生的姓名、性别,数据集C9501V包含学生的数学成绩,数据集C9501W包含学生的语文成绩,且各数据集的观测是按顺序一一对应的,就可以用如下带有MERGE语句的数据步把它们左右横向合
并到一个数据集NEW:
data new;
merge c9501u c9501v c9501w;
run;
这样虽然可以横向合并数据集,但是如果各数据集的观测顺序并不一样,就会把不同人的成绩合并到一起。所以横向合并一般应该采用按关键字合并的办法,即先把每个数据集按照相同的、能唯一区分各观测的一个(或几个)变量排序,然后用BY语句和MERGE语句联合使用,这样即使原来
观测顺序不一致也可以保证横向合并的结果没有错。下例先把C9501数据集横向拆分为包含姓名、性别的数据集C9501X和包含姓名、数学成绩、语文成绩的数据集C9501Y,然后按关键字横向合并:
data c9501x;
set c9501;
keep name sex;
run;
data c9501y;
set c9501;
keep name math chinese;
run;
proc sort data=c9501x;
by name;
run;
proc sort data=c9501y;
by name;
run;
data new;
merge c9501x c9501y;
by name;
run;
proc print;run;
其中的PROC SORT是排序过程,用来把数据集按照某个变量的次序排序(这里是按变量NAME的次序排列,用BY语句指定排序的变量名)。
2.3.8 用UPDATE语句更新数据集
如果我们发现数据集中的某些数据值有错误或者现在的值已经改变了,我们可以从更正了的原始数据重新生成数据集,或者使用更有效的方法,即建立一个只包含新数据值的数据集,用此数据集修改原数据集。使用如下的DATA步中可以实现数据集的更新:
DATA 新数据集名;
UPDATE 原数据集 更新用数据集;
BY 关键变量;
RUN;
例如,比如我们发现数据集C9501中王思明的语文成绩实际应该是91分,张红艺性别应为男,可以先生成如下的只包含更正数据值的数据集,不需要改的观测不列入,不需要改的变量不列入或取缺失值:
data upd;
input name $ sex $ chinese;
cards;
张红艺 男 .
王思明 . 91
;
run;
然后,把原数据集C9501和更新用数据集UPD均按姓名(NAME)排序:
proc sort data=c9501;
by name;
run;
proc sort data=upd;
by name;
run;
最后用UPDATE和BY更新得到新数据集NEW,其中王思明的语文成绩改成了91分,张红艺性别改成了男。
data new;
update c9501 upd;
by name;
run;
proc print;run;
2.3.9 用PROC SQL管理数据
SAS系统首先是一个数据管理系统,因此它除了可以用SAS语言程序管理SAS数据库、数据集外,还提供了其它大型数据库管理系统(如Oracle、Sybase)通用的SQL语言功能。在SAS系统中SQL语言实现在SQL过程中。SAS的SQL过程可以从一个或多个表中查询信息,生成表,向表中插入行,更
新表的内容,对表进行纵向合并、横向连接等等。SQL语言可以实现极其复杂的数据管理功能,在这里我们只对它的查询功能作简单介绍,感兴趣的读者可以自己阅读一些数据库管理方面的书籍。
用PROC SQL作查询的最简单的用法如下:
PROC SQL;
SELECT 第一项,第二项,…,第n项
FROM 数据集
WHERE 观测选择条件;
RUN;
其中SELECT是一个语句,FROM和WHERE叫做子句,注意语句是在最后结尾的,中间没有分号。SELECT子句中指定的各项一般为变量名,中间用逗号分隔(注意不是用空格分隔)。FROM子句指定要从哪个数据集查询。WHERE子句指定选择观测的条件。所以,SELECT语句可以很方便地从一个表查
询一个子集,并可以自动输出到输出窗口而不需再使用PROC PRINT。例如,下面的程序显示语文成绩在100分以上(包含)的学生的姓名和数学成绩:
proc sql;
select name, math
from c9501
where chinese>=100;
run;
结果显示
NAME MATH
--------------------
张红艺 89
张聪 98
刘颍 80
在SELECT语句中还可以加入ORDER BY子句,可以为查询结果排序。比如,下程序
proc sql;
select name, math
from c9501
where chinese>=100
order by math desc;
run;
结果为
NAME MATH
--------------------
张聪 98
张红艺 89
刘颍 80
SELECT的强大查询功能还表现在它可以从几个表联合查询。比如,考虑2.3.7中的C9501X和C9501Y,我们要从这两个数据集查询与从C9501一个数据集同样的结果,可以用此程序:
proc sql;
select c9501x.name, math
from c9501x, c9501y
where c9501x.name=c9501y.name
and chinese>=100
order by math desc;
run;
其中连接两个数据集的办法是在WHERE子句指定C9501X.NAME=C9501Y.NAME这样的连接条件。在SELECT中指定变量时如果有两个数据集中共有的变量要用C9501X.NAME这样的带有表名(数据集名)的形式。
连接的两个表有时是同一个表。比如,我们有几个学生的姓名和生日,希望找出那些有相同生日的人。可以用如下的SQL过程:
title '找出生日相同的人';
data class;
input name $ 1-8 birth yymmdd10.;
format birth yymmdd10.;
label name='姓名' birth='生日';
cards;
李明 78-6-1
王思明 78-5-19
张聪 78-6-1
刘颖 78-10-18
张红艺 78-5-19
;
proc sql;
select name, birth
from class a
where birth in select birth
from class b
where b.name ^= a.name
order by a.birth;
run;
结果如下:
找出生日相同的人 21
姓名 生日
--------------------
王思明 1978-05-19
张红艺 1978-05-19
张聪 1978-06-01
李明 1978-06-01
如果我们还希望把查询的结果存入一个数据集,可以在上面的第一个SELECT语句前面加上CREATE TABLE 表名 AS:
proc sql;
CREATE TABLE bsame AS
select name, birth
from class a
…………………
run;
proc print data=bsame label;
id name;
by birth;
run;
结果如下:
找出生日相同的人 22
------------------------------ 生日=1978-05-19 -------------------------------
姓名
王思明
张红艺
------------------------------ 生日=1978-06-01 -------------------------------
姓名
张聪
李明
如果不用SQL过程想得到同样的结果,可以使用如下数据步和过程步:
proc freq data=class noprint;
tables birth / out=bfreq;
run;
proc sort data=class;
by birth;
proc sort data=bfreq;
by birth;
data bsame;
merge class bfreq;
by birth;
if count>1;
run;
proc print data=bsame label noobs;
var name;
by birth;
run;
练习
1.用SAS数据步列出10000以下的素数,写出程序。
2.生成t分布的双侧分位数表。水平取0.001,0.002,0.005,0.01,0.02,0.05,0.10,0.20,自由度取1-100,分位数精确到小数点后3位。表格应为行、列对齐的形式,并有列标题。写出生成这样的表格并存放到一个文本文件中的SAS程序。
3.写出计算从自己生日到2000年初经过的天数的程序。
4.下表为某邮购服务部的部分顾客记录:
姓名 性别 地区 日期 金额
章文 男 华东 1996-3-20 1099
王国铭 男 华东 1996-5-19 39
童子敏 女 华北 1996-1-5 986
刘念新 男 东北 1997-10-1 3581
李思今 女 华北 1997-4-4 659
关昭 女 东北 1996-11-5 358
赵霞 女 东北 1998-9-6 2010
(1)用数据步把此数据输入到SAS数据集;
(2)用程序找出男性顾客购买金额超过1000的哪些人;
(3)把数据拆分为包含姓名、性别、地区的一个数据集和包含姓名、日期、金额的一个数据集;
(4)用MERGE和BY合并上一步拆开的两个数据集。