SAS-高级编程笔记-全-
SAS 高级编程笔记(全)
001:课程概述 🎯
欢迎来到SAS SQL基础课程。我是Peter Scs,是SAS的一名培训师。
在本课程中,你将学习如何使用SQL过程(即PRC SQL)通过结构化查询语言来处理数据。SQL是一种标准化语言,被众多软件产品用于更新、管理和检索数据。无论你是否熟悉SQL语言,你都会发现ProC SQL是一个用于数据分析的有用工具。
当你完成本课程后,你将能够结合其他PRC过程或数据步来使用ProC SQL,或者将其作为它们的替代方案。
我们有很多内容要学习,现在让我们开始吧。
002:SQL概述与入门
在本节课中,我们将要学习结构化查询语言(SQL)的基础知识。我们将探讨SQL是什么,了解其历史背景,并介绍不同的SQL实现。之后,我们将开始使用ProC SQL来探索数据。
什么是SQL?📖
上一节我们介绍了本课程的学习目标,本节中我们来看看SQL的核心定义。
SQL是Structured Query Language(结构化查询语言)的缩写。它是一种专门用于管理和操作关系型数据库的标准编程语言。SQL允许用户执行诸如查询数据、更新记录、插入新数据以及管理数据库结构等任务。
SQL的历史与发展 📜
了解了SQL的基本定义后,我们有必要了解一下它的起源与发展历程。
SQL最初由IBM的研究人员在20世纪70年代开发,当时被称为SEQUEL。其设计初衷是为了管理和处理存储在关系型数据库管理系统(RDBMS)中的数据。由于其强大的功能和清晰的语法,SQL迅速成为行业标准,并被美国国家标准协会(ANSI)和国际标准化组织(ISO)采纳。
不同的SQL实现 💻
SQL虽然是一个标准,但在不同的数据库系统中,其具体实现可能存在细微差别。以下是几种常见的SQL实现:
- Oracle PL/SQL:Oracle数据库使用的过程化语言扩展。
- Microsoft Transact-SQL (T-SQL):Microsoft SQL Server和Azure SQL数据库使用的扩展。
- MySQL:一种流行的开源关系型数据库管理系统使用的SQL。
- PostgreSQL:另一种功能强大的开源对象-关系型数据库系统。
- SAS ProC SQL:SAS软件中用于处理SAS数据集的SQL实现,它允许用户在SAS环境中使用SQL语法。
开始使用ProC SQL探索数据 🔍
在介绍了SQL的背景和不同实现后,现在我们将焦点转向SAS环境,学习如何开始使用ProC SQL。
ProC SQL是SAS中一个强大的过程步,它允许用户将SQL语句直接嵌入到SAS程序中,从而以声明式的方式查询和操作SAS数据集。与传统的SAS数据步相比,ProC SQL有时能提供更简洁、更高效的代码来解决复杂的数据操作问题。
一个基本的ProC SQL查询结构如下:
PROC SQL;
SELECT column1, column2
FROM sas_dataset
WHERE condition;
QUIT;
在这个结构中:
PROC SQL;表示开始SQL过程。SELECT指定要检索的列。FROM指定数据来源,即SAS数据集。WHERE用于设置过滤数据的条件。QUIT;用于结束PROC SQL过程(在某些情况下可省略,但显式使用是好习惯)。
本节课中我们一起学习了SQL的基础概念,包括其定义、历史以及在不同数据库系统中的实现。最后,我们介绍了在SAS环境中使用ProC SQL进行数据探索的基本方法,为后续深入学习具体的SQL查询和操作技巧打下了基础。
003:什么是SQL 🔍
在本节课中,我们将要学习SQL(结构化查询语言)的基本概念,了解它如何用于在关系型数据库中查询和管理数据。

在深入理解SQL之前,我们先回顾一下表的结构。
回想一下,SAS表是包含行和列的结构化表格。行也被称为观测或记录,列也被称为变量或字段。



数据库是一个以易于访问的形式组织起来的大型结构化表的集合。根据组织规模的不同,你可能拥有数十、数百甚至数千张表,以及数千、数百万乃至数十亿行数据。无论你的表集合规模如何,这些表之间很可能存在关联。

SQL提供了一种标准化的语言,用于搜索、分析你的数据并从中获取洞察。

上一节我们介绍了表和数据库的概念,本节中我们来看看一个具体的例子。以下是四个表的集合,每个表都包含行和列:
- 客户表:包含客户信息,如姓名、地址等。
- 交易表:包含客户交易信息,如客户ID、购买日期、商户等。
- 商户表:包含商户信息,如公司名称、地点和联系方式等。
- 银行表:包含银行信息,如银行名称、地点和联系方式。
这些表通过特定的键(通常称为主键)相互关联。主键是标识表中各行的唯一值。
- 客户表中的
Customer ID指向特定的客户信息。 - 交易表中的
Customer ID指向该客户的特定购买记录。交易表中的Customer ID通常被称为外键。 - 商户表中的
Merchant ID指向特定的商户名称。 - 交易表中的
Merchant ID指向客户在某个商户处的特定交易。
基于这些关系,我们可以开始探究数据。例如:
- 我们想找出交易表中所有来自北卡罗来纳州的客户的姓名和地址。
- 我们想计算每个商户或客户的交易数量。
- 我们想知道哪家银行通常拥有最多的交易。
我们需要一种简便的方法来提取数据并找到这些问题的答案。


你可以使用SQL在关系型数据库中对数据进行查询、操作和管理。


SQL诞生于20世纪70年代初,是在关系数据模型被提出之后发明的。自诞生以来,SQL逐渐流行,并在80年代中期通过美国国家标准协会实现了标准化。此后,该标准经历了多次修订,以包含更多功能集。
SAS在90年代通过SQL过程开始实现SQL,遵循了ANSI标准的第一个主要修订版。尽管PROC SQL包含了许多标准,但SAS SQL并非完全符合ANSI标准。


尽管SQL有一套标准,但理解不同的数据库管理系统对标准SQL有不同的实现方式非常重要。
诸如Oracle、Teradata、SQL Server、PostgreSQL等多种数据库管理系统都遵循ANSI标准SQL,然而,不同系统之间可能在关键字、功能和增强特性上略有差异。
虽然存在细微差别,但无论你学习哪种ANSI标准SQL的实现,理解一个系统中的SQL语言都能让你相对无缝地过渡到另一个系统。请务必阅读你所使用的特定DBMS的文档。


本节课中我们一起学习了SQL的基本定义、它在关系型数据库中的作用,以及表之间如何通过主键和外键建立关联。我们还了解了SQL的标准化历史以及不同数据库管理系统在实现上的细微差异。掌握这些核心概念是使用SQL进行高效数据查询和分析的基础。
004:什么是PROC SQL 🛠️
在本节课中,我们将要学习PROC SQL的基础概念,了解它如何将SQL的强大功能与SAS环境相结合,以及它在数据处理流程中的作用。
概述
PROC SQL是SAS对结构化查询语言(SQL)的基础实现。它允许你在SAS程序中使用SQL,并包含了SAS特有的增强功能。你可以将PROC SQL视为SAS与SQL的结合体。SAS的实现让你能在SAS程序中使用SQL,同时还能利用SAS提供的额外功能,例如数据步。
PROC SQL:SAS与SQL的结合

上一节我们介绍了PROC SQL的基本定位,本节中我们来看看它在数据处理流程中的具体角色。
在使用SQL使数据变得有意义和可操作的过程中,需要牢记SAS编程流程。SQL允许你遵循访问结构化表(如SAS表或数据库管理系统表)的基本步骤。然而,SQL无法直接访问非结构化文件,如文本、JSON或CSV文件。这时,你可以使用SAS来访问这些非结构化文件。
以下是使用SQL进行数据处理的主要步骤:
- 探索与理解数据:使用SQL来探索数据,更好地理解其内容,并确定需要添加或更改的部分。
- 准备数据:在理解数据之后,为分析做好准备。
- 分析与报告:一旦数据准备就绪,即可对其进行分析并生成报告。
最后,在SAS环境中使用SQL,使你能够持续访问多种格式的数据,并将结果导出到各种报告和数据格式中。
PROC SQL的输入与输出
了解了PROC SQL的流程后,我们来看看它能处理什么数据,以及能产生什么结果。
与大多数其他SAS过程步一样,PROC SQL可以读取SAS表、SAS视图或数据库管理系统视图,以及数据库管理系统表。

作为输出,默认情况下,一个PROC SQL查询会生成一份报告。然而,一个PROC SQL查询也可以创建以下内容:
- SAS表
- SAS视图或数据库管理系统视图
- 数据库管理系统表
总结

本节课中我们一起学习了PROC SQL的核心概念。我们了解到PROC SQL是SAS中实现SQL功能的过程步,它融合了SQL的标准语法和SAS的增强特性。我们明确了它在数据访问(特别是结构化数据)和完整数据处理流程(从探索、准备到分析报告)中的作用。最后,我们掌握了PROC SQL能够处理的数据源类型以及它能生成的各种结果,包括报告、新数据表和视图。
005:PROC SQL语法 🗂️
在本节课中,我们将要学习SAS中PROC SQL过程步的基本语法结构。我们将了解如何启动和终止SQL过程,以及构成SQL查询的核心语句和子句。
启动与终止SQL过程

PROC SQL过程步以 PROC SQL 语句开始,以 QUIT 语句结束。一个PROC SQL步骤中可以包含多个语句,每个语句定义一个操作并立即执行。该过程步不需要使用 RUN 语句。
PROC SQL;
/* 你的SQL语句放在这里 */
QUIT;
SELECT语句:核心查询工具
上一节我们介绍了如何启动SQL过程,本节中我们来看看最核心的查询工具——SELECT语句。SELECT语句是最常用的SQL语句,通常被称为查询。它从一个或多个表中检索数据,并生成一个显示数据的报告。
与PROC SQL中的许多语句一样,SELECT语句由称为“子句”的构建块组成,并以分号结束。正因为SELECT语句被分解为子句,SQL被描述为一种模块化语言。

SELECT语句的基本结构
一个查询在SELECT子句中至少需要指定两件事。以下是必须包含的部分:
- 必须指定要检索的列名列表,列名之间用逗号分隔。
- 在FROM子句中,必须指定包含这些列的表名。

SELECT column1, column2
FROM table_name;
SELECT语句的可选子句
剩余的SELECT子句是可选的,但你很可能会频繁使用它们。如果存在,子句必须按以下顺序出现:SELECT, FROM, WHERE, GROUP BY, HAVING, ORDER BY。但并非所有子句都必须出现。例如,你可以使用WHERE和ORDER BY子句,而不使用GROUP BY或HAVING子句。
以下是各可选子句的功能简介:
- WHERE子句:使你能够过滤数据行。
- GROUP BY子句:使你能够按组处理数据。
- HAVING子句:与GROUP BY子句配合使用,用于过滤分组后的结果。
- ORDER BY子句:指定查询返回行的顺序。
SELECT column1, SUM(column2)
FROM table_name
WHERE column1 > 100
GROUP BY column1
HAVING SUM(column2) > 500
ORDER BY column1 DESC;

PROC SQL中的其他语句
虽然SELECT语句是最常用的语句,但PROC SQL允许使用其他语句。每个语句都以一个关键字开始,并以分号结束。
在本课程中,你将看到各种其他语句。你也可以查阅SAS文档以获取所有可用语句的列表。

本节课中我们一起学习了PROC SQL的基本语法框架。我们掌握了如何用 PROC SQL 和 QUIT 语句来启动和结束一个SQL会话,并深入了解了SELECT语句的构成,包括其必需的SELECT和FROM子句,以及用于过滤、分组和排序的WHERE、GROUP BY、HAVING和ORDER BY等可选子句。理解这些基本结构是编写有效SQL查询的第一步。
006:探索表
在本节课中,我们将学习如何使用Proc SQL来探索数据表。具体来说,我们将了解如何查看表的列属性以及如何预览表中的数据行。
查看表结构
上一节我们介绍了Proc SQL的基本概念,本节中我们来看看如何查看一个表的结构。假设你是一名正在学习Proc SQL的新分析师,你的第一个任务是探索客户表。你需要查看该表的列属性。

为此,你需要运行一个DESCRIBE TABLE语句。这个语句用于查看表的列名及其属性。
DESCRIBE TABLE sasuser.customer;
执行此语句后,SAS会将表的描述信息写入日志。结果将显示列名、列类型以及特定列的相关标签(如果存在)。DESCRIBE TABLE语句返回的结果与PROC CONTENTS过程步的结果类似。
预览表数据
了解了表的结构后,下一步通常是预览表中的实际数据。你希望查看客户表的前10行数据。

为此,你需要使用一个简单的SELECT语句,并在FROM子句中使用OBS=数据选项来限制输出的行数。
以下是具体的SQL查询语句:
SELECT firstname, lastname, state
FROM sasuser.customer(OBS=10);
在这个查询中:
SELECT后面跟着用逗号分隔的列名(firstname,lastname,state)。FROM子句指定了要查询的表名,并使用(OBS=10)选项来限制只处理前10行观测。
这个查询将生成一个仅包含前10行数据的报告。你只是想预览数据,因此不需要输出整个表的报告。这种SQL查询的功能类似于PROC PRINT过程步,后者用于打印表中的观测。
注意事项与技巧

在编写查询时,有一个通用的经验法则需要注意:如果你不清楚表的大小,执行查询时需要谨慎。
根据表的大小,你可能会意外地生成一个包含10万、50万甚至上百万行的报告。这很可能是不必要的,并且可能导致问题或拖慢系统速度。
此外,需要知道的是,在Proc SQL中,你还可以使用其他SAS数据选项,例如:
KEEP=:指定要保留的变量。DROP=:指定要删除的变量。WHERE=:指定筛选观测的条件。
这些选项可以像OBS=一样,在FROM子句的表名后面使用,为你提供更灵活的数据控制能力。
总结
本节课中我们一起学习了使用Proc SQL探索数据表的两个核心操作:
- 使用
DESCRIBE TABLE语句查看表的列名、类型和标签等结构信息。 - 使用
SELECT语句并结合OBS=数据选项来安全、高效地预览表的前N行数据,避免因表过大而导致的问题。

掌握这些基础的数据探索技巧,是进行后续复杂查询和分析的重要第一步。
SAS高级程序员专项课程:P7:探索客户表演示 🧭
在本节课中,我们将学习如何使用SAS的PROC SQL过程来探索一个名为customer的数据表。我们将从查看表的结构开始,然后逐步学习如何选择特定的列和限制返回的行数,以有效地了解数据内容。
探索表结构
首先,我们需要了解customer表中包含哪些列以及它们的属性。为此,我们将使用DESCRIBE TABLE语句。
PROC SQL;
DESCRIBE TABLE SQ.customer;
QUIT;
运行上述代码后,我们可以在日志中查看结果。日志会列出表中的所有列、每列的数据类型(例如字符型或数值型),以及是否存在列标签(Label)或格式(Format)。
- 字符型列:例如
first_name、middle_name、last_name,它们都附有标签。 - 数值型列:例如
DOB列,它的列标签是“date of birth”。这意味着虽然实际的列名是DOB,但在输出结果中,默认会显示更易读的标签“date of birth”。
查看表数据
了解了表结构后,下一步是查看表中的实际数据。我们将使用SELECT语句。
选择所有列

最初,我们可以选择查看所有列。由于customer表有超过10万行,我们不需要一次性查看所有数据。可以使用OBS=选项来限制只显示前10行。

PROC SQL;
SELECT *
FROM SQ.customer
(OBS=10);
QUIT;
运行这段代码后,结果窗口会显示前10行数据的所有列。请注意,DOB列在结果中显示为“Date of Birth”,这正是PROC SQL自动使用列标签而非原始列名的效果。
选择特定列

通常,我们只关心表中的部分列。这时,可以在SELECT语句中明确指定需要的列名,而不是使用星号(*)。
例如,以下代码只选择first_name、last_name和DOB三列:

PROC SQL;
SELECT first_name, last_name, DOB
FROM SQ.customer
(OBS=10);
QUIT;

运行后,结果将仅包含指定的三列。
我们可以根据需要调整选择的列。例如,以下代码选择了customer_id、user_id、last_name和DOB四列:
PROC SQL;
SELECT customer_id, user_id, last_name, DOB
FROM SQ.customer
(OBS=10);
QUIT;
结果中同样会显示这四列,并且DOB列依然会使用其标签“Date of Birth”进行显示。
总结
本节课中,我们一起学习了探索SAS数据表的基本方法:
- 使用
DESCRIBE TABLE语句来了解表的结构,包括列名、类型和标签。 - 使用
SELECT语句从表中选择数据。 - 使用
SELECT *可以选择所有列,而明确列出列名(如SELECT col1, col2)则能精确选择所需列。 - 使用
(OBS=n)选项可以有效限制输出行数,便于快速预览大型数据集。 - PROC SQL在显示结果时,会优先使用列的标签(Label),如果标签不存在,则显示原始列名。这使得输出结果对用户更加友好。

通过组合运用这些技巧,你可以高效地初步了解和检查任何SAS数据表的内容。
008:SQL选项控制
在本节课中,我们将学习如何在PROC SQL过程中使用选项来控制数据处理和结果输出的方式。掌握这些选项能帮助你更精确地管理查询过程,优化性能,并定制输出结果的外观。
使用INOBS和OUTOBS选项限制处理行数
上一节我们介绍了PROC SQL的基本查询,本节中我们来看看如何控制查询处理的行数。你可以使用INOBS和OUTOBS选项来限制参与处理或最终输出的数据行。
-
INOBS选项:此选项用于限制从每个源表中读取并参与查询处理的行数。其作用类似于DATA步中的
OBS=数据集选项。- 代码示例:
PROC SQL INOBS=50;此语句将限制查询只读取每个输入表的前50行进行处理。
- 代码示例:
-
OUTOBS选项:此选项用于限制查询最终输出的行数。所有数据行都会被处理(除非同时使用了INOBS),但只有指定数量的结果行会被输出。
- 代码示例:
PROC SQL OUTOBS=100;此语句将确保查询结果只输出前100行。
- 代码示例:


使用NUMBER选项控制输出显示
除了控制数据量,我们还可以控制输出结果的显示格式。NUMBER选项用于在查询结果中显示行号。
- NUMBER选项:在PROC SQL语句中使用
NUMBER选项,可以控制是否将行号作为第一列显示在查询结果中。- 代码示例:
PROC SQL NUMBER;执行此语句后,输出的结果表格最左侧将增加一列显示行号。
- 代码示例:


本节课中我们一起学习了PROC SQL中几个实用的选项:通过INOBS和OUTOBS选项,我们可以分别限制查询处理的行数和最终输出的行数,这在处理大数据集或进行初步测试时非常有用;通过NUMBER选项,我们可以为输出结果添加行号列,使结果更易于阅读和核对。合理运用这些选项,能够提升你编写SAS SQL程序的效率和灵活性。
SAS高级程序员专项课程:P9:比较SQL与DATA步 🔄

在本节课中,我们将要学习PROC SQL与DATA步之间的核心区别与联系。理解它们各自的优势和适用场景,有助于我们在实际工作中选择最合适的工具来处理数据。


DATA步能够执行许多与PROC SQL相同的任务。

那么,为什么还需要学习PROC SQL呢?

PROC SQL是DATA步的补充,而非替代品。

有时PROC SQL是最佳工具,但在其他情况下,使用DATA步可能更合适。

PROC SQL的一个优势在于它可以减少所需的编码量。

一个单独的PROC SQL查询通常可以产生与多个DATA步及其他PROC步骤相同的结果。

另一方面,如果你想使用SAS读取原始文本文件,则必须使用DATA步。
DATA步可以在不使用其他SAS软件的情况下读取各种类型的原始文本文件,而PROC SQL则不能。


PROC SQL与DATA步之间还存在另一个重要区别。在使用SQL时,你只需描述期望的结果,SQL优化器会为你生成结果。
DATA步则让你对数据处理方式有更多控制权。它允许你在一个步骤中创建多个表格,并包含循环和数组处理功能。
在本课程中,你将不时看到PROC SQL与DATA步以及其他SAS过程在执行特定任务时的比较。




本节课中,我们一起学习了PROC SQL与DATA步的核心差异。PROC SQL擅长通过声明式查询高效地汇总和合并数据,而DATA步则在读取原始数据、精细控制处理流程以及执行复杂迭代操作方面更具优势。理解它们是互补工具,将帮助你在SAS编程中做出更明智的选择。
010:概述
在本节课中,我们将学习SAS编程的核心基础操作。课程内容将围绕数据查询、汇总、表格创建及系统信息获取展开,为后续深入学习打下坚实基础。
章节一:生成简单报告
上一节我们介绍了课程的整体结构,本节中我们来看看如何通过查询数据来生成简单的报告。这主要涉及对数据进行筛选和排序。
以下是生成报告的基本步骤:
- 查询数据。
- 对数据进行筛选。
- 对数据进行排序。
章节二:汇总与分组数据
在掌握了基础查询后,本节我们将学习如何对数据进行汇总和分组分析。这能帮助我们洞察数据的整体特征和分布。
以下是数据汇总与分组的关键操作:
- 消除重复行。
- 按唯一值对数据进行分组。
- 使用汇总函数(如
SUM、MEAN、COUNT等)。
章节三:创建表格与插入数据
接下来,我们将从数据查询转向数据创建。本节将介绍如何在SAS中创建新的数据表并向其中添加数据行。
以下是相关操作:
- 使用
CREATE TABLE语句创建新表。 - 使用
INSERT INTO语句向表中插入数据行。
章节四:字典表与会话信息
最后,我们将探索SAS系统的内部信息。本节将介绍字典表,这是一种特殊的只读表,用于提供当前SAS会话的元数据信息。
字典表允许我们查询诸如库名、表名、列属性等系统信息,其查询方式与查询普通数据表类似,例如:
PROC SQL;
SELECT * FROM DICTIONARY.TABLES;
QUIT;
本节课中我们一起学习了SAS编程的四个基础模块:通过筛选排序生成报告、对数据进行汇总分组、创建表格并插入数据,以及利用字典表获取SAS会话信息。这些是构建更复杂SAS程序的重要基石。
011:使用WHERE子句筛选行 🎯
在本节课中,我们将要学习如何在SAS的PROC SQL过程中使用WHERE子句来筛选数据行。WHERE子句是数据查询的核心工具之一,它允许你根据指定的条件,从数据集中精确地提取出你需要的行,而不是处理整个数据集。
概述:什么是WHERE子句?

假设你需要从客户表中生成一些简单的报告,但你并不希望看到表中的所有行。例如,在第一个报告中,你只想查看信用评分大于700、没有银行ID且收入排名前10的客户。或者,你可能想要一份出生于1940年12月31日之前且目前在职的客户报告。
这时,你就可以使用WHERE子句来过滤你的数据。WHERE子句必须位于SELECT和FROM子句之后,其结构由关键字WHERE后跟一个或多个表达式组成。
WHERE子句的基本结构

一个表达式会测试一个或多个列的值,以判断其是否符合你指定的条件。例如,在表达式 WHERE state = ‘NC’ 中,我们选择所有居住在NC州(北卡罗来纳州)的客户。如果表达式为真,则该行将被包含在结果集中。
表达式的构成
一个表达式由操作数和运算符组成。
- 操作数可以是列名、常量或SAS函数。
WHERE子句可以包含表中的任何列,即使这些列并未在SELECT子句中被选中。- 运算符是用于指定比较、算术计算或逻辑操作的符号或助记符。
以下是使用WHERE子句的基本代码结构:
PROC SQL;
SELECT column1, column2
FROM table_name
WHERE condition;
QUIT;
处理不同类型的数据
在使用WHERE子句时,需要注意不同类型数据的处理方式。
字符型数据
字符值区分大小写,并且必须用双引号或单引号括起来。双引号是SAS的增强功能,许多数据库系统只使用单引号来括住字符串字面量。
在子句 WHERE state = ‘NC’ 中,我们使用等号比较运算符来选择所有state等于‘NC’的行。字符比较区分大小写,因此你必须使用与存储值相同的大小写来指定字符常量。
数值型数据
数值不需要用引号括起来,并且必须是标准数值。不能在数值中包含逗号或美元符号等特殊符号。
在子句 WHERE income < 30000 中,我们保留所有income小于常量30000的行。
使用SAS函数
你还可以在WHERE子句中使用SAS函数。
在子句 WHERE MONTH(DOB) = 9 中,我们使用MONTH函数提取DOB列的数值月份值,并查找在9月(即九月)出生的客户。

比较运算符详解
比较运算符可以出现在任何有效的SAS表达式中以及SAS代码的任何位置,而不仅仅是在WHERE子句和PROC SQL中。所有这些比较运算符都可以使用助记符或符号。
| 含义 | 助记符 | 符号 |
|---|---|---|
| 等于 | EQ |
= |
| 不等于 | NE |
^= 或 ~= 或 ¬= |
| 小于 | LT |
< |
| 小于等于 | LE |
<= |
| 大于 | GT |
> |
| 大于等于 | GE |
>= |
助记符是SAS的增强功能,它们不符合ANSI SQL标准。而大多数比较运算符符号都符合ANSI标准。最后两个“不等于”运算符的符号(~= 和 ¬=)是SAS的增强功能,SAS提供这些是为了与某些操作系统环境和键盘保持一致。

组合多个条件:逻辑运算符
上一节我们介绍了单一条件的筛选,本节中我们来看看如何组合多个表达式。你可以使用逻辑运算符OR和AND来组合多个表达式,以检索满足多个条件或表达式的行。
OR运算符指定任一条件为真即可。AND运算符指定所有条件都必须为真。

以下是使用逻辑运算符的示例:
/* 使用OR:选择来自NY、NC或CA的客户 */
WHERE state = ‘NY’ OR state = ‘NC’ OR state = ‘CA’;
/* 使用AND:选择收入大于30000且来自NC州的客户 */
WHERE income > 30000 AND state = ‘NC’;
在第一个例子中,我们使用三个表达式和OR运算符来选择所有来自NY、NC或CA的客户。只要其中任何一个条件为真,我们就选择该行。在第二个例子中,我们使用两个表达式和AND运算符,只选择收入值大于30,000并且state等于‘NC’的客户。在这种情况下,两个条件都必须为真,该行才会被选中。
重要提示:如果你同时使用OR和AND,请务必用括号将表达式分组,以明确运算的优先级。
使用IN和NOT运算符简化条件

除了OR和AND,还有两个非常实用的运算符可以简化条件编写。
IN运算符
IN运算符用于测试值是否与列表中的某一个值匹配,其效果类似于使用多个OR表达式。IN运算符中的值列表必须用括号括起来,并用逗号或空格分隔。字符值必须用引号(单引号或双引号)括起来。
假设你想搜索客户位于NC、GA或NY的所有行。
- 一种方法是使用
OR运算符和三个表达式:state = ‘NC’ OR state = ‘GA’ OR state = ‘NY’。 - 更简洁的方法是使用
IN运算符:WHERE state IN (‘NC’, ‘GA’, ‘NY’)。
NOT运算符
你也可以使用NOT运算符来构成否定条件。在WHERE子句 WHERE state NOT IN (‘NC’, ‘GA’, ‘NY’) 中,NOT运算符会搜索所有不在NC、GA和NY州的客户。NOT运算符可以前缀于其他运算符之前,例如 WHERE income NOT > 30000。
总结

本节课中我们一起学习了WHERE子句的强大功能。我们了解了其基本语法,学习了如何处理字符型和数值型数据,并掌握了各种比较运算符(如=、<、>)和逻辑运算符(AND、OR)的用法。最后,我们还探讨了如何使用IN和NOT运算符来更简洁、高效地编写筛选条件。通过灵活运用WHERE子句,你可以从庞大的数据集中精准地提取出所需的信息,这是进行有效数据分析的关键一步。
012:特殊WHERE运算符之缺失值处理 🔍
在本节课中,我们将学习如何在SAS中使用WHERE语句来筛选数据中的缺失值。我们将介绍几种不同的方法,包括直接比较和使用特殊的运算符。
假设您需要在SAS中根据缺失值来筛选数据。
一种方法是编写一个表达式,让数值型缺失值等于一个句点(.),或者让字符型缺失值等于一个引号包围的空格(‘ ’)。





