Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
import pandas as pd
對于csv文件
df = pd.read_csv('pathtoyourfile.csv')
對于Excel文件
df = pd.read_excel('pathtoyourfile.xlsx',sheetname ='nameofyoursheet')
讀取在線HTML文件
使用以下命令,Pandas還可以在線讀取HTML表格
df = pd.read_html('linktoonlinehtmlfile')
可能需要安裝以下軟件包才能運行 eautifulsoup htmllib5 lxml
要查看前五項,我們在數據集上調用head命令。類似于查看數據集中的最后五個元素,我們使用尾部函數。檢查列的數據類型以及是否存在空值通常很重要。這可以使用info命令來實現。
由此我們可以知道,我們的數據集有24933個條目,5列,它們都是非空的。
我們可能希望按用戶名將所有推文分組,并計算每個組織的推文數量。我們可能也有興趣看到推文數量最多的前10大組織。
我們使用Sort_values按照推文的數量對數據框進行排序。
由于所有組織都推出了轉發,讓我們找出哪些組織轉推得最多。我們可以通過將組織的用戶名和推文進行匯總來實現這一點。
我們的數據集中有26個獨特的組織。
我們可以通過調用列上的唯一函數來獲取他們的名字。
不重要的是value_counts()不適用于數據框,它只適用于系列。我們可以通過在數據框中調用它來說明。
假設我們想知道每條推文中的字數。我們將創建一個新列以保存列的長度,然后將len函數應用于該列來計算字符數。
您可以通過調用describe函數來查看剛創建的列的描述。
我們可以看到最長的推文長度為158個字符。我們如何能夠看到推文?
您注意到我們只能看到部分推文。我們可以通過使用iloc函數來查看完整的推文
這意味著我們要查看位于索引零的項目,這是推文。
假設我們想要找到推文數量和推特之間的關系。這意味著我們將有一個數據框與推文的數量,另一個與推特的數量然后合并它們。
有時你可能也想加入兩個數據集。我們以Kaggle競爭數據集為例。您可能想要加入測試和訓練數據集以便使用完整的數據集。你可以使用concat來實現。
直方圖
看直方圖,我們可以看出,大多數推文長度在120到140之間
散點圖
區域圖
線圖
核密度估計圖
在HTML中,有一個很重要的理論:塊元素和行內元素。在CSS中極其重要的一個理論——CSS盒子模型。 在“CSS盒子模型”理論中,頁面中的所有元素都可以看成一個盒子,并且占據著一定的頁面空間。
一個頁面由很多盒子組成,這些盒子之間會互相影響,因此掌握盒子模型需要從兩個方面來理解:一是理解單獨一個盒子的內部結構(往往是padding),二是理解多個盒子之間的相互關系(往往是margin)。
盒模型指的是網頁元素的結構。當指定一個元素的寬度或高度時,便設置了元素內容的尺寸,可以把每個元素都看成一個盒子,盒子模型是由4個屬性組成,號稱“盒尺寸四大家族”:
此外,在盒子模型中,還有寬度(width)和高度(height)兩大輔助性屬性。記住,所有的元素都可以看成一個盒子 。如下圖所示:
內容區是CSS盒子模型的中心,它呈現了盒子的主要信息內容,這些內容可以是文本、圖片等多種類型。內容區是盒子模型必備的組成部分,其他3個部分都是可選的。 內容區有3個屬性:width、height和overflow。使用width和height屬性可以指定盒子內容區的高度和寬度。在這里注意一點,width和height這兩個屬性是針對內容區content而言的,并不包括padding部分。 當內容過多,超出width和height時,可以使用overflow屬性來指定溢出處理方式。
內邊距,指的是內容區和邊框之間的空間,可以看成是內容區的背景區域。padding屬性接受長度值或百分比值,但不允許使用負值。 關于內邊距的屬性有5種:padding-top、padding-bottom、padding-left、padding-right,以及綜合了以上4個方向的簡寫內邊距屬性padding。使用這5種屬性可以指定內容區與各方向邊框之間的距離。
因為CSS中默認的box-sizing是content-box,所以使用padding會增加元素的尺寸。
.box {
width: 80px;
padding: 20px;
}
如果不考慮其他CSS干擾,此時.box元素所占據的寬度就應該是120像素(80px+20px×2),這其實是不符合現實世界的認知的,人們總是習慣把代碼世界和現實世界做映射,因此,新人難免會在padding的尺寸問題上踩到點坑。這也導致很多人樂此不疲地設置box-sizing 為border-box,甚至全局設置。
外邊距,指的是兩個盒子之間的距離,它可能是子元素與父元素之間的距離,也可能是兄弟元素之間的距離。外邊距使得元素之間不必緊湊地連接在一起,是CSS布局的一個重要手段。 外邊距的屬性也有5種:margin-top、margin-bottom、margin-left、margin-right,以及綜合了以上4個方向的簡寫外邊距屬性margin。 同時,CSS允許給外邊距屬性指定負數值,當外邊距為負值時,整個盒子將向指定負值的相反方向移動,以此產生盒子的重疊效果,這就是傳說中的“負margin技術”。
只有元素是“充分利用可用空間”狀態的時候,margin才可以改變元素的可視尺寸。比方說,如下CSS:
.header {
width: 160px;
margin: 0 -5px;
}
此時元素寬度還是160像素,尺寸無變化。因為只要寬度設定,margin就無法改變元素尺寸,這和padding是不一樣的。
但是,如果是下面這樣的HTML和CSS:
<div class="header">
<div class="son">
</div></div>
.header { width: 160px; }
.menu { margin: 0 -5px; }
則.menu元素的寬度就是165像素了,尺寸通過負值設置變大了,因為此時的寬度表現是“充分利用可用空間”。
只要元素具有塊狀特性,無論有沒有設置width/height,無論是水平方向還是垂直方向,即使發生了margin合并,margin對外部尺寸都著著實實發生了影響。
塊級元素的上外邊距(margin-top)與下外邊距(margin-bottom)有時會合并為單個外邊距,這樣的現象稱為“margin合并”。
margin:auto的填充規則如下。 (1)如果一側定值,一側auto,則auto為剩余空間大小。 (2)如果兩側均是auto,則平分剩余空間。
在CSS盒子模型中,邊框與我們之前學過的邊框是一樣的。 邊框屬性有border-width、border-style、border-color,以及綜合了3類屬性的簡寫邊框屬性border。
border屬性總是能解決很多棘手的問題,在在圖形構建、體驗優化以及網頁布局這塊幾大放異彩,,同時保證其良好的兼容性和穩定性。下面我們一起看看border都有哪些精彩的特性表現。
我們通過比對筆記本、手機發現,雖然兩臺設備的尺寸差異很大,但是邊框的大小相比而言就可以忽略不計了。邊框是不會因為設備大就按比例變大的。因此,如果支持百分比值,是不是就意味著設備大了邊框也跟著變大?有一張圖片,大片區域都是白色的,在白底背景上和文字混在一起,就會有一片奇怪的空白區域,會讓人產生沒對齊的假象,此時,我們給這張圖片套個1px灰色邊框,區域就明顯了,對吧!設計的初衷就是為了這么點兒事,沒有需要使用百分比值的場景。于是,綜合這兩點,造成了border-width不支持百分比值。
border屬性可以輕松實現兼容性非常好的三角圖形效果,為什么可以呢?其底層原因受inset/outset 等看上去沒有實用價值的border-style屬性影響,邊框3D效果在互聯網早期其實還是挺潮的,那個時候人們喜歡有質感的東西,為了呈現逼真的3D效果,自然在邊框轉角的地方一定要等分平滑處理,然后不同的方向賦予不同的顏色。然后,這一轉角規則也被solid類型的邊框給沿用了。因此,我們就不難理解下面的4色邊框的表現了:
div {
width: 10px; height: 10px;
border: 10px solid;
border-color: #f30 #00f #396 #0f0;
}
運行一下上面的代碼看一下效果吧!
這是提高用戶體驗的一個小技巧,尤其在移動端,我們的操作工具一般就是我們的手指,但是,我們的手指粗細可以媲美胡蘿卜,而屏幕尺寸就那么點兒,如果我們正在走路,則一些精致的圖標和按鈕很容易就點不中甚至誤點。
穩妥的方法是外部再嵌套一層標簽,專門控制點擊區域大小。如果對代碼要求較高,則可以使用padding或者透明border增加元素的點擊區域大小。
現實生活中看到的盒子,有正方形、長方形、圓柱形等,依據形狀特點,可包裹不同物件。CSS中的盒子雖然沒有那么多的形狀,但在視覺呈現上不同類型的盒子還是會有很大的不同,有的盒子要占據一行,有的盒子不能定義外邊距、寬度和高度,有的盒子寬度和高度能自適應。CSS中用display指定盒類型(即框類型),常用的有 block(塊)、inline(行內)、inline-block(行內塊)、table(表格),以及CSS3新增的flexbox(伸縮盒)。 HTML 元素只有兩種默認的盒類型,即塊級元素(block-level element)和行內元素(inline-level element)。其中行內元素不可定義CSS屬性width、height、上下margin和上下padding。常用的span和div分別是行內元素和塊級元素。
由此可見,需要掌握的內容太多,要想學會所有布局相關的技術不太現實。高級的布局話題基于文檔流和盒模型等概念,這些是決定網頁元素的大小和位置的基本規則。因此理解和掌握如何設置元素的大小和位置至關重要。
為初學者提供學習指南,為從業者提供參考價值。我堅信碼農也具有產生洞見的能力。關注【碼農洞見】,一起學習和交流吧!
先聲明,我不是標題黨,我真的是用5000行左右的JS實現了一個輕量級的關系型數據庫JSDB,核心是一個SQL編譯器,支持增刪改查。
源代碼放到github上了:https://github.com/lavezhang/jsdb
如果你需要修改程序引入新的特性,請嚴格遵守GPL協議。
如果轉發此文,請注明來源。
SQL范例
前言
工作太忙,好久沒寫這種長文章了,難得今年國慶超長,又不便外出,這才有時間“不務正業”。
為什么要用一周的時間寫這么個玩意兒?看起來也沒什么用處,畢竟,沒有哪個系統需要在瀏覽器中跑一個關系型數據庫。
如果要搞一個"年度最無用項目"的頒獎,估計JSDB榜上有名。
我一直有一個夢想,要研發一款咱們中國人自己的列式存儲分布式數據庫!(此處應有掌聲_)
古人講,不積跬步無以至千里,JSDB就算探索數據庫自研的一個開端吧。
為什么用TypeScript?因為coding效率非常高,跟Python差不多,而且有瀏覽器就能運行,非常方便,很適合做技術預研,正式開發時再改為C或Rust。
如文章開頭所言,JSDB的核心是一個SQL編譯器,準確地說,是解釋器。學習過《編譯原理》的同學,對這個不會陌生。
解釋器也是屬于編譯器的范疇,所以,后面仍然會沿用“SQL編譯器”的說法。
概述
按照執行順序,JSDB的代碼由四個部分構成:
1、詞法分析,得到 token 列表。參見GitHub源代碼,SqlLexer.ts 文件,基于狀態機實現,詳見 lex_state_flow.xlsx 文件。
2、語法語義分析,得到抽象語法數。參見 SqlParser.ts 文件,自上而下解析,這是行數最多的一個文件。
3、對抽象語法樹的執行。參見SqlDatabase.ts文件,以及ast目錄下的幾十個語法節點的compute(ctx)方法。
4、單元測試和應用范例。test目錄和test.html文件里運行著所有的單元測試,index.html文件就是文章開頭的體驗頁面,語法高亮功能基于第三方組件codemirror實現,在 static/codemirror 目錄里。
JSDB確實是一個關系型數據庫,參照SQL92標準實現,但它并不完整,只實現了最核心的一小部分功能,可以滿足日常基本需求。主要特性有:
01、create table 語句
02、insert 語句
03、update 語句
04、delete 語句
05、select 語句,含:distinct / from / join / left join / where / group by / having / order by / limit
06、算數運算符:+、-、*、/、%
07、關系運算符:>、>=、<、<=、=、<>
08、條件運算符:and、or、not
09、其它操作符:like、not like、is null、is not null、between
10、動態占位符:?
11、標準函數,目前只實現了:ifnull、len、substr、substring、instr、concat。
如果需要增加新的標準函數,可以在SqlContext類的構造函數中實現,所有的標準函數都注冊到SqlContext.standardFunctions字段中。
尚未實現的重要特性有:
1、with / sub query / exists / alter / truncate 等
2、數據存儲。一直在內存中運行,大家可以修改程序,寫入瀏覽器localStorage中。
3、事務。這個需要事務日志來實現,以后再搞,不過在內存中模擬一個,問題也不大。
4、并發鎖。JS是單線程,沒有真正的并發,有了一個不用實現它的好理由。
5、其它功能。詳見大學時的《數據庫原理》。
如果大家多多點贊,我就把它實現得更加完整。_
本文針對編譯器和數據庫的入門讀者,寫了很多小白的內容,高手請飄過。
第一章 詞法分析
關于詞法分析,程序本身并不難。無論何種編程語言,它的詞法分析模塊一般都不超過300行,有些甚至只有幾十行。
很多人喜歡用 lex/yacc/antr 之類的工具來自動生成,我不喜歡,我就是喜歡手擼的感覺。
詞法分析就是要識別源代碼中的一個個token,一般包括:關鍵字、標識符、字符串、數值、布爾值、空值、運算符、操作符、分隔符。
例如,一條SQL語句:
<pre style="margin: 0px; padding: 0px; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">select name, (total_score / 4) as avg_score from student where id = '010123'</pre>
涉及如下token:
關鍵字:select、as、from、where
標識符:name、total_score、avg_score、student、id
字符串:'010123'
數值:4
運算符:/、=
分隔符:, ( )
如何識別這些token呢?兩種辦法:硬實現、狀態機。
硬實現,就是用一大坨的 if/else 識別每一個字符。
舉例來說,如果當前字符是一個單引號,程序就認為是一個字符串的開始,于是用一個while循環來判斷,直到遇到另一個單引號,表示字符串的結束。
硬實現的最大問題在于,條件分支太多,很容易遺漏或判斷錯誤。
比如,字符串中是要處理轉義符的,遇到換行符則要記錄錯誤。
再比如,'>=' 和 '> =' 是不一樣的,前者表示大于等于號,后者表示兩個運算符:大于號和等于號,因為中間有個空格,而硬寫的程序往往會忽略掉這些空白符,什么時候空白符該忽略,什么時候不該忽略,必須把規則一條條列出來,針對處理。
類似的情況還非常多,所以,硬寫出來的詞法分析程序,無一例外,都是非常復雜的。
給大家看一段用 java 硬實現的字符串識別程序:
View Code
上述java程序是我很久之前寫的,整個詞法程序漏洞百出。
即使是硬實現,也要提前梳理各種轉換關系,既然這樣,為什么不用狀態機呢?
狀態機是老一輩計算機科學家發明的理論,基于狀態機和BNF產生式,詞法分析程序完全可以被形式化了。
一個字符串識別的狀態機范例如下:
一個字符串就涉及4個狀態,完整的SQL詞法涉及幾十個狀態,如果都用狀態流轉圖畫出來,實在太復雜,所以,一般都改用等價的表格來表示。
我在github上放了一個叫 lex_state_flow.xlsx 的Excel文件,截圖如下:
SQL詞法分析狀態表
需要特別解釋兩點:
1、狀態2到狀態6的名字用紫色標記,因為這幾個狀態是中間狀態,最終不能獨立存在。
2、狀態轉換的單元格有三種顏色:灰色、白色、紅色。
灰色表示回到初始狀態;
白色表示正數狀態,轉換狀態時,前面的緩存內容作為一個token,當前新字符進入新的狀態;比如,當前狀態是 TK_IDENTITY,這時輸入一個字符 '>',則緩沖區的內容得到一個標識符token,新輸入的 '>' 字符進入 TK_GT 狀態。
紅色表示負數狀態,轉換狀態時,前面的內容加上當前字符一起進入新的狀態。比如,當前狀態是 TK_GT,這時輸入一個字符 ‘=’,則緩沖區的內容 '>' 加上新輸入的 '=',得到 '>=' ,進入新的狀態 TK_GE,表示大于等于。
詞法分析的核心,正是這個狀態表格。要完成這樣一張表格,看著容易,實際并不容易,我也是花了一天時間。因為一旦遺漏了某個狀態或輸入字符,整個表格都要改一遍,擼得手都起繭子了。
完成狀態表格后,基于此實現的詞法掃描程序,就可以非常簡單了。文件名為 SqlLexer.ts,代碼如下:
View Code
細心的同學可能會發現,代碼里的關鍵字狀態,并沒有出現在狀態表格中。
原則上來講,每個關鍵字都是一個單獨的狀態。但是,如果都列入狀態表格,這個表格就超級復雜了。比如,為了識別一個關鍵字select,要依次檢查連續字符 ‘s’ 'e' 'l' 'e' 'c' 't' ,即使到了最后一個字符 't' ,也不意味著結束,后面跟上一個數字 '1',立馬就不是關鍵字了,而是一個普通的標識符 select1。而JSDEB一共支持38個關鍵字,都要并入表格,簡直難以想象。所以,通常的做法是,先統一作為標識符來識別,完成一個token時,再進一步判斷是否為某個關鍵字,而在狀態表格中就不畫了。
一個token用一個三元組來表達,在TypeScript中是Tuple類型,實際就是JavaScript中的數組。這里有三個值,分別是number、string、number類型。
第0個值,number類型,表示token的類型,對應于狀態表格中的狀態id;
第1個值,string類型,表示token的內容,對于字符串 'abc' 來說,存的不是 abc,而是 'abc',也就是說,原原本本保存,后面在執行的時候才會翻譯為 abc;
第2個值,number類型,表示token所在的行號,提示詞法錯誤的時候,可以明確告知在哪一行。
有的同學可能會問,為什么不用一個class來表示。其實也可以用class表示,但是,掃描一段源代碼,得到的token非常多,如果用class表示,會浪費更多的資源,不如用數組,返璞歸真,簡單實用。
用一組單元測試來驗證程序是否正確。
代碼中的Assert類是一個簡單的斷言類,用于單元測試中的條件檢查。
/test/SqlLexerTest.ts 文件包含了所有詞法掃描的測試用例。截取一段代碼如下:
View Code
到這里詞法分析就結束了,得到一個token列表,接下來,會對這個token列表進行掃描,也就是語法解析。
第二章 語法解析
語法解析也叫語法分析,讀入token列表,輸出抽象語法樹。
在編譯器設計中,以抽象語法樹的形式構造一條SQL語句。例如SQL:
SELECT id, t.stf_name AS name FROM student t WHERE id = 123
會被解析成如下樹結構:
SELECT語法樹
自上而下,遞歸解析,識別出每一個節點。
每種語法節點都是一個單獨的class,比如,SqlSelectNode、SqlFromNode、SqlWhereNode、SqlIdentityNode、StringNumberNode,等等。
數量有點多,一共39個。這39個類都是繼承自語法節點基類SqlNode。
詳細介紹一下:
value字段,用于保存節點的值。SqlStringNode存的是類似 'abc'這樣的值,SqlNumber存的是類似 123 這樣的值,SqlSelectNode存的是 select。
line字段,用于保存節點所在的行號。這個行號是從前一階段的詞法分析中得到的,就是token三元組的最后一個值。
nodes字段,用于保存子節點。例如,SqlExpAddNode的value是 + 或 - ,它的nodes是兩個表達式節點,表示這個表達式的結果相加或相減。
compute方法,用于計算表達式的值。例如,id = 3,如果運行時id的值為3,則運行時返回True,否則返回False。再例如,a * 3,如果運行時a的值為10,則運行時返回30。
typeDeriva方法,用于類型推導。如果數據庫列id是number類型,那么 id + 100 的結果也應該是numer類型;如果 id列是varchar類型,那么 id + 100也是varchar類型。這就是類型推導。
類型推導非常重要,主要用于類型安全檢查。比如,count()的結果一定是numer類型,如果寫出 substr(count(),1) 這樣的表達式,就應該給出語法錯誤。此外,類型推導還可以用于提前確定查詢結果集中每一列的類型,構造好結果集,以容納接下來返回的數據。比如,對于C#或Java,查詢數據庫后得到DataTable或RecordSet,可以獲取到每一列的類型信息,這些類型信息在正式查詢數據庫之前通過語法分析就已經得到了。
推導出的類型,理論上來說,應該跟compute方法返回的值,保持一致。
實現各個語法節點子類的時候,重點是重寫compute和typeDeriva這兩個方法。
接下來講如何構造這些語法節點。
有的節點具有明確的特征,比如 select節點,以關鍵字SELECT開頭,只要掃描這個關鍵字,就可以認為是一條SELECT語句,然后按照SELECT語句的規則繼續往下掃描。
有的節點則不那么容易判斷,具有二義性。
比如 減號 -,如果是 a - b,則表示相減;如果是a = -b,則表示負號。
再比如關鍵字 AND,如果是 a AND b,則表示條件與;如果是 a BETWEEN b AND c,則表示一個數值范圍或字符串范圍。
這種情況下,需要通過上下文分析、優先級判斷、消除文法左遞歸的辦法,來消除二義性。
JSDB實現的只是SQL92的子集,SELECT語法如下:
狀態表格
由于簡化了SELECT語法,所以相對來說還算簡單。唯一有難度的地方,在于表達式的解析,采用的方法是抄自“龍書”《編譯原理》。
自上而下,根據優先級,依次解析 exp_or、exp_and、exp_eq、exp_rel、exp_add、exp_mult、exp_unary、factor。
先看一個簡單點的方法,parseExpRefNode,用于解析類似 t.id 這樣的字段引用表達式。
先嘗試解析第一個標識符,然后是一個分隔符點,最后是結尾的標識符。如果解析失敗,則添加一個SqlError。
接下來看parseExpOrNode、parseExpAndNode兩個方法,分別用于解析條件OR和AND的節點。由于函數是一層層調用進去的,所以,實際上的構造節點順序是反過來的,從factor開始,然后才依次是 unary、mult、add、rel、req、and、or 。
先是從左到右挨個解析,放到一個列表中,然后把列表中的元素轉換為一棵二叉樹,函數返回的是這棵二叉樹的根節點。
parseExpOrNode類:
parseExpAndNode類:
看著有點暈?沒關系,我畫一張圖,演示一下表達式 a OR b AND c OR d OR e 是如何轉換為二叉樹的。
測試代碼:
Assert.runCase('parse exp', function () {
let parser = new SqlParser("a OR b AND c OR d OR e");
let node = parser.parseExpOrNode(null);
console.log(node.toString());
});
輸出如下二叉樹結構:
|--SqlExpOrNode@1:or
|--SqlExpOrNode@1:or
|--SqlExpOrNode@1:or
|--SqlIdentityNode@1:a
|--SqlExpAndNode@1:and
|--SqlIdentityNode@1:b
|--SqlIdentityNode@1:c
|--SqlIdentityNode@1:d
|--SqlIdentityNode@1:e
構造該二叉樹的步驟如下圖所示:
表達式二叉樹構造順序
構造完抽象語法樹后,不用生成機器碼,直接在語法樹上計算。
第三章 計算語法樹
前面提到過,語法樹節點基類SqlNode里有一個compute方法,用于計算節點的值,子類會重寫該方法,實現具體的計算邏輯。
語法節點太多了,咱們只講幾個關鍵節點的計算邏輯:
SqlNumberNode類,根據value字段的值是否有小數點,相應返回parseInt(this.value)或parseFloat(this,value)。
SqlStringNode類,根據value字段的值返回字符串,去掉首尾的單引號,如果有轉義符,要進行轉義。
SqlExpRelNode類,計算左右兩個子節點的值,比較其大小,返回True或False。
SqlExpAddNode類,計算左右兩個子節點的值,根據value字段的值是 '+' 還是 '-',相應執行相加或相減。
SqlExpMulNode類,計算左右兩個子節點的值,根據value字段的值是 '*' 、'/' 還是 '%',相應執行相乘、相除、取余。
SqlExpAndNode類,計算左右兩個子節點的值,如果都為True,才返回True,否則返回False。
SqlExpOrNode類,計算左右兩個子節點的值,如果都為False,才返回False,否則返回True。
SqlExpUnaryNode類,一元操作符,只有一個節點,計算其值。根據操作符的值是'+'、'-'、'not',執行相應的取正、取負、取反邏輯。
SqlExpFuncNode類,執行函數。首先從SqlContext.standardFunctions字段取一下,如果取到了,說明是標準函數,直接執行,否則再看是不是聚合函數。聚合函數的執行比較復雜,咱們單獨講。
SqlInsertNode類,執行插入邏輯,返回受影響行數。
SqlUpdateNode類,執行更新邏輯,返回受影響行數。
SqlDeleteNode類,執行刪除邏輯,返回受影響行數。
SqlSelectNode類,執行查詢邏輯,返回一個二維表SqlDataTable實例。這個最復雜,咱們接下來重點講。
其它語法節點的執行邏輯,請參見源代碼。
接下來,重點講一下SqlSelectNode類和SqlExpFuncNode類的實現邏輯,也就是SELECT語句到底是怎么實現數據查詢的,這貨老復雜了,燒了不少腦細胞,大伙一定要給個贊。
第四章 SELECT語句
一條SELECT語句的執行,可以分為如下幾個步驟:
1、根據 from 節點,以及可能存在的 join 節點,合并出一張寬表(fullTable)。這里我沒有做任何優化,直接生成一個笛卡爾積,所以,測試的數據量千萬不要太大,否則,運行的速度夠你酸爽的~~~
2、如果有 join節點,執行聯結規則。JSDB只支持 join 和 left join 這兩種最常用的聯結方式,其它聯結方式暫不支持。執行on條件節點,如果返回False,表示沒有join上,這時再判斷是join還是left join,如果是join,就直接刪除;如果是left join,就填上null值。
不太好理解的是repeatJoinRows這個字段,這是為了處理重復join的問題。比如,from表有一條記錄,外鍵ID對應一個 join表中的兩條記錄,也就是說,join表存在id重復的情況。針對這種情況,需要把重復join的數據也保留下來。
3、如果有 where 節點,執行篩選規則。就是執行SqlWhereNode節點,不符合條件的記錄,直接刪除。
4、如果有 group by 節點,則執行分組規則。這個最復雜,分為以下幾個步驟:
4.1 首先要提取出 fields、having、orderby 這三個節點中的聚合表達式。
4.2 根據 group by的節點,以及上一步得到的聚合表達式列表,構造一張分組計算中間表,寫入上下文中,后面聚合函數計算時會用到。
4.3 遍歷寬表fullTable,計算分組中間表的值,得到分組中間表groupByMidTable。這段代碼不好理解,實際邏輯是在SqlExpFuncNode類中。為了遍歷一次就能算出所有聚合表達式的值,我封裝了一個SqlGroupByValue類,該類用于記錄一個聚合表達式的當前最新的count行數、sum匯總、distinctValues去重值列表,以及當前最新值,這個當前最新值可以是行數、匯總,也可以是最大值、最小值、平均值,取決于具體的聚合函數。所以,一定要注意,普通SqlDataTable的單元值是string或number,但是分組中間表的單元值是SqlGroupByValue。
4.4 基于分組中間表groupByMidTable,根據fields節點進行計算,得到結果表resultTable。為什么要再算一遍?因為,對于 count(*) * 10 這樣的表達式,在4.3小節中實際只計算了count(*),乘以10的步驟是在這里計算的。另外,并不是所有聚合表達式都是要返回的,有些聚合表達式是在having或order節點中出現的,并不在fields節點中,所以,必須在這一步中集中處理一下。
涉及的函數表達式,尤其是聚合函數表達式,計算代碼如下:
5、如果沒有 group by 節點,直接在where篩選后的fullTable上根據fields節點進行計算,得到結果表resultTable。這個就簡單很多了。
6、如果有 order by 節點,則對結果表resultTable進行排序。由于排序規則可能包含多個條件,這里要分為三個步驟來計算:
6.1 遍歷resultTable表,每一行數據都得到一個orderByValues數組,包含了排序要用的值。如果是多個排序條件,數組就包含多個值。
6.2 計算每個排序條件的方向,默認是asc。
6.3 根據排序表達式的值,以及排序方向,對數據行進行排序。這里調用的是Array類的sort方法,傳入一個function,實現自定義排序。
7、如果有 limit 節點,則返回指定范圍的數據,也就是分頁時要用的東西。如果是limit n,則返回前面n行數據;如果是limit m, n,則從第m行開始,返回n行數據的。
到這里就得到最終的結果表了。
相對于SELECT語句,其它語句就簡單多了。
第五章 其它語句
DELETE語句的執行分為兩步:執行where篩選,然后根據row.id進行刪除。
UPDATE語句也分為兩步:執行where篩選,然后set規則更新指定列的數據。
INSERT語句也分為兩步:根據表構造創建一個空行,然后更新指定列的數據。
CREATE TABLE語句,在 SqlDatabase 中創建一個新的 SqlDataTable 實例。
至此,幾個主要的語句都介紹了。
最后,我們寫幾個測試范例,展示一下運行結果,這幾個測試范例,在文章開頭的“體驗頁面”上都有展示。
第六章 程序展示
通過JS創建三張表:t_gender(性別字典表)、t_dept(部門字典表)、t_staff(員工表)。
var database = new SqlDatabase();
database.execute("create table t_gender(id number, name varchar(100))");
database.execute("create table t_dept(dept_id number, dept_name varchar)");
database.execute("create table t_staff(id varchar, name varchar, gender number, dept_id number)");
database.execute("insert into t_gender(id, name)values(1, 'Male')");
database.execute("insert into t_gender(id, name)values(2, 'Female')");
database.execute("insert into t_dept(dept_id, dept_name)values(101, 'Tech')");
database.execute("insert into t_dept(dept_id, dept_name)values(102, 'Finance')");
database.execute("insert into t_staff(id, name, gender, dept_id)values('016001', 'Jack', 1, 102)");
database.execute("insert into t_staff(id, name, gender, dept_id)values('016002', 'Bruce', 1, null)");
database.execute("insert into t_staff(id, name, gender, dept_id)values('016003', 'Alan', null, 101)");
database.execute("insert into t_staff(id, name, gender, dept_id)values('016004', 'Hellen', 2, 103)");
database.execute("insert into t_staff(id, name, gender, dept_id)values('016005', 'Linda', 2, 101)");
database.execute("insert into t_staff(id, name, gender, dept_id)values('016006', 'Royal', 3, 104)");
然后準備幾條范例sql,方便大家執行查詢,也可以自己寫一個新的sql。
SELECT s.id,
s.name,
ifnull(s.gender, '--') AS gender_id, /*處理空值*/ (CASE g.name WHEN 'Male' THEN '男' WHEN 'Female' THEN '女' ELSE '未知' END) AS gender_name,
s.dept_id,
d.dept_name FROM t_staff s LEFT JOIN t_gender g ON g.id=s.gender LEFT JOIN t_dept d ON d.dept_id=s.dept_id WHERE d.dept_name IS NOT NULL LIMIT 3
執行結果:
SQL結果
文章到這里就結束了,歡迎大家指正,多給Star,多給贊 _
作者:Lave Zhang
出處:http://www.cnblogs.com/lavezhang/
*請認真填寫需求信息,我們會在24小時內與您取得聯系。