如何在 MySQL 5 .7 中实现 CTE 功能?

Posted

技术标签:

【中文标题】如何在 MySQL 5 .7 中实现 CTE 功能?【英文标题】:How to achieve CTE functionality in MySQL 5 .7? 【发布时间】:2020-06-19 22:39:27 【问题描述】:

我有一个 USERSEARCH 表,应该用于快速搜索用户的子字符串。此功能适用于在有人输入用户名或姓名时发生的自动完成搜索。但是,我感兴趣的查询只会显示搜索者关注的用户子集的用户匹配。这可以在 USERRELATIONSHIP 表中找到。

USERSEARCH
-----------------------------------------------
user_id(FK)    username_ngram          name_ngram
1              "AleBoy leBoy eBoy..."  "Ale le e"
2              "craze123 raze123 ..."  "Craze raze aze ze e"
3              "john1990 ohn1990 ..."  "John ohn hn n"
4              "JJ_1 J_1 _1 1"         "JJ"


USERRELATIONSHIP
-----------------------------------------------
user_id(FK)    follows_id(FK)
2              1
2              3

当有人刚刚输入“Al”时会进行这样的查询(不考虑用户关系):

SELECT * FROM myapp.usersearch where username_ngram like 'Al%'
        UNION DISTINCT
        SELECT * FROM myapp.usersearch where name_ngram like 'Al%'
        UNION DISTINCT
        SELECT * FROM myapp.usersearch                            
        WHERE MATCH (username_ngram, name_ngram) AGAINST ('Al')  
        LIMIT 10

由于 username_ngram、name_ngram 和 FULLTEXT(username_ngram, name_ngram) 上的现有索引,这非常快。但是,在我的应用程序上下文中,我需要将搜索限制为搜索者所关注的用户。我想用“myapp.usersearch”表的子集替换“myapp.usersearch”表,其中仅包括搜索者关注的用户。这是我尝试过的:

    WITH

--Part 1, restrict the USERSEARCH table to just the users that are followed by searcher

        tempUserSearch AS (SELECT T1.id, T2.username_ngram, T2.name_ngram FROM
        (SELECT follows_id FROM myapp.userrelationship WHERE user_id = user_idOfSearcher ) AS T1 
        LEFT JOIN myapp.usersearch AS T2  ON T2.user_id = T1.follows_id)

            SELECT * FROM tempUserSearch where username_ngram like 'Al%'
            UNION DISTINCT
            SELECT * FROM tempUserSearch where name_ngram like 'Al%'
            UNION DISTINCT
            SELECT * FROM tempUserSearch                            
            WHERE MATCH (username_ngram, name_ngram) AGAINST ('Al')  
            LIMIT 10

不幸的是 mysql 5.7 不支持 CTE WITH 子句。

有没有办法在所有后续子查询中引用查询的第 1 部分,而无需重新查询该人关注的用户的 user_ids? (在 MySQL 5.7 中)

更新:

在 MySQL 5.7 中真的没有办法多次引用一个查询吗?在我看来,这似乎是任何数据库的一项基本任务。

为什么不这样做:“x join y on a or b or c”?我的子字符串查询的速度取决于以下指标:

index(username_ngram)

index(name_ngram)

FULLTEXT(username_ngram, name_ngram)

并且使用 OR 对任何索引都没有帮助。

【问题讨论】:

