模拟 row_number 函数

Posted

技术标签:

【中文标题】模拟 row_number 函数【英文标题】:Emulating row_number function 【发布时间】:2012-10-04 12:56:16 【问题描述】:

我有 X 个类别、Y 个论坛和 Z 个主题。

主题属于一个论坛

论坛属于一个类别

我希望能够选择 X 个类别和

为每个类别选择前 3 个论坛和 为每个论坛选择前 4 个主题。

(仅以数字为例)

我通过变量模拟 row_numbers 来做到这一点,因为 mysql 不支持这个开箱即用。

不幸的是,行号仍然有问题。也许有人可以看看这里有什么问题。 为了更好的可读性,我在此处上传了带有代码突出显示的查询和结果

SELECT
    CatRow,
    c_id,
    c_name,
    ForumRow,
    f_id,
    f_name,
    ThreadRow,
    t_id,
    t_title
FROM (
    SELECT
        @cat_row    := IF(@prev_cat    = c.id, @cat_row+1, 1)   AS CatRow,
        @forum_row  := IF(@prev_forum  = f.id, @forum_row+1, 1) AS ForumRow,
        @thread_row := IF(@prev_thread = t.id, @thread_row+1, 1)AS ThreadRow,
        c.id        AS c_id,
        c.name      AS c_name,
        f.id        AS f_id,
        f.name      AS f_name,
        t.id        AS t_id,
        t.title     AS t_title,
        @prev_cat   := c.id,
        @prev_forum := f.id,
        @prev_thread:= t.id
    FROM (
        SELECT
            *
        FROM
            forum_categories c,
            (SELECT @cat_row := 1) AS x,
            (SELECT @prev_cat := '') AS y
        ORDER BY @cat_row
    ) AS c

    LEFT JOIN (
        SELECT
            *
        FROM
            forum_forums AS f,
            (SELECT @forum_row := 1) AS x,
            (SELECT @prev_forum := '') AS y
        ORDER BY @forum_row
    ) AS f ON (c.id = f.fk_forum_category_id )

    LEFT JOIN (
        SELECT
            *
        FROM
            forum_threads AS t,
            (SELECT @thread_row := 1) AS x,
            (SELECT @prev_thread := '') As y
        ORDER BY @thread_row
    ) AS t ON (f.id = t.fk_forum_forums_id )

    ORDER BY c.id ASC, f.id ASC, t.id ASC
) c
-- This is for later to actually limit the joins
-- WHERE CatRow <= 3 AND
-- ForumRow <= 3 AND
-- ThreadRow <= 4

结果如下:

    CatRow  c_id    c_name  ForumRow f_id   f_name      ThreadRow   t_id    t_title
4   1   General 4   2   Talk            1   42  talk
5   1   General 5   2   Talk            1   43  Talk...
6   1   General 6   2   Talk            1   44  locked thread
7   1   General 7   2   Talk            1   45  closed thread
3   1   General 3   2   Talk            1   48  :(:red::confuse::)
1   1   General 1   2   Talk            1   50  gsfdgsdg
2   1   General 2   2   Talk            1   51  asdasd
9   1   General 2   5   Voting          1   47  some title
8   1   General 1   5   Voting          1   49  sadfsad
1   2   Support 1   3   Help            1   40  Hueeelefe
2   2   Support 1   4   Features and Bugs   1   41  What is a bug?
3   2   Support 1   7   Test            1   NULL    NULL    
2   3   News    2   1   News            1   39  News by admin
1   3   News    1   1   News            1   46  further news

最后我需要能够指定:

在哪里 CatRow 论坛行 AND ThreadRow

由于row_numbers 错误,这还不可能。 有什么想法???

其实我希望结果是这样的:

    CatRow  c_id    c_name  ForumRow f_id   f_name      ThreadRow   t_id    t_title
1   1   General 1   2   Talk            1   42  talk
1   1   General 1   2   Talk            2   43  Talk...
1   1   General 1   2   Talk            3   44  locked thread
1   1   General 1   2   Talk            4   45  closed thread
1   1   General 1   2   Talk            5   48  :(:red::confuse::)
1   1   General 1   2   Talk            6   50  gsfdgsdg
1   1   General 1   2   Talk            7   51  asdasd
1   1   General 2   5   Voting          1   47  some title
1   1   General 2   5   Voting          2   49  sadfsad
2   2   Support 1   3   Help            1   40  Hueeelefe
2   2   Support 2   4   Features and Bugs   1   41  What is a bug?
2   2   Support 3   7   Test            1   NULL    NULL    
3   3   News    1   1   News            1   39  News by admin
3   3   News    1   1   News            2   46  further news

