【问题标题】:How do I select from a SQL table with knex where row values are consecutive?如何从行值连续的带有 knex 的 SQL 表中进行选择?
【发布时间】:2020-02-07 01:40:03
【问题描述】:

假设我有一个代表图书馆的数据库,以及一个存储每本书中单词的表。让我们称表为“books”,并说它有这样的行:

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

如果我有一个想要查找的单词序列(可以是任意数量的单词),我可以运行什么 SQL 查询来返回具有该单词序列的 book_names 列表,其中 @987654324 @是连续的?例如,如果我有列表 ["call", "me", "ishmael"],则查询将返回“Moby Dick”,因为这本书按顺序排列了该单词序列。但是,使用 ["call", "me", "ahab"] 运行它不会返回该书,因为这些单词不是书中单词的子数组(因此它应该只返回具有匹配子数组的书籍,不是匹配的子序列)。

我正在使用 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 尽可能多地完成工作。最好的方法是什么?

【问题讨论】:

    标签: javascript sql knex.js


    【解决方案1】:

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

    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.
    

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

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

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

    )。索引您的表对于此查询很重要。 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 对真实数据库运行它,但查询字符串似乎不错。如果它不起作用,请告诉我,我希望您至少有想法来编写您的查询。

    【讨论】:

    • 这是有道理的——是的,我正在使用 MySQL,抱歉我忘了指定。谢谢!
    【解决方案2】:

    这是 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
    

    【讨论】:

    • 唯一的事情是我不确定word_in_book 的值是什么——我只知道我要搜索的字符必须是连续的。
    猜你喜欢
    • 2021-02-09
    • 1970-01-01
    • 2010-12-06
    • 1970-01-01
    • 2021-12-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-03
    相关资源
    最近更新 更多