我们分别为这两张表初始化如下数据:
原谅我随意使用名称作为主键与我对表数据类型的选择,我知道这有些不妥,但我们只需要通过实力示例理解我所表达的业务就好,那么创建代码如下:CREATE TABLE Person
(
Name NVARCHAR(5) NOT NULL PRIMARY KEY,
City NVARCHAR(10) NOT NULL
);
INSERT INTO Person VALUES('张三','北京');
INSERT INTO Person VALUES('李四','上海');
INSERT INTO Person VALUES('王五','西安');
CREATE TABLE HealthCode
(
ShowNo INT NOT NULL PRIMARY KEY,
Name NVARCHAR(5) REFERENCES Person(Name),
Status NVARCHAR(2)
);
INSERT INTO HealthCode VALUES(1,'张三','绿码');
INSERT INTO HealthCode VALUES(2,'张三','金码');
INSERT INTO HealthCode VALUES(3,'李四','金码');
INSERT INTO HealthCode VALUES(4,'王五','金码');SELECT P.Name,P.City,COUNT(H.ShowNo) AS ShowCnt
FROM Person AS P
LEFT JOIN HealthCode AS H
ON P.Name = H.Name
WHERE H.Status IN ('金码','绿码')
GROUP BY P.Name,P.City
HAVING COUNT(H.ShowNo) < 2
ORDER BY ShowCnt
OK,看到这里想必我们就要开始根据该示例进行整体的逻辑查询处理过程的分析了,在开始分析之前,我想先简要介绍一下逻辑查询处理的步骤,在我们使用的大多数编程语言中,代码是按照由上至下、从左到右顺序执行,但在SQL中我们代码的编写是按顺序编写,但执行往往由FROM子句第一个被执行,每个子句的步骤执行完毕后会生成一张虚拟表,用作被下一个子句执行时使用。 由FROM开始,那其他关键字的先后执行的处理顺序是什么呢?简单起见,我们先看看我们最常用的关键字在SQL语句中的执行顺序,一张图让你了解逻辑查询的处理步骤:
从该图中我们可以看出,典型的SQL语句基本上为关键字+表达式的方式进行组成,我们可以从SQL中提取其中的关键字,并在图中对各个关键字在逻辑处理中的执行顺序以及过程进行了简要描述。 初步了解了语句整个逻辑查询的处理步骤后,如果没看懂或者思路很模糊也不必太担心,接下来我将根据我们的示例,对每一步具体的执行过程与中间产生的临时表的结构与过程,进行逐步的详细分解,本文的结尾,我会展示一个典型的问题用于大家思考,闲话少说,接下来我们来看看每一步的背后究竟是如何执行的。第一步:执行FROM关键字 从示例SQL语句与上图描述的过程中,我们可以看到SQL语句执行的第一步为FROM,如果FROM为单表,则直接返回该表中的数据放入临时表VT1,用于下一步操作的数据源,若FROM为多表关联,会对FROM后面的表依次进行交叉联接[笛卡尔积],生成临时表VT1,根据我们的示例SQL为例,我们FROM关键字后描述了Person表与HealthCode的左外联接,那么第一步是将Person表的数据与HealthCode表的数据进行交叉联接。 先抛开左外联接的LEFT,因为他会在后面执行,为了让更多不同水平的人能看懂,接下来我会尽可能的用图的方式进行详细的描述,若您对该技术点已经特别熟悉,请直接跳过执行方式,看执行结果就好,具体执行方式如下:1、将主表中的第一行与关联表中的所有行逐一关联,形成结果集VR1;
2、以此类推,将主表中的第二行与关联表中的所有行逐一关联VR2;
3、以此类推,将主表中的第二行与关联表中的所有行逐一关联VR3;
4、主表有多少行就重复多少次,将VR1-VRn的结果集合并形成FROM步骤的最终结果,将结果放入虚拟表VT1中,按照我们的示例我们将得到VT1中数据如下:
经过以上将Person与HealthCode表交叉联接后,FROM过程就执行完毕,并且生成一张虚拟表VT1,用于下一步骤的数据源。第二步:执行ON筛选器 在上一步返回的虚拟表VT1中,对每一行数据进行ON筛选器中描述的条件表达式进行过滤,将表达式返回为TRUE或者换句话说满足表达式条件的数据,放入新生成的虚拟表VT2中,在我们示例中ON的表达式为:Person.Name = HealthCode.Name,详细图解过程如下:
通过ON筛选器设置的逻辑表达式筛选后,结果为TRUE的值,放入虚拟表VT2中,用于下一步骤的数据源。VT2该虚拟表结构如下:
第三步:执行OUTER JOIN,补全主表缺失行 这一步操作只在当SQL中采用外联接时执行,用于将主表缺失的数据行补全,在未关联到的关联表数据列中补NULL,若想深入详细了解SQL表关联的知识请参考SQL的SqlServer系列关联查询部分的相关文章,因本示例主表与关联表在执行ON筛选器后,VT2中的Person.Name均包含主表Person.Name中的所有名称,所以该步骤不会补全缺失行,详细图解如下:
因VT2包含所有主表中的行数据未进行补全,所以VT2未产生任何变化,将结果生成虚拟表VT3,用于下一步操作的数据源。VT3结构如下:
第四步:执行WHERE筛选器 该步骤对上一步返回的虚拟表VT3中的数据内容进行WHERE筛选器的过滤,通过WHERE设置的表达式对数据进行过滤,本示例的WHERE表达式为:WHERE H.Status IN ('金码','绿码'),该步骤图解与执行结果如下:
因为所有条件均满足WHERE筛选器的条件,所以VT3内容原封不动生成到虚拟表VT4,用于下一步骤的数据源。第五步:执行GROUP BY分组 该步骤通过GROUPBY后面所描述的列明进行分组,在构建虚拟表的过程中,虚拟表中会包含分组列与上一步骤返回的数据两部分组成,生成虚拟表VT5,其中在VT5虚拟表中分组列用来描述VT4表中的数据应该数据哪个组,本示例中GROUP BY是以Person表中的Name字段进行分组,那么详细图示如下:
分组列用于描述每一行数据应该数据哪一个组,加入分组列描述的数据内容,生成虚拟表VT5,用于下一步骤的数据源。第六步:执行HAVING筛选器 HAVING筛选器是专门与分组步骤搭配的筛选器,用于筛选分组后的结果内容,逻辑查询过程会根据该筛选器表达式在VT5分组虚拟表中加入对应的结果信息,用于执行实际的筛选判断,本示例的HAVING筛选器的条件为:HAVING COUNT(H.ShowNo) < 2,那么具体虚拟表的结构具体详细图示如下:
因HAVING统计的为COUNT(H.ShowNo) < 2,VT5中张三统计的数量为2,所以不满足该条件,张三这行数据被移除,同时生成新的虚拟表VT6,用于下一步骤的数据源。这里需要注意的是HAVING与分组是搭配使用,HAVING中的聚合函数默认会根据分组指定的内容进行计算。第七步:执行SELECT构建查询列表 往往虚拟表中包含的列不一定全是我们需要看到的列,SELECT用于指定生成的虚拟表中应该有哪些列,用做下一步骤的数据源,同时我们可以在此步骤中对现有的列明通过AS关键字进行重命名,那么后续步骤中会使用新的列明进行相关操作。本示例中SELECT指定的列明包括Person表中的Name列,同时还包括了聚合列并对聚合列进行重新命名为ShowCnt,需要注意的是该聚合列是在HAVING阶段产生的,若SQL语句中没有HAVING条件,那么逻辑查询会根据SELECT 阶段中指定的聚合函数,计算出聚合列的值,本示例中包含HAVING阶段,那么包含该阶段的SELECT步骤具体的图示如下:
生成虚拟表VT7,用于下一步骤的数据源。第八步:执行ORDER BY排序并返回结果集 因为本示例最后一步为排序,同时生成结果集返回客户端,我在此将这两步合并到一步进行讲解,需要注意的是,ORDER BY步骤返回的不是虚拟表,而是返回一个对象,SqlServer中将该对象成为游标,该对象包含了物理表中排序的顺序,而不是针对实际的表进行真实的排序,在C语言中该对象就好比一个指针数组,指针数组已经按照排序的规则指定了指针在数组中的具体索引位置,而在返回客户端的时候,是根据该指针数组中每个指针元素所指向行的地址获取改行数据,进行表拼装,返回客户端,具体图示如下:
根据以上示例中SQL执行步骤的分解,我们了解了整个SQL执行过程中的顺序以及每个阶段数据是如何组织变化的,不管是做RDBMS还是大数据,理解SQL的用法都是非常必要,同时,这里需要提醒一下除非必须要排序,否则在日常SQL开发中,尽量不要使用ORDER BY,毕竟对大数量排序是非常占用资源成本的。 在本文开头我描述了了解逻辑查询过程的必要性,以及不了些该过程会为开发中的我们带来一定困扰,那我想写两个SQL语句,大家能否在评论区,告诉我这两个SQL语句的执行结果有什么差别?如果有差别原因是什么?语句一:SELECT P.Name,COUNT(H.ShowNo) AS ShowCnt
FROM Person AS P
LEFT JOIN HealthCode AS H
ON P.Name = H.Name AND P.City = '西安'
GROUP BY P.Name
HAVING COUNT(H.ShowNo) < 2
ORDER BY ShowCnt;SELECT P.Name,COUNT(H.ShowNo) AS ShowCnt
FROM Person AS P
LEFT JOIN HealthCode AS H
ON P.Name = H.Name
WHERE P.City = '西安'
GROUP BY P.Name
HAVING COUNT(H.ShowNo) < 2
ORDER BY ShowCnt很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
这似乎应该有一个直截了当的答案,但在Google上花了很多时间,所以我找不到它。这可能是缺少正确关键字的情况。在我的RoR应用程序中,我有几个模型共享一种特定类型的字符串属性,该属性具有特殊验证和其他功能。我能想到的最接近的类似示例是表示URL的字符串。这会导致模型中出现大量重复(甚至单元测试中会出现更多重复),但我不确定如何让它更DRY。我能想到几个可能的方向...按照“validates_url_format_of”插件,但这只会让验证干给这个特殊的字符串它自己的模型,但这看起来很像重溶液为这个特殊的字符串创建一个ruby类,但是我如何得到ActiveRecord关联这个类模型
我的目标是转换表单输入,例如“100兆字节”或“1GB”,并将其转换为我可以存储在数据库中的文件大小(以千字节为单位)。目前,我有这个:defquota_convert@regex=/([0-9]+)(.*)s/@sizes=%w{kilobytemegabytegigabyte}m=self.quota.match(@regex)if@sizes.include?m[2]eval("self.quota=#{m[1]}.#{m[2]}")endend这有效,但前提是输入是倍数(“gigabytes”,而不是“gigabyte”)并且由于使用了eval看起来疯狂不安全。所以,功能正常,
作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代
在我的Rails(2.3,Ruby1.8.7)应用程序中,我需要将字符串截断到一定长度。该字符串是unicode,在控制台中运行测试时,例如'א'.length,我意识到返回了双倍长度。我想要一个与编码无关的长度,以便对unicode字符串或latin1编码字符串进行相同的截断。我已经了解了Ruby的大部分unicode资料,但仍然有些一头雾水。应该如何解决这个问题? 最佳答案 Rails有一个返回多字节字符的mb_chars方法。试试unicode_string.mb_chars.slice(0,50)
如何正确创建Rails迁移,以便将表更改为MySQL中的MyISAM?目前是InnoDB。运行原始执行语句会更改表,但它不会更新db/schema.rb,因此当在测试环境中重新创建表时,它会返回到InnoDB并且我的全文搜索失败。我如何着手更改/添加迁移,以便将现有表修改为MyISAM并更新schema.rb,以便我的数据库和相应的测试数据库得到相应更新? 最佳答案 我没有找到执行此操作的好方法。您可以像有人建议的那样更改您的schema.rb,然后运行:rakedb:schema:load,但是,这将覆盖您的数据。我的做法是(假设
我正在尝试测试是否存在表单。我是Rails新手。我的new.html.erb_spec.rb文件的内容是:require'spec_helper'describe"messages/new.html.erb"doit"shouldrendertheform"dorender'/messages/new.html.erb'reponse.shouldhave_form_putting_to(@message)with_submit_buttonendendView本身,new.html.erb,有代码:当我运行rspec时,它失败了:1)messages/new.html.erbshou
我在从html页面生成PDF时遇到问题。我正在使用PDFkit。在安装它的过程中,我注意到我需要wkhtmltopdf。所以我也安装了它。我做了PDFkit的文档所说的一切......现在我在尝试加载PDF时遇到了这个错误。这里是错误:commandfailed:"/usr/local/bin/wkhtmltopdf""--margin-right""0.75in""--page-size""Letter""--margin-top""0.75in""--margin-bottom""0.75in""--encoding""UTF-8""--margin-left""0.75in""-
Rails2.3可以选择随时使用RouteSet#add_configuration_file添加更多路由。是否可以在Rails3项目中做同样的事情? 最佳答案 在config/application.rb中:config.paths.config.routes在Rails3.2(也可能是Rails3.1)中,使用:config.paths["config/routes"] 关于ruby-on-rails-Rails3中的多个路由文件,我们在StackOverflow上找到一个类似的问题
给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru