小 T 導讀:TDengine 是一款開源、高性能、云原生的時序數據庫(Time Series Database, TSDB),存儲和計算都針對時序數據的特點量身定制,在支持標準 SQL 的基礎之上,還提供了一系列貼合時序業務場景的特色查詢語法,極大地方便了時序場景的應用開發。TDengine 提供的特色查詢包括數據切分查詢和窗口切分查詢,本文將從語法層面深入解讀這兩種特色查詢。
數據切分查詢
根據業務需要,有時我們需要按一定的維度對數據進行切分,當在切分出的數據空間內進行一系列的計算時,就需要使用數據切分子句,語法如下:
PARTITION BY part_list
在上述語法中,part_list 可以是任意的標量表達式,包括列、常量、標量函數和它們的組合。TDengine 按如下方式處理數據切分子句:
- 數據切分子句位于 WHERE 子句之后;
- 數據切分子句將表數據按指定的維度進行切分,每個切分的分片進行指定的計算。計算由之后的子句定義(窗口子句、GROUP BY 子句或 SELECT 子句);
- 數據切分子句可以和窗口切分子句(或 GROUP BY 子句)一起使用,此時后面的子句作用在每個切分的分片上。例如,將數據按標簽 location 進行分組,并對每個組按 10 分鐘進行降采樣,取其最大值。
select max(current) from meters partition by location interval(10m)
數據切分子句最常見的用法就是在超級表查詢中,按標簽將子表數據進行切分,然后分別進行計算。特別是 PARTITION BY TBNAME 用法,它將每個子表的數據獨立出來,形成一條條獨立的時間序列,極大地方便了各種時序場景的統計分析。
窗口切分查詢
TDengine 支持按時間段窗口切分方式進行聚合結果查詢,比如溫度傳感器每秒采集一次數據,但需查詢每隔 10 分鐘的溫度平均值,這種場景下可以使用窗口子句來獲得需要的查詢結果。想要讓查詢的數據集合按照窗口切分成查詢子集并進行聚合,就需要用到窗口子句,窗口包含時間窗口(time window)、狀態窗口(status window)、會話窗口(session window)三種窗口。其中時間窗口又可劃分為滑動時間窗口和翻轉時間窗口。窗口切分查詢語法如下:
SELECT select_list FROM tb_name
[WHERE where_condition]
[SESSION(ts_col, tol_val)]
[STATE_WINDOW(col)]
[INTERVAL(interval [, offset]) [SLIDING sliding]]
[FILL({NONE | VALUE | PREV | NULL | LINEAR | NEXT})]
窗口子句的規則
- 窗口子句位于數據切分子句之后,GROUP BY 子句之前,且不可以和 GROUP BY 子句一起使用;
- 窗口子句將數據按窗口進行切分,對每個窗口進行 SELECT 列表中表達式的計算,SELECT 列表中的表達式只能包含:
- 常量
- _wstart 偽列、_wend 偽列和_wduration 偽列
- 聚集函數(包括選擇函數和可以由參數確定輸出行數的時序特有函數)
- 包含上面表達式的表達式
- 且至少包含一個聚集函數
- 窗口子句不可以和 GROUP BY 子句一起使用;
- WHERE 語句可以指定查詢的起止時間和其他過濾條件。
FILL 子句
FILL 語句指定的是某一窗口區間數據缺失情況下的填充模式。填充模式包括以下幾種:
- 不進行填充:NONE(默認填充模式);
- VALUE 填充:固定值填充,此時需要指定填充的數值。例如:FILL(VALUE, 1.23)。這里需要注意,最終填充的值受由相應列的類型決定,如 FILL(VALUE, 1.23),相應列為 INT 類型,則填充值為 1;
- PREV 填充:使用前一個非 NULL 值填充數據,例如:FILL(PREV);
- NULL 填充:使用 NULL 填充數據,例如:FILL(NULL);
- LINEAR 填充:根據前后距離最近的非 NULL 值做線性插值填充,例如:FILL(LINEAR);
- NEXT 填充:使用下一個非 NULL 值填充數據,例如:FILL(NEXT)。
在使用 FILL 子句時,需要注意:
- 使用時可能生成大量的填充輸出,因此務必指定查詢的時間區間。針對每次查詢,系統可返回不超過 1 千萬條具有插值的結果。
- 在進行時間維度聚合時,返回的結果中時間序列嚴格單調遞增。
- 如果查詢對象是超級表,則聚合函數會作用于該超級表下滿足值過濾條件的所有表的數據。如果查詢中沒有使用 PARTITION BY 語句,則返回的結果按照時間序列嚴格單調遞增;如果查詢中使用了 PARTITION BY 語句分組,則返回結果中每個 PARTITION 內不會按照時間序列嚴格單調遞增。
時間窗口
時間窗口又可分為滑動時間窗口和翻轉時間窗口。INTERVAL 子句用于產生相等時間周期的窗口,SLIDING 用以指定窗口向前滑動的時間,在執行時間窗口查詢時,其會隨著時間流動向前滑動。在定義連續查詢時我們需要指定時間窗口(time window )大小和每次前向增量時間(forward sliding times)。

