Найти подходящие вопросы из всех записей - PullRequest
0 голосов
/ 29 января 2020

У меня есть библиотека вопросов и ответов и создание API в NodeJS, который позволяет искать ответы на основе вопроса, переданного в качестве входных данных. Вот моя цель:

  1. Разделить вопрос по пробелам
  2. Использовать токен и удалить стоп-слова
  3. База данных запросов для записей, где вопрос содержит одно или несколько слов из токенизированного массив
  4. Идеально сортировать по убыванию общего количества совпадений в вопросе. Например: если вопрос A содержит «модуль» и «решение», а вопрос B содержит только «решение», то вопрос A должен быть показан перед вопросом B

Я смог достичь 1 3, используя следующий код:

let question = req.query.question;
let arrQuestions = question.split(" ");
let tokenizedQuestion = stopwords.removeStopwords(arrQuestions);

let whereClause = tokenizedQuestion.join("%' OR answer LIKE '%");
whereClause = " answer LIKE '%" + whereClause + "%' ";

let query = "SELECT * FROM tbl_libraries WHERE " + whereClause;

Я не могу понять, как достичь 4. Может ли кто-нибудь предоставить указатели?

Спасибо!

Ответы [ 2 ]

0 голосов
/ 14 февраля 2020

Я закончил с использованием полнотекстового поиска. Ниже приведена хранимая процедура, которую я создал для включения поиска:

DROP PROCEDURE IF EXISTS SP_Search $$
CREATE PROCEDURE `SP_Search`(IN QuestionToSearch TEXT, IN TagsToSearch TEXT, IN CollectionsToSearch TEXT, IN ReturnRecordsFromIndex INT, IN TotalRecordsToReturn INT)
BEGIN
    SET @MainQuery = CONCAT("SELECT *, MATCH(question, answer_content) AGAINST (", CONCAT("'", QuestionToSearch, "'"), " IN NATURAL LANGUAGE MODE) AS score ");
    SET @MainQuery = CONCAT(@MainQuery, " FROM tbl_libraries ");
    SET @MainQuery = CONCAT(@MainQuery, " WHERE MATCH(question, answer_content) AGAINST (", CONCAT("'", QuestionToSearch, "'"), " IN NATURAL LANGUAGE MODE) ");

    IF F_IsNullOrEmpty(TagsToSearch) AND NOT F_IsNullOrEmpty(CollectionsToSearch) THEN
        SET @MainQuery = CONCAT(@MainQuery, " AND collections LIKE '%", CollectionsToSearch, "%' ");
    ELSEIF F_IsNullOrEmpty(CollectionsToSearch) AND NOT F_IsNullOrEmpty(TagsToSearch) THEN
        SET @MainQuery = CONCAT(@MainQuery, " AND tags LIKE '", TagsToSearch, "' ");
    ELSEIF NOT F_IsNullOrEmpty(TagsToSearch) AND NOT F_IsNullOrEmpty(CollectionsToSearch) THEN
        SET @MainQuery = CONCAT(@MainQuery, " AND tags LIKE '", TagsToSearch, "' AND collections LIKE '", CollectionsToSearch, "' ");
    END IF;

    SET @MainQuery = CONCAT(@MainQuery, " ORDER BY score DESC ");
    SET @MainQuery = CONCAT(@MainQuery, " LIMIT ", ReturnRecordsFromIndex, ", ", TotalRecordsToReturn);

    PREPARE SqlQuery FROM @MainQuery;
    EXECUTE SqlQuery;
END $$
DELIMITER ;

При этом используется созданная мной пользовательская функция F_IsNullOrEmpty, которая для завершения показана ниже:

CREATE FUNCTION F_IsNullOrEmpty(ValueToCheck VARCHAR(256)) RETURNS BOOL
DETERMINISTIC
BEGIN
    IF((ValueToCheck IS NULL) OR (LENGTH(ValueToCheck) = 0) OR (ValueToCheck = 'null')) THEN
        Return True;
    ELSE
        Return False;
    END IF;
END;
0 голосов
/ 30 января 2020

