Postgresql基础实践教程 准备三张表表一members每个成员都有一个ID不保证是连续的、基本地址信息、推荐他们的成员的引用如果有的话以及他们加入的时间戳。虚构的。CREATETABLEcd.members(memidintegerNOTNULL,surnamecharactervarying(200)NOTNULL,firstnamecharactervarying(200)NOTNULL,addresscharactervarying(300)NOTNULL,zipcodeintegerNOTNULL,telephonecharactervarying(20)NOTNULL,recommendedbyinteger,joindatetimestampNOTNULL,CONSTRAINTmembers_pkPRIMARYKEY(memid),CONSTRAINTfk_members_recommendedbyFOREIGNKEY(recommendedby)REFERENCEScd.members(memid)ONDELETESETNULL);表二facilities设施表列出了乡村俱乐部所拥有的所有可预订设施。俱乐部在其中存储了设施的ID/名称信息、会员和客人的预订费用、设施的初始建设成本以及预计的每月维护费用。CREATETABLEcd.facilities(facidintegerNOTNULL,namecharactervarying(100)NOTNULL,membercostnumericNOTNULL,guestcostnumericNOTNULL,initialoutlaynumericNOTNULL,monthlymaintenancenumericNOTNULL,CONSTRAINTfacilities_pkPRIMARYKEY(facid));表三bookings该表格存储了设施ID、进行预订的会员、预订开始时间以及预订的半小时“时段”数量。这种特殊的设计可能会使某些查询变得更加困难但也会带来一些有趣的挑战同时为将来处理真实世界数据库时可能遇到的复杂情况做好准备。CREATETABLEcd.bookings(bookidintegerNOTNULL,facidintegerNOTNULL,memidintegerNOTNULL,starttimetimestampNOTNULL,slotsintegerNOTNULL,CONSTRAINTbookings_pkPRIMARYKEY(bookid),CONSTRAINTfk_bookings_facidFOREIGNKEY(facid)REFERENCEScd.facilities(facid),CONSTRAINTfk_bookings_memidFOREIGNKEY(memid)REFERENCEScd.members(memid));一、如何从 cd.facilities 表中检索所有信息问题从表中检索所有内容预期结果facidnamemembercostguestcostinitialoutlaymonthlymaintenance0Tennis Court 1525100002001Tennis Court 252580002002Badminton Court015.54000503Table Tennis05320104Massage Room 13580400030005Massage Room 23580400030006Squash Court3.517.55000807Snooker Table05450158Pool Table0540015[答案与讨论]select*fromcd.facilities;SELECT语句是从数据库中读取信息的查询的基本起始块。最小的SELECT语句通常由select [某些列] from [某个表或一组表]组成。在这种情况下我们需要 facilities 表中的所有信息。from 部分很简单 - 我们只需要指定cd.facilities表。cd是表的模式schema- 这是数据库中用于逻辑分组相关信息的术语。接下来我们需要指定想要所有列。方便的是所有列有一个简写符号 - *。我们可以使用它来代替繁琐地指定所有列名。二、从表中检索特定列问题想要打印出所有设施及其会员费用的列表。如何检索仅包含设施名称和费用的列表预期结果namemembercostTennis Court 15Tennis Court 25Badminton Court0Table Tennis0Massage Room 135Massage Room 235Squash Court3.5Snooker Table0Pool Table0[答案与讨论]selectname,membercostfromcd.facilities;对于这个问题我们需要指定想要的列。我们可以通过在SELECT语句中用逗号分隔的列名列表来实现。数据库只需查看FROM子句中可用的列并返回我们请求的列如下所示一般来说对于非临时查询建议在查询中指定想要的列名而不是使用 *。这是因为如果表中添加了更多列的应用程序可能无法处理。三、控制检索哪些行(2)问题如何生成向会员收费的设施列表预期结果facidnamemembercostguestcostinitialoutlaymonthlymaintenance0Tennis Court 1525100002001Tennis Court 252580002004Massage Room 13580400030005Massage Room 23580400030006Squash Court3.517.5500080[答案与讨论]select*fromcd.facilitieswheremembercost0;FROM 子句用于构建一组候选行以读取结果。在我们目前的示例中这组行只是表的内容。将来我们将探索连接joining它允许我们创建更有趣的候选集。一旦构建了候选行集WHERE 子句允许我们过滤感兴趣的行 - 在这种情况下是会员费用大于零的行。正如将在后续练习中看到的那样WHERE 子句可以有多个组件并通过布尔逻辑组合 - 例如可以搜索费用大于 0 且小于 10 的设施。WHERE 子句对 facilities 表的过滤作用如下图所示四、控制检索哪些行(2)问题如何生成向会员收费且费用低于月维护费1/50的设施列表返回相关设施的facid、设施名称、会员费用和月维护费。预期结果facidnamemembercostmonthlymaintenance4Massage Room 13530005Massage Room 2353000[答案与讨论]selectfacid,name,membercost,monthlymaintenancefromcd.facilitieswheremembercost0and(membercostmonthlymaintenance/50.0);WHERE子句允许我们过滤感兴趣的行 - 在这种情况下是会员费用大于零且低于月维护费1/50的行。如所见由于人员成本按摩室的运行费用非常昂贵当我们要测试两个或更多条件时使用AND来组合它们。正如可能预期的那样我们可以使用OR来测试一对条件中的任何一个是否为真。可能已经注意到这是我们将WHERE子句与选择特定列相结合的第一个查询。可以在下图中看到其效果选定列和选定行的交集为我们提供了要返回的数据。这可能现在看起来不太有趣但当我们稍后添加更复杂的操作如连接时将看到这种行为的简洁优雅之处。五、匹配多个可能的值问题如何检索ID为1和5的设施的详细信息尝试在不使用OR运算符的情况下完成。预期结果facidnamemembercostguestcostinitialoutlaymonthlymaintenance1Tennis Court 252580002005Massage Room 2358040003000[答案与讨论]select*fromcd.facilitieswherefacidin(1,5);这个问题的明显答案是使用类似where facid 1 or facid 5的WHERE子句。另一种在大量可能匹配时更容易使用的是 IN 运算符。IN运算符接受一个可能的值列表并将它们与在这种情况下facid进行匹配。如果其中一个值匹配则该行的WHERE子句为真并返回该行。IN运算符很好地展示了关系模型的优雅之处。它接受的参数不仅仅是一个值列表 - 它实际上是一个单列表格。由于查询也返回表格如果创建一个返回单列的查询可以将这些结果输入到IN运算符中。举一个简单的例子select*fromcd.facilitieswherefacidin(selectfacidfromcd.facilities);这个示例在功能上等同于选择所有设施但向展示了如何将一个查询的结果输入到另一个查询中。内部查询称为_子查询_。六、将结果分类到桶中问题如何生成设施列表根据月维护费用是否超过100美元将每个设施标记为便宜或昂贵返回相关设施的名称和月维护费。预期结果namecostTennis Court 1expensiveTennis Court 2expensiveBadminton CourtcheapTable TennischeapMassage Room 1expensiveMassage Room 2expensiveSquash CourtcheapSnooker TablecheapPool Tablecheap[答案与讨论]selectname,casewhen(monthlymaintenance100)thenexpensiveelsecheapendascostfromcd.facilities;这个练习包含一些新概念。第一个是我们在查询的 SELECT 和 FROM 之间进行计算的事实。以前我们只使用这部分来选择想要返回的列但可以在这里放置任何能为每行返回结果产生单个结果的内容 - 包括子查询。第二个新概念是 CASE 语句本身。CASE 实际上类似于其他语言中的 if/switch 语句其形式如查询中所示。要添加中等选项我们只需插入另一个when...then部分。最后还有AS运算符。这仅用于标记列或表达式使其显示更美观或在用作子查询的一部分时更容易引用。七、处理日期问题如何生成2012年9月之后加入的会员列表返回相关会员的memid、姓氏、名字和加入日期。预期结果memidsurnamefirstnamejoindate24SarwinRamnaresh2012-09-01 08:44:4226JonesDouglas2012-09-02 18:43:0527RumneyHenrietta2012-09-05 08:42:3528FarrellDavid2012-09-15 08:22:0529Worthington-SmythHenry2012-09-17 12:27:1530PurviewMillicent2012-09-18 19:04:0133TupperwareHyacinth2012-09-18 19:32:0535HuntJohn2012-09-19 11:32:4536CrumpetErica2012-09-22 08:36:3837SmithDarren2012-09-26 18:08:45[答案与讨论]selectmemid,surname,firstname,joindatefromcd.memberswherejoindate2012-09-01;这是我们第一次了解 SQL 时间戳。它们按数量级降序排列YYYY-MM-DD HH:MM:SS.nnnnnn。我们可以像比较 Unix 时间戳一样比较它们尽管获取日期之间的差异稍微复杂一些也更强大。在这种情况下我们只指定了时间戳的日期部分。PostgreSQL 会自动将其转换为完整的时间戳2012-09-01 00:00:00。八、去除重复项并排序结果问题如何生成members表中前10个姓氏的有序列表该列表不能包含重复项。预期结果surnameBaderBakerBootheButtersCoplinCrumpetDareFarrellGUESTGenting[答案与讨论]selectdistinctsurnamefromcd.membersorderbysurnamelimit10;这里有三个新概念但它们都很简单。在SELECT后指定DISTINCT会从结果集中删除重复行。请注意这适用于_行_如果行 A 有多个列则只有当所有列中的值都相同时行 B 才等于它。作为一般规则不要随意使用DISTINCT- 从大型查询结果集中删除重复项不是免费的所以按需使用。指定ORDER BY在FROM和WHERE子句之后接近查询末尾允许按一个列或一组列逗号分隔对结果进行排序。LIMIT关键字允许限制检索的结果数量。这对于逐页获取结果很有用并且可以与OFFSET关键字结合使用来获取后续页面。这与 MySQL 使用的方法相同非常方便 。九、合并多个查询的结果问题出于某种原因想要一个包含所有姓氏和所有设施名称的合并列表。预期结果surnameTennis Court 2Worthington-SmythBadminton CourtPinkerDareBaderMackenzieCrumpetMassage Room 1Squash CourtTracyHuntTupperwareSmithButtersRownamBakerGentingPurviewCoplinMassage Room 2JopletteStibbonsRumneyPool TableSarwinBootheFarrellTennis Court 1Snooker TableOwenTable TennisGUESTJones[答案与讨论]selectsurnamefromcd.membersunionselectnamefromcd.facilities;UNION运算符执行可能期望的操作将两个 SQL 查询的结果合并到一个表中。需要注意的是两个查询的结果必须具有相同数量的列和兼容的数据类型。UNION删除重复行而UNION ALL不删除。除非关心重复结果否则默认使用UNION ALL。十、简单聚合问题想获取最后一位会员的注册日期。如何检索此信息预期结果latest2012-09-26 18:08:45[答案与讨论]selectmax(joindate)aslatestfromcd.members;这是首次涉足 SQL 的聚合函数。它们用于提取关于整组行的信息并允许我们轻松提出如下问题每月维护费用最昂贵的设施是什么谁推荐的新会员最多每位会员在我们的设施中花费了多少时间这里的MAX聚合函数非常简单它接收joindate的所有可能值并输出最大的那个。聚合函数还有更多功能将在未来的练习中遇到。十一、更多聚合问题想获取最后注册成员们的姓和名 - 不仅仅是日期。如何做到这一点预期结果firstnamesurnamejoindateDarrenSmith2012-09-26 18:08:45[答案与讨论]selectfirstname,surname,joindatefromcd.memberswherejoindate(selectmax(joindate)fromcd.members);在上述建议的方法中使用_子查询_来查找最近的加入日期。此子查询返回一个标量表 - 即具有单列和单行的表。由于我们只有一个值因此可以将子查询替换为任何可能放置单个常量值的地方。在这种情况下我们使用它来完成WHERE子句以查找给定会员的查询。可能希望像下面这样做selectfirstname,surname,max(joindate)fromcd.members但是这不起作用。MAX 函数不像 WHERE 子句那样限制行 - 它只是接收一堆值并返回最大的那个。然后数据库不知道如何将一长串名称与来自MAX函数的单个加入日期配对导致失败。相反必须说找到我加入日期与最大加入日期相同的行。正如提示中提到的还有其他方法可以完成这项工作 - 下面是一个示例。在这种方法中我们没有明确找出最后的加入日期而是简单地按加入日期的降序对members表进行排序并取第一个。请注意这种方法没有涵盖两个人恰好在同一时间加入的极不可能发生的意外情况 .selectfirstname,surname,joindatefromcd.membersorderbyjoindatedesclimit1;十二、基本字符串搜索问题如何生成名称中包含Tennis一词的所有设施列表预期结果facidnamemembercostguestcostinitialoutlaymonthlymaintenance0Tennis Court 1525100002001Tennis Court 252580002003Table Tennis0532010[答案与讨论]select*fromcd.facilitieswherenamelike%Tennis%;SQL 的LIKE运算符提供简单的字符串模式匹配。它几乎在所有系统中都得到实现并且使用简单 - 它只需要一个字符串其中%字符匹配任何字符串_ 匹配任何单个字符。在这种情况下我们正在寻找包含Tennis一词的名称因此在两侧放置%就符合要求。还有其他方法可以完成此任务例如Postgres 支持使用~运算符的正则表达式。使用让感到舒适的任何方法但请注意LIKE运算符在系统间的可移植性更强。