QuestDB时序数据库快速入门

简介

QuestDB是一个开源的高性能时序数据库,专门用于处理时间序列相关的数据存储与查询;

QuestDB使用列式存储模型。数据存储在表中,每列存储在其自己的文件和其自己的本机格式中。新数据被附加到每列的底部,以便能够按照与摄取数据相同的顺序有机地检索数据。

随着业务快速发展,使得海量数据在传统关系型数据库上性能瓶颈问题,转移到QuestDB时序数据库上后得到性能的极大提升,解决了海量数据高性能快速读写与简易管理问题;

QuestDB能够支持快速增长的时间序列高基数数据,提供强大的极速查询性能,安装维护简单并兼容SQL语法,从而上手使用学习难度低,并且支持RestaApi数据摄取与查询接口,对系统应用服务开发提供良好支持,非常方便系统服务的集成开发与应用;QuestDB采用Java和C++ 从头开始构建,没有依赖项,零垃圾回收;

官方说明:

QuestDB 是一个专门研究 时间序列的开源列式数据库。它提供同类领先的摄取吞吐量和快速 SQL 查询,并且操作简单。QuestDB 有助于降低运营成本并克服摄取瓶颈,并且可以大大简化整体入口基础设施。凭借对 InfluxDB Line Protocol 和 PostgreSQL Wire Protocol 等摄取协议、第三方工具和语言客户端的广泛官方支持,可以快速启动。

QuestDB在默认原生版本(vanilla)配置下,在知名数据库测试机构benchANT的时序数据库排行榜 Time Series: DevOps 场景写入吞吐量、存储占用、查询响应、成本效益等多项性能表现的第二名(本文编写时间前)。

benchANT 是国际知名的数据库评测机构,以可靠、独立及透明的方法对各种数据库进行性能评测。 benchANT 榜单收录了常见的关系型数据库、NoSQL 数据库、NewSQL 数据库及时序数据库等,通过使用固定的测试负载、相同的测试机器来保证测试结果的公平性。

榜单链接:https://benchant.com/de/ranking/datenbank-ranking

主要功能

1.大规模数据写入:默认使用4个线程,每秒处理速度接近100W行,有极强吞吐性能

2.支持SQL分析:支持SQL语法,可以采用PostgreSQL协议线路客户端查询数据

3.支持数据分区:支持自动对数据按时间进行分区存储,如按小时、天、周、月、年等;

4.提供web控制台:安装后可以在自带的WEB控制台界面管理数据表与数据

5.支持多种数据访问方式:提供基于http的RestAPI,无需集成驱动包;同时支持Postgres协议、InfluxDB协议数据传输;降低集成开发难度;支持csv数据导入;

6.支持多种时间序列SQL扩展:支持按时间间隔、时间范围、窗口时间排序、多表关联序列查询等扩展用法

更多功能请关注官网文档

安装与使用

官网地址:QuestDB | High performance time series

官网文档:Introduction | QuestDB

官方下载:Download QuestDB | QuestDB

github:https://github.com/questdb/questdb

linux环境安装

官方安装指南:Quick start | QuestDB

测试服务器硬件环境: CPU(E5-2609 v3 @ 1.90GHz) *8核,内存*16G,硬盘 > 70G

#下载安装包
wget https://github.com/questdb/questdb/releases/download/7.3.7/questdb-7.3.7-rt-linux-amd64.tar.gz
#解压包
tar -zxvf questdb-7.3.7-rt-linux-amd64.tar.gz
#移动目录
mv questdb-7.3.7-rt-linux-amd64 /opt/questdb-7.3.7
#进入目录
cd /opt/questdb-7.3.7
#创建数据目录
mkdir -p data
#启动数据库
./bin/questdb.sh start -d ./data -t questdb
#查询数据库状态
./bin/questdb.sh status -d ./data
#关闭数据库
./bin/questdb.sh stop -d ./data -t questdb

注意:此处安装开源版本是以单机部署;只有企业版才支持集群、权限管理等功能;

访问WEB控制台

QuestDB提供了WebUI版的控制台客户端界面,可通过IP+端口在浏览器中访问,如:http://localhost:9000

启动后QuestDB会启动如下端口:

  • 9000:REST API和 Web 控制台
  • 9009:InfluxDB线路协议
  • 8812:Postgres 有线协议
  • 9003:最小健康服务器

注:相关端口与配置可通过 questdb-7.3.7/data/conf/server.conf 中进行更改;

基础使用

官方提供了几个场景类型的示例数据,如:天气、金融等示例数据集;

官方示例数据:https://github.com/questdb/sample-datasets

示例数据集

该数据集约160K行,为美国芝加哥气象站传感器数据;

