简体   繁体   English

MySQL:使用别名定义 CONCAT 的数据类型

[英]MySQL: Define data type for CONCAT with alias

This question is about the possibility to define a capacity/max-length when calling CONCAT and storing it as an alias.这个问题是关于在调用CONCAT并将其存储为别名时定义容量/最大长度的可能性。

I have a rather complex MySQL query using Common Table Expressions (CTE) used to model comments.我有一个相当复杂的 MySQL 查询,它使用用于 model 评论的公用表表达式(CTE)。 It creates a new variable path consisting of a comment's score (# of votes) and id seperated by a comma and concatenated with its parent path using CONCAT .它创建了一个新的变量path ,由评论的分数(投票数)和 id 组成,以逗号分隔,并使用CONCAT与其父路径连接。 This allow sorting comments within threads by their score.这允许按分数对线程内的评论进行排序。

A path looks like eg 000010,000005,000014,000008 , which means that the comment with id 8 has a score of 14 and its parent, which itself does not have a parent, has id 5 and a score of 10 . path看起来像例如000010,000005,000014,000008 ,这意味着 id 为8的评论的得分为14 ,其父级(本身没有父级)的 id 为5且得分为10 With all comments having path of this format allows sorting them how I want.所有评论都具有这种格式的path ,允许按照我想要的方式对它们进行排序。

The bottom line is that initially the path only consists of a single score with id, and in the recursive call we will continue concatenating into longer and longer paths when visiting the children.底线是最初的path只包含一个带有 id 的分数,并且在递归调用中,我们将在访问孩子时继续连接成越来越长的路径。

