Pagination des résultats en Cassandra (CQL)
je me demande comment je peux réaliser la pagination en utilisant Cassandra.
disons que j'ai un blog. Le blog des listes de max 10 messages par page. Pour accéder aux prochains billets, un utilisateur doit cliquer sur le menu de pagination pour accéder à la page 2 (billets 11-20), à la page 3 (billets 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é à partir du début du jeu de résultats et le second argument est la quantité de lignes à récupérer. Exemple ci-dessus renvoie 10 lignes à partir de la ligne 20.
Comment puis-je obtenir le même effet en CQL?
j'ai trouvé quelques solutions sur Google, mais ils ont besoin d'avoir "le dernier résultat de la requête". Il fonctionne pour avoir" next " bouton pour paginer à un autre 10-results-set, mais que faire si je veux sauter de la page 1 à la page 5?
6 réponses
essayez d'utiliser la fonction token dans CQL: https://docs.datastax.com/en/cql/3.1/cql/cql_reference/select_r.html#reference_ds_d35_v2q_xj__paging-through-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+de+Résultats
Vous n'avez pas besoin d'utiliser des jetons, si vous utilisez Cassandra 2.0+.
maintenant les développeurs peuvent itérer sur l'ensemble du résultat, sans avoir à se soucier que sa taille est plus grande que la mémoire. Comme le code client itère au-dessus des résultats, quelques lignes supplémentaires peuvent être récupérées, tandis que les anciennes sont abandonnées.
en regardant ceci en Java, notez que SELECT statement retourne toutes les lignes, et le nombre de lignes récupérées est défini à 100.
j'ai montré une simple déclaration, mais le même code peut être écrit avec une déclaration préparée, en couple avec une limite de déclaration. 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 de fetch, car vous voudrez garder le memorize assez petit, mais pas si petit que trop de voyages aller-retour à la base de données sont pris. Découvrez blog pour voir comment la pagination fonctionne 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);
}
Pagination Manuelle
le pilote expose un objet Paginstate qui représente où nous étions dans le jeu de résultats lorsque la dernière page a été récupérée:
ResultSet resultSet = session.execute("your query");
// iterate the result set...
PagingState pagingState = resultSet.getExecutionInfo().getPagingState();
cet objet peut être sérialisé en une chaîne de caractères ou un tableau de octets:
String string = pagingState.toString();
byte[] bytes = pagingState.toBytes();
cette forme sérialisée peut être sauvegardée dans une forme de stockage persistant pour être réutilisée plus tard. Lorsque cette valeur est récupérée plus tard, nous pouvons désérialiser et de réinjecter dans un déclaration:
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 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 n'est destinée qu'à être collectée, stockée et réutilisée. Si vous essayez de modifier son contenu ou de le réutiliser avec un autre énoncé, le pilote va générer une erreur.
Si vous avez lu cette doc "Utilisation de la pagination de l'état de jeton pour obtenir la prochaine résultat",
https://datastax.github.io/php-driver/features/result_paging/
nous pouvons utiliser le "jeton d'état de pagination" pour paginer au niveau de l'application. Donc la logique PHP devrait ressembler à,
<?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']);
}
}
?>
Bien que le count est disponible en CQL, jusqu'à présent je n'ai pas vu une bonne solution pour le décalage partie...
... une solution que j'envisageais était de créer des ensembles de pages en utilisant un processus de fond.
dans un tableau, je créerais la page de blog A comme un ensemble de références à la page 1, 2, ... 10. Puis une autre entrée pour la page de blog B pointant vers les pages 11 à 20,etc.
en d'autres termes, je construirais mon propre index avec une clé de ligne définie au numéro de page. Vous pouvez encore 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, lorsqu'elle est fixée à 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 d'arrière-plan pour gérer tout cela, vous pouvez mettre à jour vos listes que de nouvelles pages sont ajoutées et les anciennes pages sont supprimées du blog. Le processus devrait être vraiment rapide (comme 1 min. pour 1.000.000 de lignes si Pair que lente...) et puis vous pouvez trouver les pages à afficher dans votre liste de quasi instantanément. (Évidemment, si vous voulez avoir des milliers d'utilisateurs chacun affichant des centaines de pages... ce nombre peut croître rapidement.)
où il devient plus compliqué est si vous vouliez offrir une clause complexe où. Par défaut un blog vous montre une liste de tous les postes de la plus récente à la plus ancienne. Vous pouvez également offrir des listes de messages avec la balise Cassandra. Peut-être vous voulez inverse l'ordre, etc. Cela rend la tâche difficile à moins que vous n'ayez une forme quelconque de méthode avancée pour créer votre(vos) index. De mon côté, j'ai un langage de type C qui va et jette un coup d'oeil aux valeurs dans une rangée pour (a) les sélectionner et si sélectionné (B) pour les trier. En d'autres termes, de mon côté, je peux déjà avoir des clauses aussi complexes que ce que vous auriez en SQL. Cependant, je ne divise pas encore mes listes en pages. Prochaine étape, je suppose...
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 travailler autour. Ci-dessous est la mise en œuvre de la pagination manuelle pour l'application de noeud en cas de n'importe qui peut obtenir une idée.
- code pour la simple liste des utilisateurs
- naviguer entre suivante et page précédente unis
- facile à reproduire
il y a deux solutions que je je vais dire ici, mais seulement donné le code pour la solution 1 ci-dessous,
Solution 1: maintenir les états de page pour next
et previous
enregistrements (maintenir la pile ou quelle que soit la structure de données correspondent le mieux)
Solution 2: Passer en boucle tous les enregistrements avec limit et sauvegarder tous les états de page possibles dans variable et générer des pages relativement à leurs états de page
en utilisant ce code commenté dans model, 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();
//}
Routeur Fonctions
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 Du 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;
};
Html côté
<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>
<li> | Total Pages : $data.TotalPages</li>
<li> | Current Page : $data.pageNumber</li>
</ul>
</div>
Je n'ai pas beaucoup d'expérience avec node js et cassandra db, cette solution peut certainement être améliorée. La Solution 1 est le code d'exemple de travail pour commencer avec l'idée de paging. Acclamations