web-dev-qa-db-fra.com

Pagination des résultats à Cassandra (CQL)

Je me demande comment puis-je réaliser une pagination à l'aide de Cassandra.

Disons que j'ai un blog. Le blog répertorie au maximum 10 messages par page. Pour accéder aux publications suivantes, un utilisateur doit cliquer sur le menu de pagination pour accéder à la page 2 (publications 11 à 20), page 3 (publications 21 à 30), etc.

En utilisant SQL sous MySQL, je pourrais faire ce qui suit:

SELECT * FROM posts LIMIT 20,10;

Le premier paramètre de LIMIT est décalé par rapport au début du jeu de résultats et le deuxième argument est la quantité de lignes à extraire. L'exemple ci-dessus renvoie 10 lignes à partir de la ligne 20.

Comment puis-je obtenir le même effet dans CQL?

J'ai trouvé des solutions sur Google, mais toutes nécessitent d'avoir "le dernier résultat de la requête précédente". Cela fonctionne pour avoir le bouton "suivant" pour paginer vers un autre ensemble de 10 résultats, mais que faire si je veux sauter de la page 1 à la page 5?

28
kazy

Essayez d'utiliser la fonction de jeton dans CQL: https://docs.datastax.com/en/cql/3.1/cql/cql_reference/select_r.html#reference_ds_d35_v2q_xj__paging-through-unrored-unordered-results

Une autre suggestion, si vous utilisez DSE, solr prend en charge la pagination en profondeur: https://cwiki.Apache.org/confluence/display/solr/Pagination+of+Results

9
phact

Vous n'avez pas besoin d'utiliser de jetons si vous utilisez Cassandra 2.0+.

Cassandra 2.0 a la pagination automatique. Au lieu d'utiliser la fonction jeton pour créer une pagination, c'est maintenant une fonctionnalité intégrée.

Désormais, les développeurs peuvent parcourir l'ensemble des résultats sans avoir à se soucier de la taille de celle-ci. Lorsque le code client parcourt les résultats, certaines lignes supplémentaires peuvent être extraites, tandis que les anciennes sont supprimées. 

En regardant cela en Java, notez que l'instruction SELECT renvoie toutes les lignes et que le nombre de lignes extraites est défini sur 100. 

J'ai montré une déclaration simple ici, mais le même code peut être écrit avec une instruction préparée, couplée à une instruction liée. Il est possible de désactiver la pagination automatique si elle n'est pas souhaitée. Il est également important de tester divers paramètres de taille d'extraction, car vous souhaiterez conserver la mémoire suffisamment petite, mais pas trop petite pour permettre un nombre excessif d'allers-retours à la base de données. Découvrez this blog pour voir comment fonctionne la pagination côté serveur.

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);
}
49
Priyank Desai

Pagination manuelle

Le pilote expose un objet PagingState qui représente où nous en étions dans le jeu de résultats lors de l'extraction de la dernière page:

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

Cet objet peut être sérialisé dans un tableau de chaînes ou d'octets:

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

Ce formulaire sérialisé peut être enregistré dans une forme de stockage persistant pour être réutilisé ultérieurement. Lorsque cette valeur est récupérée ultérieurement, nous pouvons la désérialiser et la réinjecter dans une instruction:

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

Notez que l'état de pagination ne peut être réutilisé qu'avec exactement la même instruction (même chaîne de requête, mêmes paramètres). En outre, il s’agit d’une valeur opaque qui doit uniquement être collectée, stockée et réutilisée. Si vous essayez de modifier son contenu ou de le réutiliser avec une instruction différente, le pilote générera une erreur.

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

8
Priyank Desai

Si vous lisez cette documentation "Utiliser le jeton d'état de pagination pour obtenir le résultat suivant",

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

Nous pouvons utiliser "jeton d'état de pagination" pour paginer au niveau de l'application . Ainsi, la logique PHP devrait ressembler à ceci:

<?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']);
    }
}
?>
3
Hemant Thorat

Bien que le count soit disponible en CQL, je n’ai pas encore trouvé de bonne solution pour le offset part ...

Donc… une solution que j'avais envisagée était de créer des ensembles de pages en utilisant un processus en arrière-plan.

Dans certains tableaux, je créerais la page A du blog comme un ensemble de références aux pages 1, 2, ... 10. Ensuite, une autre entrée pour la page B du blog pointant vers les pages 11 à 20, etc.

En d'autres termes, je construirais mon propre index avec une clé de ligne définie sur le numéro de page. Vous pouvez toujours le rendre quelque peu flexible puisque vous pouvez offrir à l'utilisateur de choisir de voir 10, 20 ou 30 références par page. Par exemple, lorsque défini sur 30, vous affichez les ensembles 1, 2 et 3 comme page A, les ensembles 4, 5, 6 comme page B, etc.)

Et si vous avez un processus back-end pour gérer tout cela, vous pouvez mettre à jour vos listes au fur et à mesure que de nouvelles pages sont ajoutées et que les anciennes pages sont supprimées du blog. Le processus devrait être très rapide (par exemple 1 minute pour 1 000 000 de lignes si même lent ...) et vous pourrez alors trouver les pages à afficher dans votre liste à peu près instantanément. (Évidemment, si vous voulez que des milliers d'utilisateurs publient chacun des centaines de pages ... ce nombre peut augmenter rapidement.)

Cela devient plus compliqué si vous souhaitez proposer une clause WHERE complexe. Par défaut, un blog affiche une liste de tous les articles, du plus récent au plus ancien. Vous pouvez également proposer des listes de publications avec le tag Cassandra . Peut-être voudrez-vous inverser l'ordre, etc. Cela rend les choses difficiles, à moins de disposer d'une méthode avancée pour créer vos index. De mon côté, j'ai un langage de type C qui va et qui jette un coup d'œil aux valeurs d'une ligne pour (a) les sélectionner et si elles sont sélectionnées (b) pour les trier. En d'autres termes, de mon côté, je peux déjà avoir des clauses WHERE aussi complexes que celles que vous auriez en SQL. Cependant, je ne divise pas encore mes listes en pages. Prochaine étape je suppose ...

1
Alexis Wilke

Utilisation du pilote cassandra-node pour le noeud js (koa js, marko js): Pagination Problème

En raison de l'absence de fonctionnalité de saut, nous devons contourner le problème. Vous trouverez ci-dessous la mise en œuvre de la pagination manuelle pour une application de nœud au cas où quelqu'un pourrait en avoir une idée.

  • code pour la liste des utilisateurs simples
  • naviguer entre les états de page suivants et précédents
  • facile à reproduire

Il y a deux solutions que je vais énoncer ici, mais je n'ai donné que le code de la solution 1 ci-dessous,

Solution 1: Conservez les états de page pour les enregistrements next et previous (conservez la pile ou la structure de données la mieux adaptée)

Solution 2: Parcourez tous les enregistrements avec limite, enregistrez tous les états de page possibles dans une variable et générez des pages par rapport à leurs états de page.

En utilisant ce code commenté dans le modèle, nous pouvons obtenir tous les états pour les pages

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

Fonctions du routeur

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

Fonctions de modèle

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

Côté 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>

Je n'ai pas beaucoup d'expérience avec les nœuds js et cassandra db, cette solution peut sûrement être améliorée. La solution 1 utilise un exemple de code pour commencer avec l'idée de pagination. À votre santé

0
Suhail Mumtaz Awan