如上圖,[t0s, t0e] 、[t1s , t1e]、[t2s, t2e] 分別是執行三次連續查詢的時間窗口范圍,窗口的前向滑動的時間范圍以 sliding time 標識 。查詢過濾、聚合等操作按照每個時間窗口為獨立的單位執行。當 SLIDING 與 INTERVAL 相等的時候,滑動窗口即為翻轉窗口。
INTERVAL 和 SLIDING 子句需要配合聚合和選擇函數來使用。以下 SQL 語句非法:
SELECT * FROM temp_tb_1 INTERVAL(1m);
SLIDING 的向前滑動的時間不能超過一個窗口的時間范圍。以下語句非法:
SELECT COUNT(*) FROM temp_tb_1 INTERVAL(1m) SLIDING(2m);
使用時間窗口需要注意:
- 聚合時間段的窗口寬度由關鍵詞 INTERVAL 指定,最短時間間隔 10 毫秒(10a);并且支持偏移 offset(偏移必須小于間隔),也即時間窗口劃分與“UTC 時刻 0”相比的偏移量。SLIDING 語句用于指定聚合時間段的前向增量,也即每次窗口向前滑動的時長。
- 使用 INTERVAL 語句時,除非極特殊的情況,都要求把客戶端和服務端的 taos.cfg 配置文件中的 timezone 參數配置為相同的取值,以避免時間處理函數頻繁進行跨時區轉換而導致的嚴重性能影響。
- 返回的結果中時間序列嚴格單調遞增。
狀態窗口

TDengine 使用整數(布爾值)或字符串來標識產生記錄時設備的狀態量。產生的記錄如果具有相同的狀態量數值則歸屬于同一個狀態窗口,數值改變后該窗口關閉。如上圖所示,根據狀態量我們能夠確定的狀態窗口分別是 [2019-04-28 14:22:07,2019-04-28 14:22:10] 和 [2019-04-28 14:22:11,2019-04-28 14:22:12] 兩個。
我們可以使用 STATE_WINDOW 來確定狀態窗口劃分的列。例如:
SELECT COUNT(*), FIRST(ts), status FROM temp_tb_1 STATE_WINDOW(status);
會話窗口

會話窗口會根據所記錄時間戳主鍵的值來確定是否屬于同一個會話。如上圖所示,如果設置時間戳的連續間隔小于等于 12 秒,則以上 6 條記錄會構成 2 個會話窗口,分別是 [2019-04-28 14:22:10,2019-04-28 14:22:30] 和 [2019-04-28 14:23:10,2019-04-28 14:23:30]。因為 2019-04-28 14:22:30 與 2019-04-28 14:23:10 之間的時間間隔是 40 秒,超過了連續時間間隔(12 秒)。
一般來說,我們會認為在 tol_value 時間間隔范圍內的結果都歸屬于同一個窗口,如果連續兩條記錄的時間超過 tol_val,則自動開啟下一個窗口。
SELECT COUNT(*), FIRST(ts) FROM temp_tb_1 SESSION(ts, tol_val);
示例
以智能電表為例,其建表語句如下:
CREATE TABLE meters (ts TIMESTAMP, current FLOAT, voltage INT, phase FLOAT) TAGS (location BINARY(64), groupId INT);
針對智能電表采集的數據,以 10 分鐘為一個階段,計算過去 24 小時的電流數據的平均值、最大值、電流的中位數。如果沒有計算值,則用前一個非 NULL 值填充。使用的查詢語句如下:
SELECT AVG(current), MAX(current), APERCENTILE(current, 50) FROM meters
WHERE ts>=NOW-1d and ts<=now
INTERVAL(10m)
FILL(PREV);
寫在最后
除了數據量大、結構相對簡單的特點外,時序數據在查詢場景中還大量涉及時間戳的處理,在很多業務場景的采集數據時,都需要按時間戳進行分組與計算,如果按照常規模式將原始數據讀入內存,再由應用層程序去處理時間窗口劃分的邏輯,就會因讀取海量原始時序數據導致磁盤 IO、CPU 及內存開銷的嚴重浪費,還會提升業務層代碼復雜度。
但如果我們能夠掌握并靈活運用 TDengine 所提供的上述時序數據特色查詢功能,結合業務場景選擇相應的函數就能將相關計算負荷下沉到實時數據庫層,在提升系統響應性能的同時也減少了系統資源的浪費。



























