【问题标题】:How to limit / offset query that returns several rows per unique ID?如何限制/抵消每个唯一 ID 返回多行的查询?
【发布时间】:2011-09-05 00:07:05
【问题描述】:

我的问题是我需要对这个查询中的数据进行分页:

function search($search_term, $limit, $offset)
{
    $id = $this->auth->get_user_id();

    $query = $this->db->query("
    SELECT user_id,
           first_name,
           cars_name,
           cars_id
    FROM   user_profiles
           LEFT JOIN cars
             ON cars.id_fk = user_id
    WHERE  user_id NOT LIKE '$id'
           AND activated = 1
           AND banned = 0
           AND first_name LIKE '%$search_term%'
    ORDER BY first_name ASC
    ");

    $search_data = array();
    foreach ($query->result() as $row) {

        $search_data[$row->user_id]['name'] = $row->first_name;
        $search_data[$row->user_id]['cars'][$row->cars_id] = array(
                'cars_name' => $row->cars_name);
    }
    return $search_data;
}

示例数据表/查询响应将是:

1   JOE   HONDA    123
1   JOE   TOYOTA   124
2   MAC   VW       125
2   MAC   HONDA    126
2   MAC   TESLA    127
3   STU   SUBARU   128
3   STU   KIA      129

-----------
Page 1
-----------
1   JOE   HONDA   123
          TOYOTA  124

2   MAC   VW      125
          HONDA   126

------------
Page 2
------------
3    STU    SUBARU  128
            KIA     129

如果我在 MySQL 查询结束时输入限制和偏移量

...
LIMIT $limit
OFFSET $offset;
");

限制和偏移量应用于行数,而不是按用户分组的行数。

我尝试使用GROUP BY,但无法使用。

我的目标是按上述方式进行查询,但通过计算用户数的行数限制和偏移查询,而不是所有行。

有什么想法吗?

【问题讨论】:

    标签: php mysql pagination group-by limit


    【解决方案1】:

    我没有看到在一个查询中执行此操作的方法。我的解决方案是使用具有相同参数的查询组来获取唯一 ID 的计数:

    SELECT COUNT(1) AS uid_count
    FROM   user_profiles
           LEFT JOIN cars
             ON cars.id_fk = user_id
    GROUP BY user_profiles.user_id
    WHERE  user_id NOT LIKE '$id'
           AND activated = 1
           AND banned = 0
           AND first_name LIKE '%$search_term%'
    

    然后获取 uid_countmysql_num_rows 并使用它来计算上述查询的分页变量。

    【讨论】:

    • 感谢@mel - 我认为这种方法的一个问题是,如果用户行数是3(如我上面的示例),当我使用 LIMIT 或 OFFSET 第二个查询时3行不通,行结构不一样
    【解决方案2】:

    真正的解决方案是使用GROUP BY 子句:

    SELECT SQL_CALC_FOUND_ROWS
           user_id,
           first_name,
           cars_name,
           cars_id
    FROM   user_profiles
           LEFT JOIN cars
             ON cars.id_fk = user_id
    WHERE  user_id NOT LIKE '$id'
           AND activated = 1
           AND banned = 0
           AND first_name LIKE '%$search_term%'
    GROUP BY user_id
    ORDER BY first_name ASC
    LIMIT 100
    

    顺序很重要。先是GROUP BY,然后是ORDER BY,然后是OFFSET/LIMIT

    注意到上面的SQL_CALC_FOUND_ROWS了吗?查询执行后,如果要获取总行数(包括那些因为LIMIT 子句而没有返回的行),只需使用:

    SELECT FOUND_ROWS() AS `count`
    

    并获取count 列。

    但是,正如您所说,行将折叠,您将丢失一些 cars_namecars_id 值。

    另一种解决方案是使用GROUP_CONCAT,然后在PHP中拆分:

    SELECT 
           user_id,
           first_name,
           GROUP_CONCAT(cars_name SEPARATOR ','),
           GROUP_CONCAT(cars_id SEPARATOR ','),
    FROM   user_profiles
           LEFT JOIN cars
             ON cars.id_fk = user_id
    WHERE  user_id NOT LIKE '$id'
           AND activated = 1
           AND banned = 0
           AND first_name LIKE '%$search_term%'
    ORDER BY first_name ASC
    LIMIT 100
    

    这会给你类似的东西:

    1   JOE   HONDA,TOYOTA     123,124
    2   MAC   VW,HONDA,TESLA   125,126,127
    3   STU   SUBARU,KIA       128,129
    

    【讨论】:

    • 感谢@netcoder - 该组会阻止我在查询下方构建该多数组吗?我需要访问数组中更深的变量,我想知道group by 是否会折叠行
    • @torr:更新了答案。不知道会不会有帮助。 ;)
    【解决方案3】:

    如果你想得到这样的列表

    Page 1
    ----------------------
    1   JOE   HONDA    123
    1   JOE   TOYOTA   124
    
    Page 2
    ----------------------
    2   MAC   VW       125
    2   MAC   HONDA    126
    2   MAC   TESLA    127
    
    Page 3
    ----------------------
    3   STU   SUBARU   128
    3   STU   KIA      129
    

    忘记限制,改为这样做:

    A - 首先检索用户 ID 列表并将其插入到临时表中

    CREATE TEMPORARY TABLE `test`.`temp_user_ids` (
      `id` INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
      `user_id` INTEGER UNSIGNED NOT NULL,
    PRIMARY KEY (`id`)
    )
    ENGINE = MEMORY
    

    B - 接下来将相关的 user_id 插入到表中。

    INSERT INTO temp_user_ids
      SELECT null, user_id
    FROM   user_profiles
           LEFT JOIN cars
             ON cars.id_fk = user_id
    WHERE  user_id NOT LIKE '$id'
           AND activated = 1
           AND banned = 0
           AND first_name LIKE '%$search_term%'
    ORDER BY user_id DESC  /*insert in reverse order !*/
    

    最低的user_id是temptable中的last_insert_id,temp_table 项目是按顺序排列的。

    C - 将 SQL @var @current_id 设置为 temp_table 中的 last_insert_id。

    SELECT @current_id:= LAST_INSERT_ID() 
    

    D - 接下来从表中选择相关行,仅使用您想要的 user_id。

    SELECT count(*) as row_count,
           up.user_id,
           first_name,
           group_concat(cars_name) as car_names,
           group_concat(cars_id) as car_ids,
    FROM   user_profiles up
           LEFT JOIN cars
             ON cars.id_fk = up.user_id
           INNER JOIN temp_user_ids t 
             ON (t.user_id = up.user_id)
    WHERE  t.id = @current_id
    GROUP BY up.user_id
    ORDER BY cars.id
    

    E - 现在降低@current_id

    SELECT @current_id:= @current_id - 1;
    

    F - 重复步骤 D 和 E,直到没有更多的行。

    第一个字段row_count 告诉您字段中聚合的行数
    car_namescar_ids。您可以使用 php 的 explode 分隔这些字段。

    【讨论】:

    • 感谢您的回复 - 我用我需要的实际结构更新了 OP - 它与您显示的有点不同 - 这需要更改您的代码吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-08
    • 1970-01-01
    • 1970-01-01
    • 2020-09-09
    • 2020-01-30
    • 2017-08-28
    相关资源
    最近更新 更多