Strapi - GraphQL - Issue on sorting and paginating - sorting

I’ve had an issue using Strapi when I try to combine sorting and pagination on my GraphQL requests from a NextJS front. Basically, I am trying to query the backend to fetch all records sorted by creation date from the newest to the latest, and THEN paginate them.
My issue is : the pagination occurs BEFORE the sorting. Example :
If I have 4 docs like so :
- Document 1
- Document 2
- Document 3
- Document 4
I want them sorted by date and 2 by page, which would give me :
Page 1
- Document 4
- Document 3
Page 2
- Document 2
- Document 1
But I have this instead :
Page 1
- Document 2
- Document 1
Page 2
- Document 4
- Document 3
My query looks like this :
query getDocuments {
documents (
sort:"updatedAt:asc"
pagination: {page: ${page}, pageSize: 10}
){
meta {pagination {total page pageCount}}
data {
id
attributes {
name
category
updatedAt
creator {data {attributes {username}}}
}
}
}
}
It paginates first, then sorts. Did you observe this behavior ? Is it intended ? Is it GraphQL itself working this way or Strapi ?
Thanks in advance !

Related

Strapi query for relation with IN filter and AND clause

I have a collection blogs that has&belongs to many categories which have a title field.
I want to query blogs for results that have at least two specific category titles: foo and bar.
my blogs has the following entries:
2 entry with 0 categories
1 entry with 1 category: foo
1 entry with 2 categories: foo, bar
I've tried:
GET /blogs?_where[0][categories.title_in]=foo&_where[1][categories.title_in]=bar
--> 0 results
GET /blogs?[categories.title_in]=foo&[categories.title_in]=bar
--> 2 results
GET /blogs?[categories.title_in][0]=foo&[categories.title_in][1]=bar
--> 2 results
Basically I cannot get the AND condition to take. Filtering for both having foo AND bar categories should return only 1 result (the last one in the example)
How do I do that?
This seem to work for me in Strapi v4
multiple AND Filter
localhost:1337/api/artworks?publicationState=live&sort[0]=name&fields[0]=name&fields[1]=slug&fields[2]=price&fields[3]=width&fields[4]=height&populate[0]=image&populate[1]=artist&populate[2]=artwork_art_type&populate[3]=artwork_format&populate[4]=artwork_subject&populate[5]=artwork_technique&pagination[page]=&pagination[pageSize]=40&filters[$and][0][artwork_art_type][name][$eq]=${artworkArtTypeValue}&filters[$and][0][artwork_format][name][$eq]=${artworkFormatValue}
IN filter display all except with current ID
/artworks?publicationState=live&populate[0]=image&filters[artist][id][$eq]=${artworkArtistId}&filters[id][$ne]=${artworkId}&pagination[pageSize]=100

how would I add index to all returned results when using pagination

I want to give index to each result from a query, something like this
page 1
1
2
3
4
5
page 2
6
7
8
9
10
page 3
etc
How would I do that? I can't use the id from the table, and I can't use $loop->iteration to print out a the index for each result, since loop->iteration would reset for each page load if I am correct?
How would I give index to all returned results and have it work accors all the pages
You can use $loop->iteration and pagination instance methods:
($results->currentPage() - 1) * $results->perPage() + $loop->iteration

Laravel 5.2 pagination + infinite scroll duplicates

I encountered a problem with pagination in my Laravel app.
Normally, when I want to use Laravel pagination with let's say 3 rows for one page, I use Eloquent method paginate/simplePaginate in combination with method latest() like so:
//MyController.php
$posts= Post->latest()->paginate(3);
//MyView.blade.php
#foreach ($posts as $post)
{{ $post->id }}
#endforeach
{{ $posts->links() }}
So when i have 6 posts in my database and I use Laravel pagination + Infinite scroll, that becomes:
6
5 (1. page)
4
--- ('page separator')
3
2 (2. page)
1
But, if user inserts new row into database table before I reach page 2,
the collection shifts and second page of data becomes:
6
5 (1. page)
4
--- POST WITH ID 4 IS DUPLICATED BECAUSE OF SHIFT IN COLLECTION
4
3 (2. page)
2
So, for example --- if user inserts three new rows into database before I reach paginator, then the second page will output the same three table rows as my current first page, but i'd like to output the remaining table rows:
6
5 (1. page)
4
--- AT SOME TIME BEFORE PAGINATION WAS TRIGGERED, POST WITH ID '7' HAS BEEN ADDED
3
2 (2. page) - continues normally with remaining rows (pagination ignores newly added row)
1
Is there any workaround for this problem?
Thanks.
I solved this issue by asking for the first record id of the first page served
// {initial query} $records->orderBy('created_at', 'desc');
if(is_int($last_id)){
$records = $records->where('id', '<=' , $last_id);
}
$records = $records->paginate($per_page);
This way, the paginator get's records starting from that id, ignoring new records
You could try this:
$posts= Post->orderBy('id', 'desc')
->distinct()
->paginate(3);

MongoDB ranged pagination