示例数据:https://github.com/questdb/sample-datasets/tree/main/chicago_sensors

下载:https://github.com/questdb/sample-datasets/blob/main/chicago_sensors/chicago_weather_stations.csv

创建数据表

注意:questDb没有database数据库独立实例,默认为qdb,因此只需要建数据表即可;

CREATE TABLE IF NOT EXISTS chicago_weather_stations (
    MeasurementTimestamp TIMESTAMP,
    StationName SYMBOL,
    AirTemperature DOUBLE,
    WetBulbTemperature DOUBLE,
    Humidity INT,
    RainIntensity DOUBLE,
    IntervalRain DOUBLE,
    TotalRain DOUBLE,
    PrecipitationType INT,
    WindDirection INT,
    WindSpeed DOUBLE,
    MaximumWindSpeed DOUBLE,
    BarometricPressure DOUBLE,
    SolarRadiation INT,
    Heading INT,
    BatteryLife DOUBLE,
    MeasurementTimestampLabel STRING,
    MeasurementID STRING
) timestamp(MeasurementTimestamp) PARTITION BY MONTH WAL
DEDUP UPSERT KEYS(MeasurementTimestamp, StationName);

简要说明:MeasurementTimestamp为数据库引擎时序分区字段,此表需要基于MONTH(月)时间分区,必需要有timestamp字段以进行数据写入时自动表分区;

导入csv数据集

curl -F data=@chicago_water_sensors.csv "http://localhost:9000/imp?name=chicago_water_sensors"

SQL演示

官方文档提供了大量SQL使用说明,参阅:SQL execution order | QuestDB

在官方的建议里,一旦创建数据表后,尽可能不要改变表结构,因为大量数据分区存储后,会在变更表过程中造成较大的性能开销,和冷热数据处理;

本章描述几个常见SQL用法;

创建表

-- 创建表,IF NOT EXISTS 如果表不存在则创建;
-- PARTITION BY MONTH  表示按MONTH(月)分区存储;
-- WAL表示支持预写入内存再刷新到磁盘,用于数据并发写入;
CREATE TABLE IF NOT EXISTS test_demo (
id INT,
name STRING,
value STRING,
ts TIMESTAMP
)  TIMESTAMP(ts) PARTITION BY MONTH WAL;

增改查

-- 插入
INSERT INTO test_demo (id, name, value ,ts) VALUES(100,'test','abc', to_timestamp('2023-12-30T00:00:00', 'yyyy-MM-ddTHH:mm:ss'));
INSERT INTO test_demo (id, name, value ,ts) VALUES(101,'test','abc', now());
-- 更改(在questdb表中所有数据落库后,尽量不要update改变记录,目前不提供表记录删除,但支持清空表)
update test_demo set id=102 where ts = to_timestamp('2023-12-30T00:00:00', 'yyyy-MM-ddTHH:mm:ss')
-- 查询
select * from test_demo

表删除

-- 删除表指定月分区
ALTER TABLE test_demo DROP PARTITION LIST '2013-12';
-- 清空表(截断)
TRUNCATE TABLE test_demo
-- 删除表
DROP TABLE test_demo

show用法

-- 显示所有表
SHOW TABLES;
-- 显示指定表字段
SHOW COLUMNS FROM test_demo;
-- 显示指定表分区
SHOW PARTITIONS FROM test_demo;
-- 显示参数
SHOW PARAMETERS;
-- 显示版本
SHOW SERVER_VERSION;

开发测试

本示例项目依赖Maven + JDK17,此处不再单独描述创建项目过程,请自行准备运行环境与工程;

开发环境硬件:CPU (I5-7500 3.40GHz) * 4核,内存 * 24G

RestAPI请求

QuestDB支持REST API开发模式,可基于标准HTTP功能响应请求,因此可通过http客户端访问。

java示例

package com.example.questdb.restapi;

import org.junit.jupiter.api.Test;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
/**
 * @Description 通过questDb的restApi接口执行sql脚本
 * @Version V1.0
 */
