我如何在 SQLite 中进行高级查询以按标签搜索文件?
How might I make advanced queries in SQLite to search for files by tags?
我在做什么?
我正在使用 Electron 在 JavaScript 中编写一个文件标记程序,我想使用 SQLite。不过,我不太清楚如何实现按标签搜索。我是 SQL 和 SQLite 的新手,所以我不确定这是否可以仅通过查询实现。我该如何进行如下所述的搜索?
搜索详情:
我调查了 FTS3/4。从外观上看,除了通配符搜索外,我可以做任何我想做的事情。
- 搜索具有所有给定标签的文件:
blue_sky AND green_grass
- 搜索没有给定标签的文件:
NOT blue_sky AND NOT green_grass
- 搜索具有某些给定标签的文件:
green_sky OR blue_sky
- 搜索带有通配符的文件任何地方在标签中:
*sky AND *grass AND *bl*e*
- 以上组合:
blue_sky AND green*
/ green_grass AND blue_sky OR green_sky
表格:
可能会改变
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
我在做什么?
我正在使用 Electron 在 JavaScript 中编写一个文件标记程序,我想使用 SQLite。不过,我不太清楚如何实现按标签搜索。我是 SQL 和 SQLite 的新手,所以我不确定这是否可以仅通过查询实现。我该如何进行如下所述的搜索?
搜索详情:
我调查了 FTS3/4。从外观上看,除了通配符搜索外,我可以做任何我想做的事情。
- 搜索具有所有给定标签的文件:
blue_sky AND green_grass
- 搜索没有给定标签的文件:
NOT blue_sky AND NOT green_grass
- 搜索具有某些给定标签的文件:
green_sky OR blue_sky
- 搜索带有通配符的文件任何地方在标签中:
*sky AND *grass AND *bl*e*
- 以上组合:
blue_sky AND green*
/green_grass AND blue_sky OR green_sky
表格:
可能会改变
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