Cassandra(CQL)中的结果分页

时间:2014-11-05 12:31:32

标签: cassandra cql cql3 datastax-enterprise

我想知道如何使用Cassandra实现分页。

我们说我有一个博客。该博客每页最多列出10个帖子。要访问下一篇文章,用户必须单击分页菜单才能访问第2页(帖子11-20),第3页(帖子21-30)等。

在MySQL下使用SQL,我可以执行以下操作:

SELECT * FROM posts LIMIT 20,10;

LIMIT的第一个参数偏离结果集的开头,第二个参数是要获取的行数。上面的示例从第20行开始返回10行。

如何在CQL中实现相同的效果?

我在Google上找到了一些解决方案,但所有这些解决方案都要求"以前查询的最后一个结果"。它适用于" next"按钮分页到另一个10结果集,但如果我想从第1页跳到第5页怎么办?

6 个答案:

答案 0 :(得分:49)

如果您使用的是Cassandra 2.0 +,则无需使用令牌。

Cassandra 2.0具有自动分页功能。 它不是使用令牌功能来创建分页,而是现在的内置功能。<​​/ p>

现在,开发人员可以迭代整个结果集,而不必关心它的大小是否大于内存。当客户端代码迭代结果时,可以获取一些额外的行,而丢弃旧的行。

在Java中看一下,请注意SELECT语句返回所有行,并且检索的行数设置为100.

我在这里展示了一个简单的声明,但是相同的代码可以使用预准备语句编写,并与绑定语句结合使用。如果不需要,可以禁用自动分页。测试各种提取大小设置也很重要,因为你需要保持足够小的记忆,但不要太小,以至于太多的往返数据库。查看this博文,了解分页如何在服务器端工作。

Statement stmt = new SimpleStatement(
                  "SELECT * FROM raw_weather_data"
                  + " WHERE wsid= '725474:99999'"
                    + " AND year = 2005 AND month = 6");
stmt.setFetchSize(24);
ResultSet rs = session.execute(stmt);
Iterator<Row> iter = rs.iterator();
while (!rs.isFullyFetched()) {
   rs.fetchMoreResults();
   Row row = iter.next();
   System.out.println(row);
}

答案 1 :(得分:9)

答案 2 :(得分:9)

手动分页

驱动程序公开一个PagingState对象,该对象表示在获取最后一页时我们在结果集中的位置:

ResultSet resultSet = session.execute("your query");
// iterate the result set...
PagingState pagingState = resultSet.getExecutionInfo().getPagingState();

此对象可以序列化为String或字节数组:

String string = pagingState.toString();
byte[] bytes = pagingState.toBytes();

此序列化表单可以保存在某种形式的持久存储中,以便以后重用。当稍后检索该值时,我们可以反序列化它并将其重新注入语句中:

PagingState pagingState = PagingState.fromString(string);
Statement st = new SimpleStatement("your query");
st.setPagingState(pagingState);
ResultSet rs = session.execute(st);

请注意,分页状态只能使用完全相同的语句(相同的查询字符串,相同的参数)重用。此外,它是一个不透明的值,仅用于收集,存储和重复使用。如果您尝试修改其内容或使用不同的语句重复使用它,驱动程序将引发错误。

Src:http://datastax.github.io/java-driver/manual/paging/

答案 3 :(得分:2)

如果您阅读本文档“使用分页状态令牌获取下一个结果”,

https://datastax.github.io/php-driver/features/result_paging/

我们可以使用“分页状态令牌”在应用程序级别进行分页。 所以PHP逻辑看起来应该是,

<?php
$limit = 10;
$offset = 20;

$cluster   = Cassandra::cluster()->withContactPoints('127.0.0.1')->build();
$session   = $cluster->connect("simplex");
$statement = new Cassandra\SimpleStatement("SELECT * FROM paging_entries Limit ".($limit+$offset));

$result = $session->execute($statement, new Cassandra\ExecutionOptions(array('page_size' => $offset)));
// Now $result has all rows till "$offset" which we can skip and jump to next page to fetch "$limit" rows.

while ($result->pagingStateToken()) {
    $result = $session->execute($statement, new Cassandra\ExecutionOptions($options = array('page_size' => $limit,'paging_state_token' => $result->pagingStateToken())));
    foreach ($result as $row) {
      printf("key: '%s' value: %d\n", $row['key'], $row['value']);
    }
}
?>

答案 4 :(得分:1)

虽然 count 在CQL中可用,但到目前为止我还没有看到 offset 部分的良好解决方案......

所以......我一直在考虑的一个解决方案是使用后台进程创建页面集。

在某些表格中,我会将博客页面A创建为对第1页,第2页,...的引用.10。然后,博客页面B的另一个条目指向第11页到第20页等。

换句话说,我将构建自己的索引,并将行键设置为页码。您仍然可以使其具有一定的灵活性,因为您可以让用户选择每页查看10,20或30个引用。例如,设置为30时,将第1,2和3组显示为第A页,将第4,5,6组设为第B页等。)

