What is the best method to retrieve data from a SQL table using knex when the row values are in consecutive order?

Imagine I have a database that represents a library, with a table storing the words within each book. Let's refer to this table as "books" and assume it includes rows like the following:

| book_name | word_in_book | word    |
|-----------|--------------|---------|
| Moby Dick | 1            | call    |
| Moby Dick | 2            | me      |
| Moby Dick | 3            | ishmael |

If I am looking for a specific sequence of words (which could vary in length), what SQL query could I execute to retrieve a list of book_names containing that exact sequence where word_in_book is consecutive? For instance, if my sequence is ["call", "me", "ishmael"], the query should return "Moby Dick" since the book contains those words in order. However, searching for ["call", "me", "ahab"] would not yield any results because those words do not form a subarray within the book's words (the query should only return books with a matching subarray, not a matching subsequence).

I am utilizing knex alongside Express to construct my SQL statements. My assumption is that I will need to use knex to iterate through the array of words being searched and dynamically add elements to my query object for each word, but I am uncertain about how to go about doing this.

Here is the approach I have considered so far:

const knex = require("knex")({
  // Connection details here ...
});
const words = ["call", "me", "ishmael"];

let query = knex("books");
words.forEach(word => {
  query = ??? // Unsure about constructing my query
});

The actual database I am dealing with at my workplace closely resembles this example. The main distinction is that there are thousands of books, yet each book does not contain an extensive number of words (typically only a few hundred at most). The challenge lies in the fact that retrieving all content from every book and cross-checking all words using JavaScript would be quite sluggish, hence why I prefer knex/SQL to manage as much of this process as possible. What would be the most effective way to achieve this?

Answer №1

To start, the query you need to execute is somewhat similar to this:

SELECT books.book_name
From books
join books bw2 on bw2.book_name = books.book_name AND bw2.word_in_book = books.word_in_book + 1 AND bw2.word = 'me'
join books bw3 on bw3.book_name = books.book_name AND bw3.word_in_book = books.word_in_book + 2 AND bw3.word = 'ishmael' 
where books.word = 'call'
Group by books.book_name -- avoid having twice the same book.

As seen, multiple joins are required in the query to find the next word. While there may be a simpler approach using user-defined variables in certain databases, Knex does not seem to support it based on the provided link.

To ensure this query runs efficiently, consider adding a composite index on three columns (assuming MySQL or MariaDB):

ALTER TABLE books ADD INDEX (word, book_name, word_in_book);

Indexing your table properly will play a crucial role in the performance of this query.

When utilizing Knex to construct the query:

const words = ["call", "me", "ishmael"];

var query = knex("books").select({
    book_name_searched: 'books.book_name'
}).where('books.word', words[0]);
words.forEach( (word, index) => {
    if (index < 1) return;
    query = query.join('books as bw' + index, function() {
        this.on('bw' + index + '.book_name', '=', 'books.book_name')
           .andOn(knex.raw('bw' + index + '.word = \'' + words[index] + '\''))
           .andOn(knex.raw('bw' + index + '.word_in_book = books.word_in_book + ' + index))
    })
});

query.groupBy('books.book_name');

query.toString();
// Outputs the SQL query string for reference

While I haven't executed this against an actual database using Knex, the generated query string appears to be correct. Please let me know if you encounter any issues, but hopefully, this provides guidance for constructing your query.

Answer №2

Great suggestion provided by hsibboni. Here's a more simplified query you can use:

SELECT
title 
FROM novels
WHERE
(word='search' and word_in_novel=1) OR --word_in_novel=index
(word='for' and word_in_novel=2) OR
(word='captain' and word_in_novel=3) OR
GROUP BY title
HAVING count(1)=3 --words.count

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

Obtaining and Assigning Filter Values in Material Table

Is there a way to programmatically obtain and adjust filter values with material-table? I am looking to enable users to save filter settings as reports and access them whenever necessary. ...

JavaScript - Populate canvas with selected image from gallery upon clicking an image

A photo gallery on my website is filled with images fetched from a local directory using PHP. I would like users to be able to select an image by clicking on it and then have the option to draw on that image within a canvas element. The gallery has been c ...

What is the purpose of the code snippet 'jQuery.each(lines, function(lineNo, line)'?

