Cassandra中的结果分页(CQL)

Cassandra中的结果分页(CQL),cassandra,cql,cql3,datastax-enterprise,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中实现相同的效果 我在谷歌上找到了一些解决方案,但所有的解决方案都需要“

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

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

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

SELECT * FROM posts LIMIT 20,10;
LIMIT的第一个参数是从结果集开始的偏移量,第二个参数是要获取的行数。上面的示例返回从第20行开始的10行

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


我在谷歌上找到了一些解决方案,但所有的解决方案都需要“上一次查询的最后结果”。“下一步”按钮可以分页到另一个10个结果集,但是如果我想从第1页跳到第5页怎么办?

尝试使用CQL中的令牌函数:

另一个建议是,如果您使用DSE,solr支持深度分页:

如果您使用的是Cassandra 2.0+,则不需要使用代币

卡桑德拉2.0具有自动分页功能。 它不再使用令牌函数来创建分页,而是一个内置功能

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

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

我在这里展示了一个简单的语句,但是同样的代码可以用一个准备好的语句编写,再加上一个绑定语句。如果不需要,可以禁用自动分页。测试各种获取大小设置也很重要,因为您希望保持足够小的内存,但不要太小,以至于需要多次往返数据库。查看博客文章,了解分页如何在服务器端工作

语句stmt=newsimplestatement( “从原始天气数据中选择*” +“其中wsid='725474:99999'” +“年份=2005年,月份=6”); stmt.setFetchSize(24); ResultSet rs=session.execute(stmt); 迭代器iter=rs.Iterator(); 而(!rs.isFullyFetched()){ rs.fetchmoresults(); 行=iter.next(); 系统输出打印项次(行); }
手动分页

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

ResultSet resultSet = session.execute("your query");
// iterate the result set...
PagingState pagingState = resultSet.getExecutionInfo().getPagingState();
此对象可以序列化为字符串或字节数组:

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:

虽然计数在CQL中可用,但到目前为止,我还没有看到用于偏移部分的好解决方案

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

在某些表中,我会创建博客页面A作为对页面1、2、。。。10然后是博客B页的另一个条目,指向第11页到第20页,以此类推

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

如果您有一个后端进程来处理所有这些,您可以在添加新页面和从博客中删除旧页面时更新列表。这个过程应该非常快(比如1分钟,对于1000000行,即使是这么慢…),然后你可以在几乎瞬间找到要显示在列表中的页面。(显然,如果你想让数千名用户每人发布数百页,那么这个数字可能会快速增长。)

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

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

我们可以使用“分页状态令牌”在应用程序级别进行分页。 所以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']);
    }
}
?>

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

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

  • 简单用户列表代码
  • 在下一页和上一页状态之间导航
  • 易于复制
有两种解决方案,我将在这里说明,但只给出了下面解决方案1的代码

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

解决方案2:使用limit循环遍历所有记录,并将所有可能的页面状态保存在变量中,并生成与其页面状态相对应的页面

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

            //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;
        };
        <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>