另一种选择是使用 IS NULL 或 IS NOT NULL 比较运算符。

这些比较运算符可以同时用于数值型或字符型的缺失值。
如果您的数据来自一个能区分“缺失值”和“空值”的数据库管理系统环境,那么 IS NULL 运算符是ANSI标准。


您可能还会遇到 IS MISSING 运算符。在SAS中,IS MISSING 这个特殊的WHERE运算符与 IS NULL 运算符是相同且可以互换的,但 IS MISSING 运算符不是ANSI标准。


为了在SAS和数据库环境中保持一致性,建议使用 IS NULL 这个特殊的WHERE运算符。


本节课中,我们一起学习了在SAS中筛选缺失值的几种方法。我们了解到可以直接与句点或空字符串比较,也可以使用 IS NULL 或 IS MISSING 运算符。其中,IS NULL 是ANSI标准,在与数据库交互时更具通用性,因此是推荐的做法。
013:其他特殊WHERE运算符 🧮
在本节课中,我们将学习SAS中两个非常实用的特殊WHERE运算符:BETWEEN-AND和LIKE。它们能帮助我们更灵活、更精确地从数据集中筛选出所需的数据行。
上一节我们介绍了基础的WHERE运算符,本节中我们来看看两个功能强大的特殊运算符。
BETWEEN-AND运算符 🔢
BETWEEN-AND运算符用于根据一个包含边界值的范围来筛选行。它适用于数值和字符类型的范围。
公式:
WHERE column BETWEEN value1 AND value2;
例如,要筛选信用评分在700到799之间的客户,可以这样写:
WHERE credit_score BETWEEN 700 AND 799;
由于BETWEEN-AND是包含性的,结果中会包含700分和799分的客户。

这个运算符的效果等同于使用两个表达式和AND运算符。以下是等效的写法:
WHERE credit_score >= 700 AND credit_score <= 799;

LIKE运算符与模式匹配 🔍
LIKE运算符通过将字符列的值与指定的模式进行比较来筛选行,这被称为模式匹配。你需要在LIKE后面指定一个模式。
公式:
WHERE column LIKE ‘pattern’;
模式必须是一个字符值,因此需要用引号括起来。
为了构建模式,可以使用两个特殊字符:
- 百分号
%:代表任意数量的字符(包括零个字符)。 - 下划线
_:代表任意单个字符。
以下是使用这些通配符的例子:
-
WHERE first_name LIKE ‘Z%’;- 此WHERE子句会返回所有以大写字母
Z开头,后面跟任意数量字符的名字。
- 此WHERE子句会返回所有以大写字母
-
WHERE first_name LIKE ‘Z_L%’;- 此WHERE子句会返回所有以大写字母
Z开头,后面跟任意一个字符,然后是字母L,最后再跟任意数量字符的名字。
- 此WHERE子句会返回所有以大写字母


本节课中我们一起学习了BETWEEN-AND和LIKE这两个特殊WHERE运算符。BETWEEN-AND让我们能轻松筛选出某个范围内的数据,而LIKE配合通配符%和_,则能实现强大的模糊匹配功能,是处理文本数据时的利器。掌握它们能极大地提升数据查询的效率和灵活性。
014:使用ORDER BY子句排序输出 📊
在本节课中,我们将要学习如何使用ORDER BY子句来控制查询结果的排序方式。掌握排序是数据呈现和分析的基础,能让你的报表更加清晰、有序。
控制输出顺序
上一节我们介绍了如何筛选和计算数据,本节中我们来看看如何对查询结果进行排序。你可以通过添加ORDER BY子句来指定结果集中行的排列顺序。

SELECT column1, column2
FROM table_name
ORDER BY column1;
默认情况下,ORDER BY子句会按照指定列的升序进行排序。
指定排序方向
默认的排序顺序是升序,但你可以在列名后添加DESC关键字,将其更改为降序排列。

SELECT column1, column2
FROM table_name
ORDER BY column1 DESC;
多列排序与列的选择
你可以根据多个列进行排序,并且可以使用表中的任何列,包括未被选择的列或计算得出的列。
以下是多列排序的要点:
- 如果指定了多个排序列,第一个列决定了主要的排序顺序。
- 当主要排序列的值相同时,系统会按照后续指定的列进行次级排序。

本节课中我们一起学习了ORDER BY子句的使用。我们了解到,它可以指定结果的排序顺序,默认是升序,使用DESC可改为降序。同时,排序可以基于多列,并且不限于选择列表中的列。合理使用排序功能,能让你的数据输出更具可读性和分析价值。
015:按列位置排序 📊
在本节课中,我们将学习在SAS中使用列位置编号进行数据排序的方法。这是一种替代使用变量名的排序方式,在某些情况下更为便捷。
上一节我们介绍了使用变量名进行排序,本节中我们来看看如何使用列的位置编号来实现相同的排序效果。

按列位置排序
您可以使用列的位置编号进行排序。
以下示例首先按第3列降序排序,该列是信用评分列。
proc sort data=cert.creditscores;
by descending col3;
run;

多级排序

其次,按第2列(即姓氏列)进行次级排序。

以下是实现代码:
proc sort data=cert.creditscores;
by descending col3 col2;
run;


结果对比
排序结果与之前使用变量名进行排序的活动结果完全相同。


本节课中我们一起学习了如何使用列位置编号在SAS的PROC SORT过程中对数据进行排序。您掌握了通过descending colX的语法格式进行单列或多列排序,并了解到这种方法与使用变量名排序能得到一致的结果。
016:增强报告 📊
在本节课中,我们将要学习如何通过添加标题、脚注、列标签以及应用格式来增强SAS报告的可读性和专业性。这些技巧能让你的报告更清晰、更易于理解。

聚焦报告
让我们将焦点转移到报告上。这份报告中的数据行代表的是客户还是员工?是全部数据行还是一个子集?收入单位是美元吗?报告是何时运行的?如何让生日日期变得可读?你可以做很多事情来增强报告的外观,使其更易于阅读和理解。


添加标题与脚注
你可以添加标题和脚注来说明报告内容及其创建时间。
标题是一个全局语句,用于为SAS会话中创建的所有报告建立一个永久性标题。其语法是关键字 title 后跟标题文本和闭合引号。
以下是关于标题和脚注的关键点:
- 你最多可以设置10个标题。在关键字
title后指定数字1到10来表示行号,title和title1是等效的。 - 你也可以使用
footnote语句为任何报告添加脚注。适用于标题的规则同样适用于脚注。 - 请记住,标题和脚注是全局语句,只要你的SAS会话处于活动状态,它们就保持有效。
- 如果你想清除标题和脚注,可以提交没有文本的相应
title和footnote语句,这些称为空语句,它们会清除所有标题和脚注。在程序末尾这样做是一个好习惯。 - 像SAS Studio这样的客户端应用程序会在你的代码末尾为你提交一个空的
title语句,但养成自己提交该语句的习惯是很好的。


使用列标签
上一节我们介绍了如何添加全局的标题和脚注,本节中我们来看看如何为数据列提供更清晰的描述。

列名必须遵守特定的命名约定,但这有时意味着名称可能有点难以理解,尤其对于不熟悉数据的人来说。

标签是一种为报告添加更具描述性列标题的简单方法。标签可以是任何最多256个字符的文本字符串,包括空格和特殊字符。
默认情况下,Proc SQL使用表中已保存的永久列属性来显示结果;如果没有,则使用列名。以下是两种指定列标签的方法:

- ANSI标准列修饰符:你可以在
SELECT子句中的任何列名或表达式后,使用AS关键字指定引号内的文本作为列标签。SELECT userid AS “Email Address”, income AS “Estimated Income”

- SAS增强的列修饰符:你可以利用SAS增强功能,如
LABEL=列修饰符。在SELECT子句中指定的任何列名或表达式后,指定LABEL=,然后在引号内输入你想要在结果中显示的文本。SELECT userid LABEL=“Email Address”, income LABEL=“Estimated Income”
通常,使用SAS方法使你的代码更易于阅读和遵循;但是,你可以在同一个 SELECT 子句中同时使用 LABEL= 列修饰符或 AS 列修饰符,或两者都用。



应用格式以美化数据
为了控制数值在报告中的显示方式,你可以应用SAS格式。FORMAT= 列修饰符是一个SAS增强功能,可以更轻松地创建更有用、更专业的报告。
你可以在 SELECT 子句中指定的任何列名或表达式后指定 FORMAT=,后跟格式名称。然后指定总格式宽度,包括小数位和特殊字符。句点是必需的分隔符,对于数字格式,句点后可以跟小数位数。
以下是应用格式的要点:
- 你可以指定任何SAS或用户定义的格式。
- 如果你指定的格式宽度不足以容纳一个值,SAS会自动调整以尽可能多地显示存储的值。
- 在下面的例子中,我们将宽度为16、带两位小数的美元格式应用到
income列。对于DOB列,我们应用date9.格式,它指定了两位数的日、三个字符的月份和四位数的年。 - Proc SQL 在指定宽度不够时会显示星号,但不会在日志中发出警告。
SELECT income FORMAT=dollar16.2, DOB FORMAT=date9.

需要记住的是,在 SELECT 子句中指定的格式仅影响数据值在结果中的显示方式,而不影响表中存储的实际数据值。



总结

本节课中我们一起学习了如何增强SAS报告。我们介绍了如何通过 TITLE 和 FOOTNOTE 语句添加标题和脚注来说明报告信息;如何使用 LABEL= 或 AS 为列提供更具描述性的标题;以及如何应用 FORMAT= 来美化数值(如货币和日期)的显示,使报告更专业、更易于理解。记住在程序结束时使用空语句清除全局的标题和脚注是一个好习惯。
017:使用PROC SQL创建简单报告 📊
在本节课中,我们将学习如何使用PROC SQL过程步来创建简单的数据报告。我们将通过两个具体的例子,演示如何筛选数据、排序结果、格式化输出以及修改列标题,从而生成清晰、易读的报告。
概述:创建第一个报告
首先,我们将创建一个报告,目标是找出所有没有银行ID、信用评分大于700,并且按收入排名的前10位客户。

上一节我们介绍了报告的目标,本节中我们来看看具体的实现步骤。

以下是创建第一个报告的SQL代码核心部分:
PROC SQL;
TITLE '高收入客户报告';
TITLE2 '条件:无银行ID,信用评分>700';
SELECT FirstName, LastName, State, Income, UserID
FROM CUSTOMER_TABLE
WHERE BankID IS NULL AND CreditScore > 700
ORDER BY Income DESC;
QUIT;
我们首先使用WHERE子句来筛选数据,条件是BankID为空且CreditScore大于700。然后使用ORDER BY子句按Income列降序排列结果。
运行初始代码后,结果符合预期。接下来,我们需要完善报告,使其只显示前10名客户,并对收入和列名进行格式化。
以下是完善报告的具体修改步骤:
- 添加
OUTOBS=10选项,将输出限制为10行。 - 使用
FORMAT=列修饰符为Income列应用美元格式DOLLAR11.0,使其显示为货币且不带小数。 - 使用
LABEL=列修饰符将UserID列的标签改为“Email”。 - 移除开发时使用的
OBS=100选项,让查询处理全部数据。
修改后的完整代码如下:
PROC SQL OUTOBS=10;
TITLE '高收入客户报告';
TITLE2 '条件:无银行ID,信用评分>700';
SELECT FirstName,
LastName,
State,
Income FORMAT=DOLLAR11.0,
UserID LABEL='Email'
FROM CUSTOMER_TABLE
WHERE BankID IS NULL AND CreditScore > 700
ORDER BY Income DESC;
QUIT;
运行最终代码后,报告成功显示了前10名客户,收入列已格式化为美元,并且UserID列标题已更改为“Email”。
过渡:创建第二个报告
在成功创建了第一个客户报告后,接下来我们处理第二个需求:找出所有在1940年12月31日之前出生且目前在职的客户。
以下是第二个报告的初始SQL代码框架:

PROC SQL;
TITLE '1940年前出生且在职业客户报告';
SELECT CustomerID, State, Zip, DOB, UserID, HomePhone, CellPhone
FROM CUSTOMER_TABLE
WHERE DOB < '31DEC1940'D AND Employed IN ('Yes', 'Y')
ORDER BY DOB DESC;
QUIT;

我们使用WHERE子句设置两个条件:出生日期早于‘31DEC1940’D,并且就业状态为“Yes”或“Y”。ORDER BY子句确保结果按出生日期降序排列。


运行代码后,数据筛选和排序逻辑正确,但DOB和Zip列的显示格式需要优化,以便于阅读。
以下是优化报告显示格式的步骤:
- 为
DOB列应用DATE9.格式,使日期以“DDMMMYYYY”的形式清晰显示。 - 为
Zip列应用Z5.格式,为不足5位的邮政编码自动添加前导零。

应用格式后的完整代码如下:
PROC SQL;
TITLE '1940年前出生且在职业客户报告';
SELECT CustomerID,
State,
Zip FORMAT=Z5.,
DOB FORMAT=DATE9.,
UserID,
HomePhone,
CellPhone
FROM CUSTOMER_TABLE
WHERE DOB < '31DEC1940'D AND Employed IN ('Yes', 'Y')
ORDER BY DOB DESC;
QUIT;
运行最终代码,报告中的出生日期已变得易读,并且所有邮政编码都正确显示为5位数字。

总结 🎯

本节课中我们一起学习了使用PROC SQL创建简单报告的核心技巧。
我们通过两个实例,实践了如何使用WHERE子句筛选数据、使用ORDER BY子句排序结果。
更重要的是,我们掌握了如何使用FORMAT=选项格式化数值(如货币、日期、邮政编码),以及使用LABEL=选项修改列标题,从而生成专业、清晰的数据报告。
018:创建新列 📊

在本节课中,我们将学习如何在PROC SQL查询中创建新的计算列。除了选择表中已存在的列,你还可以在查询过程中动态生成包含文本或计算结果的列。这些新列在查询期间有效,并且PROC SQL会像处理原始表中的列一样处理它们。
创建新列的基本语法
上一节我们介绍了选择列的基础知识,本节中我们来看看如何通过计算来创建新列。
在SELECT语句中,你可以通过一个表达式(文本或计算字符串)来定义一个新列,并使用关键字AS为其指定一个列名。

其基本语法结构如下:
SELECT column_expression AS new_column_name
FROM table_name;
计算年龄的实例
以下是一个具体的应用实例。假设我们需要找出所有年龄在70岁及以上的客户。
为了实现这个目标,我们首先需要创建一个名为Age的新列。这里使用了一个SAS函数YRDIF来计算年龄。
YRDIF函数用于计算两个日期之间的年份差。在本例中,它计算的是每个人的出生日期与一个固定日期(2019年1月1日)之间的年份差,从而得到该人员在2019年1月1日的年龄。
如果你想使报告更具动态性,可以使用TODAY()函数来获取当前日期进行计算。为了保持示例的一致性,我们这里使用了一个日期常量。
在函数之后,我们使用AS关键字为这个计算结果指定了列别名Age。
以下是实现此功能的代码示例:
PROC SQL;
SELECT Name,
YRDIF(BirthDate, '01JAN2019'd) AS Age
FROM Customers
WHERE calculated Age >= 70;
QUIT;
关于列别名的要点
在PROC SQL查询中,你可以为任何列(无论是原有的还是新创建的)分配一个新的名称,即别名。
以下是关于使用别名的一些重要规则和说明:
- 新的列名必须遵循SAS的命名规则。
- 该别名仅在该次查询中有效。
- 当你使用别名命名一个列后,你可以在查询的后续部分(例如
WHERE子句或ORDER BY子句中)使用这个别名来引用该列。注意,在WHERE子句中引用计算列时,通常需要加上CALCULATED关键字,如上面的示例所示。
输出结果

PROC SQL会将你定义的别名作为输出报表中的列标题。

本节课中我们一起学习了如何在PROC SQL中创建计算列。关键点包括:使用SELECT语句中的表达式和AS关键字来定义新列,利用SAS函数(如YRDIF)进行计算,以及为列设置别名以便于引用和输出。掌握创建新列的方法能极大地增强你从数据中提取和呈现信息的能力。
019:筛选计算值
在本节课中,我们将学习如何在SAS的SQL过程中,对查询结果中的计算列进行筛选。核心挑战在于,WHERE子句无法直接引用SELECT子句中定义的计算列别名。我们将探讨两种解决此问题的方法。
标准方法:重复计算表达式

上一节我们提到了WHERE子句的限制。为了确保筛选条件有效,最直接的方法是在WHERE子句中重复一遍SELECT子句中的计算表达式。
这种方法符合ANSI SQL标准,适用于所有遵循该标准的数据库系统。
示例:
proc sql;
select Name,
Salary,
Salary * 0.1 as Bonus
from sashelp.class
where Salary * 0.1 > 1000; /* 此处重复了Bonus的计算逻辑 */
quit;
以下是使用此方法时需要注意的几点:
- 它确保了
WHERE子句中引用的列确实存在于FROM子句指定的表中(尽管是动态计算的)。 - 当计算表达式简单时,这种方法清晰易懂。
- 当表达式复杂冗长,或需要基于多个计算值进行筛选时,重复书写会显得繁琐且容易出错。
便捷方法:使用CALCULATED关键字

为了解决重复计算的问题,SAS SQL提供了一个增强关键字:CALCULATED。它允许你在同一查询的WHERE子句或SELECT子句中,引用本层查询中已定义的计算列。
语法:
WHERE condition using CALCULATED column-alias
示例:
proc sql;
select Name,
Salary,
Salary * 0.1 as Bonus
from sashelp.class
where calculated Bonus > 1000; /* 直接使用计算列的别名 */
quit;
以下是关于CALCULATED关键字的关键说明:
CALCULATED是SAS对SQL语言的扩展,并非所有数据库系统都支持。- 它只能用于引用同一层
SELECT子句中定义的计算列。 - 它不能用于引用来自子查询或表连接中的计算列。
- 使用它可以使代码更简洁,尤其是当计算逻辑复杂时。
方法对比与总结
本节课中我们一起学习了在SAS SQL中筛选计算列的两种方法。
- 重复表达式法:优点是符合ANSI标准,通用性强。缺点是代码可能冗余,维护不便。
CALCULATED关键字法:优点是代码简洁直观,避免了重复。缺点是它是SAS的扩展语法,可移植性相对较弱。

在实际编程中,你可以根据代码的复杂度和对可移植性的要求来选择合适的方法。对于简单的查询,两种方法皆可;对于涉及复杂表达式筛选的情况,使用CALCULATED关键字通常能显著提升代码的可读性和可维护性。
020:使用CASE表达式按条件赋值 🧮
在本节课中,我们将学习如何在SAS查询中使用CASE表达式,根据特定条件为数据创建新的列或对现有值进行重新分类。这是一种强大的条件逻辑工具。
假设我们想基于婚姻状态创建一个新的“婚姻类别”列。其中,M代表已婚,S代表单身,D代表离异,W代表丧偶,其他任何值则归类为未知。同时,我们还想基于信用评分值的范围创建另一个“信用类别”列。如果信用评分的数值落在特定区间内,新列将包含对应的类别值。

理解CASE表达式

你可以在查询中使用条件逻辑,方法是在SELECT子句中使用CASE表达式来有条件地赋值。基本上,你可以在任何可以使用列名的地方使用CASE表达式。

标准形式(简单CASE表达式)

上一节我们介绍了CASE表达式的基本概念,本节中我们来看看它的第一种形式:标准形式或简单CASE表达式。使用这种语法,你可以进行相等性测试。
以下是其基本语法结构:
CASE column_name
WHEN value1 THEN result1
WHEN value2 THEN result2
...
ELSE default_result
END
在这个例子中,CASE表达式根据customer表中credit_score列的值,为每位客户确定信用类别。
SELECT customer_id,
credit_score,
CASE
WHEN credit_score >= 750 THEN 'Excellent'
WHEN credit_score >= 700 THEN 'Good'
WHEN credit_score >= 650 THEN 'Fair'
ELSE 'Poor'
END AS category
FROM customer;
在第一个WHEN...THEN子句中,当信用评分大于或等于750时,类别值将为“Excellent”。关键字END用于结束CASE表达式,并可选择地为该列分配一个别名(如category)。第一个评估为真的WHEN子句将决定CASE表达式返回哪个值,后续的WHEN子句将不再被评估。可选的ELSE表达式提供了当所有WHEN条件都不为真时的备用操作。如果没有ELSE表达式,且每个WHEN条件都为假,则CASE表达式返回一个缺失值。

搜索形式(CASE操作数形式)
除了标准形式,你还可以使用搜索形式(或称CASE操作数形式)来构建CASE表达式。这种形式更为灵活。
以下是其语法结构:
CASE
WHEN condition1 THEN result1
WHEN condition2 THEN result2
...
ELSE default_result
END
你可以在CASE关键字后直接指定一系列WHEN...THEN子句,每个子句包含一个完整的条件。这里我们选择married列,并根据其值将它们分配到指定的类别。
SELECT customer_id,
married,
CASE married
WHEN 'M' THEN 'Married'
WHEN 'S' THEN 'Single'
WHEN 'D' THEN 'Divorced'
WHEN 'W' THEN 'Widowed'
ELSE 'Unknown'
END AS married_category
FROM customer;

需要注意的是,当你使用CASE操作数形式(即CASE column_name)时,所有条件都必须是相等性测试,也就是说,它们不能使用比较运算符(如>=, <)或其他类型的运算符。而搜索形式(CASE WHEN condition)则允许使用任何返回布尔值的条件表达式。

应用场景与选择
以下是两种形式的主要区别和应用场景:
- 简单CASE表达式:适用于将一个列的值与一系列固定的常量进行精确匹配的场景。语法更简洁。
- 搜索CASE表达式:适用于条件更复杂的场景,例如基于数值范围(如信用评分)、组合条件(使用
AND/OR)或涉及多个列的逻辑判断。功能更强大。
总结

本节课中我们一起学习了SAS中CASE表达式的两种主要形式。简单CASE表达式适用于基于单列值的直接匹配,而搜索CASE表达式则提供了处理复杂条件逻辑的灵活性。通过掌握这两种形式,你可以在数据查询中有效地实现条件赋值和数据分类,从而更深入地分析和呈现数据。
021:按条件赋值 🎯
在本节课中,我们将学习如何使用 CASE 表达式,根据特定条件为数据创建新的列。这是一种非常强大的数据转换技术。
概述
我们将通过两个查询示例来演示 CASE 表达式的用法。首先,我们会根据信用评分创建一个分类列。然后,我们将根据婚姻状态代码创建一个描述性列。过程中,我们会学习如何处理缺失值以及如何对计算出的新列进行筛选。
使用简单CASE表达式


首先,我们来看一个使用简单 CASE 表达式的查询。我们的目标是根据客户的信用评分,为其分配一个评级类别。
以下是查询的核心逻辑:
SELECT
first_name,
last_name,
state,
credit_score,
CASE credit_score
WHEN > 750 THEN 'Excellent'
WHEN BETWEEN 700 AND 749 THEN 'Good'
-- 其他条件...
ELSE 'Unknown'
END AS credit_category
FROM customer_data;
运行这个查询后,我们得到了包含姓名、州、信用评分和新列 credit_category 的结果集。可以看到,评分被成功地分类为“Excellent”、“Good”等。


处理缺失值
然而,观察数据时我们发现,当 credit_score 列为空(缺失值)时,对应的 credit_category 列也是空的。这通常不是我们想要的结果。

我们希望明确指定:如果信用评分缺失,则类别应显示为“Unknown”。为了实现这一点,我们需要修改 CASE 表达式,添加一个 ELSE 子句来处理所有未在 WHEN 中明确列出的情况,包括缺失值。

修改后的逻辑如下:
CASE
WHEN credit_score > 750 THEN 'Excellent'
WHEN credit_score BETWEEN 700 AND 749 THEN 'Good'
-- 其他条件...
ELSE 'Unknown' -- 处理所有其他情况,包括缺失值
END
再次运行查询,现在当信用评分为空时,类别会清晰地显示为“Unknown”。


筛选计算列
上一节我们创建了 credit_category 列,现在假设我们只想查看评级为“Excellent”的客户。这涉及到对计算出的新列进行筛选。
在SQL中,我们不能在 WHERE 子句中直接使用列的别名。这时,可以使用 CALCULATED 关键字来引用前面计算出的列。我们在 FROM 子句后添加 WHERE 子句:
WHERE CALCULATED credit_category = 'Excellent'
运行这个带筛选条件的查询,结果集就只包含信用评级为“Excellent”的记录了。
使用CASE搜索表达式
接下来,我们看看 CASE 表达式的另一种形式:搜索表达式。这次,我们将根据“已婚”状态代码(一个字母代码)来创建一个描述性列。
查询开始部分选择姓名、州、信用评分和已婚状态列。然后,我们使用 CASE 表达式对 married 列的值进行判断和转换。
以下是具体的转换逻辑:
CASE married
WHEN 'D' THEN 'Divorced'
WHEN 'S' THEN 'Single'
WHEN 'W' THEN 'Widowed'
ELSE 'Unknown' -- 处理代码‘M’或其他未列出的值
END AS married_category
运行查询后,我们可以看到新的 married_category 列,其中的值已从简单的代码转换为了清晰的描述。

总结
本节课我们一起学习了 CASE 表达式的强大功能。我们掌握了:
- 使用简单
CASE表达式根据一个列的值创建新列。 - 使用
ELSE子句 处理缺失值和未预见的情况,确保数据的完整性。 - 使用
CALCULATED关键字 在WHERE子句中筛选计算出的新列。 - 使用
CASE搜索表达式 实现更灵活的条件判断。

CASE 表达式是进行数据清理、转换和创建衍生变量的核心工具,能够基于特定的值或条件逻辑有效地生成新的数据列。
022:使用 DISTINCT 关键字消除重复行 🎯
在本节课中,我们将要学习如何在SAS的PROC SQL过程中使用DISTINCT关键字,从查询结果中筛选出唯一的、不重复的值。
概述
在某些情况下,我们可能只需要获取数据列中的唯一值。例如,假设我们的客户表中有超过10万名客户,而我们想要确定这些客户具体分布在哪些州。我们需要的不是一个包含所有客户记录的列表,而仅仅是一个不重复的州名列表,以便分析客户是否来自所有州,还是仅来自特定的几十个州。
使用 DISTINCT 关键字

为了从查询结果中消除重复的行,可以在SELECT子句中使用DISTINCT关键字。这个关键字会作用于SELECT语句中列出的所有列。
基本语法:
PROC SQL;
SELECT DISTINCT column_name
FROM table_name;
QUIT;

DISTINCT 的工作原理
PROC SQL会消除结果中所有列的值都完全匹配的重复行。因此,对于每一个唯一的数值组合,结果中只会显示一行。

上一节我们介绍了DISTINCT关键字的基本用法,本节中我们来看看它的具体效果。当我们对“州”这一列使用DISTINCT关键字时,查询将消除重复的州名,最终给出一个汇总了我们客户所在州的唯一列表。
总结

本节课中我们一起学习了DISTINCT关键字的核心功能。通过将其应用于SELECT语句,我们可以轻松地从数据中提取出不重复的唯一值,这对于数据汇总和初步分析非常有用。记住,DISTINCT会基于所选列的组合值来去重。
023:数据汇总
在本节课中,我们将要学习如何在SAS SQL查询中使用汇总函数。汇总函数能够对数据进行聚合计算,无论是纵向聚合整列数据,还是横向聚合单行内的多个值。理解其工作原理是进行数据分析的关键步骤。
汇总函数简介

上一节我们介绍了在SELECT子句中使用SAS函数创建计算列。本节中我们来看看如何使用汇总函数。
汇总函数,也称为聚合函数,其作用是根据我们想要汇总的列,对行中的所有值进行聚合计算。
纵向汇总单列数据

