使用 Sqlite 设计基于标签的数据 table 的最佳方法是什么?

What is the best way to design a tag-based data table with Sqlite?

Json 从服务器收到的格式是这样的。

[
 {
  "id": 1103333,
  "name": "James",
  "tagA": [
    "apple",
    "orange",
    "grape"
  ],
  "tagB": [
    "red",
    "green",
    "blue"
  ],
  "tagC": null
  },

  {
  "id": 1103336,
  "name": "John",
  "tagA": [
    "apple",
    "pinapple",
    "melon"
  ],
  "tagB": [
    "black",
    "white",
    "blue"
  ],
  "tagC": [
    "London",
    "New York"
    ]
  }
]

一个对象可以有多个标签,一个标签可以关联多个对象。

在此列表中,我想找到一个对象,其 tagA 是苹果或葡萄,tagB 是黑色。

这是我写的第一个table

create table response(id integer primary key, name text not null, tagA text, 
tagB text, tagC text)

select * from response where (tagA like '%apple%' or tagA like '%grape%') and (tagB like '%black%')

这种table设计在使用Room等ORM库时,由于不支持fts函数的surface函数,所以存在搜索速度很慢的问题

接下来我想到的是为每个标签创建一个 table。

create table response(id integer primary key, name text not null)

create table tagA(objectID integer, value text, primary key(objectID, value))

create table tagB(objectID integer, value text, primary key(objectID, value))

create table tagC(objectID integer, value text, primary key(objectID, value))

select * from response where id in ((select objectId from tagA where value in ('apple','grape')) 
intersect
(select objectId from tagB where value in 'black'))

这大大增加了插入时间和APK的容量(大约每增加一倍table),但搜索速度远远落后于FTS虚拟table。

我使用 FTS tables 时尽可能避免这种情况,因为我需要自己管理更多的事情。

我错过了很多东西(索引等),但我不知道它是什么。

不使用FTS方法如何优化数据库?

您可以使用引用 table(又名映射 table 以及许多其他名称)来允许标签之间的多对多关系(单个 table 用于所有标签)和对象(还是单个 table)。

所以你有 objects table 每个对象都有一个 id 并且你有 tags table 再次使用每个对象的 id。所以像 :-

DROP TABLE IF EXISTS object_table;
CREATE TABLE IF NOT EXISTS object_table (id INTEGER PRIMARY KEY, object_name);
DROP TABLE IF EXISTS tag_table;
CREATE TABLE IF NOT EXISTS tag_table (id INTEGER PRIMARY KEY, tag_name);

你会填充两者,例如

INSERT INTO object_table (object_name) VALUES
    ('Object1'),('Object2'),('Object3'),('Object4');
INSERT INTO tag_table (tag_name) VALUES
    ('Apple'),('Orange'),('Grape'),('Pineapple'),('Melon'),
    ('London'),('New York'),('Paris'),
    ('Red'),('Green'),('Blue'); -- and so on

您的映射 table 类似于 :-

DROP TABLE IF EXISTS object_tag_mapping;
CREATE TABLE IF NOT EXISTS object_tag_mapping (object_reference INTEGER, tag_reference INTEGER);

超时,因为标签被分配给对象,反之亦然,您添加映射,例如:-

INSERT INTO object_tag_mapping VALUES
    (1,4), -- obj1 has tag Pineapple
    (1,1),  -- obj1 has Apple
    (1,8), -- obj1 has Paris
    (1,10), -- obj1 has green
    (4,1),(4,3),(4,11), -- some tags for object 4
    (2,8),(2,7),(2,4), -- some tags for object 2
    (3,1),(3,2),(3,3),(3,4),(3,5),(3,6),(3,7),(3,8),(3,9),(3,10),(3,11); -- all tags for object 3

然后您可以查询:-

SELECT object_name, 
    group_concat(tag_name,' ~ ') AS tags_for_this_object 
FROM object_tag_mapping 
JOIN object_table ON object_reference = object_table.id
JOIN tag_table ON tag_reference = tag_table.id
GROUP BY object_name
;
  • group_concat 是一个聚合函数(按 GROUP 应用),它将为指定列找到的所有值与(可选)分隔符连接起来。

查询结果为:-

以下可能是基于标签的搜索(并不是说您可能会同时使用 tag_name 和 tag_reference):-

SELECT object_name, tag_name 
FROM object_tag_mapping 
JOIN object_table ON object_reference = object_table.id
JOIN tag_table ON tag_reference = tag_table.id
WHERE tag_name = 'Pineapple' OR tag_reference = 9
;

这将导致:-


  • 请注意,这是一个简单的概述,例如您可能需要考虑将映射 table 作为 WITHOUT ROWID table,也许有一个复合 UNIQUE 约束。

补充评论:-

How do I implement a query that contains two or more tags at the same time?

如果您想要特定的标签但仍然可行,这会稍微复杂一些。这是一个使用 CTE(通用 Table 表达式)和 HAVING 子句(在生成输出后应用的 where 子句,因此可以应用于聚合)的示例:-

WITH cte1(otm_oref,otm_tref,tt_id,tt_name, ot_id, ot_name) AS 
    (
        SELECT * FROM object_tag_mapping 
        JOIN tag_table ON tag_reference = tag_table.id 
        JOIN object_table ON object_reference = object_table.id
        WHERE tag_name = 'Pineapple' OR tag_name = 'Apple'
    )
SELECT ot_name, group_concat(tt_name), count() AS cnt FROM CTE1 
GROUP BY otm_oref
HAVING cnt = 2
;

这导致:-