Exemple de pagination avec ScrollableResultSet
Bonjour à toutes et à tous,
Ceci est un exemple complet d'utilisation d'un %ScrollableResultSet pour la pagination des résultats à l'aide de %DynamicQuery:SQL et créer une réponse JSON incluant les détails de la page.
N'hésitez pas à l'utiliser, le partager, le commenter ou l'améliorer.
Cordialement
ClassMethod getPersonsPag(iAge As %Integer, sortField As %String = 1, sortOrder As %String = 2, pageSize As %String = 20, pageIndex As %String = 1) As %DynamicObject
{
set out = []
set vFrom = ((pageIndex -1 ) * pageSize)+1
set vTo = vFrom + (pageSize-1)
set sql = "SELECT ID,SSN,LastName,GivenName,SecondaryName, Gender, Age "_
"FROM Sample.Person WHERE Age > ? "_
"Order By "_sortField _" "_ $CASE(sortOrder,1:"ASC",2:"DESC",:"ASC")
Set rs=##class(%ScrollableResultSet).%New("%DynamicQuery:SQL")
set sc = rs.Prepare(sql)
set sc = rs.Execute(iAge) If $$$ISERR(sc) Do DisplayError^%apiOBJ(sc) Quit
if (rs.Count()=0) Quit {"msg" : "No Records Found"}
Do rs.CurrRowSet(vFrom)
if pageSize >= rs.Count() set pageSize = rs.Count()
try{
FOR i=1:1:pageSize{
Do out.%Push({
"index": (i),
"pid": (rs.%Get("ID")),
"ssn" : (rs.%Get("SSN")),
"lastname" : (rs.%Get("LastName")) ,
"givenname": (rs.%Get("GivenName")),
"secondaryname": (rs.%Get("SecondaryName")) ,
"gender": (rs.%Get("Gender")),
"age": (rs.%Get("Age") )
})
Do rs.%Next()
}
}
catch(e){ }
set outJson = []
Do outJson.%Push({
"pageSize":(pageSize),
"pageIndex":(pageIndex),
"fromIndex":(vFrom),
"toIndex":(vFrom+i - 1),
"resultSetTotal":(rs.Count()),
"pageRecords":(i),
"pages":($NORMALIZE((rs.Count()/pageSize),0)),
"resultSet":(out)
})
return outJson
}Comments
@Guillaume Rongier ajoute :
Bonjour Rubén,
Une autre proposition sur IRIS 2021.1+ peut être celle-ci avec l'utilisation de la fonction nouvelle fenêtre (OVER) :
ClassMethod getPersonsPagWindow(iAge As%Integer, sortField As%String = 1, sortOrder As%String = 2, pageSize As%String = 20, pageIndex As%String = 1) As%DynamicObject
{
set out = []
set vFrom = ((pageIndex -1 ) * pageSize)+1set vTo = vFrom + (pageSize-1)
set sql = "SELECT * "_
"FROM ( SELECT persons.* "_
" , ROW_NUMBER() OVER (ORDER By "_sortField_" "_ $CASE(sortOrder,1:"ASC",2:"DESC",:"ASC")_
" ) rn "_
" FROM Sample.Person persons where Age > ? "_
" ) tmp "_
"WHERE rn between "_vFrom_" and "_vTo_" "_
"ORDER By "_sortField_" "_ $CASE(sortOrder,1:"ASC",2:"DESC",:"ASC")
Set rs=##class(%ResultSet).%New("%DynamicQuery:SQL")
set sc = rs.Prepare(sql)
set sc = rs.Execute(iAge) If$$$ISERR(sc) Do DisplayError^%apiOBJ(sc) Quitwhile rs.%Next() {
Do out.%Push({
"pid": (rs.%Get("ID")),
"ssn" : (rs.%Get("SSN")),
"lastname" : (rs.%Get("LastName")) ,
"givenname": (rs.%Get("GivenName")),
"secondaryname": (rs.%Get("SecondaryName")) ,
"gender": (rs.%Get("Gender")),
"age": (rs.%Get("Age") )
})
}
set outJson = []
Do outJson.%Push({
"pageSize":(pageSize),
"pageIndex":(pageIndex),
"fromIndex":(vFrom),
"toIndex":(vTo),
"resultSet":(out)
})
return outJson
}Je compare les deux solutions sur un dataset de 100 000 lignes sans index avec un résultat de 20 éléments en page 1 et voici les résultats :
"getPersonsPag timed : 1,647 secondes""getPersonsPagWindow timed : 0,247 secondes"Je suppose que la fonction window est plus rapide car vous n'avez pas besoin de récupérer toutes les données de manière globale avant la pagination.
@Eduard Lebedyuk précise :
L'idée d'un ensemble de résultats défilants est d'appeler Save/OpenId - et l'ensemble de résultats continuera automatiquement sur une ligne suivante. Vous n'avez donc pas besoin de gérer les indices avant/arrière :
Un exemple ici
C'est également environ 3 fois plus rapide puisque la requête n'est exécutée qu'une seule fois :
do##class(User.Pagination).Time("Save")
Save took 0,0048 sec
do##class(User.Pagination).Time("NoSave")
NoSave took 0,0143 secEt @Vitaliy Serdtsev conclue :
Ajout à ce qui précède : pagination SQL accélérée
Les différences de performance sont impressionnantes, ce qui montre bien à mon sens qu'il est préférable de se poser les questions d'implémentation en tout début de phase de développement et/ou créer des classes façades