As a newbie to jquery and ajax, I stumbled upon some code online that caught my attention. The code snippet below prompted me to question its purpose and functionality: lines = newLine.split('#'); jQuery.each(lines, function(lineNo, line) ...

Issue with column default not being updated after executing the query

My issue involves attempting to change a column's default value by using the following SQL command: alter table table_Name alter MODIFY col_Name tinyint(1) NOT NULL DEFAULT 0; Following the execution of this command, I observe that 25 rows have been ...

Ways to time animations differently and activate two animations at the same time in every loop

I have 3 hidden text boxes that I want to fade in and slide down simultaneously by 40px when the DOM loads. They should be staggered so that each one triggers after the previous animation finishes. Below is the relevant JavaScript code snippet: jQuery(fu ...

Modifying JavaScript object values using the Object() constructor

My background is in Groovy, which has similar syntax to JavaScript. In Groovy, I can easily copy values from one map to another like this: def myMap1 = {}; def myMap2 = {}; myMap1["key1"] = "value1"; myMap1["key2"] = "value2"; myMap1["key3"] = "value3"; ...

How can we efficiently loop through all the icons in React Material-UI?

I am looking to iterate over all the icons from @material-ui/icons in a React application. If I want to import a single icon, I can do so like this import IconNameIcon from '@material-ui/icons/IconName' and then use it in my component like th ...

Rails 7 is missing the Toast element from Bootstrap 5

Having some trouble with implementing Bootstrap 5 Toast in Rails 7 for flash messages. Here is the code I am currently using: # application.html.erb <head> ... <%= javascript_importmap_tags %> <script> const toastElList = document.que ...

Creating a map with markers using the Google Maps API

I recently started working with the Google Maps API and wanted to add a marker to a specific location. I went through the documentation and attempted to implement it on my website, but encountered several undefined errors in the process. Here is the code s ...

Choose a variety of photos for each individual input file

I am facing a scenario where I need to allow users to upload 3 images using input[type="file"]. Each input should only accept 1 image, but if a user selects more than 1 image in the first input, I want to distribute them among the subsequent inputs. For in ...

Adjusting the visible options in ngOptions causes a disruption in the selected value of the dropdown menu

I have successfully implemented a feature that allows users to convert temperature values displayed in a drop-down menu to either Celsius or Fahrenheit. For this functionality, I am using a select input with ng-options as shown below: <select ng-model ...

Enhanced appearance with asynchronous functions at the top level

Is there a method to incorporate async top level with prettier? I attempted to exclude my await top level using /prettier ignore, however, prettier seems to be ignoring this line... ...

What is the process for determining the text direction of a website's title?

My website is having trouble displaying the title in the correct direction. I've attempted to add dir="rtl" to various tags like html, head, and title, but nothing seems to be working. In the English version I have "Select a service:" In the Arabic ...

Prompting for confirmation when the "Close Button" is clicked in Bootstrap V5 Alert Dismissing

Looking to enhance the user experience by incorporating Sweetalert2 for a confirmation message within the Bootstrap version 5 alert component. Curious how it operates? Upon clicking the "Close Button" (X), the intention is to trigger a confirmation messa ...

Utilize linqjs to filter an array based on the values present in another array

We have two arrays to work with here. The first array consists of objects: let objectArray = [{ FullName: "Person1", PersonId: "id1" }, { FullName: "Person2", PersonId: "id2" }, { FullName: "Person3", PersonId: "id3" }, ...

Errors persist with Angular 2 iFrame despite attempts at sanitization

Attempting to add an iFrame within my Angular 2 application has been challenging due to the error message that keeps popping up. unsafe value used in a resource URL context The goal is to create a dynamic URL to be passed as a parameter into the iFrame ...

Javascript is not recognizing if and elseif conditions

I recently developed an IQ Test using JavaScript. I implemented specific conditions for different score ranges, however, the test only displays the IQ score based on the last condition, regardless of the number of correct answers. Despite numerous attempts ...

Tips for using jQuery dropdown menus

I am currently in the process of creating a prototype for a quick dropdown menu using jQuery. However, I have reached the limits of my knowledge on jQuery and could use some assistance in solving my issue. My objective is to have a dropdown animate down w ...

Identifying whether a Alphabet or a Digit has been Pressed - JavaScript

I understand that it is possible to detect if a key has been pressed and identify which key was pressed using JavaScript. In order to check if a key is down or pressed, jQuery can be utilized with ease: $( "#some id" ).keydown(function() or $( "#m" ). ...

How can I adjust the column width in OfficeGen?

Currently, I am utilizing officeGen for the purpose of generating word documents. <sup> let table = [ [ { val: "TT", fontFamily: "Times New Roman", }, { val: "Ten hang", ...