为了更好的可读性,我上传了带有代码突出显示的查询和结果的图片: http://i.stack.imgur.com/9tzmH.png http://i.stack.imgur.com/xXF6U.png

【问题讨论】:

【参考方案1】:

SQL Fiddle

set @c_row := 0, @f_row := 0, @t_row := 0;
set @cat := 0, @forum := 0;

select CatRow, c_id, c_name, ForumRow, f_id, f_name, ThreadRow, t_id, title
from (
    select
        @c_row := if(@cat = c_id, @c_row, @c_row + 1) CatRow,
        @f_row := if(@forum = f_id, @f_row, if(@cat = c_id, @f_row + 1, 1)) ForumRow,
        @t_row := if(@forum = f_id, @t_row + 1, 1) ThreadRow,
        @cat := c_id as a,
        @forum := f_id as b,
        c_id,
        c_name,
        f_id,
        f_name,
        t_id,
        title
    from (
        select 
            c.id c_id, f.id f_id, t.id t_id,
            c.name c_name, f.name f_name, title
        from 
            forum_categories c
            inner join
            forum_forums f on f.c_id = c.id
            inner join
            forum_threads t on t.f_id = f.id
        order by 
            c.id, f.id, t.id
    ) s
) s;
+--------+------+---------+----------+------+-------------------+-----------+------+--------------------+
| CatRow | c_id | c_name  | ForumRow | f_id | f_name            | ThreadRow | t_id | title              |
+--------+------+---------+----------+------+-------------------+-----------+------+--------------------+
|      1 |    1 | General |        1 |    2 | Talk              |         1 |   42 | talk               |
|      1 |    1 | General |        1 |    2 | Talk              |         2 |   43 | Talk...            |
|      1 |    1 | General |        1 |    2 | Talk              |         3 |   44 | locked thread      |
|      1 |    1 | General |        1 |    2 | Talk              |         4 |   45 | closed thread      |
|      1 |    1 | General |        1 |    2 | Talk              |         5 |   48 | :(:red::confuse::) |
|      1 |    1 | General |        1 |    2 | Talk              |         6 |   50 | gsfdgsdg           |
|      1 |    1 | General |        1 |    2 | Talk              |         7 |   51 | asdasd             |
|      1 |    1 | General |        2 |    5 | Voting            |         1 |   47 | some title         |
|      1 |    1 | General |        2 |    5 | Voting            |         2 |   49 | sadfsad            |
|      2 |    2 | Support |        1 |    3 | Help              |         1 |   40 | Hueeelefe          |
|      2 |    2 | Support |        2 |    4 | Features and Bugs |         1 |   41 | What is a bug?     |
|      3 |    3 | News    |        1 |    1 | News              |         1 |   39 | News by admin      |
|      3 |    3 | News    |        1 |    1 | News              |         2 |   46 | further news       |
+--------+------+---------+----------+------+-------------------+-----------+------+--------------------+

【讨论】:

【参考方案2】:

我现在已经解决了: 这适用于所有级别。我也为每个线程添加了帖子

SELECT
    CatRow,
    c_id,
    c_name,
    ForumRow,
    f_id,
    f_name,
    ThreadRow,
    t_id,
    t_title,
    PostRow,
    p_id, 
    p_title