public class HttpTest {
    //查询
    @Test
    public void query() throws Exception {
        java.net.http.HttpClient client = java.net.http.HttpClient.newHttpClient();
        String url = "http://192.168.1.3:9000/exec?";
        //URL 编码的查询文本
        String queryStr = "query=" + URLEncoder.encode("select * from test_demo", StandardCharsets.UTF_8);
        //计算行数并返回该值
        String countStr = "&count=true";
        //返回前多少行
        String limitStr = "&limit=2";
        //拼装
        url = url + queryStr + countStr + limitStr;
        System.out.println("请求:" + url);
        HttpRequest request = HttpRequest.newBuilder()
                .uri(new URI(url))
                .header("Content-Type", "application/x-www-form-urlencoded;charset=utf-8")
                .header("Statement-Timeout", "5000")
                .GET()
                .build();
        HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
        String body = response.body();
        System.out.println("响应:" + body);
    }
    //插入
    @Test
    public void insert() throws Exception {
        java.net.http.HttpClient client = java.net.http.HttpClient.newHttpClient();
        String url = "http://192.168.1.3:9000/exec?";
        //URL 编码的查询文本
        String queryStr = "query=" + URLEncoder.encode("INSERT INTO test_demo (id, name, value ,ts) VALUES(104,'test','abc', now())", StandardCharsets.UTF_8);
        url = url + queryStr;
        System.out.println("请求:" + url);
        HttpRequest request = HttpRequest.newBuilder()
                .uri(new URI(url))
                .header("Content-Type", "application/x-www-form-urlencoded;charset=utf-8")
                .header("Statement-Timeout", "5000")
                .GET()
                .build();
        HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
        String body = response.body();
        System.out.println("响应:" + body);
    }
    //更改
    @Test
    public void update() throws Exception {
        java.net.http.HttpClient client = java.net.http.HttpClient.newHttpClient();
        String url = "http://192.168.1.3:9000/exec?";
        //URL 编码的查询文本
        String queryStr = "query=" + URLEncoder.encode("UPDATE test_demo SET name='test4' WHERE id=104", StandardCharsets.UTF_8);
        url = url + queryStr;
        System.out.println("请求:" + url);
        HttpRequest request = HttpRequest.newBuilder()
                .uri(new URI(url))
                .header("Content-Type", "application/x-www-form-urlencoded;charset=utf-8")
                .header("Statement-Timeout", "5000")
                .GET()
                .build();
        HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
        String body = response.body();
        System.out.println("响应:" + body);
    }
}

数据表记录

InfluxDB协议

QuestDB TCP接收器使用 InfluxDB 线路协议作为序列化和传输格式。另一方面,InfluxDB使用HTTP协议作为传输,QuestDB主要使用InfluxDB线路协议作为数据序列化格式。因此,现有的InfluxDB客户端库将无法与QuestDB一起使用,必需使用QuestDB官方java驱动包。

pom导入包

<dependency>
    <groupId>org.questdb</groupId>
    <artifactId>questdb</artifactId>
    <version>7.3.7</version>
</dependency>

java测试示例

package com.example.questdb.influxdb;

import io.questdb.client.Sender;
import org.junit.jupiter.api.Test;
import java.time.temporal.ChronoUnit;
/**
 * @Description QuestDB可基于InfluxDB协议通过tcp高性能提交数据
 * https://questdb.io/docs/reference/clients/java_ilp/
 * @Version V1.0
 */
public class InfluxDBTest {
    //仅支持数据插入,不支持查询与更新
    @Test
    public void insert(){
        try (Sender sender = Sender.builder().address("192.168.1.3:9009").build()) {
            long times = System.currentTimeMillis();
            //如果表不存在,则自动创建,并且 InfluxDB Line Protocol协议发送数据是基于PARTITION BY DAY分区
            sender.table("test_demo")
                    .longColumn("id", 103)
                    .stringColumn("name", "influxDBTest")
                    .stringColumn("value", "abc")
                    .at(times, ChronoUnit.MILLIS);
            sender.close();
        }
    }
}

数据表记录

Postgre协议

QuestDB支持Postgres传输协议。因此,QuestDB能够运行大多数Postgres查询。这意味着您可以将您最喜欢的Postgres客户端或驱动程序与QuestDB结合使用,无需额外成本。

注意:Postgres使用的存储模型与QuestDB使用的存储模型根本不同,因此Postgres的某些功能不适用于QuestDB。

pom导入包

<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <version>42.6.0</version>
</dependency>

java示例

package com.example.questdb.postgres;

import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import java.sql.*;
import java.util.Properties;
/**
 * @Description  QuestDB支持Postgres协议运行SQL脚本
 * https://questdb.io/docs/reference/api/postgres/
 * @Version V1.0
 */