It's said that using skip() for pagination in MongoDB collection with many records is slow and not recommended.
Ranged pagination (based on >_id comparsion) could be used
db.items.find({_id: {$gt: ObjectId('4f4a3ba2751e88780b000000')}});
It's good for displaying prev. & next buttons - but it's not very easy to implement when you want to display actual page numbers 1 ... 5 6 7 ... 124 - you need to pre-calculate from which "_id" each page starts.
So I have two questions:
1) When should I start worry about that? When there're "too many records" with noticeable slowdown for skip()? 1 000? 1 000 000?
2) What is the best approach to show links with actual page numbers when using ranged pagination?
Good question!
"How many is too many?" - that, of course, depends on your data size and performance requirements. I, personally, feel uncomfortable when I skip more than 500-1000 records.
The actual answer depends on your requirements. Here's what modern sites do (or, at least, some of them).
First, navbar looks like this:
1 2 3 ... 457
They get final page number from total record count and page size. Let's jump to page 3. That will involve some skipping from the first record. When results arrive, you know id of first record on page 3.
1 2 3 4 5 ... 457
Let's skip some more and go to page 5.
1 ... 3 4 5 6 7 ... 457
You get the idea. At each point you see first, last and current pages, and also two pages forward and backward from the current page.
Queries
var current_id; // id of first record on current page.
// go to page current+N
db.collection.find({_id: {$gte: current_id}}).
skip(N * page_size).
limit(page_size).
sort({_id: 1});
// go to page current-N
// note that due to the nature of skipping back,
// this query will get you records in reverse order
// (last records on the page being first in the resultset)
// You should reverse them in the app.
db.collection.find({_id: {$lt: current_id}}).
skip((N-1)*page_size).
limit(page_size).
sort({_id: -1});
It's hard to give a general answer because it depends a lot on what query (or queries) you are using to construct the set of results that are being displayed. If the results can be found using only the index and are presented in index order then db.dataset.find().limit().skip() can perform well even with a large number of skips. This is likely the easiest approach to code up. But even in that case, if you can cache page numbers and tie them to index values you can make it faster for the second and third person that wants to view page 71, for example.
In a very dynamic dataset where documents will be added and removed while someone else is paging through data, such caching will become out-of-date quickly and the limit and skip method may be the only one reliable enough to give good results.
I recently encounter the same problem when trying to paginate a request while using a field that wasn't unique, for example "FirstName". The idea of this query is to be able to implement pagination on a non-unique field without using skip()
The main problem here is being able to query for a field that is not unique "FirstName" because the following will happen:
$gt: {"FirstName": "Carlos"} -> this will skip all the records where first name is "Carlos"
$gte: {"FirstName": "Carlos"} -> will always return the same set of data
Therefore the solution I came up with was making the $match portion of the query unique by combining the targeted search field with a secondary field in order to make it a unique search.
Ascending order:
db.customers.aggregate([
{$match: { $or: [ {$and: [{'FirstName': 'Carlos'}, {'_id': {$gt: ObjectId("some-object-id")}}]}, {'FirstName': {$gt: 'Carlos'}}]}},
{$sort: {'FirstName': 1, '_id': 1}},
{$limit: 10}
])
Descending order:
db.customers.aggregate([
{$match: { $or: [ {$and: [{'FirstName': 'Carlos'}, {'_id': {$gt: ObjectId("some-object-id")}}]}, {'FirstName': {$lt: 'Carlos'}}]}},
{$sort: {'FirstName': -1, '_id': 1}},
{$limit: 10}
])
The $match part of this query is basically behaving as an if statement:
if firstName is "Carlos" then it needs to also be greater than this id
if firstName is not equal to "Carlos" then it needs to be greater than "Carlos"
Only problem is that you cannot navigate to an specific page number (it can probably be done with some code manipulation) but other than it solved my problem with pagination for non-unique fields without having to use skip which eats a lot of memory and processing power when getting to the end of whatever dataset you are querying for.

toPagedList() order differs from the LINQ order by clause

I have a small application that displays a list of shortURLs to a user. I'm using ASP.NET MVC3, Entity Framework, and an Oracle backend. I'm also using the PagedList library by Troy Goode (https://github.com/TroyGoode/PagedList)
I want the user to see the very last entry first, similar to how blog comments are ordered. To do this I added an "orderby descending" clause to the LINQ statement:
var links = from l in db.LINKS
orderby l.ID descending
select l;
In debug the "links" object is ordered as expected (in descending order, by the primary key "ID").
Now I want to pass this list of links to the view using the .toPagedList() method:
int pageSize = 3;
int pageNumber = (page ?? 1); // "page" comes from the request, if null set to 1
return View(links.ToPagedList(pageNumber, pageSize));
This works great if I only have 3 records (refer to "pageSize" above). However, when I add a 4th record I get unexpected behavior. Because I have the pageSize set to 3, adding a fourth record means there will be 2 pages. I would expect the ordering to be as follows:
Page 1:
ID: 4
ID: 3
ID: 2
Page 2:
ID: 1
That is not the case, what actually happens is this:
Page 1:
ID: 3
ID: 2
ID: 1
Page 2:
ID: 4
So my question, what the heck am I doing wrong here? Why is PagedList not following the order defined by the "orderby l.ID descending" expression in the LINQ statement? It is baffling to me because in the debugger it is clear that the "links" object is ordered properly before the .toPagedLIst() is used on it.
Any help is greatly appreciated!
You could try:
return View(links.ToPagedList(pageNumber, pageSize, m => m.ID, true));
It's off the top of my head, so I'm sorry if it doesn't work perfectly...
-- Wow, just noticed the date on this one... I need to stop trawling the unanswered pages without sorting by date!

Resources