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

时间:2017-08-31 11:42:27

标签: javascript sql database sqlite tagging

我在做什么?

我正在使用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不是正确的工作工具,我可以接受建议。

1 个答案:

答案 0 :(得分:2)

在我看来,通过修改数据库结构,您可以更轻松地实现这一目标 E.g。

  • 始终使用'file_id'和'tag_id',
    而不是在两种情况下有时都是'id'
  • 使用外键(诚然可能需要不可用的功能)

您可以在前几种情况下使用tag_ids作为inpit,具体取决于密钥的来源(“无意识的用户”当然会键入颜色)。这也可以减少拼写错误的风险。

所以你能做的是:

  • 使用'file_tags'表的连接,
    您希望在逻辑中使用的每个标记都有一个
  • 加入文件表,以访问输出的文件名
  • 使用子查询来使用标记名称而不是标记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