Query SQL pada InfluxDB 3.x ditulis dengan insting SELECT yang sama seperti pada basis data relasional, tetapi dijalankan di atas Apache DataFusion yang dibangun khusus untuk data time-series kolumnar. Topik ini membahas sintaks dasar SELECT pada tabel telemetri, fungsi time-series yang paling sering digunakan, posisi tag sebagai kolom biasa, serta bagaimana daftar kolom pada SELECT menentukan byte mana yang dibaca dari disk. Setelah membaca topik ini, query sederhana untuk rerata per jam, last-known-value, dan pemindaian rentang waktu dapat ditulis tanpa mempelajari bahasa baru.
Sintaks dasar SELECT#
Sintaks SELECT pada InfluxDB 3.x mengikuti standar SQL yang digunakan Postgres, MySQL, atau DuckDB, sehingga pengguna yang sudah akrab dengan SQL tidak perlu mempelajari dialek baru. Sebuah tabel telemetri pada 3.x memiliki kolom time sebagai cap waktu, kolom tag seperti sensor_id, dan kolom field seperti suhu_c atau kelembapan, semuanya sebagai kolom biasa. Klausa WHERE menggunakan predikat standar, GROUP BY mengelompokan per kolom, dan ORDER BY mengurutkan hasil. Pernyataan berikut tidak dijalankan di sandbox sebab tidak ada influx maupun DataFusion yang terpasang, sehingga hanya menjadi referensi sintaksis.
-- ambil 10 baris termutakhir dari tabel suhu, urut menurun
SELECT
time,
sensor_id,
suhu_c
FROM suhu
ORDER BY time DESC
LIMIT 10;time|sensor_id|suhu_c
---|---|---
2025-01-01T00:11:00|S01|24.72
2025-01-01T00:10:00|S01|24.65
2025-01-01T00:09:00|S01|24.6
2025-01-01T00:08:00|S01|24.55
2025-01-01T00:07:00|S01|24.48
2025-01-01T00:06:00|S01|24.41
2025-01-01T00:05:00|S01|24.35
2025-01-01T00:04:00|S02|25.5
2025-01-01T00:04:00|S01|24.3
2025-01-01T00:03:00|S02|25.4Hasil query dikembalikan sebagai record batch Apache Arrow, sehingga client yang mendukung Arrow dapat membaca hasil tanpa salinan tambahan. Karena Arrow adalah format in-memory kolumnar, hasil query berukuran besar dapat dipindahkan ke proses lain, misalnya pandas atau DuckDB, tanpa serialisasi menjadi JSON. Pada client yang lebih sederhana, hasil dapat diminta sebagai JSON melalui parameter tambahan pada permintaan, tetapi opsi ini membawa overhead salinan dan tidak disarankan untuk data besar.
Fungsi time-series yang sering digunakan#
Beberapa fungsi SQL menjadi alat utama pada beban time-series, dan ketiganya didukung oleh DataFusion. Fungsi date_trunc('hour', time) membulatkan cap waktu ke awal jam, sehingga GROUP BY date_trunc('hour', time) menghasilkan agregat per jam. Interval waktu dinyatakan dengan INTERVAL '1 hour', yang dapat digunakan pada predikat WHERE time >= NOW() - INTERVAL '1 hour' untuk membatasi pemindaian ke satu jam terakhir. Fungsi NOW() mengembalikan cap waktu server saat ini, sehingga query menjadi relatif terhadap waktu eksekusi. Pernyataan berikut tidak dijalankan di sandbox dan hanya menjadi referensi sintaksis.
SELECT
sensor_id,
date_trunc('hour', time) AS jam,
AVG(suhu_c) AS suhu_rerata,
COUNT(*) AS n
FROM suhu
WHERE time >= NOW() - INTERVAL '1 hour'
GROUP BY sensor_id, jam
ORDER BY sensor_id, jam;sensor_id|jam|suhu_rerata|n
---|---|---|---
S01|2025-01-01T00:00:00|24.389166666666664|12
S02|2025-01-01T00:00:00|25.3|5Last-known-value, yaitu nilai terbaru per seri, dapat diperoleh dengan ORDER BY time DESC LIMIT 1 yang dikelompokkan per tag melalui subquery atau window function. Pada DataFusion, window function ROW_NUMBER() OVER (PARTITION BY sensor_id ORDER BY time DESC) dapat digunakan untuk memilih baris termutakhir per sensor tanpa LAST() khusus. Hal ini menjadi contoh bagaimana SQL standar cukup untuk pola time-series yang dahulu memerlukan fungsi khusus pada InfluxQL.
Tag sebagai kolom biasa#
Pada era InfluxQL, tag diperlakukan berbeda dari field, sehingga klausa GROUP BY hanya menerima tag, bukan field. InfluxDB 3.x menghapus perbedaan ini, sebab seluruh kolom, baik tag maupun field, adalah kolom biasa pada tabel. Akibatnya, GROUP BY suhu_c sah secara sintaksis, walaupun hasilnya jarang berguna karena kardinalitas suhu_c tinggi. Yang membedakan tag dari field pada 3.x hanyalah konvensi dan metadata: tag biasanya bermakna identitas dengan kardinalitas rendah, sedangkan field bermakna nilai terukur dengan kardinalitas tinggi. Tabel berikut merangkum perbedaan ini.
| Aspek | Era InfluxQL (1.x/2.x) | InfluxDB 3.x (SQL) |
|---|---|---|
Tag pada GROUP BY | Wajib tag, field ditolak | Kolom biasa, tag atau field sama saja |
Field pada WHERE | Terbatas, sebagian operator ditolak | Seluruh operator standar |
| Fungsi agregat | Khusus MEAN(), MAX(), dsb. | AVG(), MAX(), COUNT(), standar SQL |
| Window function | Tidak ada | ROW_NUMBER(), LAG(), LEAD(), standar SQL |
Hilangnya perbedaan tag dan field pada level sintaksis menyederhanakan SQL secara signifikan, tetapi juga menghapus guardrail yang dahulu mencegah query naif. Sebuah GROUP BY pada kolom field dengan kardinalitas tinggi, misalnya suhu_c yang dibulatkan ke desimal, dapat menghasilkan ribuan kelompok yang sebagian besar kosong, sehingga query menjadi lambat tanpa manfaat analitik. Disiplin perancang query menjadi lebih penting, sebab mesin tidak lagi menolak operasi yang tidak masuk akal.
Daftar kolom menentukan byte yang dibaca#
Karena penyimpanan 3.x adalah kolumnar, daftar kolom pada SELECT menentukan byte mana yang dibaca dari disk. Query SELECT suhu_c FROM suhu hanya membaca kolom suhu_c, sedangkan SELECT * FROM suhu membaca seluruh kolom, sehingga disk I/O menjadi beberapa kali lipat. Skrip berikut memodelkan pemetaan antara daftar kolom pada SELECT dan jumlah kolom yang dibaca, dengan parser mini yang mengenali daftar kolom sederhana dari sebuah pernyataan SQL. Skrip tidak menjalankan SQL dan tidak memerlukan pustaka Arrow.
"""Peta kolom SELECT ke jumlah byte yang dibaca pada InfluxDB 3.x.
Skrip ini tidak menjalankan SQL dan tidak memerlukan pustaka Arrow. Tujuannya
memperlihatkan bahwa daftar kolom pada SELECT langsung menentukan kolom mana
yang dibaca dari disk pada penyimpanan kolumnar. Parser mini yang dipakai
mengenali SELECT sederhana: SELECT <kolom, ...> FROM <tabel>.
"""
import re
# Tabel hipotetis 'suhu' dengan 5 kolom dan perkiraan byte per elemen.
KOLOM = {
"time": 8, # int64 nanodetik
"sensor_id": 6, # string pendek
"suhu_c": 8, # double
"kelembapan": 8, # double
"tekanan": 8, # double
}
N_BARIS = 1_000_000 # satu juta baris sebagai asumsi
def parse_select(sql: str) -> list[str]:
"""Ambil daftar kolom dari SELECT sederhana."""
m = re.match(r"\s*SELECT\s+(.+?)\s+FROM\s+\w+", sql, re.IGNORECASE | re.DOTALL)
if not m:
return []
bagian = m.group(1).strip()
if bagian == "*":
return list(KOLOM.keys())
# pecah pada koma, ambil nama kolom (abaikan alias "AS ...").
kolom = []
for frag in bagian.split(","):
frag = frag.strip()
# buang alias "kolom AS alias" atau "kolom alias"
frag = re.split(r"\s+(?:AS\s+)?", frag, maxsplit=1, flags=re.IGNORECASE)[0]
kolom.append(frag.strip())
return kolom
def byte_dibaca(kolom_dipakai: list[str]) -> int:
return sum(KOLOM[k] * N_BARIS for k in kolom_dipakai if k in KOLOM)
def main() -> None:
print(f"tabel suhu: {N_BARIS:,} baris, {len(KOLOM)} kolom")
print(f"ukuran total seluruh kolom: {sum(KOLOM.values()) * N_BARIS:,} byte")
print()
kasus = [
"SELECT suhu_c FROM suhu",
"SELECT sensor_id, suhu_c FROM suhu",
"SELECT time, sensor_id, suhu_c FROM suhu",
"SELECT * FROM suhu",
]
for sql in kasus:
kolom = parse_select(sql)
baca = byte_dibaca(kolom)
total = sum(KOLOM.values()) * N_BARIS
pct = baca / total * 100
print(f"{sql:<42} -> {len(kolom)} kolom, {baca:>11,} byte ({pct:5.1f}% dari total)")
if __name__ == "__main__":
main()tabel suhu: 1,000,000 baris, 5 kolom
ukuran total seluruh kolom: 38,000,000 byte
SELECT suhu_c FROM suhu -> 1 kolom, 8,000,000 byte ( 21.1% dari total)
SELECT sensor_id, suhu_c FROM suhu -> 2 kolom, 14,000,000 byte ( 36.8% dari total)
SELECT time, sensor_id, suhu_c FROM suhu -> 3 kolom, 22,000,000 byte ( 57.9% dari total)
SELECT * FROM suhu -> 5 kolom, 38,000,000 byte (100.0% dari total)Keluaran di atas memperlihatkan bahwa SELECT * membaca 100 persen dari byte tabel, sedangkan SELECT suhu_c hanya membaca 21 persen. Pada tabel dengan lima kolom, perbedaannya masih moderat, tetapi pada tabel dengan 20 kolom, SELECT suhu_c hanya membaca 5 persen dari total, sehingga query berjalan beberapa kali lipat lebih cepat. Aturan praktis pada InfluxDB 3.x adalah menyebutkan kolom yang dibutuhkan secara eksplisit dan menghindari SELECT * pada tabel yang sudah lebar, sebab biaya disk I/O untuk kolom yang tidak digunakan dibayar penuh.
Kesalahan umum#
- Menggunakan
SELECT *pada tabel telemetri yang lebar. Seluruh kolom dibaca dari disk, termasuk yang tidak digunakan, sehingga disk I/O beberapa kali lipat lebih besar daripada yang seharusnya. - Membawa kebiasaan
GROUP BY <tag>dari InfluxQL dan menerapkannya pada field dengan kardinalitas tinggi. Hasilnya menghasilkan ribuan kelompok yang sebagian besar kosong dan query menjadi lambat. - Mengira
date_truncdanINTERVALadalah ekstensi Postgres yang tidak ada di DataFusion. Keduanya didukung pada InfluxDB 3.x, sehingga query analitik standar dapat ditulis tanpa penyesuaian.
Ringkasan#
SELECTpada InfluxDB 3.x mengikuti standar SQL, dengan tag dan field sebagai kolom biasa yang dapat digunakan pada klausa apa pun.- Fungsi
date_trunc,INTERVAL, danNOW()menjadi alat utama untuk query time-series, sedangkan window function standar menggantikan fungsi khusus sepertiLAST(). - Daftar kolom pada
SELECTmenentukan byte mana yang dibaca dari disk, sehinggaSELECT suhu_cjauh lebih ringan daripadaSELECT *pada tabel yang lebar.
Referensi#
- Query data with SQL — InfluxData
— Panduan resmi query SQL pada InfluxDB 3.x, termasuk fungsi
date_trunc,INTERVAL, danNOW(). - InfluxDB 3 SQL reference — InfluxData — Referensi resmi SQL InfluxDB 3.x, termasuk daftar fungsi yang didukung oleh DataFusion.
- Apache DataFusion functions — The Apache Software Foundation — Dokumentasi fungsi DataFusion yang menjadi dasar mesin query InfluxDB 3.x.