假设我们想要汇总一列数据。例如,state_population表包含了每个州未来三年的人口估计值。我们想要计算P_Estimate1列(即下一年度的估计值)的最大值、最小值和平均值。
为此,我们使用单参数的汇总函数。根据ANSI标准,单参数汇总函数会对一列中的非缺失值进行纵向聚合计算。
以下是计算示例:
SELECT
MAX(P_Estimate1) AS max_population,
MIN(P_Estimate1) AS min_population,
MEAN(P_Estimate1) AS avg_population
FROM state_population;
对于下一年度的人口估计,最大州人口估计值略高于3920万,最小州人口估计值约为58.5万,平均州人口约为620万。
横向汇总单行数据

如果我们想要横向汇总一行数据,并创建一个包含汇总值的新列,该如何操作?

在SQL中,汇总函数的工作方式取决于参数列表中指定的列数。如果汇总函数指定了多个列,则函数将使用所列列中的值为每一行计算统计量。
在本例中,我们希望确定每个州未来三年估计人口的最大值。为此,我们使用MAX汇总函数,并指定每一列:P_Estimate1、P_Estimate2和P_Estimate3。
以下是计算示例:
SELECT
State,
MAX(P_Estimate1, P_Estimate2, P_Estimate3) AS max_estimate_across_years
FROM state_population;
当使用多参数汇总函数时,非缺失值将在行内进行横向汇总。
处理缺失值

如果存在缺失值,汇总函数会忽略它。

本节课中我们一起学习了SAS SQL中汇总函数的应用。我们掌握了如何使用单参数函数对列数据进行纵向聚合,以及如何使用多参数函数对行数据进行横向聚合。同时,我们了解到汇总函数会自动忽略缺失值,这在进行数据统计时非常重要。
024:使用汇总函数分析表格 📊
在本节课中,我们将学习如何使用SAS中的汇总函数来分析表格数据。我们将通过一个具体的例子,探索如何计算一列数据的统计量,以及如何跨行进行计算。


概述
我们将使用一个名为state_population的表格,其中包含地区、州名以及人口估计值等列。我们的目标是计算人口估计列的各种描述性统计量,例如计数、平均值、标准差、最小值和最大值。
探索数据表
首先,我们来查看一下state_population表的结构。该表包含以下几列:region、division、state_name以及人口估计列P_estimate1。我们的分析将主要围绕P_estimate1列展开。
计算单列的汇总统计量
以下是如何使用PROC SQL中的汇总函数来计算P_estimate1列的统计信息。
我们使用COUNT函数来计算总州数,使用MEAN函数来计算平均值,并使用FORMAT语句来格式化输出。
PROC SQL;
SELECT
COUNT(P_estimate1) AS total_states,
MEAN(P_estimate1) AS mean FORMAT=comma16.
FROM state_population;
QUIT;
运行此查询后,我们得到一行输出。total_states的结果是52(包括华盛顿特区和波多黎各),mean的结果大约是620万。
接下来,我们扩展查询,以包含更多统计量。
以下是计算标准差、最小值和最大值的完整查询:
PROC SQL;
SELECT
COUNT(P_estimate1) AS total_states,
MEAN(P_estimate1) AS mean FORMAT=comma16.,
STD(P_estimate1) AS std_dev FORMAT=comma16.,
MIN(P_estimate1) AS minimum FORMAT=comma16.,
MAX(P_estimate1) AS maximum FORMAT=comma16.
FROM state_population;
QUIT;
现在,我们可以在结果中看到所有描述性统计量。

使用 PROC MEANS 的替代方法
在SAS中,除了PROC SQL,还可以使用PROC MEANS过程来获得相同的结果。这种方法有时更为简洁。
PROC MEANS DATA=state_population N MEAN STD MIN MAX MAXDEC=0;
VAR P_estimate1;
RUN;


运行此代码将产生与之前SQL查询相同的统计结果。这为数据分析提供了另一种有效的方法。
跨行计算汇总统计量
上一节我们介绍了如何汇总单列数据。本节中我们来看看如何对一行中的多个列进行计算。
假设我们想计算每个州在P_estimate1、P_estimate2、P_estimate3这三列上的人口估计最小值、平均值和最大值。
以下是相应的SQL查询。注意,在SAS的PROC SQL中,计算平均值应使用MEAN函数,而不是ANSI标准的AVG函数。
PROC SQL;
SELECT
state_name,
MIN(P_estimate1, P_estimate2, P_estimate3) AS min_estimate FORMAT=comma16.,
MEAN(P_estimate1, P_estimate2, P_estimate3) AS mean_estimate FORMAT=comma16.,
MAX(P_estimate1, P_estimate2, P_estimate3) AS max_estimate FORMAT=comma16.
FROM state_population;
QUIT;
当在汇总函数中指定多个参数时,SAS会跨这些列进行计算。结果将显示每个州在这三个估计值中的最小值、平均值和最大值。

关于列列表快捷方式的注意事项
对于熟悉SAS数据步的程序员,可能会想使用P_estimate1 - P_estimate3这样的快捷方式来指代一系列列。
/* 注意:此写法在 PROC SQL 中无效 */
SELECT MEAN(P_estimate1 - P_estimate3) ...
需要明确的是,这种快捷方式在PROC SQL中无法正常工作。在SQL过程中,必须明确列出每一列的名称。
总结
本节课中我们一起学习了在SAS中使用汇总函数分析表格。
- 我们使用
COUNT、MEAN、STD、MIN、MAX等函数计算了单列的描述性统计量。 - 我们了解了使用
PROC MEANS作为PROC SQL的替代方法。 - 我们探索了如何通过向汇总函数传入多个参数,来实现跨行的计算。
- 我们明确了SAS数据步中的列列表快捷方式(如
var1-var3)不适用于PROC SQL环境。

掌握这些汇总函数的使用,是进行数据摘要和分析的基础。
SAS高级程序员专项课程:P25:使用COUNT函数汇总数据 📊
在本节课中,我们将学习如何使用SAS中的COUNT函数来对数据进行汇总,特别是计算行数。COUNT函数是数据汇总中的基础且强大的工具。
COUNT函数可用于创建行计数。该函数的参数可以是一个列名,也可以是一个星号(*)。

其基本语法如下:
COUNT(argument)
其中,argument 可以是具体的列名,也可以是 *。

上一节我们介绍了COUNT函数的基本形式,本节中我们来看看其参数的具体含义和区别。
如果你指定一个列名作为参数,该函数将统计该列中非缺失值的数量。
COUNT(column-name)
如果你使用星号(*)作为参数,COUNT函数将返回一个表或一个行子集中的总行数,无论这些行中是否存在缺失值。
COUNT(*)

以下是两种参数用法的核心区别:
- 使用列名:仅计算指定列中值不为空的观测数。
- 使用星号(
*):计算所有观测的行数,不关心具体列的值是否缺失。


本节课中我们一起学习了SAS COUNT函数在数据汇总中的应用。关键点在于:通过指定列名,可以统计非缺失值的数量;而使用星号(*),则可以获取总行数。理解这一区别对于准确进行数据计数至关重要。
026:数据分组 📊
在本节课中,我们将学习如何使用SQL中的GROUP BY子句对数据进行分组,并配合聚合函数来汇总信息。我们将探讨如何计算每个组内的行数、平均值等统计量,以及如何使用HAVING子句对分组后的结果进行筛选。

概述
我们被要求调查客户表,并根据分组来汇总信息。具体问题包括:每个州有多少客户?每个州客户的平均信用评分是多少?
为了在SQL中回答这些问题,我们需要使用GROUP BY子句。

使用GROUP BY子句
GROUP BY子句根据一个或多个列的值将数据分类成组。SELECT子句中的聚合函数则为每个分组列的唯一值计算统计量。你可以在选择的任何列上使用聚合函数。
例如,要找出每个州的客户数量,我们可以使用包含COUNT函数的查询,该函数计算每个州的总客户数或行数。
以下是查询示例:
SELECT State, COUNT(*) AS TotalCustomers
FROM CustomerTable
GROUP BY State
ORDER BY TotalCustomers DESC;
GROUP BY子句对州进行分组,ORDER BY子句将总客户数按降序排列。运行此代码后,结果将显示每个州的客户数量。由于查询按总客户数降序排序,我们可以看出大多数客户在加利福尼亚州,其次是德克萨斯州和纽约州。
核心概念:必须将聚合函数与GROUP BY子句结合使用。如果不这样做,SAS会将GROUP BY子句转换为ORDER BY子句。

WHERE子句与分组
SAS在行可用于处理之前评估WHERE子句,并确定哪些单独的行可用于分组。因此,你不能使用WHERE子句通过引用计算出的汇总列(如TotalCustomers)来筛选分组行。

使用HAVING子句筛选分组
必须将HAVING子句与GROUP BY子句结合使用,以筛选汇总后的行。HAVING子句影响分组的方式类似于WHERE子句影响单独行的方式。当你使用HAVING子句时,PROC SQL只显示满足HAVING表达式的分组。
PROC SQL在分组数据并应用聚合函数后应用HAVING条件。可以将HAVING子句视为汇总后过滤。
例如,HAVING TotalCustomers > 6000将分组限制为仅包含美国客户数超过6000的州。结果将显示三个州:CA、TX和NY。
以下是包含HAVING子句的查询示例:
SELECT State, COUNT(*) AS TotalCustomers
FROM CustomerTable
GROUP BY State
HAVING TotalCustomers > 6000
ORDER BY TotalCustomers DESC;
HAVING表达式包含新列TotalCustomers的值,该列计算每个组内的行数。
总结

本节课中,我们一起学习了如何使用GROUP BY子句对数据进行分组,以及如何结合聚合函数(如COUNT)计算分组统计量。我们了解了WHERE子句在分组前的过滤作用,以及HAVING子句在分组后对汇总结果进行筛选的重要性。通过实际示例,我们掌握了如何查询每个州的客户数量,并筛选出客户数超过特定值的州。
027:使用GROUP BY子句分析数据分组 🧮
在本节课中,我们将学习如何使用SQL中的GROUP BY子句对数据进行分组,并为每个分组生成汇总统计信息。这是数据分析中聚合数据的关键技术。
概述
我们将从一个简单的查询开始,逐步添加分组、聚合函数和筛选条件,最终实现对客户数据按不同维度(如州、银行ID、就业状态)进行分组统计,并筛选出满足特定条件的组。
数据分组基础
上一节我们介绍了基本的SQL查询结构。本节中,我们来看看如何使用GROUP BY子句对数据进行分组。
首先,我们从customer表中选择state列,并限制返回10行以便于代码开发。同时,我们使用GROUP BY state进行分组。
SELECT state
FROM customer
OBS=10
GROUP BY state;
运行此查询后,虽然结果看起来正常,但日志中会出现一个警告。该警告指出,由于未使用汇总函数,GROUP BY子句已被转换为ORDER BY子句。这意味着分组并未实际生效。
使用聚合函数计数
我们的目标是统计每个州的客户数量。为此,我们需要在SELECT子句中使用COUNT聚合函数。
以下是修改后的查询。我们使用COUNT(*)来计算行数,并将结果列命名为totalCustomer,同时使用COMMA7.格式进行美化。
SELECT state,
COUNT(*) AS totalCustomer FORMAT=COMMA7.
FROM customer
OBS=1000
GROUP BY state;
运行此查询,我们现在可以看到每个州对应的客户数量。在代码开发阶段,我们使用OBS=1000限制结果。当对代码有信心后,可以移除该限制以查看完整数据。
SELECT state,
COUNT(*) AS totalCustomer FORMAT=COMMA7.
FROM customer
GROUP BY state;
对结果进行排序
为了更清晰地查看哪个州的客户最多,我们可以使用ORDER BY子句对totalCustomer列进行降序排序。
SELECT state,
COUNT(*) AS totalCustomer FORMAT=COMMA7.
FROM customer
GROUP BY state
ORDER BY totalCustomer DESC;
按其他维度分组
我们刚刚统计了按州分组的客户数。现在,如果我们想按bankID来统计客户数量呢?
只需将SELECT和GROUP BY子句中的state列替换为bankID列即可。
SELECT bankID,
COUNT(*) AS totalCustomer FORMAT=COMMA7.
FROM customer
GROUP BY bankID
ORDER BY totalCustomer DESC;
从结果中可以看到,银行ID为101010的客户最多。同时,我们注意到GROUP BY子句会包含缺失值(NULL),结果显示约有4900名客户没有银行ID。
多列分组
接下来,我们进行更深入的分析。我想同时按bankID和employed(就业状态)来统计客户数量。
这很简单,只需在SELECT和GROUP BY子句中同时添加employed列。
SELECT bankID,
employed,
COUNT(*) AS totalCustomer FORMAT=COMMA7.
FROM customer
GROUP BY bankID, employed
ORDER BY totalCustomer DESC;
运行后,结果将显示bankID和employed每个唯一组合对应的客户总数。
使用HAVING子句筛选分组
目前的结果集包含了所有分组。但有时我们只关心客户总数超过特定阈值(例如10,000)的分组。这时不能使用WHERE子句,因为它用于筛选行,而不是分组。
初学者可能会尝试以下错误写法:
SELECT bankID,
employed,
COUNT(*) AS totalCustomer FORMAT=COMMA7.
FROM customer
WHERE CALCULATED totalCustomer > 10000 /* 错误! */
GROUP BY bankID, employed;
运行上述代码会产生错误,提示“摘要函数仅限于SELECT和HAVING子句”。这意味着对聚合结果的筛选必须使用HAVING子句。
正确的做法是:将筛选条件移到GROUP BY之后,并将WHERE改为HAVING。
SELECT bankID,
employed,
COUNT(*) AS totalCustomer FORMAT=COMMA7.
FROM customer
GROUP BY bankID, employed
HAVING CALCULATED totalCustomer > 10000
ORDER BY totalCustomer DESC;
虽然在此例中不使用CALCULATED关键字也可能得到正确结果,但最佳实践是使用它来明确引用计算列,以确保查询结果的确定性,避免在其他复杂查询中出现意外情况。
运行正确的查询后,我们将只看到客户总数大于10,000的bankID和employed组合。





总结
本节课中我们一起学习了GROUP BY子句的核心用法:
- 基础分组:使用
GROUP BY对一列或多列进行分组。 - 聚合函数:必须与
COUNT、SUM、AVG等聚合函数结合使用,才能为每个组生成汇总值。 - 结果排序:使用
ORDER BY对聚合结果进行排序。 - 分组筛选:使用
HAVING子句(而非WHERE子句)来筛选基于聚合结果的分组。 - 明确引用:使用
CALCULATED关键字引用计算列是良好的编程习惯。

通过掌握这些概念,你已经能够对数据进行有效的分组和汇总分析,这是生成业务报告和进行数据洞察的基础。
028:日期和时间数据汇总 📅⏰
在本节课中,我们将学习如何在SAS中对日期和时间数据进行汇总分析。你将掌握如何从日期时间值中提取日期或时间部分,并利用这些提取出的信息进行分组和统计。
概述
当在SAS中汇总日期或时间数据时,你可以使用特定的SAS日期或时间函数。要使用这些函数,你的数据不能是日期时间值。数据必须是日期值(用于汇总月份、年份、天数等信息)或时间值(用于汇总小时、分钟、秒等信息)。这可以通过DATEPART或TIMEPART函数轻松实现。
提取日期与时间部分
上一节我们介绍了汇总日期时间数据的基本前提。本节中我们来看看如何从日期时间值中分离出日期和时间。
DATEPART和TIMEPART函数唯一的必需参数是日期时间值。假设交易表transaction包含一个合并了日期和时间的datetime列。
通过使用DATEPART和TIMEPART函数,我们可以分别提取日期值和时间值,并创建两个新列:date和time。这些函数返回原始的SAS日期和SAS时间数值,随后我们可以对其进行格式化以改善显示效果。
以下是实现此操作的代码示例:
data work.transaction_extracted;
set sashelp.transaction;
date = datepart(datetime);
time = timepart(datetime);
format date date9. time time8.;
run;
基于提取信息进行数据汇总
现在我们有了date和time列,就可以开始汇总交易数据,以分析诸如“客户在哪个月份或哪个季度消费最多?”等问题。
以下是可以进行的汇总分析步骤:
- 使用
MONTH、QTR、YEAR等函数从date列提取更具体的时间单位。 - 使用
GROUP BY语句按这些时间单位对数据进行分组。 - 结合
SUM、MEAN、MEDIAN等汇总函数计算关键指标。
嵌套函数的高级应用
我们也可以将DATEPART函数嵌套在MONTH函数内部,直接提取数字月份。

创建新值后,你可以将列命名为month,然后在GROUP BY子句中使用该列配合汇总函数。
在这个例子中,我们按月份分组,并找出每个月的消费金额中位数。
以下是相应的代码和结果示意图:
proc sql;
create table work.monthly_median as
select month(datepart(datetime)) as month,
median(amount) as median_amount
from sashelp.transaction
group by calculated month;
quit;

总结

本节课中我们一起学习了SAS中日期时间数据汇总的核心技巧。关键点包括:使用DATEPART和TIMEPART函数分离日期与时间成分,利用提取出的日期或时间部分进行分组,以及通过嵌套函数(如MONTH(DATEPART(datetime)))直接获取所需的时间维度进行聚合分析。掌握这些方法能帮助你更有效地从时间序列数据中提取商业洞察。
029:使用布尔表达式统计行数
在本节课中,我们将学习如何使用布尔表达式,在按州汇总客户数据时,统计特定年龄段的客户数量。
概述
上一节我们介绍了基本的数据汇总方法。本节中,我们来看看一个更具体的场景:如何统计每个州内年龄小于25岁和大于64岁的客户人数。为了实现这个目标,我们将使用一种称为“布尔表达式”的技术。
理解布尔表达式

布尔表达式是一种计算结果为两个值之一的表达式:1(代表“真”)或0(代表“假”)。
例如,我们可以创建一个表达式来判断客户的年龄是否小于25岁。如果条件成立,表达式的值就是1;如果不成立,值就是0。
核心概念公式:
under_25 = (age < 25) // 若年龄小于25,值为1(真);否则为0(假)
over_64 = (age > 64) // 若年龄大于64,值为1(真);否则为0(假)
应用布尔表达式进行统计
在SAS的PROC SQL或PROC SUMMARY等过程中,我们可以直接将这些布尔表达式用于统计。系统会对每一行数据计算表达式的值(1或0),然后在分组汇总时,对这些1和0进行求和。由于只有满足条件的行贡献1,求和结果自然就是满足条件的行数,即客户数量。

以下是实现此目标的关键步骤思路:
- 创建布尔表达式列:在查询或数据步中,定义两个新列,例如
under_25和over_64,其值由上述的布尔表达式决定。 - 按州分组:指定分组依据为
state。 - 汇总求和:对每个州,将
under_25和over_64列的值分别求和。每个和就代表了该州对应年龄段的客户总数。
示例代码思路(PROC SQL):
PROC SQL;
SELECT
state,
SUM( (age < 25) ) AS count_under_25,
SUM( (age > 64) ) AS count_over_64
FROM
customer_table
GROUP BY
state;
QUIT;
总结

本节课中我们一起学习了如何利用布尔表达式来统计满足特定条件的数据行数。关键点在于理解布尔表达式会为每一行返回1(真)或0(假),通过对这些值进行求和,就能轻松得到符合条件的记录总数。这种方法简洁高效,是进行条件计数的强大工具。
SAS高级程序员专项课程:P30:使用布尔表达式汇总数据演示
在本节课中,我们将学习如何在汇总函数中使用布尔表达式来对数据进行分类和统计。我们将创建一个查询,计算每个州年龄在25岁以下和64岁以上的客户数量。
我们将通过一个查询来创建名为 Custom_account 的表。查询从选择 state 列开始,并使用 year diff 函数创建一个名为 age 的新列。
运行查询并查看结果。我们得到了新表,其中包含州名和年龄值。
现在,我们希望汇总年龄在25岁以下和64岁以上的客户数据。让我们回到编辑器。
我们将使用年龄值,指定小于25岁的条件为“under 25”。这里我们使用了布尔表达式,如果年龄小于25岁,则结果为1,否则为0。
我们可以看到新列“under 25”中显示了一系列0和1。其中0代表假,1代表真。例如,第6行的值为1,表示该客户年龄在25岁以下。
接下来,我们继续找出所有年龄大于64岁的客户。复制之前的函数,添加一个逗号,然后粘贴。将条件改为大于64,并将此列命名为“over 64”。
运行查询以确保一切正常。现在,我们有了第三列“over 64”,其中也包含0和1。
我希望按州来汇总这些数据,即查看每个州有多少客户年龄在25岁以下和64岁以上。因此,我们将使用 sum 函数来对值为1的项进行求和。

这将分别对两列进行求和。然后,我们添加 GROUP BY 子句,按州进行分组。我将移除 NOs equals 选项,因为我对代码有信心,可以在生产环境中移除它。

我们得到了新表 Custom_count,其中包含州名以及对应的计数。例如,在阿肯色州,有60名客户年龄在25岁以下,57名客户年龄在64岁以上。我们可以查看每个州、华盛顿特区和波多黎各的相应数据。

在本节课中,我们一起学习了如何利用布尔表达式在SQL查询中创建条件列,并使用 SUM 函数配合 GROUP BY 子句,轻松地按类别对数据进行汇总统计。这种方法非常适用于生成清晰的分组计数报告。
031:创建表 📊
在本节课中,我们将学习使用Proc SQL创建新表的三种不同方法。了解如何根据现有数据或结构来生成新表,是高效管理数据的关键步骤。
概述
Proc SQL提供了三种创建新表的方法。选择哪种方法取决于你的起始条件。如果已有一个或多个包含所需数据的现有表,你可以通过查询将部分或全部现有列和行复制到新表中。

三种创建表的方法
上一节我们介绍了创建表的总体思路,本节中我们来看看具体的三种方法及其适用场景。
1. 通过查询复制数据
如果现有表已包含所需数据,你可以使用查询来复制数据并直接创建已填充数据的新表。
以下是其基本语法:
PROC SQL;
CREATE TABLE 新表名 AS
SELECT 列1, 列2
FROM 现有表名
WHERE 条件;
QUIT;
2. 仅复制表结构
如果你从一个现有表开始,也可以仅复制其列结构来创建一个没有行的空表。之后需要在单独的步骤中添加数据。
以下是其基本语法:
PROC SQL;
CREATE TABLE 新表名 LIKE 现有表名;
QUIT;
3. 定义新列结构
如果没有现有表具备你想要的列结构,你可以在代码中定义新列来创建一个空表。

以下是其基本语法:
PROC SQL;
CREATE TABLE 新表名 (
列名1 数据类型,
列名2 数据类型
);
QUIT;
创建表的用途
由于用Proc SQL创建的表就是SAS数据集,你可以将它们用作DATA步或其他SAS统计、可视化过程的输入。使用SQL创建表有多种原因,例如为不同的报告和分析获取更小的数据子集,或与同事共享数据。
另一个原因是通过SQL汇总或过滤数据,然后在SAS的DATA步中使用。如果你需要对数据操作进行更精细的控制,DATA步是完成此任务的完美工具。
总结

本节课中我们一起学习了使用Proc SQL创建新表的三种方法:通过查询复制数据、仅复制表结构以及定义新列结构。结合使用SAS和SQL能使你成为更高效、更灵活的程序员,这将允许你充分利用两种语言的优势。
032:从查询创建表 📊
在本节课中,我们将学习如何使用SQL查询来创建新的数据表。这种方法的核心是通过复制一个或多个现有表中的列和行来生成新表。

概述
通过查询创建表,是使用CREATE TABLE语句配合查询来实现的。这种方法最常用于创建现有表的子集或超集。它是唯一一种能在单个语句中同时完成表的创建和数据填充的方法。

从查询结果创建表
要从查询结果创建表,可以使用CREATE TABLE语句,指定新表名,后跟AS关键字和查询语句。

基本语法:
CREATE TABLE 新表名 AS
SELECT 列1, 列2, ...
FROM 源表名
WHERE 条件;
在查询的开头添加CREATE TABLE,会指示SAS创建一个物理表,而不是仅仅输出查询结果。

新表的属性
需要注意的是,以这种方式创建的表,其数据来源于查询FROM子句所引用的表。

新表的列名由查询SELECT子句中的列表指定。列的属性(如类型、长度、输入格式、输出格式和扩展属性)与所选的源列相同,除非查询中包含了列修饰符(如使用AS重命名或计算新列)。
示例解析
我们通过一个具体例子来理解这个过程。

示例代码:
CREATE TABLE work.high_credit AS
SELECT customer_id, credit_score, income
FROM sashelp.credit_data
WHERE credit_score > 700;

在这个例子中,CREATE TABLE语句通过查询创建了一个名为high_credit的新表。只有credit_score值大于700的行才会被插入到这个新表中。
重要注意事项

新创建的表不会自动显示在SAS输出窗口中,除非你后续对这个表执行查询操作。

以下是使用此方法时需要记住的几个关键点:
- 使用SQL时,一个查询只能创建一个表。
- 如果你需要在一个步骤中创建多个表,可以考虑使用DATA步来实现。

总结
本节课我们一起学习了如何使用SQL的CREATE TABLE ... AS SELECT ...语句从查询结果创建新表。我们了解了其语法结构、新表属性的继承规则,并通过示例看到了如何创建满足特定条件的数据子集。这是数据准备和子集化中一个非常高效且常用的技巧。
SAS高级程序员专项课程:P33:使用PROC SQL创建表结构 🗂️
在本节课中,我们将学习如何使用PROC SQL过程来创建表结构。我们将重点介绍两种主要方法:一种是基于现有表结构创建新表,另一种是定义全新的空表。
使用LIKE子句复制现有表结构
上一节我们介绍了PROC SQL的基本概念,本节中我们来看看如何基于现有表快速创建新表结构。第一种方法是使用CREATE TABLE语句中的LIKE子句。此方法会复制您在LIKE子句后引用的表的列结构。

核心语法:
CREATE TABLE 新表名 LIKE 原表名;
例如,我们想创建一个名为high_credit的新表,它仅包含SQ.cuser表中的first_name、last_name、user_id和credit_score这几列。
最简便的方法是结合使用CREATE TABLE的LIKE方法和SAS数据集选项。您可以在CREATE TABLE语句中,使用KEEP=数据集选项来指定需要保留的列。
示例代码:
CREATE TABLE work.high_credit LIKE SQ.cuser (KEEP=first_name last_name user_id credit_score);
您也可以使用DROP=数据集选项来删除不需要的列,从而隐式地保留其余列。
示例代码:
CREATE TABLE work.high_credit LIKE SQ.cuser (DROP=address phone_number);

定义全新的空表
除了复制现有结构,您还可以从头开始定义一个全新的空表。这需要在CREATE TABLE语句中明确定义每一列。
以下是定义列时需要指定的组成部分:
- 列名:为每一列指定一个唯一的名称。
- 数据类型:定义列中数据的类型,例如字符型(CHAR)或数值型(NUM)。
- 长度:对于字符型列,指定其最大长度。
- 格式:为列指定一个输出格式,例如日期格式。

例如,以下CREATE TABLE语句将创建一个包含四列的employee表:
示例代码:
CREATE TABLE work.employee (
first_name CHAR(20),
last_name CHAR(20),
DOB NUM FORMAT=MMDDYY10.,
MID NUM FORMAT=Z6.
);
这段代码创建了employee表,其中包含:
- 两个长度为20的字符型列:
first_name和last_name。 - 一个使用
MMDDYY10.格式的日期列DOB。 - 一个使用
Z6.格式的数值型列MID。

本节课中我们一起学习了使用PROC SQL创建表结构的两种核心方法。您学会了如何使用LIKE子句基于现有表快速复制结构,也掌握了如何通过定义列名、数据类型和格式来创建一个全新的空表。这些技能是构建和管理SAS数据集的基础。
034:向表中插入行 📝
在本节课中,我们将学习如何使用 INSERT 语句向SAS数据表中添加新的数据行。无论表是空的还是已有数据,INSERT 语句都能帮助我们高效地插入数据。
概述