public class PostgresTest {
    static Properties properties = new Properties();
    static Connection connection;
    //初始化连接
    @BeforeAll
    public static void init() throws Exception {
        //默认admin
        properties.setProperty("user", "admin");
        //默认quest
        properties.setProperty("password", "quest");
        //禁用ssl,不支持ssl协议
        properties.setProperty("sslmode", "disable");
        //默认数据库qdb
        connection = DriverManager.getConnection("jdbc:postgresql://192.168.1.3:8812/qdb", properties);
    }
    //插入数据
    @Test
    public void insert() throws Exception {
        connection.setAutoCommit(false);
        try (PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO test_demo (id, name, value ,ts) VALUES (?, ?, ?, ?)")) {
            preparedStatement.setInt(1, 104);
            preparedStatement.setString(2, "postgresTest");
            preparedStatement.setString(3, "abc");
            preparedStatement.setTimestamp(4, new Timestamp(System.currentTimeMillis()));
            preparedStatement.execute();
        }
        connection.commit();
        connection.close();
    }
    //查询数据
    @Test
    public void select() throws Exception {
        try (PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM test_demo")) {
            try (ResultSet rs = preparedStatement.executeQuery()) {
                while (rs.next()) {
                    System.out.println(rs.getObject(1));
                    System.out.println(rs.getObject(2));
                    System.out.println(rs.getObject(3));
                    System.out.println(rs.getObject(4));
                    System.out.println("------------------------------------");
                }
            }
            connection.close();
        }
    }
    //更新数据
    @Test
    public void update() throws Exception {
        try (PreparedStatement preparedStatement = connection.prepareStatement("UPDATE test_demo SET name='postgresTest5' WHERE id=104")) {
            preparedStatement.execute();
        }
        connection.close();
    }
}

数据表记录

性能测试

QuestDB可以基于InfluxDB协议实现大批量数据高吞吐插入;

本示例通模拟过将约3亿条各城市10年的气象数据,通过InfluxDB协议快速插入到QuestDB数据表中,在基于此规模数据的存储表上进行SQL查询与聚合性能测试;

创建数据表

CREATE TABLE IF NOT EXISTS weather_behavior (
city SYMBOL,
site STRING,
air INT,
temperature DOUBLE,
humidity INT,
windLevel INT,
createTime TIMESTAMP
)  TIMESTAMP(createTime) PARTITION BY MONTH WAL;

createTime为时间序列字段,基于时间序列创建月度分区存储数据,并开启WAL预写入,提升多线程下并发请求能力;

InfluxDB写入示例

使用java开发,基于influxDB协议将数据写入questDB表,开发前请在pom.xml中配置questdb-7.3.7包引入;

java示例

package com.example.questdb;

import io.questdb.client.Sender;
import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import java.math.BigDecimal;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Calendar;
import java.util.Date;
/**
 * @Description 向questdb发送模拟各省市的天气采集数据
 * @Version V1.0
 */
public class WeatherToQuestdbExample {
    static String cityNames = "海门,鄂尔多斯,招远,舟山,齐齐哈尔,盐城,赤峰,青岛,乳山,金昌,泉州,莱西,日照,胶南,南通,拉萨," +
            "云浮,梅州,文登,上海,攀枝花,威海,承德,厦门,汕尾,潮州,丹东,太仓,曲靖,烟台,福州,瓦房店,即墨,抚顺,玉溪,张家口," +
            "阳泉,莱州,湖州,汕头,昆山,宁波,湛江,揭阳,荣成,连云港,葫芦岛,常熟,东莞,河源,淮安,泰州,南宁,营口,惠州,江阴,蓬莱," +
            "韶关,嘉峪关,广州,延安,太原,清远,中山,昆明,寿光,盘锦,长治,深圳,珠海,宿迁,咸阳,铜川,平度,佛山,海口,江门,章丘," +
            "肇庆,大连,临汾,吴江,石嘴山,沈阳,苏州,茂名,嘉兴,长春,胶州,银川,张家港,三门峡,锦州,南昌,柳州,三亚,自贡,吉林," +
            "阳江,泸州,西宁,宜宾,呼和浩特,成都,大同,镇江,桂林,张家界,宜兴,北海,西安,金坛,东营,牡丹江,遵义,绍兴,扬州,常州," +
            "潍坊,重庆,台州,南京,滨州,贵阳,无锡,本溪,克拉玛依,渭南,马鞍山,宝鸡,焦作,句容,北京,徐州,衡水,包头,绵阳,乌鲁木齐," +
            "枣庄,杭州,淄博,鞍山,溧阳,库尔勒,安阳,开封,济南,德阳,温州,九江,邯郸,临安,兰州,沧州,临沂,南充,天津,富阳,泰安," +
            "诸暨,郑州,哈尔滨,聊城,芜湖,唐山,平顶山,邢台,德州,济宁,荆州,宜昌,义乌,丽水,洛阳,秦皇岛,株洲,石家庄,莱芜,常德," +
            "保定,湘潭,金华,岳阳,长沙,衢州,廊坊,菏泽,合肥,武汉,大庆";

