一条标准SQL语句是怎么执行之“步步惊心”过程详解与案例分析
SQL邏輯執行過程詳解(限標準SQL)
表與數據
-- 1 創建 HR.Employees表 CREATE TABLE HR.Employees (empid INT NOT NULL IDENTITY,lastname NVARCHAR(20) NOT NULL,firstname NVARCHAR(10) NOT NULL,title NVARCHAR(30) NOT NULL,titleofcourtesy NVARCHAR(25) NOT NULL,birthdate DATE NOT NULL,hiredate DATE NOT NULL,address NVARCHAR(60) NOT NULL,city NVARCHAR(15) NOT NULL,region NVARCHAR(15) NULL,postalcode NVARCHAR(10) NULL,country NVARCHAR(15) NOT NULL,phone NVARCHAR(24) NOT NULL,mgrid INT NULL,CONSTRAINT PK_Employees PRIMARY KEY(empid),CONSTRAINT FK_Employees_Employees FOREIGN KEY(mgrid)REFERENCES HR.Employees(empid),CONSTRAINT CHK_birthdate CHECK(birthdate <= CAST(SYSDATETIME() AS DATE)) );CREATE NONCLUSTERED INDEX idx_nc_lastname ON HR.Employees(lastname); CREATE NONCLUSTERED INDEX idx_nc_postalcode ON HR.Employees(postalcode);-- 2 初始化數據 SET IDENTITY_INSERT HR.Employees ON; INSERT INTO HR.Employees(empid, lastname, firstname, title, titleofcourtesy, birthdate, hiredate, address, city, region, postalcode, country, phone, mgrid)VALUES(1, N'Davis', N'Sara', N'CEO', N'Ms.', '19681208', '20130501', N'7890 - 20th Ave. E., Apt. 2A', N'Seattle', N'WA', N'10003', N'USA', N'(206) 555-0101', NULL); INSERT INTO HR.Employees(empid, lastname, firstname, title, titleofcourtesy, birthdate, hiredate, address, city, region, postalcode, country, phone, mgrid)VALUES(2, N'Funk', N'Don', N'Vice President, Sales', N'Dr.', '19720219', '20130814', N'9012 W. Capital Way', N'Tacoma', N'WA', N'10001', N'USA', N'(206) 555-0100', 1); INSERT INTO HR.Employees(empid, lastname, firstname, title, titleofcourtesy, birthdate, hiredate, address, city, region, postalcode, country, phone, mgrid)VALUES(3, N'Lew', N'Judy', N'Sales Manager', N'Ms.', '19830830', '20130401', N'2345 Moss Bay Blvd.', N'Kirkland', N'WA', N'10007', N'USA', N'(206) 555-0103', 2); INSERT INTO HR.Employees(empid, lastname, firstname, title, titleofcourtesy, birthdate, hiredate, address, city, region, postalcode, country, phone, mgrid)VALUES(4, N'Peled', N'Yael', N'Sales Representative', N'Mrs.', '19570919', '20140503', N'5678 Old Redmond Rd.', N'Redmond', N'WA', N'10009', N'USA', N'(206) 555-0104', 3); INSERT INTO HR.Employees(empid, lastname, firstname, title, titleofcourtesy, birthdate, hiredate, address, city, region, postalcode, country, phone, mgrid)VALUES(5, N'Mortensen', N'Sven', N'Sales Manager', N'Mr.', '19750304', '20141017', N'8901 Garrett Hill', N'London', NULL, N'10004', N'UK', N'(71) 234-5678', 2); INSERT INTO HR.Employees(empid, lastname, firstname, title, titleofcourtesy, birthdate, hiredate, address, city, region, postalcode, country, phone, mgrid)VALUES(6, N'Suurs', N'Paul', N'Sales Representative', N'Mr.', '19830702', '20141017', N'3456 Coventry House, Miner Rd.', N'London', NULL, N'10005', N'UK', N'(71) 345-6789', 5); INSERT INTO HR.Employees(empid, lastname, firstname, title, titleofcourtesy, birthdate, hiredate, address, city, region, postalcode, country, phone, mgrid)VALUES(7, N'King', N'Russell', N'Sales Representative', N'Mr.', '19800529', '20150102', N'6789 Edgeham Hollow, Winchester Way', N'London', NULL, N'10002', N'UK', N'(71) 123-4567', 5); INSERT INTO HR.Employees(empid, lastname, firstname, title, titleofcourtesy, birthdate, hiredate, address, city, region, postalcode, country, phone, mgrid)VALUES(8, N'Cameron', N'Maria', N'Sales Representative', N'Ms.', '19780109', '20150305', N'4567 - 11th Ave. N.E.', N'Seattle', N'WA', N'10006', N'USA', N'(206) 555-0102', 3); INSERT INTO HR.Employees(empid, lastname, firstname, title, titleofcourtesy, birthdate, hiredate, address, city, region, postalcode, country, phone, mgrid)VALUES(9, N'Doyle', N'Patricia', N'Sales Representative', N'Ms.', '19860127', '20151115', N'1234 Houndstooth Rd.', N'London', NULL, N'10008', N'UK', N'(71) 456-7890', 5); SET IDENTITY_INSERT HR.Employees OFF;SQL實例
/* 要求:統計員工表里2014年1月1號及之后入職的員工所在國家、所屬年份、人數, 過濾條件:以上信息里每個過國家和年份至少要對應有兩條記錄 排序:排序時按照國家和年份降序排列。 */ SELECT country, YEAR(hiredate) AS yearhired, COUNT(*) AS numemployees FROM HR.Employees WHERE hiredate >= '20140101' GROUP BY country, YEAR(hiredate) HAVING COUNT(*) > 1 ORDER BY country, yearhired DESC;執行過程
SQL不像程序語言順序去執行代碼,它有自己的執行順序。一般我們先從表和視圖開始,然后是記錄的WHERE過濾,再是分組和分組過濾,然后是SELECT列表,最后是ORDER BY排序。即對應:
特別的:
如果在Mysql里,我們可以在GROUP BY、ORDER BY、HAVING之后引用SELECT里的字段別名。即如下的代碼是可運行的:
SELECT deptno dpt,COUNT(empno) cnt FROM emp WHERE deptno IN(10,20) GROUP BY dpt HAVING cnt > 1/* 結果 dpt cnt 10 3 20 5 */過程分解
1 FROM
Step1: 首先鎖定FROM后的表HR.Employees,此時的員工表有9條記錄:
2 WHERE
Step2:接著通過WHERE關鍵字去Filter(過濾或者篩選) hiredate(入職時間)晚于2014年1月1日的記錄。這里用到到了算數表達式“≥”(大于等于)。因為針對時間類型,如果我們給的是字符串并且它是時間格式的。比如:20140101、2014-01-01、2014/01/01等,那么數據庫會將該字符串飲隱式(自動)轉成時間類型以進行時間的比較。所以過濾后的結果為:
注:
初學時我們會犯如下的錯誤:
即在WHERE后直接用SELECT里字段的別名,如:
SELECT country, YEAR(hiredate) AS yearhired FROM HR.Employees WHERE yearhired >= 2014;這顯然是錯誤,那是因為當前還沒有執行到SELECT環節,所以不知道別名yearhired。
3 GROUP BY
Step3:然后我們開始做分組了,這里要使用個函數Year來取hiredate(入職時間)里的年份。我們知道日期類型有“年月日”構成,當前需求是按照年這個維度進行統計,所以需要使用這個函數,而country(國家)字段我們放在GROUP BY之后即可。分組之后的數據長這樣:
4 HAVING
Step4:通過觀察Step3里的結果我們返現國家是USA、年份是2014、2015的兩類數據,每類只有1條,也就是說2014年、USA僅有1人入職;2015年、USA僅有1人入職,這個不是想要的,需求是每個分(類)組數據至少得有2條。這種對分組進行過濾的關鍵字就是HAVING,所以我們加上語句HAVING(COUNT(*))>1,即每個分類(組)的個數得是1以上。此時的結果長這樣:?
5 SELECT
Step5:目前Step4里的字段較多,而我們只想要COUNTRY(國家)、yearhired(年份)、numemployees(員工數),所以我們在結果集的返回列表里通過SELECT關鍵字往后依次寫上這些字段(列)或者這些字段相關的函數,那么此時的結果為:
注:類似在WHERE里引用SELECT里的字段別名,在SELECT環節,我們同樣不能引用字段的別名。即如下寫法是報錯的:
SELECT empid, country, YEAR(hiredate) AS yearhired, yearhired - 1 AS prevyear FROM HR.Employees;??
原因是SELECT后里的語句、字段、別名是在一個批次里執行的,所以無法引用別名。但放在下一步的ORDER BY是可以的。
6 ORDER BY
Step6:結果和目標很接近了,再看看需求,我們需要對年份字段要按照逆序(降序)排列,即如果是數字、時間類型的,大的在前,小的在后;如果是字符串(以英文字母為例)類型的則z到a,反之正序(升序)則反過來。所以我們需要用到ORDER BY關鍵字,后跟字段或者字段的函數以及DESC(降序)、ASC(升序,可省略)來定義排序規則。所以得到最終結果:
總結
以上是生活随笔為你收集整理的一条标准SQL语句是怎么执行之“步步惊心”过程详解与案例分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 线性代数带参数的线性方程组的求法示例详解
- 下一篇: 大口径榴弹炮在坦克周围爆炸会被摧毁吗