创建表之后,可以使用 INSERT 语句向表中插入数据值。该语句适用于空表或已包含数据的表。INSERT 语句首先向现有表添加一个新行,然后将指定的值插入该行。指定值的方式有两种:使用 SET 子句或 VALUES 子句。
使用查询结果插入行
为了将数据从一个现有表添加到另一个表,可以在 INSERT 语句中指定一个查询。例如,以下 INSERT 语句使用查询将数据行添加到 high_credit 表中。查询返回的行将被插入到表中。如果表中已有行,新行将被追加到末尾。
默认情况下,SELECT 子句为目标表中的每一列指定值,并且值的顺序必须与目标表中列的顺序匹配。

以下是使用查询插入行的基本语法:
INSERT INTO 目标表名
SELECT 列1, 列2, ...
FROM 源表名
WHERE 条件;
在这个例子中,SELECT 子句为 high_credit 表中的四列指定值:first_name、last_name、user_id 和 credit_score。并非所有列都是必需的。如果表中的列数多于列出的列,则未列出的列将被设置为缺失值。

使用 VALUES 子句插入行
可以使用带有 VALUES 子句的 INSERT 语句向单行中的列添加值。要向表中添加多行值,可以指定多个 VALUES 子句。

以下是使用 VALUES 子句插入多行数据的基本语法:
INSERT INTO 表名 (列1, 列2, 列3, ...)
VALUES (值1a, 值2a, 值3a, ...),
(值1b, 值2b, 值3b, ...);
在这个例子中,我们希望向 employee 表中插入两行数据,因为我们有了新员工。我们首先指定 INSERT INTO 语句,后跟表名和要插入的列:first_name、last_name、dob 和 mpid。在表名后指定列时,位置很重要。同样,并非所有列都是必需的。如果表中的列数多于列出的列,则未列出的列将被设置为缺失值。
VALUES 子句指定我们要插入的值。指定值时,顺序必须与括号内关键字后指定的列的位置匹配。我们指定新员工 Diego Lopez 的名字、姓氏、生日和员工ID号。对第二个员工也进行同样的操作。
使用 SET 子句插入行
也可以使用 SET 子句来指定或更改行中一个或多个列的值。SET 子句包含一个或多个列名和值对。在每一对中,列名和值由等号连接。

与前面的例子不同,列可以以任何顺序出现在 SET 子句中。在这个 INSERT 语句中,SET 子句将值插入到 employee 表的四个列中。
以下是使用 SET 子句插入数据的基本语法:
INSERT INTO 表名
SET 列1 = 值1,
列2 = 值2,
列3 = 值3;
需要注意的是,这些列必须已经存在于表中。


总结

本节课中,我们一起学习了如何使用 INSERT 语句向SAS数据表添加新行。我们探讨了三种主要方法:使用查询结果插入多行数据、使用 VALUES 子句插入单行或多行数据,以及使用 SET 子句以键值对形式插入数据。理解这些方法将帮助您灵活高效地管理和填充数据表。
035:在 PROC SQL 中删除表
在本节课中,我们将学习如何在 PROC SQL 过程中使用 DROP TABLE 语句来删除数据表。这是管理数据环境、清理临时表或移除不再需要的数据集的重要操作。
概述

有时,在 SAS 或数据库中,如果你拥有适当的权限,可能需要删除(或称为“丢弃”)一个表。DROP TABLE 语句就是用于执行此操作的 SQL 命令。
使用 DROP TABLE 语句
上一节我们介绍了删除表的需求,本节中我们来看看具体的操作方法。DROP TABLE 语句的基本语法非常简单。
其核心语法公式如下:
DROP TABLE table-name;
在这个语句中,table-name 是你希望从当前库或数据库中永久移除的表的名称。执行此操作后,该表及其所有数据将被删除,且通常无法撤销,因此使用前需谨慎确认。
操作步骤与注意事项
以下是使用 DROP TABLE 语句时需要遵循的步骤和关键点:

- 确认权限:确保你在 SAS 库或连接的数据库中拥有删除表的权限。
- 确认表名:准确指定要删除的表的名称。如果表存在于非默认库中,需要使用两级名称,格式为
library-name.table-name。 - 执行语句:在
PROC SQL过程步中提交DROP TABLE语句。 - 验证结果:删除操作完成后,可以尝试查询该表或查看库内容来确认表已被成功移除。
重要提示:DROP TABLE 操作是永久性的。被删除的表无法通过 SAS 的撤销功能恢复。建议在执行删除前对重要数据进行备份。

总结
本节课中我们一起学习了如何在 PROC SQL 中使用 DROP TABLE 语句。我们了解了其基本语法 DROP TABLE table-name;,并强调了在执行删除操作前确认权限和表名的重要性,因为该操作会永久移除表及其所有数据。掌握此语句有助于你有效地管理 SAS 数据环境。
SAS高级程序员专项课程:P36:使用字典表探索数据环境 🔍
在本节课中,我们将学习如何使用SAS的字典表来高效地探索数据环境。当您接手多个不同的数据表并希望快速了解其内容时,字典表是强大的工具。
假设我们继承了许多不同的数据表,并希望熟悉其内容。我们如何才能高效地开始探索这些表和库?如果我们想查看特定库中定义的所有表,或者查看哪些表包含特定的列,该怎么办?
此外,我们可能还需要检查相关列在所有表中是否具有相同的数据类型和长度。或者,我们可能想查看所有已分配给我们的库。
我们可以使用字典表来轻松、高效地查找这些信息。
什么是字典表?📚
上一节我们提出了探索数据环境的需求,本节中我们来看看能满足这一需求的工具——字典表。字典表是SAS系统提供的一组只读的特殊表,它们包含了关于当前SAS会话中所有库、数据表、列、格式等元数据信息。您可以像查询普通SAS数据集一样查询它们。
如何访问字典表?🔧
访问字典表主要有两种方式:
- 通过
DICTIONARY库:例如DICTIONARY.TABLES。 - 通过
SASHELP视图:例如SASHELP.VTABLE。SASHELP视图是DICTIONARY表的易用接口。
以下是几个最常用的字典表及其用途:
DICTIONARY.TABLES/SASHELP.VTABLE:包含所有SAS数据表(数据集)的信息。DICTIONARY.COLUMNS/SASHELP.VCOLUMN:包含所有SAS数据表中列的信息。DICTIONARY.LIBNAMES/SASHELP.VLIBNAM:包含所有已分配库的信息。

实践应用示例 💻
了解了核心的字典表后,我们通过几个具体场景来看看如何应用它们。

场景一:查看指定库中的所有表
假设我们想查看 WORK 库中的所有表。
proc sql;
select memname /* 表名 */, nobs /* 观测数 */, nvar /* 变量数 */
from dictionary.tables
where libname = ‘WORK‘; /* 指定库名,注意必须大写 */
quit;
场景二:查找包含特定列的所有表
如果我们想找出所有包含名为 CustomerID 的列的表。

proc sql;
select libname, memname, name as column_name, type, length
from dictionary.columns
where upcase(name) = ‘CUSTOMERID‘; /* 使用upcase避免大小写问题 */
quit;
场景三:检查列的一致性
要检查不同表中名为 Sales 的列的数据类型和长度是否一致。
proc sql;
select libname, memname, name, type, length
from dictionary.columns
where upcase(name) = ‘SALES‘
order by type, length;
quit;
通过这个查询结果,您可以快速对比 Sales 列在不同表中的定义。

场景四:查看所有已分配的库
最后,如果我们想了解当前会话中有哪些库可用。
proc sql;
select libname, path, engine
from dictionary.libnames;
quit;

总结 🎯
本节课中,我们一起学习了SAS字典表的强大功能。我们了解到,字典表是存储SAS环境元数据的特殊表,通过 DICTIONARY 库或 SASHELP 视图可以访问它们。我们重点掌握了四个核心应用:查看库中的表、查找包含特定列的表、检查列属性的一致性以及列出所有已分配的库。利用字典表进行探索,能让我们在面对陌生或复杂的数据环境时,迅速掌握全局信息,为后续的数据处理和分析打下坚实基础。
037:字典表 📚
在本节课中,我们将要学习SAS字典表。字典表是SAS会话中一组特殊的只读表,它们包含了关于当前SAS环境、数据、库和系统设置的元数据信息。理解并掌握如何使用字典表,对于监控和管理SAS会话至关重要。
什么是字典表?
上一节我们介绍了SAS会话的基本概念,本节中我们来看看字典表。字典表包含了关于每个SAS会话或批处理作业的信息。

这些特殊的表在SAS会话初始化后立即可用,并且在整个会话期间由SAS自动更新。
字典表是只读的,包含了关于SAS会话的数据或元数据。
它们检索与当前SAS会话相关的所有SAS库、SAS表、SAS系统选项以及外部文件的信息。


如何访问字典表?
SAS自动将一个名为DICTIONARY的特殊保留库分配给字典表,该库只能在PROC SQL过程中访问。
然而,SAS提供了基于字典表的PROC SQL视图,这些视图可以在其他SAS过程步以及数据步中使用。
这些视图存储在SASHELP库中,通常被称为SASHELP视图。
字典表常用于监控和管理SAS会话,因为相比其他来源(如PROC DATASETS)的输出,其数据更容易被操作。
你可以像查询任何其他表一样查询字典表,包括使用WHERE子句进行子集筛选、对结果排序以及创建PROC SQL视图。
请注意,字典表中的许多字符值都以全大写形式存储,因此在设计查询时应相应处理。
核心字典表介绍
虽然字典表有很多,但我们将重点介绍三个核心表:DICTIONARY.TABLES、DICTIONARY.COLUMNS和DICTIONARY.LIBNAMES。
以下是这三个核心表的功能简介:

DICTIONARY.TABLES:包含所有SAS库中所有数据集的元数据信息,例如表名、库名、观测数、变量数等。DICTIONARY.COLUMNS:包含所有SAS数据集中所有变量的详细信息,如变量名、类型(字符或数值)、长度、格式、标签等。DICTIONARY.LIBNAMES:包含当前SAS会话中定义的所有库的引用信息。
理解这些表非常重要。


总结
本节课中我们一起学习了SAS字典表。我们了解到字典表是存储SAS会话元数据的特殊只读表,可以通过PROC SQL直接访问,或通过SASHELP视图在其他地方使用。我们重点介绍了三个核心字典表:DICTIONARY.TABLES、DICTIONARY.COLUMNS和DICTIONARY.LIBNAMES,它们是管理和探索SAS环境的有力工具。
038:使用字典表示例 🔍
在本节课中,我们将学习如何使用SAS的字典表来查询元数据信息。我们将重点探索三个核心的字典表:DICTIONARY.TABLES、DICTIONARY.COLUMNS和DICTIONARY.LIBNAMES,并了解它们在SASHELP视图中的等价物。通过实际查询示例,你将掌握如何获取关于库、表和列的详细信息。
探索 DICTIONARY.TABLES 表

首先,我们从探索 DICTIONARY.TABLES 表开始。这个表位于SAS的字典库中,包含了所有可用表的信息。

为了限制输出行数,我们使用 NOBS=100 选项。以下是查询该表结构的代码:
DESCRIBE TABLE dictionary.tables;
运行上述代码后,我们可以在日志中查看结果。DESCRIBE TABLE 语句会显示列名、列类型以及相关的标签信息。了解实际的列名非常重要。

接下来,我们执行一个查询来查看表中的具体数据:
PROC SQL NOBS=100;
SELECT * FROM dictionary.tables;
QUIT;
在结果中,我们可以看到诸如库名(如WORK、SQ,均为大写)、成员名(即表名,也是大写)、成员类型,以及其他各种信息,例如创建或修改日期、物理观测数、长度、变量(列)数量等。

筛选特定库中的表
如果我们只想关注特定的库,例如SQ库,该怎么办呢?我们可以使用 WHERE 子句来筛选。
以下是查询SQ库中所有表的代码:

PROC SQL;
SELECT * FROM dictionary.tables
WHERE libname = ‘SQ‘;
QUIT;


注意,库名 ‘SQ‘ 必须使用大写。运行此查询后,我们将看到SQ库中的每一个表及其详细信息,总计大约有27个表。

在SAS中,DICTIONARY.TABLES 的等价视图是 SASHELP.VTABLE。我们可以通过以下方式获得相同的信息:
PROC PRINT DATA=sashelp.vtable;
WHERE libname = ‘SQ‘;
RUN;
默认情况下,PROC PRINT 不使用列标签,而是显示实际的列名。了解字典表和SASHELP视图的这两种用法,取决于你的具体使用场景。

探索 DICTIONARY.COLUMNS 表

上一节我们介绍了如何查询表信息,本节中我们来看看如何获取表中列的详细信息。我们将使用 DICTIONARY.COLUMNS 表。
首先,查看该表的结构:

DESCRIBE TABLE dictionary.columns;

查看日志,可以看到列名与 DICTIONARY.TABLES 类似,但包含的是关于列的不同信息。接着,我们查询SQ库中所有表的列信息:
PROC SQL;
SELECT * FROM dictionary.columns
WHERE libname = ‘SQ‘;
QUIT;

在结果中,我们可以看到库名(仅为SQ)、成员名(表名),以及每个表中每一列的详细信息,包括列名、类型、长度、位置等。这是比较不同表中同名列的属性(如类型、长度和格式)是否一致的绝佳方法。
DICTIONARY.COLUMNS 在SASHELP中的等价视图是 SASHELP.VCOLUMN。以下是示例代码:
PROC PRINT DATA=sashelp.vcolumn (OBS=100);
RUN;
同样,我们可以看到相同的信息。使用 PROC PRINT 过程时,默认不显示列标签。

探索 DICTIONARY.LIBNAMES 表
最后,我们来探索 DICTIONARY.LIBNAMES 表,它包含了已定义库的信息。

首先,查看其结构:

DESCRIBE TABLE dictionary.libnames;
查看日志,可以看到列数较少,但包含了库名、引擎、库路径等信息。接着,查询所有库的信息:

PROC SQL;
SELECT * FROM dictionary.libnames;
QUIT;

结果中显示了每个库的名称、使用的引擎、库的物理路径等信息。如果我们只想查看SQ库,可以添加 WHERE 子句:

PROC SQL;
SELECT * FROM dictionary.libnames
WHERE libname = ‘SQ‘;
QUIT;
一个有用的功能是,你可以查看当前连接的所有不重复的库。以下是查询代码:
PROC SQL;
SELECT DISTINCT libname FROM dictionary.libnames;
QUIT;

运行后,将列出所有已连接的库(你的列表可能略有不同)。这是检查所有可用库的一个好方法。

同样,在SAS编程(如DATA步或PROC步)中,也可以使用等价视图 SASHELP.VLIBNAM:
PROC PRINT DATA=sashelp.vlibnam;
RUN;
运行此过程,我们将看到所有库的相同信息,其中也包括SQ库。
总结
本节课中,我们一起学习了如何使用SAS字典表来查询元数据。
- 我们首先探索了
DICTIONARY.TABLES,用于获取所有表的信息。 - 接着,我们学习了如何使用
DICTIONARY.COLUMNS来查看表中每一列的详细属性。 - 最后,我们了解了
DICTIONARY.LIBNAMES,用于查询已定义库的信息。

我们还介绍了这些字典表在 SASHELP 视图(如 VTABLE、VCOLUMN、VLIBNAM)中的等价物。掌握查询这些元数据表的方法,能帮助你更高效地管理和理解SAS环境中的数据。
039:SQL连接概述 🧩
在本节课中,我们将要学习SQL连接(JOIN)的基本概念。连接是SQL中一项强大的功能,它允许你将两个或多个表中的数据基于一个或多个共同的列组合起来,从而进行更复杂的查询和分析。
上一节我们介绍了SQL的基础知识,本节中我们来看看如何将多个表的数据关联起来。
连接的基本概念
SQL连接是指,当你需要将两个或更多表格的数据,基于一个或多个列进行水平合并时,所使用的操作。
我们将讨论几种主要的连接类型。
以下是本节课将要涵盖的连接类型:
- 内连接:仅返回两个表中匹配的行。
- 外连接:返回一个表中的所有行,以及另一个表中匹配的行(不匹配的部分用NULL填充)。
- 复杂连接:涉及多个表或复杂条件的连接操作。
本节课中我们一起学习了SQL连接的核心思想及其主要类型。理解这些连接方式是进行高效数据查询和整合的关键。在接下来的课程中,我们将逐一深入探讨每种连接的具体用法和场景。
040:连接表
在本节课中,我们将学习如何使用SQL的JOIN操作,将多个表中的数据水平合并,以创建包含更完整信息的报告或输出表。
理解连接的概念
上一节我们介绍了SQL的基本查询,本节中我们来看看如何组合多个表的数据。
假设你需要创建一个报告,其中包含来自small_customer表的客户人口统计信息,并结合来自small_transaction表的客户交易信息。你希望每一行都包含同一客户的所有信息。
SQL使用连接来水平合并表。请求连接涉及将一个表中的一行数据与第二个表中的对应行进行匹配。匹配通常基于两个表中的一个或多个列进行。
主键与外键
以下是理解连接的基础概念。
- 主键:在一个表中,其值能唯一标识每一行的列。在
small_customer表中,account_id是主键。 - 外键:一个表中的列,它引用另一个表中的主键。在
small_transaction表中,account_id是外键。

你可以通过使用主键account_id在PROC SQL中组合或连接这些表。连接将来自多个源表的数据水平合并,以生成报告或输出表,而源表本身保持完整且不被修改。
笛卡尔积(交叉连接)
要理解SQL如何处理连接,首先需要了解笛卡尔积或交叉连接的概念。
最基本的连接类型是简单地在SELECT语句的FROM子句中列出多个表,并用逗号分隔。例如,一个连接了8行的small_customer表和12行的small_transaction表的查询。
一个在FROM子句中列出多个表但没有指定行匹配条件的附加子句的查询,会生成一个笛卡尔积。笛卡尔积中的行数是参与连接的各表行数的乘积。
公式:总行数 = 表1行数 × 表2行数

第一表中的每一行都与第二表中的每一行相结合。运行此查询时,笛卡尔积会创建一个包含96行(8 × 12)的报告。
在处理大型表时,笛卡尔积很少是你想要的结果,因为它可能创建不必要的大型报告或表,甚至拖慢系统资源。

本节课中我们一起学习了SQL连接的基本概念,包括主键、外键的作用,以及最基础的连接类型——笛卡尔积(交叉连接)的原理与影响。理解这些是掌握后续各种具体连接方式(如内连接、外连接)的基础。
041:连接类型
在本节课中,我们将学习SQL中的连接类型。连接是组合两个或多个表中数据的关键操作。我们将重点介绍内连接和外连接,并通过简单的图示和示例来解释它们的工作原理。
概述
通常,我们需要的不是两个表所有可能的组合(笛卡尔积),而是其一个子集。因此,我们需要更具体地声明连接类型以及希望在表之间关联的信息片段。SQL支持两种主要连接类型:内连接和外连接。为了解释每种类型的结果,我们将使用简化的维恩图,将两个表表示为圆圈。
内连接

上一节我们介绍了连接的基本概念,本节中我们来看看第一种连接类型——内连接。
内连接在维恩图中代表两个圆圈的重叠区域。它返回一个结果集,其中包含第一个表中所有在第二个表(或FROM子句中列出的其他表)中有一个或多个匹配行的行。


核心概念:内连接只返回两个表中都满足连接条件的行。其基本语法如下:
SELECT 列名
FROM 表A
INNER JOIN 表B
ON 表A.关联列 = 表B.关联列;
外连接
理解了内连接后,我们接下来探讨外连接。外连接比内连接更全面。
所有外连接都返回满足ON或WHERE子句中描述条件的结果,加上那些不满足条件的行。外连接有三种类型。
以下是三种外连接的详细说明:
- 完全外连接:在维恩图中代表所有区域。它返回所有匹配的行,加上两个表中所有不匹配的行。
- 左外连接:代表维恩图中的左圆和重叠区域。它返回所有匹配的行,加上第一个(左)表中所有不匹配的行。
- 右外连接:代表维恩图中的右圆和重叠区域。它返回所有匹配的行,加上第二个(右)表中所有不匹配的行。

核心概念:外连接会保留至少一个表中的所有行,即使它们在另一个表中没有匹配项。常用语法示例:
-- 左外连接
SELECT 列名 FROM 表A LEFT JOIN 表B ON 关联条件;
-- 右外连接
SELECT 列名 FROM 表A RIGHT JOIN 表B ON 关联条件;
-- 完全外连接
SELECT 列名 FROM 表A FULL JOIN 表B ON 关联条件;
总结

本节课中我们一起学习了SQL的两种主要连接类型。内连接仅返回两个表中有匹配的行,适用于需要精确关联数据的场景。外连接(包括左外、右外和完全外连接)则能保留一个或两个表中的所有行,即使没有匹配项,这对于需要包含所有基础数据(如所有客户或所有产品)的分析至关重要。理解这些连接类型的区别是进行有效数据合并和分析的基础。
042:使用内连接合并两个表
在本节课中,我们将学习如何使用内连接(INNER JOIN)来合并两个表。我们将通过账户ID(account ID)来合并客户表和交易表,并返回一个仅包含匹配账户ID值的结果集,从而将每位客户及其交易的所有信息整合在一起。
内连接简介

上一节我们介绍了表连接的基本概念,本节中我们来看看最常用的连接类型之一:内连接。
内连接会根据你指定的连接条件返回匹配的行。其基本语法在 FROM 子句中定义。
内连接语法
以下是内连接的基本语法结构:
FROM table1
INNER JOIN table2
ON table1.column = table2.column;

在 FROM 子句中,首先指定第一个表,然后是关键字 INNER JOIN,接着是第二个表。在表名和连接类型之后,语法要求一个 ON 子句来描述表中行匹配的连接条件。省略 ON 子句会导致语法错误。
等值连接示例
这个内连接的例子也被称为等值连接,因为 ON 子句中使用的是等号(=)。只有两个表中账户ID列值完全相同的行才会被匹配并返回。

其他比较运算符

ON 子句中的条件也可以使用其他比较运算符,例如大于(>)、小于(<),或者特殊的 WHERE 运算符。
限定列名
当引用的两个表中有同名的列时,必须在每个账户ID列引用前加上表名。
以下是处理同名列的方法:
- 限定列名由表名、一个句点(.)和列名组成。
- 使用限定列名可以避免创建模糊的列引用,确保SAS能明确知道你所指的是哪个表中的哪一列。
总结

本节课中我们一起学习了如何使用内连接合并两个表。我们了解了内连接的基本语法,知道了它通过 ON 子句指定的条件返回匹配的行。我们还学习了等值连接的概念,以及当表中有同名列时,必须使用表名来限定列名以避免歧义。掌握内连接是进行高效数据合并与分析的基础。
043:使用PROC SQL执行内连接演示 🧩
在本节课中,我们将学习如何使用SAS的PROC SQL过程步来执行两个表之间的内连接操作。我们将从探索数据表开始,逐步构建连接查询,并学习如何结合WHERE和ORDER BY子句来筛选和排序结果。

探索数据表

首先,我们需要了解将要连接的两个数据表。以下查询用于查看两个表中的所有数据。
SELECT * FROM SQ_SMALL_CUSTOMER;
SELECT * FROM SQ_SMALL_TRANSACTION;
两个数据表都包含一个名为account ID的列,这个列将作为我们连接两个表的关键字段。
执行内连接
现在,让我们开始执行内连接。我们将从两个表中选择几列数据。
以下是执行内连接的基本查询结构。我们首先指定主表,然后使用INNER JOIN关键字连接第二个表,并通过ON子句指定连接条件。

SELECT c.first_name, c.last_name, t.transaction_date, t.merchant, t.amount
FROM SQ_SMALL_CUSTOMER c
INNER JOIN SQ_SMALL_TRANSACTION t
ON c.account_ID = t.account_ID;
在这个查询中,我们选择了客户的名字、姓氏以及交易的日期、商户和金额。由于两个表都有account_ID列,我们在ON子句中使用了表别名(c和t)来明确指定列所属的表。
执行内连接后,结果集只包含两个表中account_ID匹配的行。例如,我们可以看到Gary有一笔交易,Sergio有两笔交易等。
处理列名歧义


如果我们想在结果中看到account_ID列,直接将其加入SELECT列表会导致错误。

-- 此查询会导致错误
SELECT c.first_name, c.last_name, t.transaction_date, t.merchant, t.amount, account_ID
FROM SQ_SMALL_CUSTOMER c
INNER JOIN SQ_SMALL_TRANSACTION t
ON c.account_ID = t.account_ID;
运行上述查询会报错:“ambiguous reference, column account_ID is in more than one table”。这是因为SQL无法确定我们想选择哪个表中的account_ID列。
为了解决这个问题,我们必须在SELECT子句中明确限定列名。在内连接中,由于只返回匹配的行,选择任意一个表中的account_ID都可以。

SELECT c.first_name, c.last_name, t.transaction_date, t.merchant, t.amount, c.account_ID
FROM SQ_SMALL_CUSTOMER c
INNER JOIN SQ_SMALL_TRANSACTION t
ON c.account_ID = t.account_ID;
现在,查询可以成功运行,并且account_ID作为最后一列出现在结果中。

结合其他子句
上一节我们完成了基本的内连接。本节中,我们来看看如何将内连接与之前学过的WHERE和ORDER BY子句结合使用,以进一步筛选和排序数据。
我们可以像在普通查询中一样,在连接查询后使用WHERE子句来筛选行,使用ORDER BY子句来排序结果。

以下是一个结合了所有子句的完整示例。我们将筛选出州为‘NY’的客户,并按照交易金额降序排列结果。
SELECT c.first_name, c.last_name, t.transaction_date, t.merchant, t.amount, c.account_ID
FROM SQ_SMALL_CUSTOMER c
INNER JOIN SQ_SMALL_TRANSACTION t
ON c.account_ID = t.account_ID
WHERE c.state = ‘NY’
ORDER BY t.amount DESC;

运行此查询后,生成的结果报告将只包含州为‘NY’的行,并且所有行按照交易金额从高到低排列。

总结
本节课中,我们一起学习了使用PROC SQL执行内连接的核心步骤。我们首先探索了源表,然后构建了基本的连接查询,并解决了列名歧义的问题。最后,我们演示了如何将内连接与WHERE和ORDER BY子句结合,以生成更具体、有序的数据报告。掌握这些技巧是进行复杂数据合并与分析的基础。
044:SQL内连接替代语法 ⚙️
在本节课中,我们将学习在SQL中创建内连接的另一种语法形式。这种语法不使用INNER JOIN和ON关键字,而是通过逗号和WHERE子句来实现相同的连接效果。
语法结构对比
上一节我们介绍了使用INNER JOIN和ON子句的标准内连接语法。本节中,我们来看看它的替代写法。
核心变化在于:
- FROM子句:用逗号
,分隔要连接的表名,替代INNER JOIN。 - 连接条件:将
ON子句改为WHERE子句。

以下是两种语法的对比示例:

标准语法:
SELECT *
FROM table1
INNER JOIN table2
ON table1.column = table2.column;
替代语法:
SELECT *
FROM table1, table2
WHERE table1.column = table2.column;
功能等效性
在FROM子句中使用逗号分隔表,并在WHERE子句中指定连接列,其功能与使用INNER JOIN关键字和ON子句列出表是完全相同的。
以下代码使用了WHERE构造,但产生的输出结果与之前使用INNER JOIN的代码完全一致。


使用注意事项
虽然替代语法能达到相同目的,但在使用时需要格外小心。
使用WHERE子句创建连接时,如果忘记指定连接条件,可能会无意中创建笛卡尔积。如果涉及的表数据量很大,这将极大地消耗系统资源。
而使用INNER JOIN和ON的语法结构则有助于避免这个问题,因为它明确地将连接条件与表关联在一起。