如果您有后端处理来处理所有这些,您可以在添加新页面并从博客中删除旧页面时更新列表。这个过程应该非常快(如果即使那么慢,也需要1分钟,1,000,000行......)然后你可以立即找到要在列表中显示的页面。 (显然,如果你要让成千上万的用户分别发布数百页......这个数字可以快速增长。)

如果你想提供一个复杂的WHERE子句,那就变得更复杂了。默认情况下,博客会显示从最新到最旧的所有帖子的列表。您还可以提供标记为 Cassandra 的帖子列表。也许你想要颠倒顺序等等。除非你有某种形式的高级方法来创建你的索引,否则这很难。在我的结尾,我有一个类似C语言的东西,可以查看和查看连续的值,以便(a)选择它们,如果选择(b)对它们进行排序。换句话说,在我的最后,我已经有了WHERE子句,就像你在SQL中拥有的一样复杂。但是,我还没有在页面中分解我的列表。下一步我想......

答案 5 :(得分:0)

  

对节点js使用cassandra-node驱动程序(koa js,marko js):分页   问题

由于缺少跳过功能,我们需要解决。以下是节点应用程序的手动分页的实现,以防任何人都能得到一个想法。

  • 简单用户列表的代码
  • 在下一页和上一页状态之间导航
  • 易于复制

我将在此处说明两种解决方案,但仅提供下面的解决方案1的代码,

解决方案1:维护nextprevious记录的页面状态(维护堆栈或最适合的数据结构)

解决方案2:循环遍历所有带限制的记录,并将所有可能的页面状态保存在变量中并生成相对于其页面状态的页面

在模型中使用此注释代码,我们可以获取页面的所有状态

            //for the next flow
            //if (result.nextPage) {
            // Retrieve the following pages:
            // the same row handler from above will be used
            // result.nextPage();
            //}

路由器功能

    var userModel = require('/models/users');
          public.get('/users', users);
          public.post('/users', filterUsers);

    var users = function* () {//get request
        var data = {};
        var pageState = { "next": "", "previous": "" };
        try {
            var userCount = yield userModel.Count();//count all users with basic count query

            var currentPage = 1;
            var pager = yield generatePaging(currentPage, userCount, pagingMaxLimit);
            var userList = yield userModel.List(pager);
            data.pageNumber = currentPage;
            data.TotalPages = pager.TotalPages;
            console.log('--------------what now--------------');
            data.pageState_next = userList.pageStates.next;
            data.pageState_previous = userList.pageStates.previous;
            console.log("next ", data.pageState_next);
            console.log("previous ", data.pageState_previous);

            data.previousStates = null;

            data.isPrevious = false;
            if ((userCount / pagingMaxLimit) > 1) {
                data.isNext = true;
            }

            data.userList = userList;
            data.totalRecords = userCount;
            console.log('--------------------userList--------------------', data.userList);
            //pass to html template
        }
        catch (e) {
            console.log("err ", e);
            log.info("userList error : ", e);
        }
   this.body = this.stream('./views/userList.marko', data);
   this.type = 'text/html';
    };

    //post filter and get list
    var filterUsers = function* () {
        console.log("<------------------Form Post Started----------------->");
        var data = {};
        var totalCount;
        data.isPrevious = true;
        data.isNext = true;

        var form = this.request.body;
        console.log("----------------formdata--------------------", form);
        var currentPage = parseInt(form.hdpagenumber);//page number hidden in html
        console.log("-------before current page------", currentPage);
        var pageState = null;
        try {
            var statesArray = [];
            if (form.hdallpageStates && form.hdallpageStates !== '') {
                statesArray = form.hdallpageStates.split(',');
            }
            console.log(statesArray);

            //develop stack to track paging states
            if (form.hdpagestateRequest === 'next') {
                console.log('--------------------------next---------------------');
                currentPage = currentPage + 1;
                statesArray.push(form.hdpageState_next);
                pageState = form.hdpageState_next;
            }
            else if (form.hdpagestateRequest === 'previous') {
                console.log('--------------------------pre---------------------');
                currentPage = currentPage - 1;
                var p_st = statesArray.length - 2;//second last index
                console.log('this index of array to be removed ', p_st);
                pageState = statesArray[p_st];
                statesArray.splice(p_st, 1);
                //pageState = statesArray.pop();
            }
            else if (form.hdispaging === 'false') {
                currentPage = 1;
                pageState = null;
                statesArray = [];
            }


            data.previousStates = statesArray;
            console.log("paging true");

            totalCount = yield userModel.Count();

            var pager = yield generatePaging(form.hdpagenumber, totalCount, pagingMaxLimit);
            data.pageNumber = currentPage;
            data.TotalPages = pager.TotalPages;

            //filter function - not yet constructed
            var searchUsers = yield userModel.searchList(pager, pageState);
            data.usersList = searchUsers;
            if (searchUsers.pageStates) {
                data.pageStates = searchUsers.pageStates;
                data.next = searchUsers.nextPage;
                data.pageState_next = searchUsers.pageStates.next;
                data.pageState_previous = searchUsers.pageStates.previous;

                //show previous and next buttons accordingly
                if (currentPage == 1 && pager.TotalPages > 1) {
                    data.isPrevious = false;
                    data.isNext = true;
                }
                else if (currentPage == 1 && pager.TotalPages <= 1) {
                    data.isPrevious = false;
                    data.isNext = false;
                }
                else if (currentPage >= pager.TotalPages) {
                    data.isPrevious = true;
                    data.isNext = false;
                }
                else {
                    data.isPrevious = true;
                    data.isNext = true;
                }
            }
            else {
                data.isPrevious = false;
                data.isNext = false;
            }
            console.log("response ", searchUsers);
            data.totalRecords = totalCount;

           //pass to html template
        }
        catch (e) {
            console.log("err ", e);
            log.info("user list error : ", e);
        }
        console.log("<------------------Form Post Ended----------------->");
   this.body = this.stream('./views/userList.marko', data);
   this.type = 'text/html';
    };

    //Paging function
    var generatePaging = function* (currentpage, count, pageSizeTemp) {
        var paging = new Object();
        var pagesize = pageSizeTemp;
        var totalPages = 0;
        var pageNo = currentpage == null ? null : currentpage;
        var skip = pageNo == null ? 0 : parseInt(pageNo - 1) * pagesize;
        var pageNumber = pageNo != null ? pageNo : 1;
        totalPages = pagesize == null ? 0 : Math.ceil(count / pagesize);
        paging.skip = skip;
        paging.limit = pagesize;
        paging.pageNumber = pageNumber;
        paging.TotalPages = totalPages;
        return paging;
    };

