1. 程式人生 > >sqlite中對於group by的處理

sqlite中對於group by的處理

was time nts 鍵值 strong 數值 register ica lar

一、除重
在一些數據庫查詢中,我們希望一些記錄是唯一的,比如希望查找某一項的最大值,由於table中可能有多條記錄他們的值相同,都是最大值,例如一些有上限的屬性,很容易出現多條record的屬性值都達到上限的情況,此時我們可能只需要一條,驗證它的確曾經達到過上限,此時比較好的辦法就是使用group by,這個修飾天生具有消除冗余的特征。即使加上其它屬性,即使其它屬性不重復,此時每個group也只會顯示一個,此時比較好奇的是數據庫是如何處理一個group的,而對於一個group的非by屬性,在最終的展示過程中將會顯示成什麽樣的結果。
二、例子
文檔的描述沒有代碼準確,文字的描述沒有圖形的描述直觀,但是前者成本較低。這裏還是用最原始的方式說明一下問題:
sqlite> create table tsecer(F1 int, F2 int, F3 int, F4 int);
sqlite> .tables
tsecer
sqlite> .explain
sqlite> explain select F1 , max(F2), avg(F3), sum(F4) from tsecer where F3>0 group by 888*F4 , 999*F1;
addr opcode p1 p2 p3 p4 p5 comment
---- ------------- ---- ---- ---- ------------- -- -------------
0 Trace 0 0 0 00
1 OpenEphemeral 1 7 0 keyinfo(2,BINARY,BINARY) 00 Open a new cursor P1 to a transient table P2 is the number of columns in the virtual table
2 Integer 0 9 0 00 clear abort flag
3 Integer 0 8 0 00 indicate accumulator empty
4 Gosub 11 60 0 00
5 Integer 0 16 0 00
6 Goto 0 69 0 00
7 OpenRead 0 2 0 4 00 tsecer
8 Rewind 0 25 0 00

9 Column 0 2 17 00 tsecer.F3 每個列編號從0開始,所以F3編號為2
10 Le 16 24 17 collseq(BINARY) 6c
11 Integer 888 17 0 00
12 Column 0 3 18 00 tsecer.F4
13 Multiply 18 17 19 00 888*F4放入新record的第一個成員中
14 Integer 999 17 0 00
15 Column 0 0 26 00 tsecer.F1 26寄存器放入F1值
16 Multiply 26 17 20 00 F1×999放入新record的第二個成員中
17 Sequence 1 21 0 00 Find the next available sequence number for cursor P1.Write the sequence number into register P2 The sequence number on the cursor is incremented after this instruction 定義處內容 i64 seqCount; /* Sequence counter */ 表示在cursor中添加的元素的添加序列號,應該是一個內部結構,對於此次查詢透明。
18 SCopy 26 22 0 00 22放入F1
19 Column 0 1 23 00 tsecer.F2
20 Column 0 2 24 00 tsecer.F3
21 SCopy 18 25 0 00 F4
22 MakeRecord 19 7 17 00 Convert P2 registers beginning with P1 into a single entry suitable for use as a data record in a database table or as a key in an index,17為輸出地址
23 IdxInsert 1 17 0 00

24 Next 0 9 0 01
25 Close 0 0 0 00

26 Sort 1 68 0 00 GROUP BY sort

27 Column 1 0 14 00 888*F4
28 Column 1 1 15 00 999*F1
29 Compare 12 14 2 keyinfo(2,BINARY,BINARY) 00 Compare to vectors of registers in reg(P1)..reg(P1+P3-1) (all this one "A") and in reg(P2)..reg(P2+P3-1) ("B"). Save the result of the comparison for use by the next OP_Jump instruct.P4 is a KeyInfo structure that defines collating sequences and sort orders for the comparison. The permutation applies to registers only. The KeyInfo elements are used sequentially. 12開始和14開始的兩個寄存器逐個比較,所有想等才認為一個相同的group。
30 Jump 31 35 31 00 如果鍵值想等,執行累加輸出,否則
31 Move 14 12 2 00 將當前值保存在前一個值中
32 Gosub 10 49 0 00 output one row
33 IfPos 9 68 0 00 check abort flag
34 Gosub 11 60 0 00 reset accumulator