本节课中我们一起学习了SQL内连接的替代语法。我们了解到,通过逗号分隔FROM子句中的表名,并将连接条件移至WHERE子句,可以实现与标准INNER JOIN ... ON ...语法相同的效果。但需要注意的是,替代语法在遗漏条件时容易产生笛卡尔积,因此在实际编程中应谨慎使用,或优先选择更清晰、更安全的INNER JOIN语法。
045:使用表别名 📝
在本节课中,我们将学习如何在SQL查询中为数据表指定一个别名。别名可以简化查询语句的编写,特别是在涉及多个表的复杂查询中。
概述
SQL允许您在FROM子句中为数据表分配一个别名或昵称。这通过添加可选的AS关键字和您选择的别名来实现。别名是数据表的一个临时替代名称。随后,您可以在查询的其他子句中使用该别名来代替完整的表名,以限定列名。
表别名的定义与作用
上一节我们了解了别名的基本概念,本节中我们来看看它的具体语法和作用。
别名是数据表的一个临时替代名称。其核心语法是在FROM子句中,在表名后使用AS关键字(该关键字是可选的)来指定。
代码示例:
SELECT column_name
FROM table_name AS alias_name;
-- 或者省略 AS 关键字
SELECT column_name
FROM table_name alias_name;
定义别名后,您可以在SELECT、WHERE、JOIN等子句中使用这个别名来引用原表,使代码更简洁清晰。
别名使用实例
理解了别名的定义后,让我们通过一个具体例子来看看它的实际应用。
以下是一个使用别名的查询示例。假设我们有两个表:small_customer(客户表)和small_transaction(交易表)。


在这个例子中,小型客户表的别名是C,小型交易表的别名是T。在查询中,我们可以使用C.column_name和T.column_name的方式来明确指定每一列属于哪个表。
代码示例:
SELECT C.customer_id, C.name, T.transaction_date, T.amount
FROM small_customer AS C
INNER JOIN small_transaction AS T
ON C.customer_id = T.customer_id;
选择别名的技巧
学会了如何使用别名,我们再来探讨一下如何为表选择一个合适的别名。
通常,在分配表别名时,您希望使用一个能代表该表的简称。例如,用Cust代表Customer表,用Prod代表Product表。选择简短且有意义的别名可以提高查询语句的可读性。

再次强调,使用AS关键字是可选的。以下两种写法是等效的:

以下是两种等效的别名定义方式:
FROM table_name AS alias_nameFROM table_name alias_name
总结
本节课中我们一起学习了SQL中表别名的使用。我们了解到,别名是表的临时替代名称,通过FROM table_name [AS] alias的语法定义,它能使多表查询的语句更加简洁和易读。记住,AS关键字是可选的,并且为表选择简短、有代表性的别名是一个好习惯。
046:使用自然连接匹配行
在本节课中,我们将学习SAS PROC SQL中的自然连接。自然连接是一种简化代码的连接方式,它会自动识别并匹配两个表中具有相同名称和数据类型的列。
自然连接概述
上一节我们介绍了不同类型的连接操作,本节中我们来看看自然连接。自然连接会自动从每个表中选择具有相同名称和数据类型的列,并基于这些列的相等值来匹配行。

自然连接的工作原理
PROC SQL会自动识别两个表中所有名称和数据类型都相同的列,并将这些列作为连接条件。其基本语法如下:
PROC SQL;
SELECT *
FROM table1
NATURAL JOIN table2;
QUIT;
自然连接的优势与注意事项
使用自然连接的主要优势在于代码简洁。它隐含了ON子句,并且对于两个表共有的列名,你不需要使用表别名来限定。
然而,在使用自然连接前,你必须充分了解你的数据。因为它基于所有同名同类型列的相等值进行连接。如果你对没有至少一个共同列名的表使用自然连接,结果将是一个笛卡尔积。此时,你可以使用WHERE子句来限制输出。
如果你想基于不等式或其他比较运算符进行连接,则应使用标准的连接语法。
使用PROC SQL选项辅助调试
到目前为止,我们已经见过NOEQUALS和OUTOBS=选项,它们通过限制行数来减少代码开发时的查询执行时间。
另一个非常有用的PROC SQL选项是FEEDBACK选项。FEEDBACK选项会将SELECT语句的扩展形式输出到SAS日志中。如果表没有别名,列名会以表名作为前缀。
以下是使用FEEDBACK选项的示例代码:
PROC SQL FEEDBACK;
SELECT *
FROM table1
NATURAL JOIN table2;
QUIT;
在调试自然连接查询时,FEEDBACK选项尤其有用,因为它能让你清晰地看到PROC SQL是如何具体执行你的查询的。
总结

本节课中我们一起学习了PROC SQL中的自然连接。我们了解到自然连接通过自动匹配同名同类型的列来简化连接代码,但使用前必须充分了解数据结构。我们还介绍了FEEDBACK选项,它对于理解和调试自然连接等复杂查询非常有帮助。记住,对于非等值连接,应使用标准的连接语法。
047:从两个以上表中选择数据 🧩
在本节课中,我们将学习如何通过一次查询,将两个以上的数据表连接起来,以获取更完整、更易于理解的信息。

上一节我们介绍了两个表之间的内连接。本节中我们来看看如何将三个或更多的表连接在一起。
内连接结果分析

对小型客户表和小型交易表执行内连接后,结果包含以下列:first_name、last_name、state、income、date_time、merchant_id、amount、account_id 和 bank_id。


虽然这些结果提供了信息,但我们希望更深入地了解银行和商户的详细信息。
连接多个表的必要性



我们不想通过查找 bank_id 和 merchant_id 来获取对应的银行名和商户名。

我们希望直接找到一个包含这些必要信息的表。
解决方案:多表连接

为了完成这个任务,我们需要找到包含 bank_id 和 merchant_id 对应信息的查找表,然后在一次查询中将所有这些表连接起来。
以下是实现多表连接的基本步骤:

- 识别关键列:确定连接各个表所需的关键列,通常是主键或外键。
- 编写连接语句:在
PROC SQL的SELECT语句中使用多个JOIN子句。 - 指定连接条件:为每个
JOIN明确指定表之间的连接条件。
一个典型的多表连接 PROC SQL 代码如下:
PROC SQL;
SELECT
c.first_name,
c.last_name,
t.amount,
b.bank_name, -- 来自银行查找表
m.merchant_name -- 来自商户查找表
FROM
work.small_customer AS c
INNER JOIN
work.small_transaction AS t
ON c.customer_id = t.customer_id
LEFT JOIN
work.bank_lookup AS b
ON t.bank_id = b.bank_id
LEFT JOIN
work.merchant_lookup AS m
ON t.merchant_id = m.merchant_id;
QUIT;




通过这种方式,我们可以将客户信息、交易记录、银行详情和商户详情整合到一个清晰的结果集中,无需手动查找ID对应的名称。
本节课中我们一起学习了如何从两个以上的表中选择和连接数据。关键在于理解表之间的关系,并依次使用 JOIN 子句将它们组合起来,从而在一次查询中获得丰富、直观的分析结果。
048:对四个表执行内连接演示 🧩
在本节课中,我们将学习如何使用Proc SQL对四个数据表执行内连接操作。我们将从一个简单的两表连接开始,逐步添加第三和第四个表,以获取更丰富的信息,例如商户名称和银行名称。

概述
我们将使用Proc SQL执行内连接。首先,我们会探索将要使用的数据表,然后分步演示如何连接它们,最终生成一个包含客户信息、交易记录、商户名称和银行名称的完整报告。
探索数据表

以下是我们要使用的四个数据表:


- 小型客户表:包含客户信息。
- 小型交易表:包含客户的交易记录。我们之前通过
AccountID来连接此表。 - 商户表:包含商户ID和对应的商户名称。
- 银行表:包含银行ID和对应的银行名称。

我们的目标是连接这些表,以便在结果中不仅看到商户ID和银行ID,还能看到它们对应的名称。
第一步:连接客户表与交易表
首先,我们运行一个查询来连接小型客户表和小型交易表。这是后续多表连接的基础。

PROC SQL;
SELECT *
FROM work.small_customer AS C
INNER JOIN work.small_transaction AS T
ON C.AccountID = T.AccountID;
QUIT;
在结果中,我们可以看到MerchantID和BankID,但我们不知道它们具体代表哪个商户或银行。
第二步:加入商户表以获取商户名称
上一节我们得到了客户和交易信息的合并结果。本节中,我们来看看如何加入第三个表——商户表,以将商户ID替换为更具可读性的商户名称。
我们将使用INNER JOIN来连接上一步的结果与商户表。
以下是连接三个表的关键步骤:
- 使用
INNER JOIN引入SQ.merchant表,并为其指定别名M。 - 使用
ON子句指定连接条件:交易表的MerchantID等于商户表的MerchantID。 - 在
SELECT语句中,将MerchantID替换为M.Name以选择商户名称。
修改后的查询代码如下:

PROC SQL;
SELECT C.*, T.TransactionDate, T.Amount,
M.Name AS MerchantName, -- 替换MerchantID为商户名称
T.BankID
FROM work.small_customer AS C
INNER JOIN work.small_transaction AS T
ON C.AccountID = T.AccountID
INNER JOIN work.small_merchant AS M -- 加入第三个表
ON T.MerchantID = M.MerchantID;
QUIT;
运行此查询后,结果中原本的MerchantID列现在显示为从商户表获取的MerchantName,这为我们提供了更多信息。
第三步:加入银行表以获取银行名称

现在,我们的结果已经包含了商户名称。接下来,我们执行最后一步,加入第四个表——银行表,以获取银行名称。
我们将再次使用INNER JOIN来连接当前结果与银行表。
以下是加入银行表的步骤:
- 在前一个
INNER JOIN后,继续添加一个INNER JOIN,引入work.small_bank表,并为其指定别名B。 - 使用
ON子句指定新的连接条件:客户表的BankID等于银行表的BankID。 - 在
SELECT语句中,将C.BankID替换为B.Name以选择银行名称。
完整的四表连接查询代码如下:
PROC SQL;
SELECT C.CustomerID, C.Name AS CustomerName, C.AccountID,
T.TransactionDate, T.Amount,
M.Name AS MerchantName,
B.Name AS BankName -- 替换BankID为银行名称
FROM work.small_customer AS C
INNER JOIN work.small_transaction AS T
ON C.AccountID = T.AccountID
INNER JOIN work.small_merchant AS M
ON T.MerchantID = M.MerchantID
INNER JOIN work.small_bank AS B -- 加入第四个表
ON C.BankID = B.BankID;
QUIT;

运行最终的查询。现在,在结果的最后一列,我们可以看到从银行表获取的BankName。这份报告比最初的两表连接包含了更丰富的信息。
总结

本节课中我们一起学习了如何使用Proc SQL对多个数据表执行内连接。我们从连接两个核心表开始,然后逐步加入商户表和银行表,最终生成了一个集成了客户信息、交易详情、商户名称和银行名称的综合报告。这个演示清楚地表明,你可以通过连续使用INNER JOIN子句来连接任意多个存在关联关系的表,从而从分散的数据中提取出有意义的业务洞察。
049:处理缺失值
在本节课中,我们将学习在SAS PROC SQL中进行表连接时,如何处理缺失值。理解缺失值在连接操作中的行为至关重要,因为它可能导致意想不到的查询结果。
缺失值在连接中的行为
大多数数据库产品将缺失值视为空值。由于它们不包含任何实际值,因此在条件评估中通常被排除。
在SAS PROC SQL中,情况有所不同。当基于包含缺失值的列进行表连接时,PROC SQL会将缺失值视为可匹配的值。
一个连接示例
以下是一个连接示例,它基于 account ID 列连接 small_customer2 和 small_transaction2 两个表。两个表的 account ID 列中都存在缺失值。
PROC SQL;
SELECT *
FROM small_customer2 AS c, small_transaction2 AS t
WHERE c.account_ID = t.account_ID;
QUIT;
PROC SQL 将缺失值作为缺失值本身来处理并进行匹配连接。任何缺失值都会与同一类型(字符型或数值型)的其他任何缺失值相匹配。
连接结果分析
这可能会返回意想不到的结果。如下图所示,输出结果显示,small_customer2 中的缺失值行匹配了 small_transaction2 中的所有缺失值,导致返回了15行数据。



这很可能不是此次连接操作期望得到的结果。
如何排除缺失值进行连接
为了避免这种情况,我们可以指定只连接非缺失值。通过在 WHERE 子句中添加 IS NOT NULL 运算符,可以防止缺失值相互连接在一起。

更新后的SQL代码如下:
PROC SQL;
SELECT *
FROM small_customer2 AS c, small_transaction2 AS t
WHERE c.account_ID = t.account_ID
AND c.account_ID IS NOT NULL
AND t.account_ID IS NOT NULL;
QUIT;

此修改确保了连接操作只针对 account_ID 列中具有有效、非缺失值的行进行。
本节总结

本节课中,我们一起学习了SAS PROC SQL中缺失值在表连接时的特殊行为。关键点是,PROC SQL默认会将同类型的缺失值相互匹配,这可能导致查询结果行数异常增多。为了获得预期的连接结果,我们学会了使用 IS NOT NULL 运算符在连接条件中明确排除缺失值,从而确保只对有效数据进行操作。
050:创建非等值连接 🔗
在本节课中,我们将要学习如何创建非等值连接。虽然内连接或等值连接非常重要,但有时我们需要根据非等值的条件来关联数据表。
概述
上一节我们介绍了基于等值条件的连接。本节中我们来看看如何使用比较运算符(如大于、小于)来创建连接,这种连接被称为非等值连接。
应用场景:确定客户税级
假设我们需要确定每位客户的税级。为此,我们必须将每位客户的收入与税级表中的收入范围进行比较。
如果客户的收入介于税级表的某个最低值和最高值之间,那么该税级就适用于这位客户。


解决方案:使用比较运算符
为了解决这个问题,我们可以调整 ON 子句,使用比较运算符来代替等号。
以下是实现此逻辑的SQL代码示例:
SELECT c.customer_id, c.income, t.tax_bracket
FROM customers c
INNER JOIN tax_brackets t
ON c.income > t.low_income AND c.income <= t.high_income;
在这段代码中,我们使用了大于(>)和小于等于(<=)运算符。我们将客户的收入与税级表中的低收入值和高收入值进行比较,利用这个范围来确定每位客户的税级。

核心步骤解析
以下是创建非等值连接的关键步骤:
- 确定连接条件:明确需要比较的字段和逻辑关系(例如,介于某个范围)。
- 选择比较运算符:根据逻辑关系,在
ON子句中选用合适的运算符,如>、<、>=、<=、BETWEEN。 - 组合条件:通常需要使用
AND来组合多个比较条件,以定义一个有效的范围或规则。

总结
本节课中我们一起学习了非等值连接的创建方法。我们了解到,通过将 ON 子句中的等号(=)替换为其他比较运算符,可以根据更灵活的条件(如数值范围)来关联多个表中的数据。这在处理诸如税级划分、折扣区间等基于范围的业务逻辑时非常有用。
051:SQL外连接详解 🧩
在本节课中,我们将要学习SQL中的外连接。外连接不仅返回匹配的行,还会返回不匹配的行,这对于数据合并与分析至关重要。
上一节我们介绍了内连接,本节中我们来看看外连接的不同类型及其应用。
外连接类型概述
外连接主要分为三种类型:左外连接、右外连接和全外连接。以下是这三种连接的核心定义:
- 左外连接:返回左表的所有行,以及右表中与左表匹配的行。如果右表中没有匹配项,则结果中对应列显示为缺失值。
- SQL代码示例:
SELECT * FROM table1 LEFT JOIN table2 ON table1.key = table2.key;
- SQL代码示例:
- 右外连接:返回右表的所有行,以及左表中与右表匹配的行。如果左表中没有匹配项,则结果中对应列显示为缺失值。
- SQL代码示例:
SELECT * FROM table1 RIGHT JOIN table2 ON table1.key = table2.key;
- SQL代码示例:
- 全外连接:返回左表和右表中的所有行。当某一行在另一个表中没有匹配时,另一个表的对应列将显示为缺失值。
- SQL代码示例:
SELECT * FROM table1 FULL JOIN table2 ON table1.key = table2.key;
- SQL代码示例:


外连接语法解析
理解了外连接的类型后,我们来看看它的具体语法结构。
在SQL中,外连接的FROM子句会列出两个表名,并在它们之间使用关键字来指明连接类型。请注意,这些关键字中不包含“outer”一词。
以下是外连接语法的核心组成部分:
- 连接类型关键字:直接使用
LEFT JOIN、RIGHT JOIN或FULL JOIN。 - ON子句:用于指定连接条件,例如
ON table1.column = table2.column。
在确定“左表”和“右表”时,需要依据它们在FROM子句中的位置。在左外连接或右外连接中:
- 左表 是
FROM子句中列出的第一个表。结果表中,来自左表的列将首先出现。 - 右表 是
FROM子句中列出的第二个表。结果表中,来自右表的列将随后出现。


本节课中我们一起学习了SQL外连接的三种主要类型:左外连接、右外连接和全外连接,并掌握了它们的基本语法和左右表的定义规则。理解外连接是进行完整数据集合并与分析的关键步骤。
052:执行左外连接与右外连接
在本节课中,我们将学习如何使用SAS SQL执行左外连接和右外连接。这两种连接方式允许我们在合并表时,保留其中一个表中的所有行,无论它们在另一个表中是否有匹配项。这对于生成包含所有客户或所有交易的报告非常有用。
左外连接:保留左表所有行
假设我们需要一份报告,列出所有客户,无论他们是否有交易记录。

上一节我们介绍了内连接,本节中我们来看看如何执行左外连接。左外连接会返回左表(small_customer)中的所有行,以及右表(small_transaction)中与连接条件匹配的行。如果右表中没有匹配项,结果集中对应的列将显示为缺失值。
以下是执行左外连接的SQL代码结构:
PROC SQL;
SELECT *
FROM small_customer AS c
LEFT JOIN small_transaction AS t
ON c.account_id = t.account_id;
QUIT;
FROM small_customer AS c:指定左表为small_customer,并为其定义别名c。LEFT JOIN small_transaction AS t:指定进行左外连接,右表为small_transaction,别名为t。ON c.account_id = t.account_id:定义连接条件,即两个表通过account_id列进行匹配。
如果使用内连接,则只会列出有匹配交易的客户。而左外连接确保了报告包含small_customer表中的所有客户。

右外连接:保留右表所有行
那么,如果我们想查看一份报告,列出所有交易记录,无论其是否有对应的客户信息,该如何操作呢?

上一节我们使用左外连接保留了客户表的所有行,本节中我们来看看其反向操作——右外连接。右外连接会返回右表(small_transaction)中的所有行,以及左表(small_customer)中与连接条件匹配的行。
以下是执行右外连接的SQL代码结构:
PROC SQL;
SELECT *
FROM small_customer AS c
RIGHT JOIN small_transaction AS t
ON c.account_id = t.account_id;
QUIT;
RIGHT JOIN small_transaction AS t:指定进行右外连接,此时small_transaction表作为右表。- 连接条件
ON子句保持不变。

右外连接与左外连接逻辑相反。在FROM子句中,第二个列出的表(即RIGHT JOIN后面的表)成为右表。查询结果将包含右表的所有行(无论是否匹配)以及左表中所有匹配的行。
外连接中选择列的重要区别
在执行内连接时,选择哪个表的连接条件列(如account_id)通常没有区别,返回的结果是相同的。
然而,在执行外连接时,选择来自左表还是右表的连接条件列,可能会导致不同的结果。这是因为外连接会为一侧表中未匹配的行生成缺失值。如果选择包含缺失值的那一方的列,那么这些行的连接条件列也会显示为缺失。理解这一点对于准确解读外连接的结果至关重要。

本节课中我们一起学习了SAS SQL中的左外连接与右外连接。左外连接(LEFT JOIN)保留左表全部行,右外连接(RIGHT JOIN)保留右表全部行。它们都是生成包含“所有”记录报告的有力工具,同时需要注意在外连接中选择输出列可能对结果产生影响。
053:使用全连接合并两个表
在本节课中,我们将学习如何使用全连接来合并两个表,以生成一份包含所有匹配及不匹配记录的完整报告。我们还将介绍如何使用COALESCE函数来处理合并后可能出现的缺失值问题。
概述:全连接的应用场景
假设你需要生成一份报告,其中包含所有客户及其交易记录,无论这些记录在两个表中是否存在匹配项。

使用全连接合并数据
上一节我们介绍了合并表的基本概念,本节中我们来看看如何实现包含所有记录的合并。你可以使用全连接来同时包含匹配的记录和不匹配的记录。

以下是全连接操作的图示:

处理合并后的列值问题
当我们使用全连接合并两个表时,来自两个表的账户ID值并不总是相等。如果我们需要在最终报告中包含账户ID列,该如何选择包含有效值的ID呢?
以下是该问题的图示:


使用COALESCE函数整合列

为了解决上述问题,我们可以使用COALESCE函数来叠加列。COALESCE函数返回其参数列表中第一个非缺失的值。
以下是一个在SAS中使用COALESCE函数的代码示例:
/* 使用全连接合并表,并用COALESCE函数处理账户ID */
proc sql;
create table full_join_report as
select
coalesce(a.account_id, b.account_id) as account_id,
a.customer_name,
b.transaction_amount
from
customers as a
full join
transactions as b
on a.account_id = b.account_id;
quit;
这个例子在前一个全连接示例的基础上增加了COALESCE函数,用于叠加两个账户ID列。该函数在结果中返回一个单一的ID列。
以下是使用COALESCE函数后的结果图示:

总结

本节课中我们一起学习了全连接的用法,它能够合并两个表的所有行,无论是否存在匹配键。我们还掌握了如何使用COALESCE函数来智能地选择第一个可用的非缺失值,从而在合并后生成整洁、可用的数据列。这是生成包含完整数据视图报告的关键技术。
054:使用 PROC SQL 执行全连接 🧩
在本节课中,我们将学习如何使用 PROC SQL 在 SAS 中执行全连接操作。全连接能够合并两个表中的所有记录,无论它们在连接键上是否匹配。我们将通过一个具体的例子,演示如何编写查询、处理结果,并解决连接后可能出现的重复列问题。
我们使用 PROC SQL 在两个表之间执行全连接。让我们查看这个查询。
我们选择名字、姓氏、收入,并且同时选择两个账户 ID:一个来自 small_customer 表,一个来自 small_transaction 表。我们还选择日期时间、商户 ID 和金额。我们基于 account_id 对 small_customer 和 small_transaction 表进行全连接。
我们运行查询并查看结果。

请注意观察两个 account_id 列。它们完全相同吗?

可以看到一些差异:有些行两个值都缺失,有些行两个值都有。有时左边的值缺失,有时右边的值缺失。这是因为我们同时从两个表中选择了 account_id 列。我们的目标是合并这两列。

我将移除 T.account_id,即来自 small_transaction 表的账户 ID。然后运行查询。


现在你看到有多少个缺失的 account_id 值?



我看到有五个。让我们回到代码,改为选择 T.account_id,即交易表的账户 ID。


我们将代码中的 C 改为 T。


然后运行查询。现在我看到 account_id 有三个缺失值。

因此,根据你选择哪个 account_id,你会得到不同的值。


我们可以使用 COALESCE 函数来合并这两个列。


回到编辑器,使用 COALESCE 函数。我们将两个 account_id 作为参数传入。
我将删除旧的 account_id 列。并稍微清理一下代码。
我将这个新列命名为 acct_id,并赋予它 10. 的格式,以确保显示完整的数字。

现在查看 account_id 列,可以看到我们只有一个缺失值。
这样我们就合并了这两列,并取用了第一个非缺失值。
通过这份报告,我们完成了一个全连接。我想进一步分析一下。
查看第一行,我们没有账户 ID、名字、姓氏或收入值。我们不知道是哪位顾客进行了这笔购买,也许这个人是用现金支付的。
可以看到剩余的行都有账户 ID,因此那些有顾客信息的行可以轻松匹配。但有些行没有顾客姓名和收入,由于某些原因我们缺少这些值,这可能需要进一步调查。
最后,我想看看最后两行。我们有顾客 Ada 和 Samantha。她们没有日期时间、商户 ID 或金额信息。这些顾客没有购买任何东西,所以她们出现在报告中,但没有任何购买记录。

总结

在本节课中,我们一起学习了如何使用 PROC SQL 执行全连接。我们了解到全连接会返回两个表中所有的行,无论连接条件是否匹配。当两个表都有连接键列时,会产生重复列,我们演示了如何使用 COALESCE 函数来合并这些列,取用第一个非缺失值。最后,我们分析了连接结果,识别出那些只有交易记录没有顾客信息,以及只有顾客信息没有交易记录的行,这有助于我们理解数据的完整性和潜在的数据质量问题。
055:识别不匹配的行 🔍
在本节课中,我们将学习如何利用左连接(LEFT JOIN)来识别两个数据表中不匹配的行。具体来说,我们将找出那些在客户表中存在,但在交易表中没有对应记录的客户。

回顾左连接
上一节我们介绍了表连接的基本概念。现在,让我们回顾之前使用小型客户表(small_customer)和小型交易表(small_transaction)创建的左连接。

这个左连接的结果集包含了所有客户,无论他们是否有交易记录。有交易的客户,其交易信息会被匹配并显示;没有交易的客户,其交易表对应的列则会显示为缺失值。

明确目标:找出无交易的客户
假设我们的目标是生成一份年度内没有任何交易记录的客户名单。获取这份信息的目的,可能是为了向这些客户进行营销,鼓励他们更频繁地使用我们的信用卡服务。

为了达成这个目标,我们首先完成客户表与交易表之间的左连接。这一步我们已经熟悉,它会生成一份包含所有客户(无论有无交易)的报告。然而,我们的任务是专门找出那些没有交易的客户。
深入分析连接结果
让我们仔细查看这份连接后的报告。最后两行对应的是客户 Ada 和 Samantha。你注意到 small_transaction 表的 account_ID 列在这两行中的值有什么特点吗?

关键点在于,对于没有匹配交易记录的客户(如 Ada 和 Samantha),来自交易表(即右表)的列(例如 T_account_ID)的值是缺失的(在SAS中表示为空值或 null)。

因此,要生成无交易客户的名单,我们需要筛选出那些 T_account_ID(即来自 small_transaction 表的 account_ID)值为缺失的行。这些行就标识了所有没有交易记录的客户。

实现方法:添加 WHERE 子句过滤
我们可以在左连接的 PROC SQL 语句中添加一个 WHERE 子句,来筛选出 small_transaction 表的连接键(account_ID)为 null 的所有行。
以下是实现此逻辑的SQL代码示例:

PROC SQL;
CREATE TABLE customers_no_transaction AS
SELECT c.*
FROM small_customer c
LEFT JOIN small_transaction t
ON c.account_ID = t.account_ID
WHERE t.account_ID IS NULL;
QUIT;
这段代码将创建一个左连接,但只返回客户没有交易记录的结果。WHERE 子句在连接操作完成之后对行进行过滤。

通过这种方式,我们最终得到的数据集 customers_no_transaction 将只包含那些在 small_transaction 表中没有匹配项的客户信息。
本节总结

本节课中,我们一起学习了如何利用左连接结合 WHERE 子句来识别两个表之间不匹配的行。核心步骤是:
- 执行左连接,保留左表(本例中的客户表)的所有行。
- 在
WHERE子句中,筛选右表(本例中的交易表)的连接键值为NULL的行。
这种方法非常适用于查找“存在于A表但不存在于B表”的数据场景,例如找出未下单的客户、未注册的用户等。
056:使用自反连接 👥
在本节课中,我们将学习如何使用自反连接(Self-Join)来解决一个常见的数据查询问题:如何从一个员工表中,同时获取员工及其直接经理的姓名。
概述
假设我们需要创建一个表格,其中列出所有员工以及每位员工的直接经理姓名。
员工表包含了所有员工的列表以及多个列。员工ID列包含了所有员工,包括经理本人。然而,该表并不直接包含员工的经理姓名,只包含每位员工的经理ID。
因此,我们需要为每位员工找到其经理的姓名。
问题分析与解决思路

