如何使用行值连续的 knex 从 SQL table select?

How do I select from a SQL table with knex where row values are consecutive?

假设我有一个代表图书馆的数据库,还有一个 table 存储每本书中的单词。让我们调用 table "books" 并说它有这样的行:

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

如果我有一个想要查找的单词序列(可以是任意数量的单词),我可以 运行 return 查询什么 运行 return book_name 具有 word_in_book 连续的单词序列?例如,如果我有列表 ["call"、"me"、"ishmael"],查询将是 return "Moby Dick",因为这本书在命令。但是,运行 它与 ["call"、"me"、"ahab"] 不会 return 那本书,因为那些单词不是书中单词的子数组(因此它应该只有 return 本书具有匹配的子数组, 而不是 匹配的子序列)。

我正在使用 knex 和 Express 来构建我的 SQL 语句。我的直觉是我需要使用 knex 遍历要搜索的单词数组,并为每个单词在我的查询对象上添加一些内容,但我不知道该怎么做。

目前我能想到的就这些了:

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

let query = knex("books");
words.forEach(word => {
  query = ??? // Not sure how to build my query
});

我在工作中使用的真实数据库与此非常相似。不同的是有几千本书,但每本书没有那么多字(最多只有几百字)。问题是,选择每本书的所有内容并用 JavaScript 检查所有单词会很慢,所以我希望 knex/SQL 做尽可能多的工作。执行此操作的最佳方法是什么?

首先,您要执行的查询类似于:

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.

如您所见,您必须多次加入同一个 table 才能找到下一个单词。在某些数据库上使用用户定义的变量可能有一个更简单的查询,但 knex 似乎不支持它(无法在您提供的 link 中读取它)。

为了让这个查询工作不太慢,你应该在三列上添加一个复合索引(你没有提供你的支持数据库,但如果你使用 mysql / mariadb 它将是:

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

)。索引您的 table 对此查询很重要。 SQL Demo

接下来,使用 knex 创建查询:

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();
// "select `books`.`book_name` as `book_name_searched` from `books` inner join `books` as `bw1` on `bw1`.`book_name` = `books`.`book_name` and bw1.word = 'me' and bw1.word_in_book = books.word_in_book + 1 inner join `books` as `bw2` on `bw2`.`book_name` = `books`.`book_name` and bw2.word = 'ishmael' and bw2.word_in_book = books.word_in_book + 2 where `books`.`word` = 'call' group by `books`.`book_name`"

我没有 运行 它针对带有 knex 的真实数据库,但查询字符串似乎不错。如果它不起作用,请告诉我,我希望您至少有想法来编写您的查询。

这是 hsibboni 的一个很好的解决方案。 您可以构建的更简单的查询是:

SELECT
book_name 
FROM books
WHERE
(word='call' and word_in_book=1) OR --word_in_book=index
(word='me' and word_in_book=2) OR
(word='ishmael' and word_in_book=3) OR
GROUP BY book_name
HAVING count(1)=3 --words.count