//執行所有agg中指定的函數
35 Column 1 4 19 00 F2放入19寄存器
36 CollSeq 0 0 0 collseq(BINARY) 00
37 AggStep 0 19 2 max(1) 01
38 Column 1 5 20 00 F3
39 AggStep 0 20 3 avg(1) 01
40 Column 1 6 21 00 F4
41 AggStep 0 21 4 sum(1) 01
42 Column 1 3 1 00 F1放入reg1中,<1,0>存放888*F4 <1,1>存放F1×999 <1,2>存放未使用的sequence number,接下來從3到6為F1到F4
43 Integer 1 8 0 00 indicate data in accumulator The 32-bit integer value P1 is written into register P2.
44 Next 1 27 0 00 Advance cursor P1 so that it points to the next key/data pair in its table or index. If there are no more key/value pairs then fall through to the following instruction. But if the cursor advance was successful,jump immediately to P2.
45 Gosub 10 49 0 00 output final row
46 Goto 0 68 0 00
47 Integer 1 9 0 00 set abort flag
48 Return 10 0 0 00

49 IfPos 8 51 0 00 Groupby result generator entry point
50 Return 10 0 0 00
51 AggFinal 2 1 0 max(1) 00
52 AggFinal 3 1 0 avg(1) 00
53 AggFinal 4 1 0 sum(1) 00
54 SCopy 1 27 0 00 Make a shallow copy of register P1 into register P2. 將寄存器1中的值拷貝到寄存器2中
55 SCopy 2 28 0 00
56 SCopy 3 29 0 00
57 SCopy 4 30 0 00
58 ResultRow 27 4 0 00 輸出從27開始的四個值 The registers P1 through P1+P2-1 contain a single row of results This opcode causes the sqlite3_step() call to terminate with an SQLITE_ROW return code and it sets up the sqlite3_stmt structure to provide access to the top P1 values as the result row
59 Return 10 0 0 00 end groupby result generator

清空accumulator
60 Null 0 1 0 00
61 Null 0 5 0 00
62 Null 0 6 0 00
63 Null 0 7 0 00
64 Null 0 2 0 00
65 Null 0 3 0 00
66 Null 0 4 0 00
67 Return 11 0 0 00

68 Halt 0 0 0 00
69 Transaction 0 0 0 00
70 VerifyCookie 0 1 0 00
71 TableLock 0 2 0 tsecer 00
72 Goto 0 7 0 00
sqlite>
三、大致方法
在sqlite的實現中,它首先會創建一個內存中的record集合結構,這個結構用sqlite的屬於terminology來說就是cursor。記得有一句諺語所說:to the man with a hammer every problem looks like a nail,每種系統都嘗試用自己最擅長的工作來解決自己所有的問題,例如gcc用自己來編譯自己,linux用文件系統來虛擬一切,而sqlite則用BTree來組織所有記錄。sqlite創建的臨時cursor同樣適用bTree來管理自己所有的record。
對於上面的命令,sqlite為每個group by指定的表達式創建一個對應的新屬性,然後把所有結果中需要的其他原始屬性一起組合成一個新的record,然後插入新創建的臨時cursor中。
之後遍歷臨時創建的cursor結構,之後的操作應該是比較直觀的,就好象之前完成的是sort的功能,接下來的處理就是相當於uniq工具的作用了,因為record已經按照group by的公式排序,所以具有相同值編號一定是相鄰的。對於具有多列的group by屬性,在之前的compare中比較是一組數值比較的,所以對於group by的字段只有全部相等時,才能算作一組。
從這個實現中來看,在輸出結果中不受avg影響的列應該是最後一個record中的字段,但是這個是sqlite的實現,而且沒有驗證。mysql會不會進行優化,例如不在每個agg循環中更新這個和agg無關的字段,而只在開始更新一次,想來也是極好的。

sqlite中對於group by的處理