Вы уверены, что не хотите использовать MySQL полнотекстовый поиск для этого?

Если ответом является «Нет», вы можете продолжить чтение ...

В одном из моих проектов я реализовывал нечто подобное. Запрос выглядит так (упрощенная версия):

SELECT
    name
FROM
    table
WHERE
    name REGEXP 'term1|term2|term3' -- you can use your OR + LIKE way
ORDER BY
    SP_TermsWeitght(name, 'term1 term2 term3') DESC

Все маги c находятся в моей функции SP_TermsWieght, которая возвращает «вес» (число), и я предоставляю список терминов (очищено) и нормализовано) к функции.

Функция:

CREATE FUNCTION `SP_TermsWeight`(
    `sValue` TEXT,
    `sTerms` VARCHAR(127)
)
RETURNS INT
DETERMINISTIC
BEGIN
    DECLARE i INT DEFAULT 1;
    DECLARE p INT DEFAULT 1;
    DECLARE w INT DEFAULT 0;
    DECLARE l INT;
    DECLARE c CHAR(1);
    DECLARE s VARCHAR(63);
    DECLARE delimiters VARCHAR(15) DEFAULT ' ,';

    SET sTerms = TRIM(sTerms);
    SET l = LENGTH(sTerms);

    IF (l > 0) THEN
        -- checking is value matched terms exactly
        IF (sTerms = sValue) THEN
            SET w = 50000;
        ELSE
            -- supposing that "the terms" is one single term so it it match in full, the weight will be high
            IF (l <= 63) THEN
                SET w = w + SP_TermWeight(sValue, sTerms, 5000, 1000, 100);
            END IF;
            -- not processing it term by term if it is already matched as full
            IF (w = 0) THEN
                -- processing term by term using space or comma as delimiter
                WHILE i <= l DO
                    BEGIN
                        SET c = SUBSTRING(sTerms, i, 1);
                        IF (LOCATE(c, delimiters) > 0) THEN
                            SET s = SUBSTRING(sTerms, p, i - p);
                            SET w = w + SP_TermWeight(sValue, s, 50, 10, 0);
                            SET p = i + 1;
                        END IF;
                        SET i = i + 1;
                    END;
                END WHILE;

                IF (p > 1 AND p < i) THEN
                    SET s = SUBSTRING(sTerms, p, i - 1);
                    SET w = w + SP_TermWeight(sValue, s, 50, 10, 0);
                END IF;
            END IF;
        END IF;
    END IF;

    RETURN w;
END

С технической точки зрения это «разделение» терминов с использованием разделителей и проверка, «содержит» ли значение термин. Немного сложно объяснить все, что он делает (я добавил несколько комментариев в код для вас). Не стесняйтесь задавать вопросы, если вы не понимаете некоторые моменты.

В вашем случае это может быть значительно упрощено, поскольку вам не нужно различать начальное / конечное / среднее совпадения.

Еще одна вспомогательная функция, которая используется внутри:

CREATE FUNCTION `SP_TermWeight`(
    `sValue` TEXT,
    `sTerm` VARCHAR(63),
    `iWeightBegin` INT,
    `iWeightEnd` INT,
    `iWeightMiddle` INT
)
RETURNS INT
DETERMINISTIC
BEGIN
    DECLARE r INT DEFAULT 0;
    SET sTerm = TRIM(sTerm);
    IF (LENGTH(sTerm) > 1) THEN
        IF (iWeightBegin != 0 AND sValue REGEXP CONCAT('[[:<:]]', sTerm)) THEN
            SET r = r + iWeightBegin;
        END IF;

        IF (iWeightEnd != 0 AND sValue REGEXP CONCAT(sTerm, '[[:>:]]')) THEN
            SET r = r + iWeightEnd;
        END IF;

        IF (r = 0 AND iWeightMiddle != 0 AND sValue REGEXP sTerm) THEN
            SET r = r + iWeightMiddle;
        END IF;
    END IF;

    RETURN r;
END

Эта функция используется для присвоения различных весов, если термин соответствует значению от начала строки, в конце строки или в середине. Это важно в моем случае. В вашем случае это может быть просто как.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...