2014-11-05 17 views
19

Ich frage mich, wie kann ich Paginierung mit Cassandra erreichen.Ergebnisse Paginierung in Cassandra (CQL)

Lassen Sie uns sagen, dass ich einen Blog habe. Der Blog listet maximal 10 Beiträge pro Seite auf. Um ein Benutzer nächste Beiträge zugreifen muss Menü klicken Sie auf Paginierung zuzugreifen Seite 2 (Beiträge 11-20), Seite 3 (Beiträge 21-30) usw.

mit SQL unter MySQL, konnte ich folgendes tun:

Der erste Parameter von LIMIT ist vom Anfang der Ergebnismenge versetzt und das zweite Argument ist die Anzahl der abzurufenden Zeilen. Das obige Beispiel gibt 10 Zeilen beginnend mit Zeile 20 zurück.

Wie kann ich den gleichen Effekt in CQL erreichen?

Ich habe einige Lösungen auf Google gefunden, aber alle von ihnen erfordern "das letzte Ergebnis der vorherigen Abfrage". Es funktioniert, wenn Sie den "nächsten" Knopf haben, um zu einem anderen 10er-Set zu paginieren, aber was, wenn ich von Seite 1 zu Seite 5 springen möchte?

Antwort

8

Probieren Sie die Token-Funktion in CQL mit: http://www.datastax.com/documentation/cql/3.0/cql/cql_using/paging_c.html

Ein weiterer Vorschlag, wenn Sie DSE verwenden, unterstützt solr tief Paging: https://cwiki.apache.org/confluence/display/solr/Pagination+of+Results

+1

Wird tiefes Paging bereits in DSE unterstützt? Soweit ich weiß, wird diese Funktion in Solr 4.7 eingeführt, aber DSE 4.5 (aktuell) verwendet immer noch Solr 4.6. Wird versuchen, diese schwierige –

+1

Es gibt eine Verbesserung für tiefe Paging in 4.7 - Cursor-Funktionalität https://issues.apache.org/jira/browse/SOLR-5463 jedoch Paging gibt es in 4.6 – phact

+2

auch unseren Beispielcode in GitHub Es gibt ein schönes Paging-Beispiel https://github.com/DataStaxCodeSamples/datastax-paging-demo – phact

46

Sie brauchen keine Token zu verwenden, wenn Sie verwenden Kassandra 2.0+.

Cassandra 2.0 hat automatisches Paging. Anstatt die Token-Funktion zum Erstellen von Paging zu verwenden, ist sie jetzt eine integrierte Funktion.

Jetzt können Entwickler über die gesamte Ergebnismenge iterieren, ohne sich darum kümmern zu müssen, dass die Größe des Speichers größer ist. Wenn der Clientcode über die Ergebnisse iteriert, können einige zusätzliche Zeilen abgerufen werden, während alte gelöscht werden.

bei dieser Suche in Java, beachten Sie, dass SELECT-Anweisung alle Zeilen zurückgibt, und die Anzahl der Zeilen auf 100

Ich habe hier eine einfache Erklärung gezeigt wird abgerufen, aber der gleiche Code kann mit geschrieben werden eine vorbereitete Aussage, ein Paar mit einer gebundenen Aussage. Es ist möglich, das automatische Paging zu deaktivieren, wenn dies nicht erwünscht ist. Es ist auch wichtig, verschiedene Einstellungen für die Abrufgröße zu testen, da Sie die Speicherung klein genug halten möchten, aber nicht so klein, dass zu viele Rundreisen zur Datenbank unternommen werden. Schauen Sie sich this Blogpost an, um zu sehen, wie Paging serverseitig funktioniert.

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

Dies sollte die akzeptierte Antwort sein. – sebnukem

+14

Dieses Beispiel hilft nicht wirklich, wenn Sie paginierte Ergebnisse für eine Webseite bereitstellen. Der Benutzer kann eine Seite mit einem Lesezeichen versehen und später wiederkommen, wenn er Tage oder Wochen später dieselben Ergebnisse erwartet. Sie müssen ein Token zurückgeben können, um an genau der gleichen Stelle im Ergebnis fortzufahren. – user3170530

+0

@ user3170530: Überprüfen Sie meine neue Antwort zum manuellen Paging. –

8

Handbuch Paging

Der Fahrer eines PagingState Objekt stellt, der darstellt, wo wir in der Ergebnismenge waren, als die letzte Seite geholt wurde:

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

Dieses Objekt kann serialisiert werden ein String oder ein Byte-Array:

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

Dieses serialisierte Formular kann in einer Form von persistentem Speicher gespeichert werden, um später wiederverwendet zu werden.Wenn dieser Wert später abgerufen wird, können wir es deserialisieren und es in einer Erklärung reinject:

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