我们可以通过员工表自身进行自反连接来满足这个需求。
观察第一行数据,员工Abbott Ray的经理ID是121144。如果我们向下查看员工ID列,会发现员工ID 121144对应的也是一位员工,但她同时也是一位经理。通过将员工表与自身进行连接,我们就可以获取到经理的姓名。
实施步骤:自反连接
为了从同一张表中读取两次,该表必须在FROM子句中出现两次。同时,需要使用不同的表别名来区分这两个实例。
我们将第一次出现的表别名为 E(代表员工),第二次出现的表别名为 M(代表经理)。
以下是实现此连接的核心SQL代码结构:

PROC SQL;
CREATE TABLE work.emp_mgr AS
SELECT E.Employee_ID,
E.Employee_Name,
E.Manager_ID,
M.Employee_Name AS Manager_Name
FROM sashelp.employees AS E
LEFT JOIN sashelp.employees AS M
ON E.Manager_ID = M.Employee_ID;
QUIT;
关键连接逻辑公式:
E.Manager_ID = M.Employee_ID
这个连接条件意味着:用员工表(E)中的经理ID,去匹配经理表(M)中的员工ID,从而找到对应的经理记录。
总结

本节课我们一起学习了自反连接的应用。通过将同一张表使用不同别名进行自连接,我们成功地解决了从单一数据源中同时获取员工及其上级经理信息的问题。这种方法在处理具有层级关系(如组织架构、产品分类)的数据时非常实用。
057:执行自反连接演示 🧩
在本节课程中,我们将学习如何使用PROC SQL在SAS中执行自反连接(或称自连接)。自反连接是一种特殊的连接操作,它允许我们将同一张表视为两个独立的实体进行连接,常用于解决如查找员工与其经理对应关系这类问题。

初始查询与问题定义
首先,我们运行一个基础查询来查看员工表的结构。该查询选取了员工ID、员工姓名、入职日期以及经理ID。
SELECT EmployeeID, EmployeeName, StartDate, ManagerID
FROM SQ.emp;

查询结果显示了每位员工的ID、姓名、入职日期及其对应的经理ID。我们的目标是找出每位员工的经理姓名。
执行自反连接
为了找到经理姓名,我们需要将员工表(SQ.emp)与其自身进行连接。以下是实现自反连接的具体步骤。
我们将使用内连接(INNER JOIN)并在FROM子句中两次引用同一张表。为此,必须为表使用不同的别名。
以下是修改后的SQL代码:
SELECT e.EmployeeID,
e.EmployeeName,
e.StartDate,
e.ManagerID,
m.EmployeeName AS ManagerName
FROM SQ.emp AS e
INNER JOIN SQ.emp AS m
ON e.ManagerID = m.EmployeeID
ORDER BY ManagerName;

代码解析:
SQ.emp AS e:将员工表第一次引用,别名为e,代表“员工”视角。SQ.emp AS m:将员工表第二次引用,别名为m,代表“经理”视角。ON e.ManagerID = m.EmployeeID:这是连接条件。它表示将员工(e)记录中的ManagerID与经理(m)记录中的EmployeeID进行匹配,从而找到对应的经理。m.EmployeeName AS ManagerName:从经理视角(m)选取员工姓名,并将其重命名为ManagerName作为输出列。
常见错误与修正
在初次尝试编写连接条件时,一个常见的错误是在ON子句中错误地引用了表别名。
错误示例:
ON e.ManagerID = e.EmployeeID -- 错误!这试图在同一行数据内匹配,逻辑错误。
此错误会导致SAS日志提示“执行笛卡尔积”,因为连接条件逻辑无效,无法正确关联两个表实例。

正确做法:
必须确保连接条件关联的是两个不同的表实例。应将条件更正为:
ON e.ManagerID = m.EmployeeID -- 正确!将员工的经理ID与经理表的员工ID关联。

运行修正后的查询,我们即可得到包含ManagerName列的完整结果集,清晰展示了每位员工及其对应经理的信息。
本节总结

本节课中,我们一起学习了自反连接(自连接)的概念与应用。通过为同一张表赋予不同的别名(如 e 和 m),我们能够在SQL查询中将其视为两个独立的表。关键步骤在于正确编写连接条件(ON e.ManagerID = m.EmployeeID),从而建立员工记录与其经理记录之间的关联。这种技术是处理层次化数据(如组织架构、产品分类)的强大工具。
058:使用函数连接表
在本节课中,我们将学习如何使用SAS函数作为连接条件来合并没有共同列的数据表。我们将通过一个具体案例,演示如何利用SUBSTR函数提取信息,并以此为基础连接两个表。
概述
有时,我们需要连接的两个表之间没有可以直接匹配的列。例如,一个表包含完整的地址信息,而另一个表使用州代码缩写。本节将介绍如何运用SAS函数从现有数据中提取关键信息,从而建立表之间的连接关系。
使用函数建立连接条件
假设我们需要将交易明细表与州代码表进行连接,目的是为每位客户获取对应的州名称。
然而,交易明细表中并没有一个独立的列来存放与州代码表直接匹配的州缩写。


为了解决这个问题,我们可以使用子字符串函数。具体方法是,从交易明细表的StateID列中提取前两个字符,这部分内容包含了州的缩写。
然后,利用提取出的州缩写信息,与州代码表中包含州缩写的StateCode列进行连接。

我们可以在SQL的ON子句中直接使用SUBSTR函数来定义连接条件。
以下是实现此操作的示例代码:
PROC SQL;
CREATE TABLE work.joined_table AS
SELECT a.*, b.StateName
FROM work.transaction_full a
LEFT JOIN work.state_code b
ON SUBSTR(a.StateID, 1, 2) = b.StateCode;
QUIT;

在这段代码中:
SUBSTR(a.StateID, 1, 2)函数从a.StateID列的第一个字符开始,提取长度为2的子字符串。- 提取的结果与
b.StateCode列的值进行相等匹配,从而将两个表连接起来。
总结
本节课我们一起学习了在SAS中使用函数进行表连接的高级技巧。当表之间缺乏直接关联列时,我们可以通过SUBSTR等函数对现有数据进行加工和提取,创造出可用于连接的匹配键。这种方法极大地增强了数据处理的灵活性,是解决复杂数据合并问题的有效工具。
059:当连接列类型不同时使用函数进行连接
在本节课中,我们将学习当两个数据表的连接列具有不同数据类型(例如字符型与数值型)时,如何进行有效的连接操作。我们将探讨直接连接可能遇到的问题,并学习如何使用SAS函数来解决这些问题。
问题引入:连接列类型不匹配
上一节我们介绍了基于相同列进行表连接的基本方法。本节中我们来看看当连接列的数据类型不同时会发生什么情况。

在下面的例子中,我们有两个数据表。customer_zip 表包含字符型的邮政编码,而 zip_codes 表包含一个数值型的邮政编码列。

直接连接尝试与问题
如果连接列具有不同的数据类型,能否成功连接这两个表呢?
让我们看看当我们尝试连接一个表,其中一个列是字符型而另一个是数值型时会发生什么。

直接使用 PROC SQL 进行连接,例如 ON customer_zip.zip_char = zip_codes.zip_num,SAS会尝试进行隐式类型转换。然而,这种转换可能导致以下问题:
- 连接失败:如果转换不成功(例如,字符列中包含非数字字符),则相应的行无法匹配。
- 性能下降:隐式转换会增加处理开销。
- 结果不可预测:依赖于SAS的默认转换规则,可能导致意料之外的结果。
解决方案:使用函数进行显式类型转换

为了保证连接的准确性和效率,最佳实践是使用SAS函数对连接列进行显式的类型转换,使它们的数据类型保持一致。
以下是常用的类型转换函数:
-
INPUT函数:将字符型数据转换为数值型数据。- 公式:
数值变量 = INPUT(字符变量, 数值格式.); - 示例代码:
zip_num = INPUT(zip_char, 5.);将5位字符邮政编码转换为数值。
- 公式:
-
PUT函数:将数值型数据转换为字符型数据。- 公式:
字符变量 = PUT(数值变量, 字符格式.); - 示例代码:
zip_char = PUT(zip_num, z5.);将数值邮政编码转换为5位字符,不足位左补零。
- 公式:
应用示例
假设我们需要连接 customer_zip 和 zip_codes 表,我们可以选择将字符型的 zip_char 转换为数值型,或者将数值型的 zip_num 转换为字符型。
方法一:将字符型转换为数值型后连接
PROC SQL;
CREATE TABLE joined_table AS
SELECT a.*, b.*
FROM customer_zip a
INNER JOIN zip_codes b
ON INPUT(a.zip_char, 5.) = b.zip_num;
QUIT;
方法二:将数值型转换为字符型后连接
PROC SQL;
CREATE TABLE joined_table AS
SELECT a.*, b.*
FROM customer_zip a
INNER JOIN zip_codes b
ON a.zip_char = PUT(b.zip_num, z5.);
QUIT;
选择哪种方法取决于数据特性和后续分析的需要。通常,选择转换后能更好保持数据完整性和便于比较的格式。

总结
本节课中我们一起学习了如何处理连接列数据类型不同的情况。核心要点是避免依赖SAS的隐式转换,而是使用 INPUT 或 PUT 函数进行显式的类型转换,确保连接条件两边数据类型一致,从而获得准确可靠的连接结果。记住,在编写连接代码时,始终检查并处理好连接列的数据类型是成为一名高级SAS程序员的重要习惯。
060:使用函数转换列值
在本节课中,我们将学习如何在SAS中转换列的数据类型。SAS不允许使用不同类型的数据列来连接表。为了明确且准确地将数据从一种类型转换为另一种类型,我们可以使用特定的函数。
数据类型转换的必要性

上一节我们介绍了SAS中数据类型的重要性。本节中我们来看看,当需要连接两个表,但连接键的数据类型不匹配时,我们该如何处理。SAS不允许使用不同类型的数据列来连接表。为了明确且准确地将数据从一种类型转换为另一种类型,我们可以使用特殊的函数。
转换函数概述
以下是两种核心的数据类型转换函数及其用途:

- INPUT函数:用于将字符值转换为数值。
- 我们使用一个输入格式来指示应如何读取该字符串。
- 公式:
数值变量 = INPUT(字符变量, 输入格式);
- PUT函数:用于将数值转换为字符值。
- 使用一个输出格式来指示应如何写入该值。
- 公式:
字符变量 = PUT(数值变量, 输出格式);
实战示例:转换邮政编码
现在,我们通过一个具体的例子来应用这些函数。假设我们有一个包含美国邮政编码的数值列,需要将其转换为字符类型以便进行表连接。
对于这个例子,我们可以使用PUT函数将Z.zip列从数值转换为字符。
以下是转换步骤:
- 指定源列
Z.zip。 - 使用
Z5.格式。 Zw.格式会以指定的宽度写入带有前导零的标准数值数据。- 这里,我们指定宽度为5,因为美国邮政编码的长度是5位。
- 例如,如果邮政编码是数值
4429,使用Z5.格式的PUT函数会将其转换为字符串"04429"。 - 这个转换可以在
ON子句的连接条件中直接执行。
代码示例:
/* 在PROC SQL的JOIN条件中直接转换数据类型 */
proc sql;
create table combined as
select a.*, b.*
from table_a as a
inner join table_b as b
on a.char_zip = put(b.num_zip, z5.); /* 将数值邮编转换为字符格式进行匹配 */
quit;
课程总结

本节课中我们一起学习了SAS中数据类型转换的关键技巧。我们了解到,使用INPUT函数可以将字符数据转换为数值,而使用PUT函数可以将数值数据转换为字符。通过在实际的表连接操作中应用PUT函数和Zw.格式,我们可以有效地解决因数据类型不匹配而无法连接表的问题,例如为数值邮政编码添加前导零以符合标准的字符格式。
061:子查询概述 🎯
在本节课中,我们将要学习SQL中一个非常强大的工具——子查询。子查询,顾名思义,是嵌套在另一个查询内部的查询。我们将从它在WHERE或HAVING子句中的应用开始,逐步深入到更复杂的用法。
什么是子查询? 🤔
上一节我们介绍了课程的整体安排,本节中我们来具体看看子查询的核心概念。一个子查询是一个被嵌入到另一个SQL语句中的完整SELECT语句。它通常用于为主查询提供条件或数据。
其基本形式可以表示为:
SELECT column1, column2, ...
FROM table1
WHERE columnN OPERATOR (SELECT columnM FROM table2 WHERE condition);
子查询的三种主要用法 📝
以下是子查询在SQL中最常见的三种应用场景,我们将逐一进行详细探讨。
1. 在 WHERE 或 HAVING 子句中使用
这种用法最为常见。子查询在这里充当一个条件过滤器,为主查询的WHERE或HAVING子句返回一个值或一组值,用于比较。
例如,查找薪水高于平均薪水的员工:
SELECT employee_id, salary
FROM employees
WHERE salary > (SELECT AVG(salary) FROM employees);
2. 在 FROM 子句中使用(派生表)
当子查询出现在FROM子句中时,它被称为“派生表”或“内联视图”。它本质上是一个临时的虚拟表,主查询可以像使用普通表一样使用它。
例如,先计算每个部门的平均薪水,再从中筛选:
SELECT dept_id, avg_salary
FROM (SELECT dept_id, AVG(salary) AS avg_salary FROM employees GROUP BY dept_id) AS dept_avg
WHERE avg_salary > 50000;
3. 在 SELECT 子句中使用(标量子查询)
这种子查询必须只返回单个值(一行一列)。它通常用于为主查询的每一行计算一个相关的值。
例如,在查询员工信息的同时,显示其所在部门的平均薪水:
SELECT employee_id, name, salary,
(SELECT AVG(salary) FROM employees e2 WHERE e2.dept_id = e1.dept_id) AS dept_avg_salary
FROM employees e1;
本节课中我们一起学习了SQL子查询的三种核心用法:在WHERE/HAVING子句中作为条件,在FROM子句中创建派生表,以及在SELECT子句中返回标量值。理解并掌握子查询,能让你编写出更强大、更灵活的SQL语句来处理复杂的数据检索需求。
062:什么是子查询 🔍
在本节课中,我们将要学习SAS Proc SQL中一个重要的数据组合技术——子查询。我们将了解子查询的基本概念、它与连接(JOIN)的区别、工作原理以及主要类型。
概述

您知道可以通过使用不同类型的连接(JOIN)来组合来自多个表的数据。连接是在单个查询中指定的。另一种组合数据的技术是在WHERE或HAVING子句中使用子查询。
子查询的定义与工作原理
上一节我们提到了子查询是组合数据的一种技术,本节中我们来看看它的具体定义。
子查询,有时也称为内部查询,是嵌套在外部查询或主查询中的一个查询。Proc SQL采用由内而外的方式评估查询,即先处理子查询,最后处理外部查询。
在WHERE或HAVING子句中的子查询只能返回一列。它可以返回该列的一个单一值或多个值。
子查询的类型

了解了子查询的基本工作原理后,接下来我们区分一下子查询的两种主要类型。
子查询分为两种类型:相关子查询和非相关子查询。
非相关子查询
最常见的子查询类型是非相关子查询。非相关子查询是一个独立的、自包含的子查询。它在外部查询之前独立执行,然后将一个或多个值传递回外部查询。非相关子查询使您能够分块构建和测试代码。
在本课程中,为简便起见,我们将重点讨论非相关子查询,并直接将其称为子查询。
总结

本节课中我们一起学习了SAS Proc SQL中子查询的核心概念。我们了解到子查询是嵌套在主查询中的内部查询,它主要在WHERE或HAVING子句中使用,用于返回一列的一个或多个值以供外部查询筛选。Proc SQL以由内而外的顺序执行它。我们还区分了相关与非相关子查询,并明确了本课程将重点讨论独立执行的非相关子查询。掌握子查询是进行复杂数据操作和分析的关键一步。
063:在WHERE子句中使用子查询 🧩
在本节课中,我们将学习如何在SAS的WHERE子句中有效地使用子查询。子查询是一种强大的工具,它允许您在一个查询内部嵌套另一个查询,从而使程序更加动态和高效。我们将通过一个具体的例子来理解其工作原理。
概述

假设您需要分析明年的预估人口数据,以决定在哪些州增加市场投入。您被要求生成一份报告,列出预估人口高于所有州平均人口的那些州。
为了完成这个任务,我们将使用一个包含各州及其未来三年预估人口的表格。

理解子查询的步骤
使用子查询时,将其分解为步骤有助于理解。通常,第一步是计算子查询(或内部查询)的值。接下来,在主查询中使用从子查询计算出的这个值。
最后,将子查询与外部查询结合起来。

分步构建查询
以下是构建查询的具体步骤。
第一步:计算内部查询的单一值
首先,我们需要确定内部查询需要返回的单一值,并计算它。在这个例子中,我们需要计算所有州预估人口的平均值。
/* 第一步:计算所有州明年预估人口的平均值 */
PROC SQL;
SELECT AVG(P_estimate1) INTO :avg_pop
FROM state_population;
QUIT;
此查询对 P_estimate1 列使用 AVG 函数,返回结果 6,278,420。这就是所有州的平均预估人口。第一步查询成功。
第二步:手动应用平均值进行筛选
接下来,我们需要找出人口预估超过我们计算出的平均值的那些州。我们可以手动将第一步查询得到的值放入WHERE子句中。
/* 第二步:使用静态平均值进行筛选 */
PROC SQL;
SELECT State
FROM state_population
WHERE P_estimate1 > 6278420;
QUIT;
虽然这种方法可行,但它不够动态和高效。如果州人口表中的预估人口数据发生变化怎么办?您将不得不重新运行第一个查询以获得新的平均值,然后用新值替换第二个查询中的静态值。此外,您使用了两个查询来完成本可以用一个查询完成的任务。
第三步:整合为单一查询(使用子查询)
这正是我们可以使用子查询来动态、高效解决问题的完美例子。一旦前两个查询正常工作,我们就可以进入第三步,将其编写为单一查询。

/* 第三步:在WHERE子句中使用子查询 */
PROC SQL;
SELECT State
FROM state_population
WHERE P_estimate1 > (SELECT AVG(P_estimate1) FROM state_population);
QUIT;
在第三步中,您只需将子查询插入到外部查询中。这里,我们将返回明年所有州预估人口平均值的第一个查询,放在了之前静态值的位置。
子查询的执行过程
当这个查询运行时,SAS首先评估内部查询并返回一个值。然后,SAS执行外部查询,并使用子查询返回的值。
这个子查询独立于外部查询执行,在执行前将一个或多个值传递回外部查询,因此它是一个非关联子查询。

具体过程如下:
- 子查询执行,并将值
6,278,420返回给外部查询。 - 外部查询执行,在WHERE子句中使用这个平均值。
优势与注意事项
使用子查询是使您的程序更加动态和高效的好方法,并且可以解决独特的问题。
请注意,当使用子查询时,您不会直接看到从子查询检索到的值。查看该值的一种方法是将其存储在宏变量中,这个主题将在其他地方介绍。
总结

在本节课中,我们一起学习了如何在SAS的WHERE子句中使用子查询。我们通过一个从州人口表中筛选出高于平均预估人口的州的例子,理解了子查询的分步构建过程、执行原理及其带来的动态性和效率优势。掌握子查询将极大地增强您处理复杂数据筛选任务的能力。
064:使用返回单个值的子查询 🎯
在本节课中,我们将学习如何在SAS查询中使用一种特殊的子查询——返回单个值的子查询。这种技术允许我们动态地获取一个值(如平均值、总和),并将其用于主查询的条件判断中,从而使代码更加灵活和自动化。
探索数据表

首先,我们来查看将要使用的数据表 state population。

该表包含州名缩写和人口估计值等列。我们的目标是找出所有“人口估计值1”高于总平均值的州。
计算平均值

为了进行比较,我们需要先计算出“人口估计值1”列的平均值。


以下是计算平均值的查询语句:
SELECT AVG(P_estimate_1) FROM state_population
运行该查询后,我们得到了平均值。


结果显示,平均人口约为620万。接下来,我们将把这个值用于主查询。

使用静态值进行查询
最直接的方法是将计算出的平均值作为一个静态数字写入主查询的条件中。


以下是主查询语句,我们暂时将平均值 6200000 硬编码进去:
SELECT name, P_estimate_1
FROM state_population
WHERE P_estimate_1 > 6200000
运行查询后,我们得到了所有人口高于平均值的州。


为了使结果更清晰,我们可以添加 ORDER BY 子句,按人口降序排列。
SELECT name, P_estimate_1
FROM state_population
WHERE P_estimate_1 > 6200000
ORDER BY P_estimate_1 DESC
再次运行查询,结果将按人口从高到低显示。


静态方法的局限性

然而,使用静态值存在一个问题:如果底层数据更新,平均值发生变化,我们就必须重新计算平均值,并手动更新主查询中的数字。

这个过程既繁琐又容易出错。为了解决这个问题,我们可以使用子查询。
引入子查询
子查询可以嵌入到主查询中,动态地提供所需的值。这样,每次运行查询时,都会自动计算最新的平均值。
以下是使用子查询的步骤:
- 复制计算平均值的查询语句(注意不要复制末尾的分号)。
- 在主查询中,用括号
()包裹这个子查询,替换掉静态值。
修改后的查询语句如下:
SELECT name, P_estimate_1
FROM state_population
WHERE P_estimate_1 > (SELECT AVG(P_estimate_1) FROM state_population)
ORDER BY P_estimate_1 DESC
运行这个查询,我们会得到与之前完全相同的结果,但过程是全自动的。


子查询的灵活性
子查询的强大之处在于,它返回的值不一定非要来自主查询所使用的表。


例如,我们可以修改子查询,从另一个表 SAShelp.us_data 中计算2010年的人口平均值,并用它作为筛选条件。
SELECT name, P_estimate_1
FROM state_population
WHERE P_estimate_1 > (SELECT AVG(Population_2010) FROM SAShelp.us_data)
ORDER BY P_estimate_1 DESC
运行此查询,我们将基于2010年的平均人口值得出结果。


总结

本节课中,我们一起学习了如何使用返回单个值的子查询。
- 我们首先通过计算静态平均值来筛选数据,但指出了这种方法在数据变化时不便于维护。
- 然后,我们引入了子查询的概念,将计算平均值的语句直接嵌入到
WHERE条件中,实现了条件的动态化。 - 最后,我们还了解到子查询可以引用其他表中的数据,这大大增加了查询的灵活性和功能性。

掌握这种子查询技术,能使你的SAS编程更加高效和健壮。
065:在HAVING子句中使用子查询 🎯
在本节课中,我们将学习如何在SQL的HAVING子句中使用子查询。我们将通过一个具体的例子来理解如何筛选分组聚合后的数据。
概述
上一节我们讨论了在WHERE子句中使用子查询。本节中,我们来看看如何在HAVING子句中使用子查询来对分组后的数据进行筛选。
场景介绍
美国被划分为九个区域,每个区域都有一个区域编号。现在需要生成一份报告,显示那些平均预估人口大于所有州总平均人口的区域。

这个场景与之前的类似,但区别在于,我们现在需要对分组后的数据进行筛选。
使用HAVING子句筛选分组数据
要对分组数据进行筛选,我们可以使用HAVING子句。我们将使用state_population表中的division和P_estimate1列来创建报告。

手动方法生成程序
首先,我们使用手动方法来生成程序。这个例子再次计算了state_population表中P_estimate1的平均值。
以下是核心的SQL代码结构:

SELECT division, AVG(P_estimate1) AS avg_pop
FROM state_population
GROUP BY division
HAVING AVG(P_estimate1) > (SELECT AVG(P_estimate1) FROM state_population);
子查询与外部查询的配合
然后,我们使用子查询返回的值来完成外部查询的HAVING子句。外部查询将每个州分组到其对应的区域,然后计算每个区域的预估人口平均值。

在外部查询中,我们必须使用HAVING子句来筛选我们的分组聚合结果。
总结

本节课中,我们一起学习了在SQL的HAVING子句中使用子查询的方法。通过一个具体的报告需求,我们了解了如何先通过子查询计算出一个整体的平均值,然后在外部查询的分组聚合中使用HAVING子句进行筛选,从而得到符合条件的分组结果。
066:返回多个值的子查询 🔍
在本节课中,我们将要学习如何在SAS的WHERE子句中使用能够返回多个值的子查询。之前我们介绍的子查询都只返回单个值,而本节将扩展这一概念,处理返回多行结果的子查询。
子查询返回值的限制
上一节我们介绍了返回单个值的子查询。在WHERE和HAVING子句中使用的子查询必须只返回一列数据。

但是,这一列可以包含单个行值,也可以包含多个行值。
业务场景引入
假设你被要求为团队中的分析师创建一个表格,其中包含所有居住在“第三分区”的客户。分析师需要此表格来对这些州的客户进行可视化分析。

完成任务所需的信息存储在state_population(州人口)表和customer(客户)表中。
分步任务分析
以下是完成此任务的两个关键步骤:
- 在
state_population表中,需要筛选出division(分区)为3的行,然后返回属于该分区的所有州名。 - 在
customer表中,需要使用属于第三分区的那些州名来筛选表格。
你可以使用子查询来完成这项任务,下面我们来看看具体如何操作。
构建子查询

首先,我们使用一个独立查询来从state_population表中筛选出所有位于第三分区的州。
SELECT state
FROM state_population
WHERE division = 3;
执行此查询的结果显示,有五个州位于第三分区。这个查询将作为我们的子查询。
构建主查询(静态方法)

在第二个查询中,我们希望创建一个名为Division3的新表,其中包含居住在我们之前结果中任一州的客户。
最初,我们可以手动在WHERE子句中键入州名,并使用IN操作符。
CREATE TABLE Division3 AS
SELECT *
FROM customer
WHERE state IN ('IL', 'IN', 'MI', 'OH', 'WI');
当我们执行第二个查询时,将获得一个包含来自IL、IN、MI、OH、WI州客户列表的新表。
虽然这种方法有效,但我们的程序是静态的。如果我们想将分区从3改为4,或者为了代码整洁而希望用一个查询而不是两个查询来编写,该怎么办?
整合为动态子查询
我们可以将第一个查询作为子查询嵌入到WHERE子句中,动态构建第三分区的州列表,而第二个查询则成为主查询。
CREATE TABLE Division3 AS
SELECT *
FROM customer
WHERE state IN (SELECT state
FROM state_population
WHERE division = 3);
这个解决方案更加高效,并且可以根据分区动态生成结果。
本节总结

本节课中我们一起学习了如何使用返回多个值的子查询。关键点在于,在WHERE子句中使用IN操作符配合子查询,可以动态地基于一个条件(如分区号)筛选出符合多个值(如州名)的记录。这种方法比硬编码值列表更灵活、更易于维护。
SAS高级程序员专项课程:P67:使用返回多值的子查询 🧩
在本节课程中,我们将学习如何在SAS查询中,使用一个能返回多个值的子查询来动态筛选数据。这种方法比手动输入静态值更高效、更灵活。
上一节我们讨论了子查询的基本概念,本节中我们来看看一个具体的应用场景:使用子查询返回一个州列表,并用这个列表来筛选客户数据。
首先,我们运行一个查询来找出所有属于“第3分区”的州。查询结果如下:



我们可以看到,第3分区包含以下州:IL, IN, MI, OH, WI。
接下来,我们的目标是创建一个名为 division_3 的新表,其中包含所有位于上述州内的客户信息。最初,我们可以手动将这些州名输入到外部查询的 WHERE 子句中。代码如下:
CREATE TABLE division_3 AS
SELECT * FROM customer_table
WHERE state IN ('IL', 'IN', 'MI', 'OH', 'WI');
运行此查询后,我们成功创建了 division_3 表。在输出数据标签页中,可以看到该表有16,022行和22列。

