1. sqlite简介
sqlite是一款非常轻便小巧的数据库,以C语言开发,已流行了数十年,据说是世界上部署最多的数据库。为什么是部署最多的呢?因为它根本不需要数据库服务器,且可以在任意设备、任意操作系统上部署。因此,很多应用程序,无论是Windows的、Linux的、Mac的、甚至嵌入式的,它们都内嵌了sqlite数据库。可想而知,就部署数量而言,确实没什么数据库可以和它竞争。为什么它可以做到在任意设备任意操作系统上部署呢?一是因为它的一个数据库就是一个单一的文件;二是因为它的数据库程序是纯C写的,只要编译好就可以运行在这些地方,并且据说只有一个C文件。
- sqlite的官网:https://www.sqlite.org/index.html
- sqlite手册网站:https://www.sqlitetutorial.net/
2. sqlite的安装
sqlite的安装特别简单。如果不想从源码编译的话,直接下载二进制文件即可运行。
sqlite下载页面:https://www.sqlite.org/download.html
本文以Windows为例,因此下载 “Precompiled Binaries for Windows” 这下面的。
这下面有 sqlite-dll-win-x64-3440200.zip (1.24MB) 和 sqlite-tools-win-x64-3440200.zip (4.71MB) 这2个。我们只要下载后者即可。前面那个dll的可能是应用程序调用sqlite的API用的,和本次实验无关。
下载完并解压之后,我们在这个解压好的文件夹下可以看到 3 个exe文件,而本次我们只需用到 sqlite3.exe.
3. 用sqlite制作对局记录管理
3.1 问题描述
有一些对局记录,可以利用sqlite进行管理。这些对局记录很简单,只包含日期、对手ID、结果这三个要素。而结果可以用1、-1、0来分别表示胜、负、平。
这些对局记录已写入csv文件,样例 20231216.csv 如下:
2023-12-16,57793,1
2023-12-16,41864,1
2023-12-16,41864,-1
3.2 表设计
根据以上所描述的对局记录的特点,很容易地设计表 go_records 如下:
CREATE TABLE go_records
(id INTEGER PRIMARY KEY AUTOINCREMENT,date DATE,player INT,result INT
);
这里使用自增id是为了避免将来在删除某条记录时,因存在其他三要素完全一致的记录而造成误删。
貌似设计好了,但这里有一个问题:将来我们可以利用 sqlite 的.import
命令来导入csv文件时,由于自增id的存在就会引起导入失败,因为csv文件中没有自增id这一列。
怎么处理呢?
笔者不是数据库专家,没有想到特别好的办法。想到的一个办法是:再创一张临时表;先将csv导入到临时表,再将临时表导入到上面的 go_records 表中。临时表不需要id,而临时表导入到最终表的过程中,因使用 INSERT INTO 语句,从而可以实现自创并自增id.
临时表的设计如下:
CREATE TABLE tmp_go_records
(date DATE,player INT,result INT
);
而导入完csv文件之后,将临时表再导入到 go_records 表中的语句如下:
INSERT INTO go_records (date, player, result)
SELECT date,player,result FROM tmp_go_records;
3.3 运行sqlite并导入csv文件
步骤如下:
- 运行
sqlite3 .\go.db
这是打开 go.db 文件记录的数据库。另一种运行方式如下:
sqlite3
.open C:\users\Finix\sqlite3\go.db
值得一提的是,.help
命令可以看到各个帮助选项。
- 创建临时表和最终表
这里将上面的语句拷贝过来,以作为一份完整的步骤。
DROP TABLE IF EXISTS go_records;
DROP TABLE IF EXISTS tmp_go_records;CREATE TABLE go_records
(id INTEGER PRIMARY KEY AUTOINCREMENT,date DATE,player INT,result INT
);CREATE TABLE tmp_go_records
(date DATE,player INT,result INT
);
- 导入已准备好的csv文件
.mode csv
.import ./records/20231216.csv tmp_go_records
.import ./records/20231215.csv tmp_go_records
以上是导入了2天的csv数据。注意,以上语句不能加分号,否则无效但又不会报错。
- 运行sql将临时表导入最终表
INSERT INTO go_records (date, player, result)
SELECT date,player,result FROM tmp_go_records;
- 备份数据库
如果不太放心一个数据库文件,可以再备份一个。命令如下:
.save go.db.bak
- 退出sqlite
.exit
3.4 一些有趣的SQL
这么一张只有4列的表(其中一列还是自增id)能有什么有趣的SQL呢?
- 与多少棋手交过手
select count(distinct player) from go_records;
- 某天下了多少盘
select count(*) from go_records where date in ('2023-12-16');
- 总胜率
# 总胜率
SELECT result,cnt, sum(cnt) over() as total_cnt,concat(round((cnt*100.0/sum(cnt) over()), 2), '%') as rate
FROM (SELECT result, count(*) AS cnt FROM go_records GROUP BY result
) src;
- 对特定棋手的胜率
select concat(round((select (select count(*) as cnt from go_records where player=40040 and result>0)*100.0/ (select count(*) as cnt from go_records where player=40040) as rate), 2), '%'
);
- 对每一位棋手的胜率
select * from (select player, result, cnt, sum(cnt) over(PARTITION BY player) as total_cnt, round((cnt*100.0/(sum(cnt) over(PARTITION BY player))), 2) rate,concat(round((cnt*100.0/(sum(cnt) over(PARTITION BY player))), 2), '%') as percentfrom (select player, result, count(*) as cnt from go_records group by player, result) src
) final
where result > 0
order by final.rate desc;
- 同一棋手出现在不同的2天
select distinct src.player from ((select player from go_records where date='2023-12-15') t1 inner join (select player from go_records where date='2023-12-16') t2 on t1.player=t2.player
) src;
关于这最后一个问题,如果是打印出同一棋手出现在不同的多天,则按以上方法需写多个inner join
,这显然是不能扩展和无法接受的。笔者一时之间还没有想出特别好的SQL的解决方案,留待以后的思考吧。
(END)