模型功能

    var clientdb = require('../utils/cassandradb')();
    var Users = function (options) {
      //this.init();
      _.assign(this, options);
    };

    Users.List = function* (limit) {//first time
            var myresult; var res = [];
            res.pageStates = { "next": "", "previous": "" };

            const options = { prepare: true, fetchSize: limit };
            console.log('----------did i appeared first?-----------');

            yield new Promise(function (resolve, reject) {
                clientdb.eachRow('SELECT * FROM users_lookup_history', [], options, function (n, row) {
                    console.log('----paging----rows');
                    res.push(row);
                }, function (err, result) {
                    if (err) {
                        console.log("error ", err);
                    }
                    else {
                        res.pageStates.next = result.pageState;
                        res.nextPage = result.nextPage;//next page function
                    }
                    resolve(result);
                });
            }).catch(function (e) {
                console.log("error ", e);
            }); //promise ends

            console.log('page state ', res.pageStates);
            return res;
        };

        Users.searchList = function* (pager, pageState) {//paging filtering
            console.log("|------------Query Started-------------|");
            console.log("pageState if any ", pageState);
            var res = [], myresult;
            res.pageStates = { "next": "" };
            var query = "SELECT * FROM users_lookup_history ";
            var params = [];

            console.log('current pageState ', pageState);
            const options = { pageState: pageState, prepare: true, fetchSize: pager.limit };
            console.log('----------------did i appeared first?------------------');

            yield new Promise(function (resolve, reject) {
                clientdb.eachRow(query, [], options, function (n, row) {
                    console.log('----Users paging----rows');
                    res.push(row);
                }, function (err, result) {
                    if (err) {
                        console.log("error ", err);
                    }
                    else {
                        res.pageStates.next = result.pageState;
                        res.nextPage = result.nextPage;
                    }
                    //for the next flow
                    //if (result.nextPage) {
                    // Retrieve the following pages:
                    // the same row handler from above will be used
                    // result.nextPage();
                    //}
                    resolve(result);
                });
            }).catch(function (e) {
                console.log("error ", e);
                info.log('something');
            }); //promise ends

            console.log('page state ', pageState);

            console.log("|------------Query Ended-------------|");
            return res;
        };

Html方

        <div class="box-footer clearfix">
        <ul class="pagination pagination-sm no-margin pull-left">
             <if test="data.isPrevious == true">
             <li><a class='submitform_previous' href="">Previous</a></li>
             </if>
             <if test="data.isNext == true">
                <li><a class="submitform_next" href="">Next</a></li>
             </if>
         </ul>
         <ul class="pagination pagination-sm no-margin pull-right">
                    <li>Total Records : $data.totalRecords</li>&nbsp;&nbsp;
                    <li> | Total Pages : $data.TotalPages</li>&nbsp;&nbsp;
                    <li> | Current Page : $data.pageNumber</li>&nbsp;&nbsp;
         </ul>
         </div>

我对节点js和cassandra db不太熟悉,这个解决方案肯定可以改进。解决方案1是工作示例代码,以分页思想开始。干杯