FROM (
    SELECT
        @cat_row    := IF(@prev_cat    = c.id, @cat_row, @cat_row+1)   AS CatRow,
        @forum_row  := IF(@prev_forum  = f.id, @forum_row,  IF(@prev_cat   = c.id, @forum_row+1, 1)) AS ForumRow,
        @thread_row := IF(@prev_thread = t.id, @thread_row, IF(@prev_forum = f.id, @thread_row+1, 1))AS ThreadRow,
        @post_row   := IF(@prev_post   = t.id, @post_row,   IF(@prev_thread= t.id, @post_row+1, 1))  AS PostRow,
        c.id        AS c_id,
        c.name      AS c_name,
        f.id        AS f_id,
        f.name      AS f_name,
        t.id        AS t_id,
        t.title     AS t_title,
        p.id        AS p_id,
        p.title     AS p_title,
        @prev_cat   := c.id,
        @prev_forum := f.id,
        @prev_thread:= t.id,
        @prev_post  := p.id
    FROM (
        SELECT
            *
        FROM
            forum_categories,
            (SELECT @cat_row := 0) AS x,
            (SELECT @prev_cat := '') AS y
        ORDER BY YOUR_ORDER_HERE
    ) AS c

    LEFT JOIN (
        SELECT
            *
        FROM
            forum_forums,
            (SELECT @forum_row := 0) AS x,
            (SELECT @prev_forum := '') AS y
        ORDER BY YOUR_ORDER_HERE
    ) AS f ON (c.id = f.fk_forum_category_id )

    LEFT JOIN (
        SELECT
            *
        FROM
            forum_threads,
            (SELECT @thread_row := 0) AS x,
            (SELECT @prev_thread := '') As y
        ORDER BY YOUR_ORDER_HERE
    ) AS t ON (f.id = t.fk_forum_forums_id )

    LEFT JOIN (
        SELECT
            *
        FROM
            forum_posts AS p,
            (SELECT @post_row := 0) AS x,
            (SELECT @prev_post := '') As y
        ORDER BY YOUR_ORDER_HERE
    ) AS p ON (t.id = p.fk_forum_thread_id )
) c
-- WHERE CatRow <= HOW_MANY_CATS AND ForumRow <= HOW_MANY_FORUMS_PER_CAT AND ThreadRow <= HOW_MANY_THREADS_PER_FORUM AND PostRow <= HOW_MANY_POSTS_PER_THREAD



CatRow  c_id    c_name  ForumRow f_id   f_name      ThreadRow   t_id    t_title         PostRow p_id    p_title
1   1   General 1   2   Talk            1   50  gsfdgsdg        1   NULL    NULL
1   1   General 1   2   Talk            2   51  asdasd          1   NULL    NULL
1   1   General 1   2   Talk            3   48  :(:red::confuse::)  1   80  t1
1   1   General 1   2   Talk            3   48  :(:red::confuse::)  2   79  NULL
1   1   General 1   2   Talk            3   48  :(:red::confuse::)  3   78  NULL
1   1   General 1   2   Talk            4   42  talk            1   76  NULL
1   1   General 1   2   Talk            4   42  talk            2   75  sdfg
1   1   General 1   2   Talk            4   42  talk            3   74  NULL
1   1   General 1   2   Talk            4   42  talk            4   73  NULL
1   1   General 1   2   Talk            4   42  talk            5   72  NULL
1   1   General 1   2   Talk            5   43  Talk...         1   NULL    NULL
1   1   General 1   2   Talk            6   44  locked thread       1   NULL    NULL
1   1   General 1   2   Talk            7   45  closed thread       1   NULL    NULL
1   1   General 2   5   Voting          1   49  sadfsad         1   77  NULL
1   1   General 2   5   Voting          2   47  some title      1   NULL    NULL
2   3   News    1   1   News            1   46  further news        1   NULL    NULL
2   3   News    1   1   News            2   39  News by admin       1   71  NULL
2   3   News    1   1   News            2   39  News by admin       2   68  NULL
2   3   News    1   1   News            2   39  News by admin       3   67  NULL
3   2   Support 1   3   Help            1   40  Hueeelefe       1   70  NULL
3   2   Support 1   3   Help            1   40  Hueeelefe       2   69  NULL
3   2   Support 2   4   Features and Bugs   1   41  What is a bug?      1   NULL    NULL
3   2   Support 3   7   Test            1   NULL    NULL            1   NULL    NULL

【讨论】:

【参考方案3】:

使用 LIMIT 的依赖子查询可能会解决您的问题:

SELECT CatRow, c_id, c_name, ForumRow, f_id, f_name, ThreadRow, t_id, t_title
FROM forum_categories c
INNER JOIN (SELECT * FROM forum_forums f WHERE f.fk_forum_category_id=c.id LIMIT 3) AS t1
INNER JOIN (SELECT * FROM forum_threads t WHERE t.fk_forum_forums_id=f.id LIMIT 4) AS t2

【讨论】:

这有两个错误。首先every derived table must have its own alias。其次,无法从联接中的子查询中引用另一个表。

以上是关于模拟 row_number 函数的主要内容,如果未能解决你的问题,请参考以下文章

JPQL row_number()模拟

LINQ to SQL 模拟实现 ROW_NUMBER() OVER(ORDER BY ...) 的功能

Informix 的 Row_number() 函数

Oracle中ROW_NUMBER() OVER()函数用法

ROW_NUMBER() OVER函数的基本用法

使用 ROW_NUMBER() 窗口函数选择行