SQL是一套標(biāo)準(zhǔn),全稱(chēng)結(jié)構(gòu)化查詢(xún)語(yǔ)言,是用來(lái)完成和數(shù)據(jù)庫(kù)之間的通信的編程語(yǔ)言,SQL語(yǔ)言是腳本語(yǔ)言,直接運(yùn)行在數(shù)據(jù)庫(kù)上。同時(shí),SQL語(yǔ)句與數(shù)據(jù)在數(shù)據(jù)庫(kù)上的存儲(chǔ)方式無(wú)關(guān),只是不同的數(shù)據(jù)庫(kù)對(duì)于同一條SQL語(yǔ)句的底層實(shí)現(xiàn)不同罷了,但結(jié)果相同。這有點(diǎn)類(lèi)似于java中接口的作用,一個(gè)接口可以有不同的實(shí)現(xiàn)類(lèi),不同的實(shí)現(xiàn)類(lèi)對(duì)于接口中方法的實(shí)現(xiàn)方式可以不同,結(jié)果可以相同。這里SQL語(yǔ)言的作用就類(lèi)似于java中的接口,數(shù)據(jù)庫(kù)就類(lèi)似于java中接口的實(shí)現(xiàn)類(lèi),SQL語(yǔ)句就類(lèi)似于java接口中的方法。不同的是java中接口的不同實(shí)現(xiàn)類(lèi)對(duì)于接口中方法的執(zhí)行結(jié)果可以相同,也可以不同,而不同的數(shù)據(jù)庫(kù)對(duì)于同一條SQL語(yǔ)句的執(zhí)行是相同的。(這里只是做一個(gè)類(lèi)比,方便我們理解) 一般情況下,大部分SQL語(yǔ)句在不同的數(shù)據(jù)庫(kù)上是通用的,但我們知道每個(gè)數(shù)據(jù)庫(kù)都有自己獨(dú)有的特性,像在MySql數(shù)據(jù)庫(kù)中,可以使用substr(取字符串),trim(去空格),ifnull(空值處理函數(shù)),還可以使用limit語(yǔ)句對(duì)數(shù)據(jù)庫(kù)表進(jìn)行截取,但這些都是oracle數(shù)據(jù)庫(kù)沒(méi)有的。(類(lèi)比接口實(shí)現(xiàn)類(lèi)中,實(shí)現(xiàn)類(lèi)獨(dú)有的方法,而接口中沒(méi)有的) 這里簡(jiǎn)單介紹一下mysql數(shù)據(jù)庫(kù),mysql數(shù)據(jù)庫(kù)是一款關(guān)系型數(shù)據(jù)庫(kù),所謂關(guān)系型數(shù)據(jù)庫(kù)就是以二維表的形式存儲(chǔ)數(shù)據(jù),使用行和列方便我們對(duì)數(shù)據(jù)的增刪改查。 這篇博客,我們以mysql數(shù)據(jù)庫(kù)為例,對(duì)一條sql語(yǔ)句的執(zhí)行流程進(jìn)行分析。(本篇博客不涉及到表連接) 首先,創(chuàng)建一張student表,字段有自增主鍵id,學(xué)生姓名name,學(xué)科subject,成績(jī)grade 建表語(yǔ)句: ![]() DROP TABLE IF EXISTS student; CREATE TABLE `student` ( `id` int(5) NOT NULL AUTO_INCREMENT, `name` varchar(10) DEFAULT NULL, `subject` varchar(10) DEFAULT NULL, `grade` double(4,1) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=40 DEFAULT CHARSET=utf8; ![]() 初始化數(shù)據(jù): ![]() INSERT INTO student(`name`,`subject`,grade)VALUES('aom','語(yǔ)文',88); INSERT INTO student(`name`,`subject`,grade)VALUES('aom','數(shù)學(xué)',99); INSERT INTO student(`name`,`subject`,grade)VALUES('aom','外語(yǔ)',55); INSERT INTO student(`name`,`subject`,grade)VALUES('jack','語(yǔ)文',67); INSERT INTO student(`name`,`subject`,grade)VALUES('jack','數(shù)學(xué)',44); INSERT INTO student(`name`,`subject`,grade)VALUES('jack','外語(yǔ)',55); INSERT INTO student(`name`,`subject`,grade)VALUES('susan','語(yǔ)文',56); INSERT INTO student(`name`,`subject`,grade)VALUES('susan','數(shù)學(xué)',35); INSERT INTO student(`name`,`subject`,grade)VALUES('susan','外語(yǔ)',77); INSERT INTO student(`name`,`subject`,grade)VALUES('alice','語(yǔ)文',88); INSERT INTO student(`name`,`subject`,grade)VALUES('alice','數(shù)學(xué)',77); INSERT INTO student(`name`,`subject`,grade)VALUES('alice','外語(yǔ)',100); INSERT INTO student(`name`,`subject`,grade)VALUES('rajo','語(yǔ)文',33); INSERT INTO student(`name`,`subject`,grade)VALUES('rajo','數(shù)學(xué)',55); INSERT INTO student(`name`,`subject`,grade)VALUES('rajo','外語(yǔ)',55); ![]() 下面我們來(lái)看一下,數(shù)據(jù)在數(shù)據(jù)庫(kù)中的存儲(chǔ)形式。 (圖1.0) 現(xiàn)在針對(duì)這張student表中的數(shù)據(jù)提出一個(gè)問(wèn)題:要求查詢(xún)出掛科數(shù)目多于兩門(mén)(包含兩門(mén))的前兩名學(xué)生的姓名,如果掛科數(shù)目相同按學(xué)生姓名升序排列。 下面是這條查詢(xún)的sql語(yǔ)句 SELECT `name`,COUNT(`name`) AS num FROM student WHERE grade < 60 GROUP BY `name` HAVING num >= 2 ORDER BY num DESC,`name` ASC LIMIT 0,2; 執(zhí)行結(jié)果: 圖(1.1) 以上這條sql語(yǔ)句基本上概括了單表查詢(xún)中所有要注意的點(diǎn),那么我們就以這條sql為例來(lái)分析一下一條語(yǔ)句的執(zhí)行流程。 1,一條查詢(xún)的sql語(yǔ)句先執(zhí)行的是?FROM student?負(fù)責(zé)把數(shù)據(jù)庫(kù)的表文件加載到內(nèi)存中去,如圖1.0中所示。(mysql數(shù)據(jù)庫(kù)在計(jì)算機(jī)上也是一個(gè)進(jìn)程,cpu會(huì)給該進(jìn)程分配一塊內(nèi)存空間,在計(jì)算機(jī)‘服務(wù)’中可以看到,該進(jìn)程的狀態(tài)) 圖(1.2) 2,WHERE grade?<?60,會(huì)把(圖1.0)所示表中的數(shù)據(jù)進(jìn)行過(guò)濾,取出符合條件的記錄行,生成一張臨時(shí)表,如下圖所示。 圖(1.3) ? 3,GROUP?BY `name`會(huì)把圖(1.3)的臨時(shí)表切分成若干臨時(shí)表,我們用下圖來(lái)表示內(nèi)存中這個(gè)切分的過(guò)程。
圖(1.4) 圖(1.5) 圖(1.6) ?圖(1.7) 4,SELECT?的執(zhí)行讀取規(guī)則分為sql語(yǔ)句中有無(wú)GROUP?BY兩種情況。 ?。?)當(dāng)沒(méi)有GROUP?BY時(shí),SELECT?會(huì)根據(jù)后面的字段名稱(chēng)對(duì)內(nèi)存中的一張臨時(shí)表整列讀取。 (2)當(dāng)查詢(xún)sql中有GROUP?BY時(shí),會(huì)對(duì)內(nèi)存中的若干臨時(shí)表分別執(zhí)行SELECT,而且只取各臨時(shí)表中的第一條記錄,然后再形成新的臨時(shí)表。這就決定了查詢(xún)sql使用GROUP?BY的場(chǎng)景下,SELECT后面跟的一般是參與分組的字段和聚合函數(shù),否則查詢(xún)出的數(shù)據(jù)要是情況而定。另外聚合函數(shù)中的字段可以是表中的任意字段,需要注意的是聚合函數(shù)會(huì)自動(dòng)忽略空值。 我們還是以本例中的查詢(xún)sql來(lái)分析,現(xiàn)在內(nèi)存中有四張被GROUP?BY `name`切分成的臨時(shí)表,我們分別取名為?tempTable1,tempTable2,tempTable3,tempTable4分別對(duì)應(yīng)圖(1.4)、圖(1.5)、圖(1.6),圖(1.7)下面寫(xiě)四條"偽SQL"來(lái)說(shuō)明這個(gè)查詢(xún)過(guò)程。 SELECT `name`,COUNT(`name`) AS num FROM tempTable1; SELECT `name`,COUNT(`name`) AS num FROM tempTable2; SELECT `name`,COUNT(`name`) AS num FROM tempTable3; 最后再次成新的臨時(shí)表,如下圖: 圖(1.8) 5,HAVING num?>=?2對(duì)上圖所示臨時(shí)表中的數(shù)據(jù)再次過(guò)濾,與WHERE語(yǔ)句不同的是HAVING?用在GROUP?BY之后,WHERE是對(duì)FROM student從數(shù)據(jù)庫(kù)表文件加載到內(nèi)存中的原生數(shù)據(jù)過(guò)濾,而HAVING?是對(duì)SELECT?語(yǔ)句執(zhí)行之后的臨時(shí)表中的數(shù)據(jù)過(guò)濾,所以說(shuō)column AS otherName ,otherName這樣的字段在WHERE后不能使用,但在HAVING?后可以使用。但HAVING的后使用的字段只能是SELECT?后的字段,SELECT后沒(méi)有的字段HAVING之后不能使用。HAVING num?>=?2語(yǔ)句執(zhí)行之后生成一張臨時(shí)表,如下: ? 圖(1.9) 6,ORDER?BY num?DESC,`name`?ASC對(duì)以上的臨時(shí)表按照num,name進(jìn)行排序。 7,LIMIT?0,2取排序后的前兩個(gè)。 以上就是一條sql的執(zhí)行過(guò)程,同時(shí)我們?cè)跁?shū)寫(xiě)查詢(xún)sql的時(shí)候應(yīng)當(dāng)遵守以下順序。 SELECT XXX FROM XXX WHERE XXX GROUP BY XXX HAVING XXX ORDER BY XXX LIMIT XXX; ? 最后說(shuō)一點(diǎn),我們作為程序員,研究問(wèn)題還是要仔細(xì)深入一點(diǎn)的。當(dāng)你對(duì)原理了解的有夠透徹,開(kāi)發(fā)起來(lái)也就得心應(yīng)手了,很多開(kāi)發(fā)中的問(wèn)題和疑惑也就迎刃而解了,而且在面對(duì)其他問(wèn)題的時(shí)候也可做到觸類(lèi)旁通。當(dāng)然在開(kāi)發(fā)中沒(méi)有太多的時(shí)間讓你去研究原理,開(kāi)發(fā)中要以實(shí)現(xiàn)功能為前提,可等項(xiàng)目上線(xiàn)的后,你有大把的時(shí)間或者空余的時(shí)間,你大可去刨根問(wèn)底,深入的去研究一項(xiàng)技術(shù),為覺(jué)得這對(duì)一名程序員的成長(zhǎng)是很重要的事情。 ? 來(lái)源:http://www./content-2-132151.html |
|
來(lái)自: 印度阿三17 > 《開(kāi)發(fā)》