虽然手动输入可行,但我们可以用子查询来改进它,使其自动化。以下是使用子查询的步骤:
- 构建子查询:首先,我们编写一个能返回第3分区所有州名的子查询。
- 替换静态值:然后,用这个子查询替换外部查询
WHERE IN子句中的静态州名列表。
以下是修改后的代码。请注意,子查询必须只返回一列(州名),不能包含其他列(如分区号),否则会导致错误。
CREATE TABLE division_3 AS
SELECT * FROM customer_table
WHERE state IN (
SELECT state_name FROM division_table WHERE division = 3
);
运行这个查询,我们得到了与手动输入完全相同的结果:16,022行和22列。这说明子查询工作正常。
使用子查询的最大优势在于其灵活性。例如,如果现在我们需要创建第6分区的客户表,使用静态方法需要:
- 先查询第6分区有哪些州。
- 再手动修改外部查询中的州列表。
而使用子查询,我们只需修改两个地方:
CREATE TABLE division_6 AS /* 1. 修改目标表名 */
SELECT * FROM customer_table
WHERE state IN (
SELECT state_name FROM division_table WHERE division = 6 /* 2. 修改子查询条件 */
);
运行此查询,我们便快速得到了第6分区约4,900名客户的数据。

总结

本节课中,我们一起学习了如何使用返回多值的子查询来动态筛选数据。核心方法是:将一个查询(子查询)的结果,作为另一个查询(外部查询)中 WHERE IN 子句的条件列表。这比硬编码静态值更高效、更易于维护,尤其是在筛选条件需要频繁变化时。在后续课程中,我们还将学习如何进一步优化这类查询。
068:使用ANY关键字
在本节课中,我们将学习如何在SAS中使用ANY关键字。ANY关键字与子查询结合使用,用于判断主查询中的值是否满足与子查询返回的任意一个值之间的特定条件。
概述
ANY关键字的功能类似于IN运算符,但它允许与比较运算符(如>、<、=等)结合使用,提供更灵活的条件判断。通过本节学习,你将掌握ANY关键字的基本语法、工作原理及其应用场景。
ANY关键字的基本概念
与IN运算符类似,你可以使用ANY关键字来指定:从子查询获得的一组值中,至少有一个值满足给定条件时,整个表达式即为真。

其基本语法结构可以表示为:
主查询表达式 比较运算符 ANY (子查询)
例如:value > ANY (SELECT column FROM table)
ANY关键字的工作原理
为了帮助你理解ANY关键字如何工作,我们来看一个具体的例子。
假设你正在处理一个返回两个值的子查询:佛罗里达州和纽约州的人口估计值。你想知道哪些州的人口估计值大于这两个州中的任意一个。
以下是实现此查询的代码示例:
proc sql;
SELECT State
FROM census_data
WHERE P_Estimate1 > ANY (
SELECT P_Estimate1
FROM census_data
WHERE State IN ('Florida', 'New York')
);
quit;

在这个例子中,子查询前的表达式将ANY关键字与大于运算符(>)结合使用。因此,当指定列的值大于子查询返回的任何一个值时,该表达式即为真。
深入理解ANY与比较运算符
让我们更具体地分析一下。实际上,当使用value > ANY (子查询)时,只要value大于子查询结果中的最小值,条件就成立。
延续上面的例子,如果子查询返回佛罗里达州的人口值21,000,000和纽约州的人口值19,641,589,那么ANY表达式为真的条件是:主查询中州的人口值大于19,641,589(即两个值中的较小者)。
换句话说,当列中的任何值大于子查询返回的最小值时(在本例中为19,641,589),表达式即为真。

ANY关键字的替代写法
你还可以在子查询内部使用MIN函数来返回最小的P_Estimate1值。然后,你可以直接将该值与比较运算符一起使用,而无需ANY关键字。
以下是等价的写法:
proc sql;
SELECT State
FROM census_data
WHERE P_Estimate1 > (
SELECT MIN(P_Estimate1)
FROM census_data
WHERE State IN ('Florida', 'New York')
);
quit;
这种方法先通过子查询计算出明确的最小值,再在主查询中进行比较,逻辑上更直观,但ANY关键字写法通常更简洁。

应用场景与注意事项
在结束之前,我们总结一下ANY关键字的核心应用与要点。
以下是ANY关键字的主要使用场景:
- 与各类比较运算符结合:除了
>,还可以与<、=、>=、<=、<>(不等于)等结合使用。 - 简化查询逻辑:当需要判断主查询值是否满足与子查询结果集中任一值的关系时,使用
ANY可以使SQL语句更清晰。 - 处理未知的结果集:当子查询可能返回多个值,且数量不确定时,
ANY提供了一种动态的比较方式。
需要注意的是:
ANY与SOME关键字在SAS中功能完全相同,可以互换使用。- 当子查询返回空集时,
ANY条件的结果为假。 - 理解
ANY与ALL关键字的区别至关重要(ALL要求满足与子查询所有值的比较关系)。
总结

本节课中,我们一起学习了SAS中ANY关键字的使用。我们了解到,ANY关键字用于构建主查询与子查询之间的条件关系,它要求主查询的值满足与子查询返回的任意一个值之间的比较条件。我们通过人口数据的例子,探讨了它的语法、工作原理,并介绍了使用MIN函数的等效写法。掌握ANY关键字,能让你编写出更强大、更灵活的SQL查询语句。
069:使用关联子查询 🔗
在本节课中,我们将要学习关联子查询的概念、工作原理,以及它与非关联子查询的区别。我们还将探讨为何应尽量避免使用关联子查询,并介绍更优的替代方案。
上一节我们介绍了非关联子查询,它们是自包含的,可以独立于外部查询执行。
关联子查询则依赖于外部查询。
它需要外部查询传递一个或多个值给它,然后子查询才能被解析。
这意味着ProCSQL必须多次处理关联子查询。
外部查询处理的每一行表数据,子查询都会执行一次。
关联子查询往往非常消耗资源。
你通常应该避免使用关联子查询,我们稍后会简要讨论其原因。

以下是使用关联子查询的一个示例场景。
我们想找出有多少客户来自某个州且属于Division1。
我们之前已经见过使用非关联子查询解决此问题的方案。
但我们也可以使用关联子查询来解决它。
我们在WHERE子句中使用静态值‘1’来代表Division1。
WHERE子句中的关联子查询会为每一行执行一个查询,该查询在州人口表和客户表之间进行连接。
连接的结果将返回居住在Division1的客户。
关联子查询不是独立的,因为它们需要从主查询中获取额外信息。
内部查询中的WHERE表达式引用了外部查询中某个表的值。
关联子查询会针对外部查询中的每一行进行评估。外部查询获取一行数据。
然后利用子查询的结果来测试WHERE条件,以判断该州是否属于Division 1。
如果是,则向外部查询返回一行。
然而,存在更好的方法。

更好的方法是连接这些表,并使用WHERE子句来获取所需的行。

本节课中我们一起学习了关联子查询。我们了解到,关联子查询依赖于外部查询的值,会为外部查询的每一行执行一次,因此效率较低。在大多数情况下,使用表连接配合WHERE子句是更高效、更推荐的数据检索方式。
070:使用临时表与内联视图 🧮
在本节课中,我们将学习如何生成一份报告,用于展示基于各州估计人口计算的客户百分比。我们将探讨两种实现方法:使用临时表和使用内联视图,并比较它们的优劣。
概述

假设我们需要一份报告,根据各州的估计人口来显示每个州的客户百分比。所需的数据分别位于 customer(客户)表和 state_population(州人口)表中。
使用临时表的方法
一种创建此报告的方法是使用临时表。我们可以编写一个查询,从 customer 表中统计每个州的客户总数,并将结果存入一个名为 Tot_customer 的临时表中。
以下是创建该临时表的查询示例:
CREATE TABLE Tot_customer AS
SELECT State, COUNT(*) AS Total_Customers
FROM customer
GROUP BY State;

创建临时表后,我们需要将其与 state_population 表进行连接,然后计算每个州的客户百分比。
连接与计算的查询示例如下:
SELECT sp.State, sp.Estimated_Population,
tc.Total_Customers,
(tc.Total_Customers / sp.Estimated_Population) * 100 AS Percent_Customers
FROM state_population sp
JOIN Tot_customer tc ON sp.State = tc.State;

这个解决方案是有效的,但它需要进行一次连接操作并依赖一个临时表。我们希望即使客户列表不断增长,也能生成这份报告,以确保每个州的客户百分比始终准确。
临时表方法的局限性
如果 customer 表中的数据每日更新,那么每次运行此查询时,我们都必须重复第一步,即重新创建 Tot_customer 临时表,然后再连接 Tot_customer 和 state_population 表。这个过程可能效率较低。
使用内联视图的解决方案

针对此问题的另一个解决方案是使用内联视图。内联视图允许我们在一个查询中定义子查询,而无需创建物理临时表。
以下是使用内联视图的查询示例:
SELECT sp.State, sp.Estimated_Population,
cust_counts.Total_Customers,
(cust_counts.Total_Customers / sp.Estimated_Population) * 100 AS Percent_Customers
FROM state_population sp
JOIN (
SELECT State, COUNT(*) AS Total_Customers
FROM customer
GROUP BY State
) cust_counts ON sp.State = cust_counts.State;
在这个查询中,(SELECT ... FROM customer GROUP BY State) 部分就是一个内联视图(或称为派生表)。它直接在 JOIN 子句中执行分组和计数,其结果会与 state_population 表即时连接。这样就不再需要先创建和维护一个单独的临时表。
总结

本节课中,我们一起学习了生成州级客户百分比报告的两种方法。我们首先介绍了使用临时表的步骤,包括创建表、连接和计算。随后,我们探讨了该方法的局限性,特别是在数据频繁更新时的重复操作问题。最后,我们介绍了更高效的内联视图解决方案,它通过在一个查询内嵌入子查询来避免创建物理临时表,使代码更简洁且易于维护。理解这两种方法有助于你根据不同的数据更新频率和场景,选择最合适的查询策略。
071:什么是内联视图 🔍
在本节课中,我们将要学习SAS SQL中一个重要的概念——内联视图。我们将了解它的定义、作用、语法规则以及它与子查询的区别。
概述

内联视图是嵌套在另一个查询FROM子句中的查询。它充当一个虚拟表,供外层查询使用,而不是使用物理表。
内联视图的定义与作用
上一节我们介绍了内联视图的基本概念,本节中我们来看看它的具体表现形式和作用。

内联视图是一个被嵌套在另一个查询FROM子句中的查询。其基本语法结构如下:
SELECT ...
FROM (SELECT ... FROM ...) AS inline_view_name
WHERE ...
它充当一个虚拟表,外层查询可以像使用普通物理表一样使用它。
内联视图的语法规则

了解了内联视图是什么之后,我们来看看使用它时必须遵守的语法规则。
一个内联视图可以包含SELECT语句中的大多数子句,但有一个关键限制。以下是其主要语法特征:
- 内联视图可以包含
SELECT语句中的任何子句,除了ORDER BY子句。 - 内联视图必须用圆括号括起来。
- 内联视图只能在定义它的查询中被引用。
内联视图与子查询的区别

初学者有时会混淆内联视图和子查询。它们的关键区别在于返回的结果。
与子查询不同,内联视图可以向外层查询返回单列或多列数据。而子查询通常作为表达式使用,返回单个值或一列值。
内联视图的应用场景

那么,在什么情况下我们会使用内联视图呢?

当你构建复杂的SQL查询时,内联视图通常非常有用。它可以帮助你分步处理数据、简化连接操作或预先进行数据聚合,从而使主查询的逻辑更加清晰。
总结
本节课中我们一起学习了SAS SQL中的内联视图。我们了解到它是一个嵌套在FROM子句中的虚拟表,必须用括号括起,并且可以返回多列数据。掌握内联视图有助于我们编写更加模块化和强大的复杂查询。
072:使用内联视图
在本节课中,我们将学习如何使用内联视图来解决一个具体的数据查询问题。我们将通过一个计算各州客户占比的案例,演示如何将临时表查询转换为更高效的内联视图查询。
概述
我们将要解决的问题是:计算每个州的客户数量占该州总人口的百分比。首先,我们会使用创建临时表的方法来实现,然后将其优化为使用内联视图的单一查询。
探索数据与问题定义
首先,我们需要了解涉及的两张表:客户表和州人口表。我们的目标是统计每个州的客户总数,然后将其与州人口表中的估计人口数结合,通过公式 客户数 / 估计人口数 来计算客户占比。
解决方案一:使用临时表
以下是使用临时表解决问题的分步方法。

第一步,我们创建一个名为 TotalCustomer 的临时表,用于存放每个州的客户总数。

CREATE TABLE TotalCustomer AS
SELECT State, COUNT(*) AS Total_Customers
FROM SQ.Customer
GROUP BY State;
运行上述查询后,TotalCustomer 临时表将包含两列:State(州名)和 Total_Customers(该州客户总数)。
接下来,我们需要将这个临时表与州人口表进行连接。
SELECT
c.State,
c.Total_Customers,
s.Estimated_Population,
(c.Total_Customers / s.Estimated_Population) AS Percent_Customer
FROM TotalCustomer c
INNER JOIN StatePopulation s
ON c.State = s.Name
ORDER BY Percent_Customer;
运行此连接查询后,结果将按客户百分比升序排列。例如,佛蒙特州(VT)的客户占比可能最低,而华盛顿特区(DC)的占比可能最高。这个方案虽然有效,但需要创建和维护一个额外的临时表。
解决方案二:使用内联视图
上一节我们介绍了使用临时表的方法,本节中我们来看看如何用内联视图来优化这个过程。内联视图允许我们将一个查询的结果作为虚拟表,直接在另一个查询的 FROM 子句中使用。
以下是使用内联视图的查询语句。它与临时表方案的核心逻辑相同,但将所有步骤合并到了一个查询中。
SELECT
c.State,
c.Total_Customers,
s.Estimated_Population,
(c.Total_Customers / s.Estimated_Population) AS Percent_Customer
FROM (
-- 这是内联视图,替代了之前的临时表
SELECT State, COUNT(*) AS Total_Customers
FROM SQ.Customer
GROUP BY State
) c
INNER JOIN StatePopulation s
ON c.State = s.Name
ORDER BY Percent_Customer;
关键点:在编写内联视图时,需要注意,内联视图内部不能包含 ORDER BY 子句。如果包含,会导致语法错误。因此,在将临时表查询转换为内联视图时,必须移除内层查询的 ORDER BY。
运行此查询,你将得到与临时表方案完全相同的结果集。然而,使用内联视图有一个重要优势:内联视图在查询执行的瞬间动态计算。这意味着,如果源数据表 SQ.Customer 中的数据增长或变化,下次运行此查询时,内联视图会自动计算最新的客户总数,无需手动更新临时表。
总结

本节课中我们一起学习了内联视图的应用。我们首先通过创建临时表的方法解决了计算州客户占比的问题,然后将其优化为使用单一查询的内联视图方案。内联视图的核心优势在于其动态性和简洁性,它避免了创建物理临时表的开销,并能实时反映数据变化。记住,在使用内联视图时,要确保内层查询不包含 ORDER BY 子句。
SAS高级程序员专项课程:P73:创建视图 👁️
在本节课中,我们将学习如何创建和使用Proc SQL视图,以解决内联视图无法在其他查询中重复使用的问题。
上一节我们介绍了内联视图的使用,本节中我们来看看如何创建可重复使用的视图。
什么是Proc SQL视图?
内联视图没有被分配表名,不能像表一样在其他查询或SAS过程中被引用。它们只能在定义它们的查询中被引用。为了重用这个内联视图,每次需要时都必须重新输入代码。虽然这个内联视图很短,但其他视图可能更复杂,重新输入它们可能非常耗时。此外,如果需要编辑代码,则必须在每个使用该内联视图的地方进行编辑。

然而,有一个更高效的解决方案。

你可以创建一个可以在其他查询中引用的Proc SQL视图。Proc SQL视图是一个存储的查询,可以基于一个或多个表或任何类型的SAS视图(例如,数据步视图或SAS访问视图)。因此,Proc SQL视图包含查询代码,但不包含实际数据。它不是一个物理表。相反,视图有时被称为虚拟表,因为它可以像物理表一样在查询和其他SAS程序中被引用。视图在每次使用时都会提取底层数据,并访问最新的数据。
如何创建Proc SQL视图?
要创建Proc SQL视图,需要使用CREATE VIEW语句。与CREATE TABLE语句不同,CREATE VIEW语句只有一种形式,即包含一个查询。

当你提交CREATE VIEW语句时,查询的报告输出会被抑制。在CREATE VIEW关键字之后,你需要指定视图的名称。视图名称必须遵循SAS命名规则,并且不能指定同一SAS库中现有表或视图的名称。
接下来,指定关键字AS,后跟查询子句。在这个例子中,CREATE VIEW语句创建了一个名为SQ.total_customer的Proc SQL视图。
从技术上讲,你可以在CREATE VIEW语句中使用任何可选的查询子句。然而,为了提高效率,建议避免在定义视图的查询中使用ORDER BY子句,因为它会强制Proc SQL在每次引用视图时都对数据进行排序。相反,你可以在引用视图的查询中使用ORDER BY子句。
以下是创建视图的基本语法:
CREATE VIEW 视图名称 AS
SELECT 列1, 列2, ...
FROM 表名
WHERE 条件;
如何使用Proc SQL视图?
要使用视图,只需将内联视图替换为已创建的视图。

在这个基本的Proc SQL查询中,FROM子句引用了名为SQ.total_customer的Proc SQL视图。当这个程序运行时,视图会执行并从底层数据源提取最新的数据。

如果你需要对视图进行更改,这个更改将在所有使用该视图的地方反映出来。


Proc SQL视图的优势
以下是使用Proc SQL视图的主要优势:
- 代码复用:避免重复编写复杂的查询逻辑。
- 数据实时性:每次使用都访问最新的底层数据。
- 维护简便:只需修改视图定义,所有引用该视图的程序都会自动更新。
- 逻辑封装:将复杂的查询逻辑封装在视图中,使主查询更简洁。

总结
本节课中我们一起学习了Proc SQL视图的创建和使用。我们了解到,视图是一种存储的查询(虚拟表),它不包含数据本身,而是在被引用时动态地从底层数据源提取数据。通过使用CREATE VIEW语句,我们可以创建可重复使用的视图,从而避免代码重复、简化维护,并确保数据访问的实时性。记住,在定义视图时通常应避免使用ORDER BY子句以提升效率。
074:使视图可移植 📂
在本节课中,我们将学习如何创建可移植的SAS视图。根据ANSI标准,视图通常必须与其源表位于同一物理位置,这限制了视图的灵活性。我们将探讨如何通过使用USING子句来打破这一限制,使视图能够独立于其源表的位置被存储和使用。
标准视图的位置限制
根据ANSI标准,视图必须与其引用的源表位于同一物理位置。
因此,在FROM子句中引用的表的隐式库引用,就是包含该视图的库,或者是SQL库和SASWORK数据文件夹。

由于视图和数据源在同一位置,按照ANSI标准,你可以在FROM子句中使用单层名称来指定表。
这里的单层名称并非指代SASWORK库中的临时表。相反,它表示视图与其源表存储在同一位置。

视图移植性问题示例

假设一个营销团队将一个视图移动到他们的SASWORKSHOP文件夹,并在那里定义了MKT库。
然后,他们尝试使用该视图执行查询,却收到一个错误,提示customer表不存在。为什么会这样?让我们回顾一下这个视图是如何创建的。
存储的查询使用了单层命名约定,因此PROC SQL会假定customer表位于SQL库中。
这违反了ANSI的单层命名约定。当营销团队移动了视图并使用它运行查询时,存储的查询会假定customer表现在位于SASWORKSHOP的MKT库中。然而,customer表实际上并不在那个位置。

创建可移植视图的解决方案
通过使用一项增强功能,你可以创建一个与其源表存储在不同物理位置的视图。换句话说,你可以使视图变得可移植。
当你基于永久表创建永久视图时,可以在CREATE VIEW语句中添加USING子句来指定源表库的位置,从而使你的视图可移植。
USING子句是一个嵌入式的LIBNAME语句,它允许你为源表分配一个库引用。
这个USING子句指定了customer表的位置(例如SASWORK.DATA),而视图本身位于SASWORKSHOP。这被称为库名子句,因为它出现在另一个子句内部。
通常,当你基于永久表创建永久的PROC SQL视图时,最佳实践是使用USING子句。USING子句必须是CREATE VIEW语句中的最后一个子句。
总结

本节课中,我们一起学习了SAS视图的可移植性问题及其解决方案。我们了解到,标准的ANSI约定要求视图与源表同库,这带来了使用上的不便。通过引入USING子句,我们可以明确指定源表的库位置,从而创建出可以独立存储和迁移的视图,这大大提高了代码的灵活性和可维护性。记住,在创建基于永久表的永久视图时,养成使用USING子句的习惯是一个好做法。
075:在SELECT子句中使用子查询 📊
在本节课中,我们将学习如何在SQL的SELECT子句中使用子查询。子查询可以嵌套在SELECT语句中,用于返回一个单一的计算值,该值将作为外部查询的一部分被使用。
概述

上一节我们介绍了子查询的基本概念。本节中,我们来看看子查询在SELECT子句中的具体应用。这种用法通常用于为查询结果中的每一行计算一个基于整体数据集的单一值。
核心概念与应用场景
您可以在SELECT子句中使用子查询,以便为外部查询的每一行返回一个单一的计算值。
例如,假设我们需要生成一份报告,展示基于总预估人口计算的、每个州明年的预估人口百分比。
因此,我们需要将每个州的P_estimate1值除以所有州的P_estimate1值的总和。

我们可以使用SELECT子句中的子查询来完成这项任务。
实现步骤与代码示例
以下是实现该需求的SQL查询结构:
SELECT
state,
P_estimate1,
/* 在SELECT子句中使用子查询计算总人口 */
P_estimate1 / (SELECT SUM(P_estimate1) FROM state_population) AS percentage_next_year
FROM
state_population;
这个子查询使用SUM函数对state_population表中的P_estimate1值进行求和,并返回明年的总预估人口。
子查询解析完成后,这个总值将作为分母,用于计算每个州的百分比。
技术要点总结

本节课中我们一起学习了在SELECT子句中使用子查询的方法。这种解决方案符合ANSI SQL标准,其核心在于子查询返回一个标量值(单一值),该值随后被用于外部查询的每一行计算中。通过这种方式,我们可以便捷地实现基于整体汇总数据的行级计算。
076:在PROC SQL中重新合并汇总统计量 📊
在本节课中,我们将学习PROC SQL中一个强大的功能——重新合并。这个功能允许我们在查询中直接使用汇总函数(如求和、平均值),并将汇总结果与原始数据行进行合并,从而简化复杂计算。
概述
上一节我们介绍了PROC SQL的基础查询。本节中,我们来看看如何使用SAS对SQL语言的增强功能,即通过重新合并数据来创建包含汇总统计量的报告。
SAS对SQL语言的一个重要增强是,能够使用汇总函数使相同的计算为每一行重复执行。这发生在PROC SQL重新合并数据时。当SELECT子句中包含由汇总函数创建的列、其他未汇总的列,并且没有GROUP BY子句时,就会发生重新合并。
重新合并的工作原理

PROC SQL的重新合并功能会对表进行两次处理。第一次处理创建的数据将在第二次处理中用于完成查询。
以下是重新合并过程的步骤说明:

- 执行内部查询以汇总数据:PROC SQL首先运行一个内部查询,对整个目标列进行汇总计算。
- 执行第二个内部查询以选择数据:接着,PROC SQL运行另一个内部查询,从每一行中选择所需的列(如名称和原始值)。
- 合并结果:计算出的汇总值(例如总和)会与每一行的原始数据合并,从而在结果集的每一行中重复出现。
例如,考虑以下查询,它计算了每个州人口占总人口的百分比:
proc sql;
select State,
PopEstimate1,
PopEstimate1 / sum(PopEstimate1) as Percent format=percent8.2
from census;
quit;

在这个查询中:
sum(PopEstimate1)汇总了整个PopEstimate1列,得到明年的总估计人口。- 每个州的人口值被除以这个汇总后的总值,计算出百分比。
- PROC SQL先运行内部查询计算总和,再运行另一个内部查询进行除法运算。
重要注意事项
在使用重新合并功能时,有几点需要牢记:
- 日志提示:当一个查询重新合并数据时,PROC SQL会在日志中显示一条注释,表明发生了数据重新合并。
- 数据来源限制:重新合并只允许你重新合并外部查询表中存在的数据。它不能直接用于合并来自不同表或子查询的汇总数据,除非这些数据已在主查询的FROM子句中可用。
总结

本节课中我们一起学习了PROC SQL中的重新合并功能。我们了解到,通过在SELECT子句中结合使用汇总函数和非汇总列(且不使用GROUP BY),SAS可以自动执行两次数据传递,将汇总结果便捷地合并到每一行原始数据中。这个功能极大地简化了诸如计算百分比、与总体平均值比较等常见分析任务的代码编写。
077:重新合并汇总统计量 📊
在本节课中,我们将学习如何在SAS查询中重新合并汇总统计量,并利用这一特性计算每个州人口占总人口的百分比。
概述
上一节我们介绍了汇总统计量的基本概念。本节中,我们来看看如何将计算出的汇总统计量(如总和)重新合并回原始数据行,以进行进一步的计算,例如计算百分比。
查询演示
以下是初始查询步骤。我们首先从state_population表中选择name和P_estimate1列,同时使用SUM函数计算P_estimate1的总和。
PROC SQL;
SELECT name, P_estimate1, SUM(P_estimate1) FORMAT=COMMA12.
FROM state_population;
QUIT;
运行此查询后,结果会显示每一行的name和P_estimate1值,并且SUM(P_estimate1)计算出的单个总和值会出现在每一行中。这表明汇总统计量已被重新合并。
查看日志,可以看到一条注释:“查询将汇总统计量重新合并回原始数据”。这虽然不完全是我们最终想要的结果,但为我们解决问题提供了基础。
计算百分比
我们的目标是计算每个州的人口估计值占总人口的百分比。为此,我们需要用每个州的P_estimate1除以它的总和。
以下是修改后的查询,我们添加了百分比计算和格式化:
PROC SQL;
SELECT name,
P_estimate1,
P_estimate1 / SUM(P_estimate1) AS PCT_Pop FORMAT=PERCENT7.2
FROM state_population;
QUIT;


注意,我们使用AS关键字为计算出的百分比列命名(PCT_Pop),并使用PERCENT7.2格式将其显示为百分比。
运行此查询后,我们得到了每个州人口占总人口的百分比。例如,加利福尼亚州约占12%。
排序结果
为了更清晰地查看数据,我们可以对结果进行排序。我们希望按PCT_Pop降序排列,以看到人口占比最高的州。
以下是添加了ORDER BY子句的最终查询:
PROC SQL;
SELECT name,
P_estimate1,
P_estimate1 / SUM(P_estimate1) AS PCT_Pop FORMAT=PERCENT7.2
FROM state_population
ORDER BY PCT_Pop DESC;
QUIT;
运行最终查询后,结果按人口百分比从高到低排列。加利福尼亚州位居榜首,其次是德克萨斯州、佛罗里达州等。列表底部是怀俄明州和佛蒙特州,占比约为0.19%和0.18%。
总结
本节课中,我们一起学习了SAS中“重新合并汇总统计量”的特性。通过演示,我们掌握了如何:
- 在
SELECT语句中使用聚合函数(如SUM)。 - 利用重新合并的特性,在行级别进行基于总和的计算(如计算百分比)。
- 使用
AS关键字为计算列命名。 - 使用
FORMAT语句格式化输出结果。 - 使用
ORDER BY子句对查询结果进行排序。

