我如何在 SQLite 中进行高级查询以按标签搜索文件?

How might I make advanced queries in SQLite to search for files by tags?

我在做什么?

我正在使用 Electron 在 JavaScript 中编写一个文件标记程序,我想使用 SQLite。不过,我不太清楚如何实现按标签搜索。我是 SQL 和 SQLite 的新手,所以我不确定这是否可以仅通过查询实现。我该如何进行如下所述的搜索?

搜索详情:

我调查了 FTS3/4。从外观上看,除了通配符搜索外,我可以做任何我想做的事情。

表格:

可能会改变

CREATE TABLE files (
  id INTEGER PRIMARY KEY,
  name TEXT
);

CREATE TABLE tags (
  id INTEGER PRIMARY KEY,
  name TEXT
);

CREATE TABLE file_tags (
  id INTEGER PRIMARY KEY,
  file_id INTEGER,
  tag_id INTEGER
);

示例:

INSERT INTO files (name) VALUES ('file_1.png');
INSERT INTO files (name) VALUES ('file_2.png');
INSERT INTO files (name) VALUES ('file_3.png');
INSERT INTO files (name) VALUES ('file_4.png');

INSERT INTO tags (name) VALUES ('blue_sky');
INSERT INTO tags (name) VALUES ('green_sky');
INSERT INTO tags (name) VALUES ('green_grass');
INSERT INTO tags (name) VALUES ('blue_grass');
INSERT INTO tags (name) VALUES ('greenish_blue_sky');


INSERT INTO file_tags (file_id, tag_id) VALUES(file1_id, blue_sky_id);
INSERT INTO file_tags (file_id, tag_id) VALUES(file1_id, green_grass_id);

INSERT INTO file_tags (file_id, tag_id) VALUES(file2_id, blue_sky_id);
INSERT INTO file_tags (file_id, tag_id) VALUES(file2_id, blue_grass_id);

INSERT INTO file_tags (file_id, tag_id) VALUES(file3_id, greenish_blue_sky_id);

INSERT INTO file_tags (file_id, tag_id) VALUES(file4_id, green_sky_id);
INSERT INTO file_tags (file_id, tag_id) VALUES(file4_id, blue_grass_id);

查询:blue_sky and green_grass
结果:file_1

查询:blue_sky or green_sky
结果:file_1, file_2, file_4

查询:blue_sky and green_grass or blue_grass
结果:file_1, file_2

查询:*ish*
结果:file_3

查询:*bl*e*
结果:file_1, file_2, file_3, file_4

查询:*sky and not blue_grass
结果:file_1, file3

注意:如果 SQLite 不是这项工作的正确工具,我愿意接受建议。

在我看来,您可以通过修改数据库结构来简化此过程。
例如

  • 始终如一地使用 'file_id' 和 'tag_id',
    而不是有时 'id' 在这两种情况下
  • 使用外键(不可否认,这可能需要不可用的功能)

在前几种情况下,您可以使用 tag_ids 作为 inpit,具体取决于键的来源("unexpecting user" 当然会键入颜色)。这也将减少拼写错误的风险。

所以你可以做的是:

  • 使用 'file_tags' table,
    的连接 每个你想在你的逻辑中使用的标签一个
  • 加入文件table,以访问输出的文件名
  • 使用子查询来使用标签名称而不是标签 ID 或者使用更多连接,我在下面进行了演示
  • 直接将搜索逻辑复制到 'where'
  • 按文件名分组,以便每个文件只得到一次

基于您出色的 MCVE,以下是针对您的示例查询的建议:

select fs.name from file_tags t1 
         inner join file_tags t2 on t1.file_id = t2.file_id
         inner join files fs on fs.id = t1.file_id
where t1.tag_id = (select id from tags where name = 'blue_sky')
  and t2.tag_id = (select id from tags where name = 'green_grass')
group by fs.name;

select fs.name from file_tags t1
         inner join file_tags t2 on t1.file_id = t2.file_id
         inner join files fs on fs.id = t1.file_id
where t1.tag_id = (select id from tags where name = 'blue_sky')
   or t2.tag_id = (select id from tags where name = 'green_sky')
group by fs.name;

-- note, here I had to derive from your desired output
-- that you want a '()' around the 'or'
select fs.name from file_tags t1
         inner join file_tags t2 on t1.file_id = t2.file_id
         inner join file_tags t3 on t1.file_id = t3.file_id
         inner join files fs on fs.id = t1.file_id
where t1.tag_id = (select id from tags where name = 'blue_sky')
 and (t2.tag_id = (select id from tags where name = 'green_grass')
   or t3.tag_id = (select id from tags where name = 'blue_grass')
     )
group by fs.name;

select fs.name from file_tags t1 
         inner join files fs on fs.id = t1.file_id
         inner join tags ts on ts.id = t1.tag_id
where ts.name like '%ish%'
group by fs.name;

select fs.name from file_tags t1 
         inner join files fs on fs.id = t1.file_id
         inner join tags ts on ts.id = t1.tag_id
where ts.name like '%bl%e%'
group by fs.name;

select fs.name from file_tags t1 
         inner join files fs on fs.id = t1.file_id
         inner join tags ts on ts.id = t1.tag_id
where ts.name like '%sky' and not ts.name = 'blue_grass'
group by fs.name;

select name from file_tags t1
         inner join files fs on t1.file_id = fs.id
where (select name from tags where id = t1.tag_id) like "%sky"
and not file_id in
        (select file_id from file_tags 
         where tag_id = (select id from tags where name = 'blue_grass')
        );

输出:

name
----------
file_1.png
name
----------
file_1.png
file_2.png
file_4.png
name
----------
file_1.png
file_2.png
name
----------
file_3.png
name
----------
file_1.png
file_2.png
file_3.png
file_4.png
name
----------
file_1.png

如果我另外加上:

INSERT INTO tags (name) VALUES ('greenish_blue_sky');
INSERT INTO file_tags (file_id, tag_id) VALUES(file3_id, greenish_blue_sky_id);

那么最后输出的部分是:

name
----------
file_1.png
file_3.png

使用 SQLite 3.18.0