扁平文件NoSQL解决方案

13

在SQLite(或类似)中,是否有一种内置的方法可以为小型项目保留SQL / NoSQL的双重优势,即:

  • 像SQLite一样存储在一个(平面)文件中(没有客户端/服务器方案,无需安装服务器;更确切地说:除了pip install <package>之外不需要安装其他内容)
  • 可以将行存储为dict,而无需为每个行定义一个公共结构,就像NoSQL数据库一样
  • 支持简单查询

示例:

db = NoSQLite('test.db')
db.addrow({'name': 'john doe', 'balance': 1000, 'data': [1, 73.23, 18]})
db.addrow({'name': 'alice', 'balance': 2000, 'email': 'a@b.com'})
for row in db.find('balance > 1500'):
    print(row)

# {'id': 'f565a9fd3a', 'name': 'alice', 'balance': 2000, 'email': 'a@b.com'}   # id was auto-generated

注意:多年来,SQLite 令我不断惊叹于它能以极简的代码实现许多有趣的功能,这就是为什么我问是否可以仅使用少量 SQLite 核心特性就实现我在此描述的内容。

附注:shelve 或许看起来像是一个解决方案,但它实际上只是一个持久化的键值存储,没有查询/find 函数;而且bsddb(Python 的 BerkeleyDB)似乎已经过时,并且没有类似 API 的查询功能。

2个回答

9

SQLite

  • JSON1 extension and json_extract (see accepted answer). Example:

    import sqlite3, json  # tested with precompiled Windows binaries from https://www.sqlite.org/download.html (sqlite3.dll copied in C:\Python37\DLLs)
    
    class sqlitenosql:
        def __init__(self, f):
            self.db = sqlite3.connect(f)
            self.db.execute('CREATE TABLE test(data TEXT);')
    
        def close(self):
            self.db.commit()
            self.db.close()
    
        def addrow(self, d):
            self.db.execute("INSERT INTO test VALUES (?);", (json.dumps(d),))
    
        def find(self, query):
            for k, v in query.items():
                if isinstance(v, str):
                    query[k] = f"'{v}'"
            q = ' AND '.join(f" json_extract(data, '$.{k}') = {v}" for k, v in query.items())
            for r in self.db.execute(f"SELECT * FROM test WHERE {q}"):
                yield r[0]
    
    db = sqlitenosql(':memory:')
    db.addrow({'name': 'john', 'balance': 1000, 'data': [1, 73.23, 18], 'abc': 'hello'})
    db.addrow({'name': 'alice', 'balance': 2000, 'email': 'a@b.com'})
    db.addrow({'name': 'bob', 'balance': 1000})
    db.addrow({'name': 'richard', 'balance': 1000, 'abc': 'hello'})
    for r in db.find({'balance': 1000, 'abc': 'hello'}):
        print(r)
    # {"name": "john", "balance": 1000, "data": [1, 73.23, 18], "abc": "hello"}
    # {"name": "richard", "balance": 1000, "abc": "hello"}    
    db.close()
    
  • sqlitedict as mentioned in Key: value store in Python for possibly 100 GB of data, without client/server and Use SQLite as a key:value store with:

    key = an ID

    value = the dict we want to store, e.g. {'name': 'alice', 'balance': 2000, 'email': 'a@b.com'}

  • Further reading about use of SQLite with JSON: https://community.esri.com/groups/appstudio/blog/2018/08/21/working-with-json-in-sqlite-databases

TinyDB

TinyDB 看起来是一个不错的解决方案:

>>> from tinydb import TinyDB, Query
>>> db = TinyDB('path/to/db.json')
>>> User = Query()
>>> db.insert({'name': 'John', 'age': 22})
>>> db.search(User.name == 'John')
[{'name': 'John', 'age': 22}]

然而,文档提到如果我们需要以下内容,则不是正确的工具:多进程或线程的访问为表创建索引HTTP服务器管理表之间的关系或类似内容ACID保证。所以这只是一个半解决方案 :)

其他解决方案

另一种看起来很有趣: WhiteDB


1
你的答案对我帮助很大,帮我快速解决了问题。我改编了你的代码并使用jsonpickle处理编码,查询时返回类类型。由于问题已关闭,我将在此发布我的解决方案以帮助未来的开发人员。 https://gist.github.com/ahancock1/72e459e294875988f4e6138d348122de - Adam H

8
使用JSON1扩展可以查询存储在列中的JSON数据,是可行的。
sqlite> CREATE TABLE test(data TEXT);
sqlite> INSERT INTO test VALUES ('{"name":"john doe","balance":1000,"data":[1,73.23,18]}');
sqlite> INSERT INTO test VALUES ('{"name":"alice","balance":2000,"email":"a@b.com"}');
sqlite> SELECT * FROM test WHERE json_extract(data, '$.balance') > 1500;
data
--------------------------------------------------
{"name":"alice","balance":2000,"email":"a@b.com"}

如果您需要频繁查询同一字段,则可以通过在表达式上添加索引来提高效率:
CREATE INDEX test_idx_balance ON test(json_extract(data, '$.balance'));

将在上述查询中使用该索引,而不是扫描每一行。

哇,好棒的解决方案!那么我猜,它会循环遍历整个数据库来执行 find('name="john doe"')?我想这是因为SQLite必须提取所有行并测试名称是否匹配,对吗? - Basj
@Basj 你可能可以通过表达式索引来完成某些操作,但我没有尝试过。否则,是的,它必须查看每一行 - 注意我并没有说这样做是一个“好”主意,只是它是可行的。 :) - Shawn
好的,一个表达式上的索引将适用于 json_extract()。 - Shawn
谢谢!您能否提供一个示例,说明在使用CREATE INDEX test_idx_balance ON test(json_extract(data, '$.balance'));创建索引后,应该如何进行查询? - Basj

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接