    /**
     * 主程入口
     * questdb参考:https://questdb.io/docs/reference/clients/java_ilp/
     * 注意:客户端仅支持 TCP。它不支持UDP作为传输。
     * @param args
     */
    public static void main(String[] args) throws Exception {
        //生产者发送消息
        Sender sender = Sender.builder().address("192.168.110.36:9009").build();
        String table = "weather_behavior";
        String [] citys = StringUtils.split(cityNames, ",");
        long i = 1;
        System.out.println("开始执行数据导入,time:" + new Date());
        //从指定时间开始生成10年的假数据,10年约315532800秒,模拟每秒产生1条数据,平均每天产生86400条,一年约3153万条,起始时间:2014-01-01 00:00:00,结束时间:2024-01-01 00:00:00
        Date dateTime = DateUtils.parseDate("2014-01-01 00:00:00", "yyyy-MM-dd HH:mm:ss");
        java.util.Calendar calendar = Calendar.getInstance();
        calendar.setTime(dateTime);
        while(true){
            String city  = citys[RandomUtils.nextInt(0, citys.length)] ;
            String site = String.format("%s-%s",city,RandomUtils.nextInt(0, 100));
            int air = RandomUtils.nextInt(10, 200);
            double temperature = RandomUtils.nextDouble(0.0, 40.00);
            BigDecimal temperatureBig = new BigDecimal(temperature);
            temperature = temperatureBig.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
            int humidity = 0;
            int windLevel = 0;
            if (temperature<0){
                humidity = RandomUtils.nextInt(0, 5);
                windLevel = RandomUtils.nextInt(0, 6);
            }else if (temperature>0 && temperature<15){
                humidity = RandomUtils.nextInt(5, 25);
                windLevel = RandomUtils.nextInt(0, 8);
            } else {
                humidity = RandomUtils.nextInt(25, 75);
                windLevel = RandomUtils.nextInt(0, 10);
            }
            //每循环递增1秒
            calendar.add(Calendar.SECOND, 1);

            //向questdb发送数据:地市,站点,PM2.5,温度,湿度,风速,采集时间
            sender.table(table)
                    //symbol索引字段
                    .symbol("city", city)
                    //string UTF-16 编码字符的长度前缀序列,其长度存储为带符号的 32 位整数
                    .stringColumn("site", site)
                    //long 有符号整数,pm2.5 = 0~250
                    .longColumn("air", air)
                    //double 双精度 IEEE 754 浮点值。
                    .doubleColumn("temperature", temperature)
                    .longColumn("humidity", humidity)
                    .longColumn("windLevel", windLevel)
                    //设置分区数据时间戳取系统纳秒
                    //.at(System.nanoTime(), ChronoUnit.NANOS);
                    //指定业务时间
                    .at(calendar.getTimeInMillis(), ChronoUnit.MILLIS);

            //每5000行提交一次
            if (i % 5000 == 0) {
                sender.flush();
            }
            //当达到10年总秒数,则退出
            if (i >= 315532800) {
                break;
            }
            i ++ ;
        }
        sender.close();
        System.out.println("批量数据导入成功,time:" + new Date());
    }
}

写入性能测试

IDEA控制台打印日志,单线程循环插入3亿多条记录,

每5000条批量提交一次,累计用时:12分52秒( 772s);平均每秒:315532800记录 / 772s用时 = 约408721记录(s);

略 ... 
2024-01-04T11:50:45.215547Z I i.q.c.l.t.PlainTcpLineChannel Send buffer size change from 65536 to 131072
开始执行数据导入,time:Thu Jan 04 19:50:47 CST 2024
批量数据导入成功,time:Thu Jan 04 20:03:39 CST 2024
略 ... 

从统计数据上来看,通过InfluxDB协议写入QuestDB性能极强,如果客户端是多线程写入,相信性能还会有一定提升;

在写入QuestDB所在服务器(虚拟机)为8核 CPU+16G内存,在大量写入过程中,CUP使用率较高均值在70%左右,内存使用比较少并且无较大波动,Java进程大约在1G上下(QuestDB采用Java和C++开发);

未插入数据前服务器磁盘空间已用为16G;

数据入表后磁盘空间已使用了51G,3亿数据增长了35G,推测questDB数据存储文件未做压缩,因此个人觉得占用磁盘空间偏大;

SQL查询与聚合

统计全表数量,查询已入库记录315532800条(3亿)多条

select count(1) from weather_behavior

按月建立分区后,查询分区信息,总共121个分区

SHOW PARTITIONS FROM weather_behavior;

查询pm大于150的城市上报次数

select city,count(1) from weather_behavior where air>=150 group by city limit 100;

查询2022年全年pm大于150的城市上报次数(前100条)

