使用 MYSQL 生成分数时如何检查所有连接

Posted

技术标签:

【中文标题】使用 MYSQL 生成分数时如何检查所有连接【英文标题】:How to check against all joins when generating a score using MYSQL 【发布时间】:2013-04-24 15:38:39 【问题描述】:

我正在使用 mysql 为查询返回的每个结果生成一个分数。然后按分数对结果进行排序。

似乎无法正常工作的部分是当我尝试为已搜索的每个标签添加分数并将结果分配给时。所以假设我搜索标签“example”、“test”和“tag”,我的一个结果被分配给标签“example”、“test”、“someothertag”它应该得到10分因为有 2 个匹配项。

实际发生的情况是,如果匹配,无论匹配多少标签,我都会得到 5 分。如果没有匹配的标签,则为 0。

以下是通过搜索生成的查询之一的示例。

        SELECT DISTINCT results.*,
                    ( 
                        5*(MATCH(tags.name) AGAINST('"self employed"' IN BOOLEAN MODE)) +
            5*(MATCH(tags.name) AGAINST('"rental income"' IN BOOLEAN MODE)) +
            5*(MATCH(tags.name) AGAINST('"commission income"' IN BOOLEAN MODE)) +
            5*(MATCH(tags.name) AGAINST('"bankruptcy"' IN BOOLEAN MODE)) +
            5*(MATCH(tags.name) AGAINST('"condo approval"' IN BOOLEAN MODE)) +

                        1*usefulness + 
                        10*shares 
                    ) AS score 
        FROM results
        INNER JOIN categories c on results.ID = c.RESULT_ID
        INNER JOIN tags ON results.id = tags.result_id
        WHERE c.name in ('purchase', 'condo', 'va')
        AND ( tags.name = 'self employed' OR tags.name = 'rental income' OR tags.name = 'commission income' OR tags.name = 'bankruptcy' OR tags.name = 'condo approval'  )
        AND ( results.scope = 'all' OR results.scope = 'hi' )
        AND published = 1

        GROUP BY results.ID
        having count(distinct c.c_id) = 3
        ORDER BY score DESC 
        LIMIT 8 OFFSET 0

【问题讨论】:

您可能不需要使用全文索引来检查此查询中的标签名称。直接进行相等比较会更快更简洁。 如果您可以写下表的架构和一些示例数据,并列出您希望查询为该示例数据生成的内容,那么人们可能会更清楚。跨度> Groupin 可能会导致匹配失败。您是否尝试将 COUNT 添加到您的五个 5*(MATCH(tags.name)... 项目中? 【参考方案1】:

根据 Sam Dufel 的建议,您可能不需要全文搜索,尤其是因为您在 WHERE 子句中使用了精确的字符串比较。

此外,由于resultscategories 之间的多对多关系(假设来自HAVING COUNT(c_id) = 3 子句),我认为您绝对不能同时加入categoriestags相同的查询。

如果没有GROUP BY 子句,对于给定的result,每个匹配的category 将获得一行。对于每个匹配对(resultcategory),每个匹配的tag.name 将获得一行。我认为没有办法处理这样的结果。

我的建议是:

第 1 步:让results 出现在所有三个类别中

SELECT results.ID
FROM results
JOIN categories ON results.id = categories.result_id
WHERE categories.name IN ('purchase', 'condo', 'va')
GROUP BY results.ID
HAVING COUNT(DISTINCT c.c_id) = 3

第二步:计算任意results匹配至少一个搜索字符串的分数

SELECT
    DISTINCT results.*, -- DISTINCT is redundant because of the GROUP BY clause
    ( 
        5*(COUNT(tags.result_id)) + -- you actually want to count the number of matches!
        1*usefulness +  -- warning, see below 
        10*shares       -- warning, see below
    ) AS score 
FROM results
INNER JOIN tags ON results.id = tags.result_id
WHERE
    tags.name = 'self employed'
    OR tags.name = 'rental income'
    OR tags.name = 'commission income'
    OR tags.name = 'bankruptcy'
    OR tags.name = 'condo approval'
GROUP BY results.ID

第 3 步:将所有内容放在一起

SELECT
    results.*,
    ( 
        5*(COUNT(tags.result_id)) +
        1*usefulness +  -- warning, see below 
        10*shares       -- warning, see below
    ) AS score 
FROM (
        SELECT results.id
        FROM results
        JOIN categories ON results.id = categories.result_id
        WHERE
            categories.name IN ('purchase', 'condo', 'va')
            AND ( results.scope = 'all' OR results.scope = 'hi' )
            AND published = 1
        GROUP BY results.id
        HAVING COUNT(DISTINCT categories.c_id) = 3
) AS results_subset
JOIN results ON results_subset.id = results.id
JOIN tags ON results.id = tags.result_id
WHERE
    tags.name = 'self employed'
    OR tags.name = 'rental income'
    OR tags.name = 'commission income'
    OR tags.name = 'bankruptcy'
    OR tags.name = 'condo approval'
GROUP BY results.ID

请注意,我选择在 scopepublished 上包含条件 WHERE。这种选择是基于应尽早声明过滤器的原则。如果将它们放在外部查询中,您可能会获得更好的性能,但这实际上取决于基数。

警告:usefulnessshares 字段既不是 GROUP BY 函数的一部分,也不包含在聚合函数中。这是allowed by MySQL,但非常危险。如果usefulnessshares 属于result 以外的表(该表被GROUP'ed BY),则查询中返回的值是未定义的。

【讨论】:

非常感谢您的详细回答,很抱歉这么长时间才回复。最近几天我一直不在。今晚晚些时候我会试一试,让你知道我过得怎么样!【参考方案2】:

这样写:

   "sum((5*(MATCH(tags.name) AGAINST('"self employed"' IN BOOLEAN MODE))), 
        (5*(MATCH(tags.name) AGAINST('"rental income"' IN BOOLEAN MODE))) ,
        (5*(MATCH(tags.name) AGAINST('"commission income"' IN BOOLEAN MODE))),
        (5*(MATCH(tags.name) AGAINST('"bankruptcy"' IN BOOLEAN MODE))),
        (5*(MATCH(tags.name) AGAINST('"condo approval"' IN BOOLEAN MODE))),
      (1*usefulness), (10*shares)) as score"

【讨论】:

【参考方案3】:

您需要 SUM() 分数,因为 ONE 行仅匹配 ONE Tag。

在您的查询中选择了多行并按 ID 对它们进行分组,因此您只能获得 ONE Row 的结果,并且在您的情况下始终为 5。

【讨论】:

【参考方案4】:

我认为您的查询过于复杂。试试这个:

SELECT
    results.*,
    5 * count(distinct tags.name) + 1*usefulness + 10*shares AS score 
FROM results
JOIN categories c on results.ID = c.RESULT_ID
    AND c.name in ('purchase', 'condo', 'va')
JOIN tags ON results.id = tags.result_id
    AND tags.name in ('self employed', 'rental income', 'commission income', 'bankruptcy', 'condo approval')
WHERE results.scope in ('all', 'hi')
AND published = 1
GROUP BY 1, 2, 3, 4, 5 -- list as many numbers here as there are columns in "results" 
HAVING count(distinct c.c_id) = 3
ORDER BY score DESC 
LIMIT 8 OFFSET 0

您遇到的一个关键问题是分组 - 为了使其正常工作,您需要命名或按选定位置引用results 表的所有 列。你还没有给出表模式,所以我不知道该写什么。我猜到了 5 列,因此是 GROUP BY 1, 2, 3, 4, 5,但您需要确保这是正确的。

我整理了您的 ORs,将它们更改为 INs - 如果存在此类索引,这样做将允许在这些列上使用索引(“OR”不会使用索引)。

我已将一些 WHERE 子句条件移到有意义的 JOIN 条件中 - 这应该会提高性能。

【讨论】:

以上是关于使用 MYSQL 生成分数时如何检查所有连接的主要内容,如果未能解决你的问题,请参考以下文章

如何在我的循环中找到所有测试分数的最高、最低和平均值?

使用 numberFromString 生成 NSDecimalNumber 时如何在没有分数错误的情况下获得舍入值?

沙漠之王(0/1分数规划+ 最小生成树)

沙漠之王(0/1分数规划+ 最小生成树)

需要帮助显示排序的数组值

从单选按钮提交值