2014-11-05 192 views
19

我想知道如何使用Cassandra来实现分页。Cassandra的结果分页(CQL)

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

在MySQL下使用SQL,我可以做到以下几点:

SELECT * FROM posts LIMIT 20,10; 

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

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

我在Google上找到了一些解决方案,但是他们都需要有“上一个查询的最后结果”。它适用于将“下一个”按钮分页到另一个10结果集,但如果我想从第1页跳转到第5页,该怎么办?

回答

8

尝试使用CQL令牌功能: http://www.datastax.com/documentation/cql/3.0/cql/cql_using/paging_c.html

另一项建议,如果你正在使用DSE,Solr的支持深分页: https://cwiki.apache.org/confluence/display/solr/Pagination+of+Results

+1

DSE已经支持深度寻呼了吗?据我所知,这个特性是在Solr 4.7中引入的,但是DSE 4.5(最新版本)仍在使用Solr 4.6。将尝试这种艰难 – 2014-11-06 06:45:24

+1

4.7 - 深度分页有一个改进 - 游标功能https://issues.apache.org/jira/browse/SOLR-5463但分页在4.6 – phact 2014-11-06 06:49:41

+2

也检查我们的示例代码在github有一个很好的分页示例https://github.com/DataStaxCodeSamples/datastax-paging-demo – phact 2014-11-06 15:50:09

46

你并不需要使用令牌,如果你正在使用Cassandra 2.0+。

Cassandra 2.0具有自动寻呼功能。 而不是使用标记功能来创建分页,它现在是一个内置的功能。

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

在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); 
} 
+4

这应该是被接受的答案。 – sebnukem 2015-11-27 15:25:40

+14

如果您为网页提供分页结果,则此示例无效。用户可以为一个页面添加书签,稍后再回来,期望在几天或几周后找到相同的结果。他们需要能够传回令牌以恢复结果中完全相同的位置。 – user3170530 2016-03-06 07:33:38

+0

@ user3170530:查看我关于手动分页的新答案。 – 2016-07-12 19:31:47

8

手动分页

的驱动程序公开表示,我们是在结果集时最后一页是取一个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); 

注意,传呼状态只能用完全一样的语句(相同的查询字符串,相同参数)被重用。此外,这是一个不透明的价值,只是意味着收集,存储和重用。如果您尝试修改其内容或使用其他语句重新使用它,则驱动程序将引发错误。

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

+1

如果数据库在使用该“PagingState”的调用之间改变会怎么样?它仍然有效吗?我知道它可能会在这里或那里错过一个页面,那很好,但是它仍然会找到它的方式吗?如果索引所在的页面被删除,该怎么办? – 2016-09-27 05:35:20

1

虽然计数是CQL可用,到目前为止,我还没有看到对很好的解决偏移部分...

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

在一些表格,我将创建博客页面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)对它们进行排序。换句话说,就我而言,我已经可以拥有与SQL中所拥有的一样复杂的WHERE子句。但是,我还没有在页面中分列我的列表。下一步我想...

3

如果你看过这个文档“使用传呼状态令牌来获取下一个结果”,

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']); 
    } 
} 
?> 
0

使用对节点JS卡桑德拉节点驱动程序(KOA JS,马尔科JS):分页 问题

由于不存在的跳过功能,我们需要解决。下面是节点应用程序的手动分页的实现,以便任何人都可以得到想法。

  • 简单的用户列表的代码
  • 下一个和前一页的状态之间进行切换
  • 容易复制

有两种解决方案,我要去在此声明,但只给了代码下面的解决方案1,

解决方案1:维护页面状态nextprevious记录(维护堆栈或任何数据结构为ST FIT)

解决方案2:循环通过与限制的所有记录和保存所有可能的页面的状态变量和相对生成页面他们pageStates

使用模型这个注释的代码,我们可以得到所有国家

  //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和卡桑德拉分贝经历,这个解决方案一定能得到改善。解决方案1是工作示例代码,以分页的想法开始。欢呼声