select city,count(1) as upNum from weather_behavior 
where air>=150 and 
createTime >= to_timestamp('2022-12-31T16:00:00', 'yyyy-MM-ddTHH:mm:ss') and createTime <= to_timestamp('2023-12-31T15:59:59', 'yyyy-MM-ddTHH:mm:ss')
group by city order by upNum desc limit 100;

查询2022年全年上报最多top100的城市(前100条)

select city,count(1) as upNum from weather_behavior 
where createTime >= to_timestamp('2022-12-31T16:00:00', 'yyyy-MM-ddTHH:mm:ss') and createTime <= to_timestamp('2023-12-31T15:59:59', 'yyyy-MM-ddTHH:mm:ss')
group by city order by upNum desc limit 100;

注:因该命中了questDB数据缓存分区,所以重复执行对2022年数据做聚合统计时,速度极快;

查询2022年全年pm最大值top100的城市(前100条)

select city,max(air) as aitNum from weather_behavior 
where createTime >= to_timestamp('2022-12-31T16:00:00', 'yyyy-MM-ddTHH:mm:ss') and createTime <= to_timestamp('2023-12-31T15:59:59', 'yyyy-MM-ddTHH:mm:ss')
group by city order by aitNum desc limit 10;

按2022年全年pm排名查询topN条记录,条件:每城市只取站点排名第一(前100条)

select * from (
select city, site,maxnum,row_number() OVER (partition by city order by maxnum desc) as ranking from (
select city, site, max(air) as maxnum from weather_behavior 
where createTime >= to_timestamp('2022-12-31T16:00:00', 'yyyy-MM-ddTHH:mm:ss') and createTime <= to_timestamp('2023-12-31T15:59:59', 'yyyy-MM-ddTHH:mm:ss')
group by city,site 
) tt ) ttt where ranking<2 order by city limit 100

按2022年全年计算指定城市每个月的PM2.5的最大值,最小值,平均值,总值,总记录数,月度排名,以及每个月递增的平均总记录数、月递增的平均值

select month,city,maxAir,minAir,avgAir,sumAir,airCount, 
row_number() OVER (partition by city order by avgAir desc) as ranking,
avg(airCount) over (PARTITION BY city ORDER BY month) moving_air_count,
avg(avgAir) over (PARTITION BY city ORDER BY month) moving_avg_air
from (select 
city,max(air) as maxAir, min(air) as minAir,avg(air) as avgAir, sum(air) as sumAir, count(1) as airCount, date_trunc('month',  dateadd('h', 8, createTime)) month 
from weather_behavior where city='济南' and 
createTime >= to_timestamp('2022-12-31T16:00:00', 'yyyy-MM-ddTHH:mm:ss') and createTime <= to_timestamp('2023-12-31T15:59:59', 'yyyy-MM-ddTHH:mm:ss')
group by city,month
) tt order by month desc

查询城市济南10年中每年的PM2.5的最大值,最小值,平均值,总值,总记录数(查全表3亿多条)

select 
city,max(air) as maxAir, min(air) as minAir,avg(air) as avgAir, sum(air) as sumAir, count(1) as airCount, date_trunc('year', dateadd('h', 8, createTime)) as year 
from weather_behavior where city='济南' 
group by city,year

总结

     QuestDB开箱即用,没有什么安装配置难度,官方文档编写详细易懂(纯英文,用插件翻译即可),学习与使用曲线低,很容易上手;QuestDB在性能方面有极强的优势,吞吐量大,数据写入速度快,基于InfuluxDB线路协议写入性能最佳,默认设置下可以达到每秒约40W条(本示例硬件条件下,不同环境可能会存在差异),PostgreSQL协议则最贴合项目开发过程中使用SQL语法,基于http请求则无需额外驱动包集成从而简化使用成本,因此可以根据业务需要与性能选择不同的技术方案;在基于时间维度建立分区后,存储的大量数据按时间序例,拆分存储在不同分区内,支持按小时、天、周、月、年建立分区,数据写入过程中按分区时间字段自动存储在时间维度分区中;在大规模数据下基于时间序例条件查询数据,查询速度快,如:在单表约3亿数据存储(121个月分区)规模上,按某一年度范围查询基本上在1秒以内响应,全表搜索并进行复杂聚合统计约在10秒内响应;因此在大规模存储数据下,相对于传统事务型数据库来说,有着跨越式的性能差距;可应用在历史数据存储、高基数时间序列等场景,如:监控指标数据、日志数据、历史订单数据、用户行为明细数据、物联网数据、游戏上报数据、业务备份数据等,适用于写入到表里后不在频繁变更以及基于时间序例追溯的数据;

       QuestDB在使用过程中,也存一些限制与不足,如:权限管理、支持对象存储、高可用集群等部份功能需要企业授权版才能支持,社区开源免费版不提供以上功能,因此使用者需要根据实际情况选择QuestDB版本;根据个人的体验,单实例运行性能是最好的,无需考虑集群过程中的数据交付与节点通讯等性能开销与维护事项,在中小企业中,如果不涉及到大量高并发查询使用(每次查询会使用较多硬件资源,高并发下会超成资源竞争,降低查询性能),在足够大的磁盘空间与合理的内存、CPU、分区等软硬件条件下,社区开源版已足够支持TB级的数据交付使用;QuestDB在对SQL的兼容性上已经能覆盖大部份标准查询,但可能还存在部份语法不能满足(使用过程中有遇到,未做详细记录),基本SQL开发是足够使用的;QuestDB不支持表行级数据delete删除操作,QuestDB则采取删除分区的方式来删除数据,不适用数据细颗粒度管理,官方建议数据一旦入库尽量不在做更新;

     更多使用体验与学习,请参考官方网站资源;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/332273.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

