What tips do you have for running complex SQLite queries to find files based on tags?

What Is My Current Project?

Currently, I am working on developing a file tagging program using JavaScript with Electron. I have decided to incorporate SQLite into the project for data management. However, I am facing a challenge when it comes to implementing tag-based searches. Since SQL and SQLite are relatively new to me, I am unsure if it is feasible to achieve this solely through queries. Can anyone provide guidance on how I can execute searches based on tags as outlined below?

Specifics of Searching:

After exploring FTS3/4 options, I have discovered that while they meet my requirements, wildcard searches seem to be unsupported.

  • Find files with all specified tags: blue_sky AND green_grass
  • Discover files without specific tags: NOT blue_sky AND NOT green_grass
  • Detect files with any of the given tags: green_sky OR blue_sky
  • Look for files with wildcards anywhere in a tag: *sky AND *grass AND *bl*e*
  • Combine the above search criteria: blue_sky AND green* /
    green_grass AND blue_sky OR green_sky

Database Schema:

The structure may undergo modifications

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
);

Examples:

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);

Query: blue_sky and green_grass
result: file_1

Query: blue_sky or green_sky
result: file_1, file_2, file_4

Query:

blue_sky and green_grass or blue_grass

result: file_1, file_2

Query: *ish*
result: file_3

Query: *bl*e*
result: file_1, file_2, file_3, file_4

Query: *sky and not blue_grass
result: file_1, file3

Note: If SQLite does not appear suitable for solving this problem, I am open to alternative suggestions.

Answer №1

From my perspective, enhancing the database structure could simplify this process.
For example:

  • Consistently use 'file_id' and 'tag_id' instead of switching to 'id'
  • Implement foreign keys (although this feature may not be available)

You could potentially utilize tag_ids as input in initial cases, depending on the source of the keys (an "unprepared user" might naturally type colors). This approach can help minimize typos.

One strategy you could adopt includes:

  • Utilize joins from the 'file_tags' table, one for each tag needed in the logic
  • Merge the files table to access file names for output
  • Incorporate subqueries to use tag names rather than tag ids or increase join operations, as exemplified below
  • Directly transfer the search logic into the 'where' clause
  • Group by file names to ensure each file appears only once

Based on your detailed MCVE, here are proposed queries for your specified examples:

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')
        );

Result:

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

If I include the following additions:

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

The updated output would be:

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

Tested with SQLite 3.18.0

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Encountering the error message "Unable to instantiate User: constructor not found" while testing API functionality for

Currently, I am in the process of integrating my backend with MongoDB ATLAS. In order to achieve this, I am utilizing express, mongoose, and node.js for testing purposes. Specifically, I am focusing on testing my API routes, such as adding a user. Below i ...

Tips for validating an email address using ReactJS

I'm currently working on customizing the email verification process for a signup form in ReactJS. My goal is to replace the default email verification with my own validation criteria. Initially, I want to ensure that the entered email address contains ...

Tips for creating text that adjusts to the size of a div element

I'm currently working on developing my e-commerce website. Each product is showcased in its own separate div, but I've encountered a challenge. The issue arises when the product description exceeds the limits of the div, causing it to overflow ou ...

Embracing Laravel for Enhanced Functionality on an Established Website

During my internship at a company, I have been tasked with updating the database by adding new features and forms directly through editing PHP files on the network using notepad++ and FileZilla for the past two weeks. Some users suggested I try Laravel t ...

There was an issue converting the value {null} to the data type 'System.Int32', resulting in a 400 error

Attempting to make a POST request with some missing data is causing errors in my angular form. Here is the payload I am using: DeviceDetail{ deviceId:'332', sideId: null, deviceName:'test' } Unfortunately, I encountered a 400 bad re ...

Firefox causing issues with Rails Ajax functionality

My Rails application includes an AJAX call that currently triggers a JavaScript alert message. While this functionality works smoothly on Safari and Chrome, it strangely fails to function on Firefox (across all its recent versions). I tried debugging the c ...

Is there a way to transform a Phaser 3 game into an android-game application?

As someone who is new to Phaser, I am curious about the possibility of converting a Phaser game into an Android game. Currently, I am utilizing Phaser with NPM for my projects. Any insights or advice on this matter would be greatly appreciated. Thank you! ...

Interrogate the documents, make revisions, and then send them back to MongoDB

I am working on an application based on MongoDB 3.2 where I need to process documents. To prevent processing the same document multiple times, I would like to update its flag and save this updated document in the database. One possible approach is: Query ...

The Heroku app seems to be malfunctioning (Issue with deployment?)

After deploying my application that utilizes JavaScript, Express, and Node.js on Heroku, I encountered an issue where the functionality of the app is not working at all when accessed through Heroku. Interestingly, everything works perfectly fine when teste ...

What is the most effective way to send an email in PHP with a table format

<?php $result = mysqli_query($con, "SELECT * FROM cse"); $mail_data = ""; while ($row = mysqli_fetch_array($result)) { $Hallticket_No = $row['Hallticket No']; $Name = $row['Name']; $Email_ID = $row[&apos ...

Vue's computed property failing to compute

I have developed an ecommerce application using Vue.js, and here is a snippet of the store configuration: state: { cart: [], totalItems: { count: 0 } }, getters: { totalItems(){ if(window.localStorage.totalItems){ return ...

Using jQuery to include the value of an array index in the output

Looking for guidance on jQuery & JavaScript. I have successfully implemented code that adds a new text input field based on the user's selection from a dropdown select field. <script> $(function() { var input = $('<input placeholder= ...

JQuery for Seamless Zoom Functionality

I am working with some divs that have images as backgrounds, and I have added a zooming effect on hover. However, the zoom effect is not smooth. Is there a way to modify the script to make the zoom effect smoother? Here is an example of my HTML structure: ...

Exploring table iteration in Angular 7

I am looking to create a table with one property per cell, but I want each row to contain 4 cells before moving on to the next row... This is what I want: <table> <tr> <td> <mat-checkbox>1</mat-checkbox& ...

Automated system is responsible for flagging and disabling comments

We are facing a puzzling issue at the moment. Our comments form allows users to submit their thoughts on news articles, and each submission is immediately accepted and displayed on the same page. Within each comment, there is a link that enables users to ...

Transform a string into a boolean value for a checkbox

When using v-model to show checked or unchecked checkboxes, the following code is being utilized: <template v-for="(item, index) in myFields"> <v-checkbox v-model="myArray[item.code]" :label="item.name" ...

Transferring Information from Angular Interface to NodeJS through a JSON Document

I am currently working on establishing a connection between an AngularJS front end and a NodeJS back end application. The main objective is to manipulate data in a JSON file instead of a traditional database. I have attempted to set up the post method on ...

What is the best way to fix the lint check error within a Vue file's styling?

https://i.sstatic.net/PPA88.jpg Is there a way to eliminate the red wavy lines on the screen? ...

Shifting axios requests outside of the React Component

Do you think this approach in React could be considered an anti-pattern? How would you suggest improving it? In my project, I utilize an EventItem Component along with a separate helper file named EventHelper.js to manage all axios calls related to the co ...

Using optional chaining along with the methods toLowerCase and indexOf while iterating through an array using the map

I have implemented an autocomplete input that searches for project properties while typing. I am looking to enhance the existing code for better performance. filterProjects(value: string) { return this.projects.filter( project => project.key ...