请在代码问题中给出minimal reproducible example--cut & paste & runnable code,包括最小的代表性示例输入作为代码;期望和实际输出(包括逐字错误消息);标签和版本;明确的规范和解释。给出尽可能少的代码,即您显示的代码可以通过您显示的代码扩展为不正常的代码。 (调试基础。)对于包含 DBMS 和 DDL(包括约束和索引)和输入为格式化为表的代码的 SQL。 How to Ask 暂停总体目标的工作,将代码砍到第一个表达式,没有给出你期望的内容,说出你期望的内容和原因。 不要使用 MySQL 进行这种搜索。使用基于文本的 nosql 数据库,如 lucene 或弹性搜索等,并为每个用户添加一个“跟随”字段并使用该字段和部分名称进行搜索。 你有比较过 x join y on a or b or c 吗? PS 通过教科书和网络和 SO,特别是通过 DBMS 手册,通过查询引擎学习关系和 SQL 优化/实现的基础知识——所有这些都立即导致索引、计划、统计和 SARGability。在您学习并应用了这些基础知识后,要求重新优化。期望对问题进行适当的研究。请参阅 How to Ask、其他 help center 链接和投票箭头鼠标悬停文本 请通过编辑而非 cmets 进行澄清。 PS该评论太简洁了,无法理解。 PS 谷歌搜索(比如)“MySQL 站点中 CTE 的替代方案:***.com”或“在 MySQL 站点:***.com 中重用子查询”对您有何帮助? (修辞。) @philipxy 我在过去的两个小时里。 【参考方案1】:

MySQL 5.7 不支持公用表表达式; WITH 语法仅在 8.0 版中可用。

由于您现有的查询运行速度很快,因此在外部查询中进行过滤可能是可行的解决方案:

SELECT ur.id, ng.username_ngram, ng.name_ngram
FROM myapp.userrelationship ur
INNER JOIN (
    SELECT * FROM myapp.usersearch WHERE username_ngram LIKE 'Al%'
    UNION DISTINCT
    SELECT * FROM myapp.usersearch WHERE name_ngram LIKE 'Al%'
    UNION DISTINCT
    SELECT * FROM myapp.usersearch WHERE MATCH (username_ngram, name_ngram) AGAINST ('Al')  
) ng ON ng.user_id = ur.follows_id
WHERE ur.user_id = user_idOfSearcher
ORDER BY ??
LIMIT 10

注意事项:

我将 LEFT JOIN 转为 INNER JOIN,因为我认为这更接近您想要的(如果不符合您的要求,您可以将其改回)

您需要一个 ORDER BY 子句与 LIMIT 一起使用,否则当结果集中有超过 10 行时,结果是不确定的

【讨论】:

你的怀疑是对的,我刚刚更新了问题,请看一下。 (2,3) 中的 ID 是搜索者所遵循的 user_ids 的简化,这实际上是对 USERRELATIONSHIP 表进行子查询。我不一定需要 LIMIT 10 是确定性的,省略 ORDER BY 会加快速度吗? @Rage:现在它更有意义(尽管查询中的连接列与您选择的列不对齐)。我更新了我的答案。将ORDER BY 分开可能会加快查询速度,具体取决于其他因素,例如查询返回的行数(在应用LIMIT 之前)。您需要根据您的实际数据进行评估。 感谢您的修改,但我不确定我是否理解您的查询。您能否添加一些 cmets 以进行澄清?只是为了刷新我们正在尝试做的事情:查询 USERRELATIONSHIP 表以获取搜索者关注的所有 user_id。然后在 USERSEARCH 表上左加入这些 follow_id,这大大减小了它的大小。只有这样我们才搜索字符串匹配。我相信您的查询首先在 USERSEARCH 表中搜索所有匹配项,而不管关系如何,然后过滤结果,这对我来说似乎可能不必要地昂贵。想法? @Rage:你的理解是正确的。为什么我建议在我的回答中解释... 由于您现有的查询运行速度很快,因此在外部查询中进行过滤可能是可行的解决方案 @Rage: 真的没有办法在 MySQL 5.7 中多次引用查询吗? 不,我不知道。

以上是关于如何在 MySQL 5 .7 中实现 CTE 功能?的主要内容,如果未能解决你的问题,请参考以下文章

在 CTE 中实现标量函数

如何使用 Laravel 5.6 在 Laravel Eloquent 中实现嵌套 MySQL 查询

怎样在MySql中实现筛选数据的功能?

在 Apache Cassandra 中实现 Mysql 或 Psql 关系表(外键约束)功能

如何使用 laravel 5.1+ 在 web 应用程序中实现许可功能

如何在 laravel 5.1 中实现“记住我”?