However, it seems that the initial call to CONCAT immediately limits the size to 15 of all subsequent concatenations to the longest initial concatenation, so they are just cut after 15 characters.但是,对CONCAT的初始调用似乎立即将所有后续串联的大小限制为 15 个最长的初始串联,因此它们仅在 15 个字符后被剪切。 Making the initial concatenation longer than 15, will limit the subsequent concatenations to exactly the longest initial concatenation (so effectively doesn't concatenate anything).使初始连接长于 15,会将后续连接限制为恰好最长的初始连接(因此实际上不会连接任何东西)。

Currently I have worked around this issue by initially padding a lot of zeros to the right and remove them in the recursive call.目前我已经通过最初在右侧填充很多零并在递归调用中删除它们来解决这个问题。 However, this uses regular expression and even though it is fairly simply I am afraid it is not good for performance.但是,这使用了正则表达式,尽管它相当简单,但恐怕它对性能不利。

Is there any way to define with the initial call of CONCAT what the capacity/maximum-length of the created alias variable should be?有什么方法可以通过CONCAT的初始调用来定义创建的别名变量的容量/最大长度应该是多少?

This is the query that is made:这是进行的查询:

WITH RECURSIVE first_comments (id, content, parent_id, user_id, created, votes, path) AS (
        (
            SELECT r.id, r.content, r.parent_id, r.user_id, r.created, r.votes, CONCAT_WS(",", LPAD(r.votes,6,0), LPAD(r.id,6,0), LPAD(0,243,0)) as path
            FROM (
                SELECT c.id, c.content, c.parent_id, c.user_id, c.created, COUNT(DISTINCT v.id) AS votes
                FROM comments AS c
                LEFT JOIN comment_votes AS v ON c.id = v.comment_id
                WHERE c.post_id = ? AND c.parent_id IS NULL
                GROUP BY c.id
            ) as r
        )
UNION ALL
        (
            SELECT r.id, r.content, r.parent_id, r.user_id, r.created, r.votes, CONCAT_WS(",", REGEXP_REPLACE(fle.path, ",[0]+$", ""), LPAD(r.votes,6,0), LPAD(r.id,6,0)) as path
            FROM first_comments AS fle
                JOIN (
                    SELECT c.id, c.content, c.parent_id, c.user_id, c.created, COUNT(DISTINCT v.id) AS votes
                    FROM comments AS c
                    LEFT JOIN comment_votes AS v ON c.id = v.comment_id
                    WHERE c.post_id = ?
                    GROUP BY c.id
                ) AS r ON fle.id = r.parent_id
        )
    )
SELECT id, content, parent_id, user_id, path, created, votes FROM first_comments
ORDER BY pat

(Inspired by: Order comments by thread path and by number of total votes ) (灵感来源: 按线程路径和总票数排序评论

Initially I create path with CONCAT_WS(",", LPAD(r.votes,6,0), LPAD(r.id,6,0), LPAD(0,243,0)) as path , which creates the path containing the score and id of top-most comments (without parents), and pads 243 zeros to the right.最初我使用path CONCAT_WS(",", LPAD(r.votes,6,0), LPAD(r.id,6,0), LPAD(0,243,0)) as path创建路径,它创建包含分数的路径和最顶部评论的 id(没有父母),并在右侧填充 243 个零。 So eg 000010,000005,0...0 for the top-most comment with id 5 .因此,例如000010,000005,0...0用于 id 为5的最高评论。

Then recursively (but effectively only with the first recursive call, as thereafter the pattern never matches), we use regular expressions to remove all the trailing zeros including the last comma and add the score and id of this comment: CONCAT_WS(",", REGEXP_REPLACE(fle.path, ",[0]+$", ""), LPAD(r.votes,6,0), LPAD(r.id,6,0)) as path .然后递归(但仅在第一次递归调用时有效,因为此后模式永远不会匹配),我们使用正则表达式删除所有尾随零,包括最后一个逗号,并添加此注释的分数和 id: CONCAT_WS(",", REGEXP_REPLACE(fle.path, ",[0]+$", ""), LPAD(r.votes,6,0), LPAD(r.id,6,0)) as path

It would therefore be nice to just add something to the initial definition of path instead of this work-around.因此,最好在path的初始定义中添加一些东西而不是这种解决方法。 But I don't know what other way could be possible and better?但我不知道还有什么其他方法可能更好?

Any help and idea is appreciated!任何帮助和想法表示赞赏!

// Edit: Problem was solved (and simplified) with GMB's help and a small addition, see my comment under the accepted answer. // 编辑:在 GMB 的帮助和少量补充下,问题已解决(并简化),请参阅我在接受的答案下的评论。

How about appending the paths into a JSON array instead of a string?如何将路径附加到 JSON 数组而不是字符串? This seamlessly overcomes the problem that you are having, and you can still order by .这无缝地解决了您遇到的问题,您仍然可以order by

So:所以:

WITH RECURSIVE first_comments (id, content, parent_id, user_id, created, votes, js_path) AS (
    SELECT 
        c.id, 
        c.content, 
        c.parent_id, 
        c.user_id, 
        c.created, 
        COUNT(DISTINCT v.id) AS votes,
        JSON_ARRAY(LPAD(COUNT(DISTINCT v.id), 6, 0), LPAD(c.id, 6, 0)) as js_path
    FROM comments AS c
    LEFT JOIN comment_votes AS v ON c.id = v.comment_id
    WHERE c.post_id = ? AND c.parent_id IS NULL
    GROUP BY c.id
    UNION ALL
    SELECT 
        r.id, 
        r.content, 
        r.parent_id, 
        r.user_id, 
        r.created, 
        r.votes, 
        JSON_ARRAY_APPEND(
            fle.js_path, 
            '$', LPAD(r.votes, 6, 0), 
            '$', LPAD(r.id, 6, 0)
        ) as js_path
    FROM first_comments AS fle
    JOIN (
        SELECT 
            c.id, 
            c.content, 
            c.parent_id, 
            c.user_id, 
            c.created, 
            COUNT(DISTINCT v.id) AS votes
        FROM comments AS c
        LEFT JOIN comment_votes AS v ON c.id = v.comment_id
        WHERE c.post_id = ?
        GROUP BY c.id
    ) AS r ON fle.id = r.parent_id
)
SELECT id, content, parent_id, user_id, js_path, created, votes 
FROM first_comments
ORDER BY js_path

Note that I simplified the query as follows:请注意,我将查询简化如下:

  • there is no need for a subquery in the anchor of the recursive query递归查询的锚点中不需要子查询

  • union all does not require parentheses around the two queries union all不需要在两个查询周围加上括号

Just for completion, the following is the code I came up with to use with MariaDB, for which the initial creation of the JSON array limits its capacity making it impossible to append to it.只是为了完成,以下是我想出的与 MariaDB 一起使用的代码,其中 JSON 数组的初始创建限制了它的容量,使其无法使用 append。

Instead I use CONCAT_WS and CAST the initial creation of path to a VARCHAR(255) , which should be large enough to contain the longest paths.相反,我使用CONCAT_WSCAST初始创建到VARCHAR(255)path ,它应该足够大以包含最长的路径。

I also had to change the calculation of the score in the path slightly, so that the order they appear is as expected.我还必须稍微更改路径中分数的计算,以便它们出现的顺序符合预期。

WITH RECURSIVE first_comments (id, content, parent_id, user_id, created, level, votes, path) AS (
        SELECT
            c.id,
            c.content,
            c.parent_id,
            c.user_id,
            c.created,
            0 as level,
            COUNT(DISTINCT v.id) AS votes,
            CAST(CONCAT_WS(",", LPAD(999999-COUNT(DISTINCT v.id), 6, 0), LPAD(c.id, 6, 0)) AS VARCHAR(255)) as path
        FROM comments AS c
        LEFT JOIN comment_votes AS v ON c.id = v.comment_id
        WHERE c.post_id = ? AND c.parent_id IS NULL
        GROUP BY c.id
        UNION ALL
        SELECT
            r.id,
            r.content,
            r.parent_id,
            r.user_id,
            r.created,
            fle.level+1 as level,
            r.votes,
            CONCAT_WS(
                ",",
                fle.js_path,
                LPAD(999999-r.votes, 6, 0),
                LPAD(r.id, 6, 0)
            ) as path
        FROM first_comments AS fle
        JOIN (
            SELECT
                c.id,
                c.content,
                c.parent_id,
                c.user_id,
                c.created,
                COUNT(DISTINCT v.id) AS votes
            FROM comments AS c
            LEFT JOIN comment_votes AS v ON c.id = v.comment_id
            WHERE c.post_id = ?
            GROUP BY c.id
        ) AS r ON fle.id = r.parent_id
    )
    SELECT id, content, parent_id, user_id, created, level, votes, path
    FROM first_comments
    ORDER BY path ASC

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM