Python 具有重复值的Sqlite列

Python 具有重复值的Sqlite列,python,database,sqlite,duplicates,database-performance,Python,Database,Sqlite,Duplicates,Database Performance,假设SQLite DB的列a非常重复,始终有相同的4个值。稍后可能会出现其他值,但不同的值将少于1000个 VALUES=[“你好,世界”,“多次存储这个str真是太遗憾了”,“再见”,“abc”] 导入sqlite3,随机 db=sqlite3.connect('repetitive1.db') execute(“如果不存在数据,则创建表(id整数主键,一个文本);”) 对于范围(1000*1000)内的i: db.execute(“插入数据(a)值(?),(random.choice(值),

假设SQLite DB的列
a
非常重复,始终有相同的4个值。稍后可能会出现其他值,但不同的值将少于1000个

VALUES=[“你好,世界”,“多次存储这个str真是太遗憾了”,“再见”,“abc”]
导入sqlite3,随机
db=sqlite3.connect('repetitive1.db')
execute(“如果不存在数据,则创建表(id整数主键,一个文本);”)
对于范围(1000*1000)内的i:
db.execute(“插入数据(a)值(?),(random.choice(值),)
db.commit()
在这里,对于一百万个项目,DB的大小是24MB,即平均24字节

多次重新存储所有字符串有点遗憾,因为它总是一次又一次地使用相同的值。当然,解决方案是对重复值使用ID=0、1、2、3(以后最多1000个),并且只存储整数ID:

db = sqlite3.connect('repetitive2.db')
db.execute("CREATE TABLE IF NOT EXISTS data(id INTEGER PRIMARY KEY, a INT);")
for i in range(1000*1000):
    db.execute("INSERT INTO data (a) VALUES (?)", (random.randint(0, 3),))
db.commit()
增益:DB只有9MB,即平均每行9字节,这要好得多

但缺点是我们必须手动执行此操作:

  • 用ID和字符串之间的对应关系维护另一个表
  • 检测新值(以前从未见过)何时出现,给它一个新ID,等等
  • 如果删除了行,最后字符串不再出现在任何地方,我们可能需要进行一些清理,并从第二个表中删除其ID
  • 等等
这是可能的,也不是很困难,但多年来我注意到,SQLite通常对类似的事情有聪明的优化/好的技巧


问题:有没有办法让SQLite自动完成所有工作?i、 e.设置一种模式,在这种模式下,SQLite将在内部尽最大努力消除列中的重复数据,例如,为此列使用ID,而不是一次又一次地存储相同的字符串?(不必自己维护任何东西?

这个问题与非常相似,但它还讨论了进一步的方面-自动清理未使用的实体

有没有办法让SQLite自动完成所有的事情。。。(不需要自己维护任何东西)

不。基本上,您希望在基表和引用表(如果它还不存在)中插入行,同时通过值而不是其值指定引用。事实上,这在其他RDBMS中也不是一个简单的任务。其中一些支持:

  • 存储过程
  • 可写(可更新)视图
  • 而不是
    在视图上触发
从上面的列表中,SQLite只支持
而不支持
触发器。下面是它如何应用于您的用例(我已经从您的数据库中采纳了表
words
,并将其列
a
重命名为
value
):

PRAGMA外键=ON;
创建表格单词(
id整数主键,
值文本
);
在单词(值)上创建唯一索引唯一单词值;
创建表数据(
id整数主键,
word_id整数不为空,
外键(word_id)引用单词(id)
);
创建视图数据\u视图为
从数据中选择d.id,w.value作为d个内部连接词,w.id=d.word\u id上的w;
创建触发器数据\视图\插入,而不是在数据\视图上插入
开始
在单词(value)值(NEW.value)中插入或忽略;
在数据(word_id)值中插入或忽略(
(从value=NEW.value的单词中选择id)
);
结束;
插入数据视图(值)值
(‘1’),
(‘2’),
(‘3’),
(‘1’),
(‘3’),
('4');
INSERT
语句生成了表
words
的以下内容:

身份证件 价值 1. 随机数1 2. 随机数2 3. 随机数3 4. 随机数4
完全归功于@PeterWolf的出色回答,这里是一个稍加修改的版本,可以运行代码:

import sqlite3, random
VALUES = ["hello world", "it's a shame to store this str many times", "bye bye", "abc"]
db = sqlite3.connect('repetitive3.db')
db.executescript("""CREATE TABLE words(id INTEGER PRIMARY KEY, value TEXT UNIQUE);
CREATE TABLE data(id INTEGER PRIMARY KEY, word_id INTEGER NOT NULL);
CREATE VIEW data_view AS SELECT d.id, w.value FROM data AS d INNER JOIN words AS w on w.id = d.word_id;
CREATE TRIGGER data_view_insert INSTEAD OF INSERT ON data_view
BEGIN
  INSERT OR IGNORE INTO words(value) VALUES(NEW.value);
  INSERT OR IGNORE INTO data(word_id) VALUES((SELECT id FROM words WHERE value = NEW.value));
END;""")
for i in range(1000*1000):
    db.execute("INSERT INTO data_view (value) VALUES (?)", (random.choice(VALUES),))
print(list(db.execute("SELECT * FROM words")))
print(list(db.execute("SELECT * FROM data WHERE id BETWEEN 100 AND 105")))
print(list(db.execute("SELECT * FROM data_view WHERE id BETWEEN 100 AND 105")))
轻微修改(结果相同:数据库大小仅为9 MB)

  • 单词
    上没有索引,只有
    唯一
    关键字,确保
    插入或忽略到
    中(如果该值已存在)属于
    忽略

  • 没有使用外键


想法相同,但没有视图:

db.execute('CREATE TABLE words(value TEXT UNIQUE);')
db.execute('CREATE TABLE data(id INTEGER PRIMARY KEY, word_id INTEGER NOT NULL);')
for i in range(1000*1000):
    v = random.choice(VALUES)
    db.execute("INSERT OR IGNORE INTO words(value) VALUES(?);", (v,))
    db.execute("INSERT INTO data(word_id) VALUES ((SELECT rowid FROM words WHERE value = ?));", (v,))
searched_word = 'hello world'
print(list(db.execute("SELECT id, word_id FROM data WHERE id BETWEEN 100 AND 120 AND word_id = (SELECT rowid FROM words WHERE value = ?)", (searched_word,))))

SQLite是非常轻量级的数据库,这个“特性”远远超出了它的能力。它不支持您描述的开箱即用的场景。它支持一些功能,可以让您通过一点帮助实现目标。请参阅最近的这个问题以供参考:。链接视图+触发器技术是一种很好的等待来完成任务的方法,是的。@Shawn我目前正在阅读它,以及实时的“fiddle”示例,但我现在还不清楚如何在我的用例中应用类似的技术。如果你有时间,你能在回答主要问题时概述一下这个方法吗?@PeterWolf我读了相关的问题,但因为它对我来说比较新,我不确定这个方法。如果您有几分钟的时间,您认为您可以显示主要的查询来完成这项工作吗?这里是@PeterWolf的起点:但我不知道如何链接包含这4个单词的表
words
(最终将增长到最多1000个项目),表
data
包含100万个元素。注意:如果我们在创建
data
表时删除
FOREIGN KEY(word\u id)引用words(id)
,它不会改变数据库大小和速度,请参见下面我的答案。这个
外键在这里真正有用的是什么?@Basj外键用于确保引用完整性。它们可以使用
PRAGMA
命令。在这种情况下,FK仅注释两个表之间的关系。由于与较旧版本的SQLite向后兼容,默认情况下禁用外键。您知道@PeterWolf这两个查询
INSERT或IGNORE-INTO(…)
INSERT-INTO-data…
是否可以在一个查询中分组,或者至少在一个查询中分组