2014-11-05 190 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是工作示例代碼,以分頁的想法開始。歡呼聲