Beachten Sie, dass der Paging-Zustand kann nur mit der exakt gleichen Aussage (gleicher Abfrage-String, gleicher Parameter) wieder verwendet werden. Außerdem ist es ein undurchsichtiger Wert, der nur dazu bestimmt ist, gesammelt, gespeichert und wiederverwendet zu werden. Wenn Sie versuchen, den Inhalt zu ändern oder ihn mit einer anderen Anweisung wiederzuverwenden, löst der Treiber einen Fehler aus.

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

+1

Was passiert, wenn die Datenbank zwischen Anrufen mit diesem 'PagingState' wechselt? Funktioniert es noch? Ich verstehe, dass es hier und da eine Seite verpassen könnte und das wäre in Ordnung, aber wird es immer noch seinen Weg finden? Was passiert, wenn die Seite gelöscht wird, auf der sich der Index befindet? –

1

Obwohl die Zählung in CQL verfügbar ist, bisher habe ich noch keine gute Lösung für die Offset Teil ...

Also ... man gesehen Lösung I Überlegt wurde, Seiten mit einem Hintergrundprozess zu erstellen.

In einiger Tabelle, würde ich die Blog-Seite A als eine Reihe von Referenzen auf Seite 1, 2, erstellen ... 10. Dann ein weiterer Eintrag für Blog-Seite B zeigt auf den Seiten 11 bis 20 usw.

Mit anderen Worten, ich würde meinen eigenen Index mit einem Zeilenschlüssel auf die Seitenzahl setzen. Sie können es dennoch etwas flexibler gestalten, da Sie dem Benutzer anbieten können, 10, 20 oder 30 Referenzen pro Seite anzuzeigen. Wenn Sie zum Beispiel 30 einstellen, zeigen Sie die Sätze 1, 2 und 3 als Seite A an, die Sätze 4, 5, 6 als Seite B.

Und wenn Sie einen Backend-Prozess haben, um all das zu handhaben Sie können Ihre Listen aktualisieren, wenn neue Seiten hinzugefügt und alte Seiten aus dem Blog gelöscht werden. Der Prozess sollte wirklich schnell sein (wie 1 Minute für 1.000.000 Zeilen, wenn sogar so langsam ...) und dann können Sie die Seiten finden, die in Ihrer Liste ziemlich sofort angezeigt werden. (Offensichtlich, wenn Sie Tausende von Benutzern haben, die jeweils Hunderte von Seiten veröffentlichen ... kann diese Zahl schnell wachsen.)

Wo es komplizierter wird, wenn Sie eine komplexe WHERE-Klausel anbieten wollten. Standardmäßig zeigt Ihnen ein Blog eine Liste aller Beiträge vom neuesten zum ältesten. Sie können auch Listen von Beiträgen mit dem Tag Cassandra anbieten. Vielleicht möchten Sie die Reihenfolge umkehren, usw. Das macht es schwierig, es sei denn, Sie haben eine Art fortgeschrittenen Weg, um Ihre Index (e) zu erstellen. An meinem Ende habe ich eine C-ähnliche Sprache, die in die Reihe geht und die Werte hintereinander anstößt, um (a) sie auszuwählen und, falls ausgewählt (b), sie zu sortieren. Mit anderen Worten, an meinem Ende kann ich bereits WHERE-Klauseln haben, die so komplex sind wie das, was Sie in SQL hätten. Allerdings zerlege ich meine Listen noch nicht auf Seiten. Nächster Schritt nehme ich an ...

3

Wenn Sie diesen doc "Use Paging-Zustand Token nächstes Ergebnis zu erhalten" lesen,

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

können wir "Paging-Zustand Token" verwenden auf Anwendungsebene Paginieren. So PHP-Logik sollte wie folgt aussehen,

<?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

Mit cassandra-Knoten Treiber für Knoten js (koa js, marko js): Paginierung Problem

Aufgrund des Fehlens von Sprung Funktionalität, Wir müssen arbeiten. Unten ist die Implementierung des manuellen Paging für Knoten-App für den Fall, dass jeder eine Idee bekommen kann.

  • Code für eine einfache Benutzerliste
  • navigate zwischen nächsten und vorherigen Seite Staaten
  • einfach zu replizieren

Es gibt zwei Lösungen, die ich hier werde erklären, sondern gab nur den Code für Lösung 1 unten,

Lösung 1: Pflegen Sie Seitenzustände für next und previous Datensätze (Pflegen Sie den Stapel oder was auch immer Datenstruktur sein st fit)

Lösung 2: Schleife über alle Datensätze mit Limit und alle möglichen Seitenzustände in Variablen speichern und Seiten erzeugen relativ zu ihrem pageStates

in Modell diesen kommentierten Code verwenden, können wir alle Zustände für Seiten

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

Routerfunktionen

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

Modellfunktionen

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 Seite

 <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> 

Ich bin nicht sehr viel mit Knoten js und cassandra db erfahren, kann diese Lösung sicherlich verbessert werden. Lösung 1 arbeitet mit einem Beispielcode, um mit der Paging-Idee zu beginnen. Prost