storm统计服务开启zookeeper、kafka 、Storm(sasl认证)

部署storm统计服务开启zookeeper、kafka 、Storm&#xff08;sasl认证&#xff09; 当前测试验证结果&#xff1a; 单独配置zookeeper 支持acl 设置用户和密码&#xff0c;在storm不修改代码情况下和kafka支持当kafka 开启ACL时&#xff0c;storm 和ccod模块不清楚配置用户和密…

【分享】MathWorks中国汽车年会:“软件定义汽车”

从软件赋能到软件定义&#xff0c;汽车行业不仅需要解决诸如错误发现滞后带来的高昂代价、功能融合所需的跨学科知识、功能安全与实施成本之间的权衡等老问题&#xff0c;也面临着新的挑战&#xff1a;软件复杂度的不断提升、利用数据驱动创造价值、人工智能的引入和实现、数字…

PyCharm 快捷键(Ctrl + R)正则表达式批量替换

目录 一、使用快捷键CtrlR&#xff0c;打开替换界面 二、输入替换格式 三、点击全部替换 一、使用快捷键CtrlR&#xff0c;打开替换界面 二、输入替换格式 在第一个框输入 (.*): (.*) 第二个框输入 $1:$2, 三、点击全部替换

【JVM】并发的可达性分析详细解释

​ &#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a;JVM ⛳️ 功不唐捐&#xff0c;玉汝于成 ​ 目录 前言 正文 可达性分析的基本原理&#xff1a; 根集合&#xff08;Root Set&#xff09;&#xff1a; 对象引用关系&#xff1a; 标记…

Java导出Excel并合并单元格

需求&#xff1a;需要在导出excel时合并指定的单元格 ruoyi excel 项目基于若伊框架二次开发&#xff0c;本着能用现成的就不自己写的原则&#xff0c;先是尝试了Excel注解中needMerge属性 /*** 是否需要纵向合并单元格,应对需求:含有list集合单元格)*/public boolean needMer…

记一次 .NET某道闸收费系统 内存溢出分析

一&#xff1a;背景 1. 讲故事 前些天有位朋友找到我&#xff0c;说他的程序几天内存就要爆一次&#xff0c;不知道咋回事&#xff0c;找不出原因&#xff0c;让我帮忙看一下&#xff0c;这种问题分析dump是最简单粗暴了&#xff0c;拿到dump后接下来就是一顿分析。 二&…

11.1 pcl_ros的点云学习

本文是看了两个博主的内容&#xff0c;整理在这里是为了以后用时方便查找&#xff0c;更容易理解。引用的博文路径如下&#xff08;本人也是刚开始看PCL的运用&#xff0c;本文是完全抄下面博主的内容&#xff0c;觉得这位博主写的很详细很清楚&#xff0c;并且自己运行了一遍有…

Java17新特性详解含示例代码(值得珍藏)

1. 概述 Java 17 是 Java 开发工具包&#xff08;JDK&#xff09;的一个重要版本&#xff0c;它带来了一系列的新特性和改进&#xff0c;以进一步增强 Java 语言的功能和性能。以下是 Java 17 中的一些主要新特性及其详细说明。 2. 新特性详解 JEP 356: Enhanced Pseudo-Ran…

uniapp uni.chooseLocation调用走失败那里,错误码:112

问题&#xff1a;我配置了百度上所有能配置的&#xff0c;一直调用不成功&#xff0c;如下图配置的 1:第一个 配置 代码&#xff1a; "permission": {"scope.userLocation": {"desc": "你的位置信息将用于小程序位置接口的效果展示"}…