这个功能是SAS SQL的一个增强特性,能够高效地解决需要在详细数据旁展示汇总信息的常见分析需求。
078:控制重新合并
在本节课中,我们将学习SAS Proc SQL中一个重要的概念——重新合并。我们将了解它的作用、可能引发的问题,以及如何通过系统选项来控制它,以避免产生非预期的结果。
重新合并的概念与常见问题
上一节我们介绍了重新合并的基本机制。本节中我们来看看一个因疏忽而导致重新合并出错的常见场景。
重新合并是一个强大的工具,但它并不总能提供期望的结果。最常见的例子是在查询中忘记使用GROUP BY子句。
在以下代码中,我们希望找出每个区域下一年度的预估总人口。
SELECT region, SUM(p_estimate1)
FROM table_name;
因为我们没有包含GROUP BY语句,所以意外地触发了数据的重新合并,并得到了一个非预期的答案。总计的区域值被显示在了报告中的每一行。


如何禁用重新合并
了解了问题所在后,我们来看看如何防止这种情况发生。当你在SELECT子句或HAVING子句中使用汇总函数时,Proc SQL可能会重新合并数据。
以下是控制重新合并的两种方法:
PROC SQL NOREMERGE选项:在Proc SQL步骤中设置此选项。NOSQLREMERGE系统选项:在SAS会话级别设置此系统选项。
如果你设置了PROC SQL NOREMERGE选项或NOSQLREMERGE系统选项,Proc SQL将不会处理数据的重新合并。
禁用重新合并后的效果
那么,禁用重新合并后,之前出错的查询会怎样呢?提交带有NOREMERGE选项的查询不会产生输出,并会在日志中生成一条错误信息。
错误信息表明:该查询需要将汇总统计量重新合并回原始数据;但由于设置了NOREMERGE Proc选项或NOSQLREMERGE系统选项,此操作被禁止。

课程总结

本节课中我们一起学习了如何控制Proc SQL中的重新合并。我们首先回顾了因遗漏GROUP BY子句导致错误重新合并的典型问题,然后介绍了通过NOREMERGE选项来禁用此功能的方法。理解并正确控制重新合并,对于编写准确、高效的SQL查询至关重要。
079:重新合并分组汇总统计量 📊
在本节课中,我们将学习如何利用“重新合并”技术,在分组内计算汇总统计量,并将其与原始数据合并。我们将通过一个具体案例——计算每个州在其所属区域内的总人口占比——来掌握这一方法。
上一节我们介绍了如何重新合并单个汇总值。本节中,我们来看看如何在分组内进行重新合并。
假设我们需要计算每个州在其所属区域内的估计人口占比。如何利用重新合并汇总统计量来实现这个目标?
PCT_region 列将展示每个州基于其所属区域的估计人口百分比。这个值的计算方法是:用每个州的 P_estimate1 列数值,除以该州所属区域的估计人口总数。
我们需要为每个区域内的每个州确定这个百分比。可以通过重新合并汇总统计量来计算按区域划分的人口占比。具体做法是:在 GROUP BY 子句中使用 region 列,然后使用 SUM 函数汇总每个区域的人口。
SELECT region, SUM(P_estimate1) AS region_total
FROM census_data
GROUP BY region;


每个区域的人口总和随后会与未汇总的原始数据(如州名 name 和该州人口 P_estimate1)重新合并。
以下是实现重新合并并计算百分比的关键步骤:


- 在查询中使用
GROUP BY region对数据进行分组。 - 使用聚合函数
SUM(P_estimate1)计算每个区域的总人口。 - SAS会自动将这个区域级别的汇总值“重新合并”到该区域内每一个州的观测记录中。
- 最后,我们可以创建一个新变量,其计算公式为:
P_estimate1 / region_total,从而得到每个州在区域内的占比。
SELECT name,
region,
P_estimate1,
SUM(P_estimate1) AS region_total,
(P_estimate1 / CALCULATED region_total) AS PCT_region FORMAT=PERCENT8.2
FROM census_data
GROUP BY region;




本节课中,我们一起学习了如何利用SAS的重新合并功能,在分组(如区域)内部计算汇总值(如区域总人口),并将其与组内详细数据合并,进而计算出像“州人口占区域总人口百分比”这样的衍生指标。这种方法高效且简洁,是进行分组内相对值分析的强大工具。
080:集合运算符概述
在本节课中,我们将要学习如何使用SAS的集合运算符来垂直拼接数据表。上一节我们介绍了如何水平合并表格,本节中我们来看看如何纵向组合数据。
集合运算符用于将两个或多个数据集(表)按行(即观测值)进行组合。它们的主要功能是垂直连接表格,这与SET语句的功能类似,但提供了更强大的集合运算能力。
以下是本课将介绍的四种核心集合运算符:
- INTERSECT:返回同时出现在所有输入数据集中的观测行。
- EXCEPT:返回仅出现在第一个输入数据集中,且不出现在后续输入数据集中的观测行。
- UNION:返回所有输入数据集中所有不重复的观测行。
- OUTER UNION:返回所有输入数据集中所有的观测行,无论是否重复。
这些运算符的基本语法结构如下:
DATA 新数据集;
SET 数据集1 集合运算符 数据集2;
RUN;
例如,要获取两个数据集class1和class2中所有不重复的学生记录,可以使用UNION运算符:
DATA all_students;
SET class1 UNION class2;
RUN;
本节课中我们一起学习了SAS集合运算符的基本概念,包括INTERSECT、EXCEPT、UNION和OUTER UNION。它们为垂直合并数据集提供了基于集合理论的、灵活且强大的方法。在后续课程中,我们将详细探讨每个运算符的具体用法和实际应用场景。
081:使用集合运算符合并数据 📊
在本节课中,我们将学习如何使用SAS中的集合运算符来合并数据,以回答关于客户联系方式的四个具体业务问题。我们将通过分析三个独立的数据表,演示如何垂直组合查询结果。
假设您的经理要求提供四份报告,内容涉及所有通过电话或电子邮件联系的目标销售客户。公司希望联系每一位目标客户,并确定哪些客户需要再次联系以获得回应。我们的目标是分析我们与客户的联系情况。报告需要回答以下四个问题:
- 哪些客户同时回应了电子邮件和电话请求?
- 哪些客户回应了电话和/或电子邮件?
- 哪些客户仅回应了电子邮件请求?
- 是否存在一份包含所有客户回应的完整列表?
如果我们有一个包含所有客户信息和联系方式的数据表,那么编程任务将非常简单。
然而,回答这些问题所需的数据存储在三个独立的表中:sales_list、sales_email 和 sales_phone。

sales_list 表包含了我们希望在营销活动中联系的目标客户的联系信息。它包含客户ID、电子邮件以及可以联系到客户的电话号码。

sales_email 表包含了通过电子邮件接受或拒绝我们报价的客户回应。sales_phone 表则包含了来自电话销售的回应。如果客户未出现在这些表中,则意味着该客户目前尚未回应我们。如果客户要求回电,他们可能会在 sales_phone 表中出现两次。

现在我们来比较一下数据的组织方式。sales_email 和 sales_phone 两个表都包含 Customer_ID 列和一个回应列,但回应列的名称不同。这两个表存储回应的方式也不同。
在 sales_email 表中,每个电子邮件对应一个回应,回应可以是 Accepted 或 Declined。在 sales_phone 表中,Sales_Rep 列表示进行呼叫的销售代表,Phone_Response 列表示回应,回应可以是 Declined、Callback 或 Accepted。
所有三个表都包含 Customer_ID 列,但该列在各表中的位置并不相同。
了解了数据结构后,您有何想法?仅通过查询一个表,能否回答经理提出的四个问题中的任何一个?所有这些问题都需要您查询多个表。
您可以使用集合运算符来垂直组合查询,并创建能够回答这四个问题的报告。

上一节我们介绍了业务背景和数据概况,本节中我们来看看如何使用SAS集合运算符来解决具体问题。以下是使用集合运算符的基本思路:

- INTERSECT(交集):用于找出同时出现在两个查询结果中的行,对应问题1(哪些客户同时回应了电子邮件和电话请求?)。
- UNION(并集):用于合并两个查询结果并去除重复行,对应问题2(哪些客户回应了电话和/或电子邮件?)和问题4(所有客户回应的完整列表)。
- EXCEPT(差集):用于找出只出现在第一个查询结果中,而不出现在第二个查询结果中的行,对应问题3(哪些客户仅回应了电子邮件请求?)。
在编写代码时,需要确保参与运算的查询结果具有相同数量和类型的列。通常,我们只选择关键的标识列(如 Customer_ID)进行比较和合并。

本节课中,我们一起学习了如何利用SAS的集合运算符(INTERSECT、UNION、EXCEPT)来垂直合并来自不同数据表的查询结果,从而高效地回答复杂的多表关联业务问题。关键在于理解每个运算符的数学含义,并将其映射到具体的业务逻辑上。
082:集合运算符详解
在本节课中,我们将要学习SAS中集合运算符的工作原理、类型及其行为差异。集合运算符是垂直组合两个查询结果集以生成最终结果集的重要工具。
集合运算符如何工作?
集合运算符垂直组合来自两个查询的中间结果集,以生成最终结果集。
它们的中间结果集包含行和列,集合运算符作用于这些中间结果集,而非直接作用于输入表。
集合运算符的类型
要垂直组合两个查询的结果,可以使用以下四种集合运算符之一:INTERSECT、EXCEPT、UNION和OUTER UNION。
其中,INTERSECT、EXCEPT和UNION运算符是SQL ANSI标准中规定的。OUTER UNION运算符则是SAS的增强功能。
为了解释每种方法的结果,我们将用简化的维恩图将两个表表示为圆圈。
INTERSECT(交集)
INTERSECT运算符返回同时出现在第一个查询和第二个查询中的行。换句话说,它返回两个查询中共同存在的唯一行。
这在维恩图中表示为两个圆圈的重叠区域。
EXCEPT(差集)
EXCEPT运算符返回来自第一个查询但不在第二个查询中的行。换句话说,它仅返回第一个查询中的唯一行。
这在维恩图中表示为顶部的圆圈。
UNION(并集)
UNION运算符组合两个查询的结果。它生成来自两个查询的所有唯一行。也就是说,如果一个行出现在第一个表、第二个表或两个表中,它都会被返回。
这在维恩图中表示为顶部和底部的圆圈。UNION不返回重复行。如果一个行出现多次,则只返回一次。
OUTER UNION(外并集)
OUTER UNION运算符组合两个查询的结果。它包含所有的行和列,且没有任何重叠。
因此,图表显示为两个分离的圆圈。


列处理方式的差异
上一节我们介绍了四种集合运算符,本节中我们来看看不同集合运算符在处理列时的默认行为差异。
INTERSECT、EXCEPT和UNION集合运算符根据两个结果集中列的位置来对齐列。
例如,这些集合运算符根据列在参考表中的位置来组合两个查询的列,而不考虑单个列名。
两个查询中相同相对位置的列必须具有相同的数据类型。第一个查询中表的列名将成为输出表的列名。
而OUTER UNION集合运算符则包含两个结果集中的所有列。

总结

本节课中我们一起学习了SAS中四种集合运算符:INTERSECT、EXCEPT、UNION和OUTER UNION。我们了解了它们如何垂直组合查询结果,并通过维恩图直观地理解了每种运算符返回的数据范围。同时,我们也掌握了标准集合运算符与OUTER UNION在列对齐和包含规则上的关键区别。理解这些运算符是有效进行数据合并与比较的基础。
083:使用集合运算符
在本节课中,我们将要学习如何在PROC SQL中使用集合运算符。集合运算符允许你将多个查询的结果组合成一个单一的结果集。我们将重点了解集合运算符的默认行为,以及如何使用ALL和CORR关键字来修改这些行为,以满足不同的数据处理需求。
集合运算符基础

集合运算由两个查询子句组成,通过四种集合运算符之一进行组合。
整个集合运算是一个单一的SELECT语句,因此你只需在最后一个SELECT语句后放置一个分号。
代码示例:
SELECT column1 FROM table1
UNION
SELECT column1 FROM table2;
修改默认行处理行为

上一节我们介绍了集合运算符的基本结构,本节中我们来看看如何修改其默认行为。当你使用集合运算符时,并不局限于它们的默认设置。

请记住,INTERSECT、EXCEPT和UNION集合运算符在默认情况下只产生唯一的行。PROC SQL必须对数据进行第二次扫描以消除重复行。
为了改变这种针对行的默认行为,你可以在代码中添加ALL关键字,这样SAS就不会移除重复行。
代码示例:
SELECT column1 FROM table1
UNION ALL
SELECT column1 FROM table2;
在以下任一条件发生时,应考虑使用ALL关键字:
- 最终结果集中存在重复行不会导致问题。
- 重复行不可能出现。例如,如果列上存在唯一或主键约束。
再次强调,使用ALL关键字可以提高集合运算符的效率,因为SAS无需进行第二次扫描来移除重复项。
修改默认列处理行为

了解了如何控制行的去重后,我们再来看看如何调整列的匹配方式。你可以使用CORR关键字来修改列的默认对齐行为。
请记住,INTERSECT、EXCEPT和UNION集合运算符根据列在中间结果集中的位置来对齐列。
CORR关键字会根据两个中间结果集中具有相同名称的列来对齐它们。对于OUTER UNION集合运算符,CORR同样会根据列名来对齐列。
代码示例:
SELECT name, age FROM students
UNION CORR
SELECT name, score FROM exams;

本节课中我们一起学习了PROC SQL中集合运算符的使用。我们首先了解了集合运算的基本构成,然后探讨了如何使用ALL关键字来保留重复行以提高效率,最后学习了如何使用CORR关键字根据列名而非位置来对齐列。掌握这些技巧能让你更灵活地组合和比较数据集。
SAS高级程序员专项课程:P84:使用 INTERSECT 运算符 🔍
在本节课中,我们将学习如何使用 SAS 中的 INTERSECT 集合运算符。该运算符用于查找两个查询结果之间的交集,即找出同时出现在两个数据集中的行。我们将通过一个具体的客户响应分析案例来演示其用法。
概述

我们的目标是找出那些对我们的销售活动响应积极的客户。具体来说,我们希望找到那些既回复了电子邮件营销,又接听了电话营销的客户,无论他们最终是接受了还是拒绝了我们的报价。这些客户可以被视为“高响应度”客户。
数据来源
符合“回复了电子邮件”或“接听了电话”条件的客户列表,分别存储在 sales.email 和 sales.phone 两个数据表中。我们的任务是从这两个表中找出匹配的客户 ID。

使用 INTERSECT 运算符
为了找出两个查询结果中相交的客户 ID,我们将使用 INTERSECT 集合运算符。在本例中,我们只引用 Customer_ID 这一列。
以下是实现此目标的 SAS 代码结构:
PROC SQL;
SELECT Customer_ID FROM sales.email
INTERSECT
SELECT Customer_ID FROM sales.phone;
QUIT;
- 第一个查询:返回
sales.email表中的所有客户 ID。 - INTERSECT 运算符:连接两个查询。
- 第二个查询:返回
sales.phone表中的所有客户 ID。

INTERSECT 的工作原理
INTERSECT 运算符的执行分为两个关键步骤:
- 消除各结果集中的重复行:首先,它会分别对两个查询的中间结果集进行去重。例如,即使
sales.phone表中可能存在重复的客户 ID 记录,在此步骤中也会被移除。 - 选取共有行:接着,运算符会从第一个去重后的结果集中,选取那些也存在于第二个去重后结果集中的行。这些就是两个数据集共有的“等效行”。
这个过程确保了最终结果集中每个客户 ID 都是唯一的,并且同时出现在两个源表中。
结果解读
执行上述 INTERSECT 查询后,得到的结果列表将包含那些对我们邮件和电话销售尝试都做出了响应的客户 ID。这份列表精准地标识出了我们寻找的“高响应度”客户群体。

总结
本节课我们一起学习了 INTERSECT 运算符的核心应用。通过一个客户分析的实例,我们了解到:
INTERSECT用于获取两个查询结果集的交集。- 它在内部会自动进行去重处理。
- 它是筛选同时满足多个条件的数据行的有效工具,例如在本例中识别对多种营销渠道均做出响应的客户。

掌握 INTERSECT 能帮助你在数据比对、客户细分和一致性检查等场景中高效工作。
085:使用 EXCEPT 运算符 🧩
在本节课中,我们将学习如何使用 EXCEPT 集合运算符。这个运算符能帮助我们从一个结果集中筛选出不存在于另一个结果集中的行,是进行数据对比和筛选的实用工具。
概述
假设我们有一个销售目标客户列表,并且已经向列表中的所有客户发送了电子邮件营销。我们尚未进行电话跟进,但希望只联系那些未对初始邮件做出回应的客户。使用 EXCEPT 运算符可以高效地生成这份“待电话跟进”的客户名单。
构建查询思路
以下是创建该名单的逻辑步骤。

首先,我们需要定义两个结果集。第一个结果集包含销售目标列表中的所有客户ID。
SELECT customer_id FROM sales_list_table;
第二个结果集则包含已对营销邮件做出回应的客户ID,这些信息可能存储在另一个表中。
SELECT customer_id FROM sales_email_table;

EXCEPT 运算符的工作原理
上一节我们介绍了查询的构建思路,本节中我们来看看 EXCEPT 运算符具体是如何工作的。它的处理逻辑与 INTERSECT 运算符类似,遵循两个核心步骤。
- 去除重复行:运算符会先分别在两个中间结果集中查找并移除重复的行。在本案例中,由于我们目前只向每位客户发送了一封邮件,所以两个结果集内部均不存在重复的客户ID。
- 执行差集运算:接着,运算符会从第一个结果集中,剔除那些也出现在第二个结果集中的所有行。
最终,我们得到的结果就是那些存在于第一个结果集(所有目标客户)但不存在于第二个结果集(已回应邮件的客户)中的客户ID列表。
结果与应用
通过上述操作,我们成功获得了一份尚未回应邮件的客户名单。


利用这份精准的名单,我们的销售团队可以有针对性地进行电话跟进,从而提升工作效率,避免打扰已对邮件感兴趣的客户。
总结

本节课中我们一起学习了 EXCEPT 运算符的用途与工作原理。我们通过一个具体的营销场景,演示了如何利用该运算符从全部目标客户中筛选出未回应邮件的人员,实现了数据的差异化筛选。掌握此运算符能帮助你在数据处理中更高效地进行集合比较和记录筛选。
086:使用UNION运算符
在本节课中,我们将学习如何使用SQL中的UNION运算符,来合并两个查询的结果集并去除重复值,从而找出响应了电话或邮件营销活动的唯一客户总数。
上一节我们介绍了基础的查询操作,本节中我们来看看如何组合多个查询的结果。
概述与目标
我们的目标是找出那些响应了我们邮件或电话联系的客户,无论他们最终是接受还是拒绝了我们的优惠。然后,利用这些信息计算出响应了电话或邮件的唯一客户总数量。
为了实现这个目标,我们需要分别从销售邮件表和销售电话表中查询客户ID,然后将两个结果集合并起来。
使用UNION运算符
UNION是一个集合运算符,它首先将两个或多个SELECT语句的结果集组合在一起。
以下是使用UNION的基本语法结构:
SELECT column_name(s) FROM table1
UNION
SELECT column_name(s) FROM table2;
关键点在于,每个SELECT语句中的列数必须相同,并且对应列的数据类型需要兼容。

UNION的工作流程
UNION运算符的执行主要分为两个步骤:
第一步,组合结果集。运算符会获取第一个查询的所有行,然后附加第二个查询的所有行,形成一个包含所有记录的中间结果集。

第二步,去除重复值。在组合了结果集之后,UNION会自动删除其中完全相同的行,只保留唯一的行。这是UNION与UNION ALL操作符的核心区别。

因此,最终返回的是来自两个源表的、不重复的客户ID列表。
应用实例:查找响应客户
根据我们的业务需求,具体的SQL查询步骤如下:

- 从邮件营销表(例如
sales_email)中选取客户ID。 - 从电话营销表(例如
sales_phone)中选取客户ID。 - 使用
UNION运算符将两个查询结果合并,并自动去重。
以下是完整的查询示例:
PROC SQL;
SELECT Customer_ID FROM sales_email
UNION
SELECT Customer_ID FROM sales_phone;
QUIT;
运行此查询后,得到的结果集就是所有至少通过一种方式(邮件或电话)给出了回复的唯一客户集合。要计算总数,只需使用COUNT函数。

本节课总结
本节课中我们一起学习了UNION运算符的用法。我们了解到,UNION用于合并多个查询的结果,并自动移除重复的行,从而高效地获取唯一值的集合。通过将其应用于邮件和电话响应数据的案例,我们掌握了如何找出对营销活动做出响应的唯一客户总数量。记住,当你需要合并数据并确保结果没有重复时,UNION是一个非常有用的工具。
087:使用UNION运算符查找所有唯一行 📊
在本节课中,我们将学习如何使用SQL中的UNION集合运算符,来统计对电子邮件或电话销售尝试做出回应的唯一客户数量。
概述与数据探索

首先,我们来查看将要使用的两个数据表:sales_email和sales_phone。

可以看到两个表的结构。第一个表sales_email包含customer_id和email_response两列,其中email_response记录了“accepted”或“declined”。第二个表sales_phone包含customer_id、sales_rep和response三列。
我们的目标是合并这两个表,找出所有做出过回应的不重复客户。

初次尝试与错误分析
上一节我们查看了数据,本节中我们尝试使用UNION运算符来组合这两个表。
我首先编写查询,从sales_email表中选择所有列,然后使用UNION运算符,再从sales_phone表中选择所有列。代码如下:
SELECT * FROM sq.sales_email
UNION
SELECT * FROM sq.sales_phone;
在运行之前,请思考一下结果会怎样。第一个表有两列,第二个表有三列。回顾之前的截图,我们需要特别注意第二列:第一个表的第二列是字符型的email_response,而第二个表的第二列是数值型的sales_rep。





现在运行这个查询,我们得到了一个错误。





错误信息指出:第一个贡献表的第二列与第二个贡献表的第二列数据类型不同。UNION操作要求对应列的数据类型必须兼容。
使用CORRESPONDING关键字修正
回到代码中。我记得两个表中都有customer_id列。因此,这次我将添加CORRESPONDING关键字。这个关键字会根据列名进行匹配,在这里就是匹配customer_id列。

修改后的查询如下:
SELECT * FROM sq.sales_email
UNION CORRESPONDING
SELECT * FROM sq.sales_phone;
重新运行查询,现在我们得到了唯一的客户列表。



指定列名以提升代码明确性
虽然使用CORRESPONDING关键字可以解决问题,但我个人在编码时更喜欢明确指定列名,这样代码意图更清晰。
因此,我将移除CORRESPONDING修饰符,并在两个SELECT语句中明确指定只选择customer_id列。
修改后的查询如下:
SELECT customer_id FROM sq.sales_email
UNION
SELECT customer_id FROM sq.sales_phone;
运行这个查询,我们得到了相同的结果。明确指定列名使代码更易于理解和维护。

统计唯一客户数量

我们的最终目标是统计唯一客户的数量。虽然可以手动数出来,但实际工作中通常会处理更大的表,所以我们需要让SQL来计数。
以下是实现计数的步骤:
- 将上面使用
UNION的查询作为一个内联视图(子查询)。 - 在外层查询中使用
COUNT(*)函数对这个视图的结果进行计数。
具体代码如下:
SELECT COUNT(*) AS total_num
FROM (
SELECT customer_id FROM sq.sales_email
UNION
SELECT customer_id FROM sq.sales_phone
) AS unique_customers;
这个查询会计算我们定义为内联视图的那个虚拟表中的行数。

查看结果,我们看到有8位不同的客户做出了回应。


总结

本节课中我们一起学习了如何使用SQL的UNION运算符。我们首先尝试合并两个结构不同的表并遇到了数据类型错误,然后通过使用CORRESPONDING关键字根据列名匹配解决了问题。接着,我们采用了更佳实践,通过明确选择customer_id列来执行UNION操作,最终通过将UNION查询作为子查询并结合COUNT(*)函数,成功统计出了做出回应的唯一客户总数为8位。UNION运算符是合并结果集并自动去除重复行的有效工具。
088:UNION运算符的默认行为 ⚙️
在本节课中,我们将要学习SAS中UNION集合运算符的默认行为。我们将了解它与其他集合运算符(如INTERSECT和EXCEPT)在处理顺序上的关键区别,以及当合并的表格结构不同时,SAS如何处理列名和列数。
处理顺序的差异
上一节我们介绍了集合运算符的基本概念,本节中我们来看看UNION运算符独特的工作流程。
UNION集合运算符的工作顺序与INTERSECT和EXCEPT运算符不同。
UNION集合运算符首先合并结果集,然后删除重复的行。

INTERSECT和EXCEPT运算符则是先删除重复的行,然后再合并结果集。
这个顺序差异在处理大数据集时可能对性能和结果产生影响。
列数不匹配的处理
当需要合并的两个中间结果集拥有不同数量的列时,SAS会进行自动调整以确保操作能够进行。
如果两个中间结果集的列数不同,SAS会通过添加空列来扩展其中一个表,从而使两个中间结果集拥有相同的列数。
如果结果集一被扩展了空列,那么结果集二中对应列的名称将被用于最终结果。
在这些情况下,SAS会向日志写入一条说明性注释。
以下是SAS处理此情况的逻辑伪代码描述:
IF ncol(table1) != ncol(table2) THEN DO;
/* 扩展列数较少的表 */
extended_table = ADD_NULL_COLUMNS(smaller_table, difference);
/* 使用未扩展表的列名 */
final_column_names = COLUMN_NAMES(larger_table);
END;
总结

本节课中我们一起学习了UNION运算符的核心行为。我们明确了其先合并后去重的顺序,这与INTERSECT和EXCEPT的先去重后合并形成对比。同时,我们也了解了SAS如何智能地处理列数不匹配的情况——通过扩展空列并采用特定列的命名规则。理解这些默认行为对于编写正确、高效的SAS集合运算代码至关重要。
089:组合集合运算符 🧩
在本节课中,我们将学习如何结合使用集合运算符,以解决一个常见的业务问题:如何找出那些既未回复电子邮件也未接听电话的潜在客户。
上一节我们介绍了基础的集合运算符,本节中我们来看看如何将它们组合起来,实现更复杂的逻辑筛选。
概述与问题定义
假设您需要一份客户名单,这些客户既未回应电子邮件销售,也未回应电话销售。解决此问题需要两个步骤:
- 首先,找出所有已回应过任何一种销售方式的客户。
- 然后,从总客户名单中排除这部分已回应的客户。
解决方案:结合UNION与EXCEPT
以下是实现此逻辑的步骤分解。
第一步:获取已回应客户的唯一列表
我们需要将sales_email(邮件销售)表和sales_phone(电话销售)表合并,并去除重复项。这可以通过UNION集合运算符实现。

PROC SQL;
SELECT Customer_ID
FROM sales_email
WHERE Responded = ‘Yes’
UNION
SELECT Customer_ID
FROM sales_phone
WHERE Responded = ‘Yes’;
QUIT;
这段代码会返回一个不重复的客户ID列表,包含了所有回应过邮件或电话销售的客户。
第二步:从总名单中排除已回应客户
接下来,我们从完整的销售名单表(sales_list)中,排除上一步通过UNION得到的结果集。这需要使用EXCEPT集合运算符。

以下是完整的组合查询代码:
PROC SQL;
SELECT Customer_ID
FROM sales_list
EXCEPT
(SELECT Customer_ID
FROM sales_email
WHERE Responded = ‘Yes’
UNION
SELECT Customer_ID
FROM sales_phone
WHERE Responded = ‘Yes’);
QUIT;
结果解读
执行上述代码后,最终结果将只包含那些在sales_list表中,但没有出现在UNION结果集中的客户ID。根据示例,最终会剩下两名未对任何销售尝试做出回应的客户。
总结

本节课中我们一起学习了如何组合使用UNION和EXCEPT集合运算符。关键思路是:先利用UNION合并条件并去重,再使用EXCEPT从主集合中剔除这部分数据,从而高效地筛选出符合复杂否定条件(既非A也非B)的记录。这种方法在数据清洗、客户细分和异常检测等场景中非常实用。

浙公网安备 33010602011771号