用sqlite制作对局记录管理

1. sqlite简介

sqlite是一款非常轻便小巧的数据库,以C语言开发,已流行了数十年,据说是世界上部署最多的数据库。为什么是部署最多的呢?因为它根本不需要数据库服务器,且可以在任意设备、任意操作系统上部署。因此,很多应用程序,无论是Windows的、Linux的、Apple的、甚至嵌入式的,它们都内嵌了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文件

步骤如下:

  1. 运行 sqlite3 .\go.db
    这是打开 go.db 文件记录的数据库。另一种运行方式如下:
sqlite3
.open C:\users\Finix\sqlite3\go.db

值得一提的是,.help命令可以看到各个帮助选项。

  1. 创建临时表和最终表
    这里将上面的语句拷贝过来,以作为一份完整的步骤。
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
);
  1. 导入已准备好的csv文件
.mode csv
.import ./records/20231216.csv tmp_go_records
.import ./records/20231215.csv tmp_go_records

以上是导入了2天的csv数据。注意,以上语句不能加分号,否则无效但又不会报错。

  1. 运行sql将临时表导入最终表
INSERT INTO go_records (date, player, result)
SELECT date,player,result FROM tmp_go_records;
  1. 备份数据库

如果不太放心一个数据库文件,可以再备份一个。命令如下:

.save go.db.bak
  1. 退出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 player, result, cnt, sum(cnt) over(PARTITION BY player) as total_cnt, 
    concat(round((cnt*100.0/(sum(cnt) over(PARTITION BY player))), 2), '%') as rate
from (
    select count(*) as cnt, player, result from go_records where player=88667 group by player, result
) src 
order by result desc;
  • 对每一位棋手的胜率
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 percent
    from (
        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)

你可能感兴趣的:(数据库,sqlite,数据库)