openpose之使用摄像头检测并输出到json文件

编程如画&#xff0c;我是panda&#xff01; 前言 之前给大家分享了如何搭建openpose环境&#xff0c;并进行了测试案例&#xff0c;但是如果要使用摄像头的话&#xff0c;还需要修改一下运行文件&#xff0c;并且这次会教大家如何输出到json文件 。 如果环境还没有搭建好&am…

Unix时间戳

时间戳&#xff0c;相信很多相关专业的人&#xff0c;计算机软件电子等等都会听过。由于最早是由Unix系统使用所以又叫Unix时间戳。 Unix 时间戳&#xff08;Unix Timestamp&#xff09;定义为从UTC&#xff08;世界协调时&#xff09;/GMT&#xff08;格林尼治时&#xff09;…

iPhone解锁工具---AnyMP4 iPhone Unlocker 中文

AnyMP4 iPhone Unlocker是一款功能强大的iPhone解锁软件&#xff0c;旨在帮助用户轻松解锁iPhone&#xff0c;从而在电脑上进行数据备份、传输和编辑。该软件支持多种iPhone型号&#xff0c;包括最新的iPhone 14系列&#xff0c;并支持多种解锁模式&#xff0c;如屏幕密码解锁、…

【Docker】安装 Nacos容器并根据Nginx实现负载均衡

&#x1f389;&#x1f389;欢迎来到我的CSDN主页&#xff01;&#x1f389;&#x1f389; &#x1f3c5;我是Java方文山&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f31f;推荐给大家我的专栏《Docker实战》。&#x1f3af;&#x1f3af; &…

Go后端开发 -- 反射reflect 结构体标签

Go后端开发 – 反射reflect && 结构体标签 文章目录 Go后端开发 -- 反射reflect && 结构体标签一、反射reflect1.编程语言中反射的概念2.interface 和反射3.变量内置的pair结构4.reflect的基本功能TypeOf和ValueOf5.从relfect.Value中获取接口interface的信息6…

2018年认证杯SPSSPRO杯数学建模D题(第二阶段)投篮的最佳出手点全过程文档及程序

2018年认证杯SPSSPRO杯数学建模 D题 投篮的最佳出手点 原题再现&#xff1a; 影响投篮命中率的因素不仅仅有出手角度、球感、出手速度&#xff0c;还有出手点的选择。规范的投篮动作包含两膝微屈、重心落在两脚掌上、下肢蹬地发力、身体随之向前上方伸展、同时抬肘向投篮方向…

SpringBoot:前端提交数据,服务端无法获取数据

http://www.xxx.com?phone111111111111&code1332 上述访问传值方式为键值对方式&#xff0c;服务端springmvc获取 >> // 在HttpServlet实现类的doGet、doPost方法中获取前端传来的值 doGet(ServerHttpRequest request){String phone request.getParameter("…

Codeforces Round 767 (Div. 1) D2. Game on Sum (Hard Version)(博弈 期望 dp 贡献)

题目 t(t<1e5)组样例&#xff0c;每次给定n,m,k(m<n<1e6&#xff0c;0<k<1e97) 有一个游戏&#xff0c;持续n轮&#xff0c;每轮Alice先选一个[0,k]的实数&#xff0c; Bob决定从总分里加上这个值还是减去这个值 特别地&#xff0c;n轮里&#xff0c;Bob选择…

Unity Mirror VR联机开发 实战篇(二)

一、迁移示例中的联机物体 1、将MirrorExamplesVR工程中的部分文件夹复制到自己的工程中。 1、打开MirrorExamplesVR中的 SceneVR-Common场景。 2、将场景中没用的东西都删掉&#xff0c;只留下面这些&#xff0c;新建一个空物体XR Mirror&#xff0c;将所有剩下的物体拖成XR …

Elastic 8.12:AI Assistant for Observability 正式发布,更新至 Apache Lucene 9.9

作者&#xff1a;来自 Elastic Brian Bergholm 今天&#xff0c;我们很高兴地宣布 Elastic 8.12 全面上市。 有哪些新的功能&#xff1f; 8.12 版本的两个最重要的组成部分包括 Elastic AI Assistant for Observability 的 正式发布版 和 Apache Lucene 9.9 的更新&#xff08…

网络安全B模块(笔记详解)- SQL注入

简单sql注入 1.使用渗透机场景kali中工具扫描服务器场景,将apache的端口号和版本号作为Flag提交(格式:端口号_版本号) Flag:8081_7.5 2.使用渗透机场景windows7访问服务器场景SQL网站,并将网站中概述页面中的Flag提交; Flag:sql_is_good 3.使用渗透机场景windows7访问…