How to handle pagination queries properly with mongodb and php? - php

Am I doing this right? I went to look at some old PHP code w/ MySQL and I've managed to get it to work, however I'm wondering if there's a much "cleaner" and "faster" way of accomplishing this.
First I would need to get the total number of "documents"
$total_documents = $collection->find(array("tags" => $tag,
"seeking" => $this->session->userdata('gender'),
"gender" => $this->session->userdata('seeking')))->count();
$skip = (int)($docs_per_page * ($page - 1));
$limit = $docs_per_page;
$total_pages = ceil($total_documents / $limit);
// Query to populate array so I can display with pagination
$data['result'] = $collection->find(array("tags" => $tag,
"seeking" => $this->session->userdata('gender'),
"gender" => $this->session->userdata('seeking')))->limit($limit)->skip($skip)->sort(array("_id" => -1));
My question is, can I run the query in one shot? I'm basically running the same query twice, except the second time I'm passing the value to skip between records.
-- New code ...
Ok, unless someone knows of another way to do this (if it's possible), I'm going to say it's not doable. With that said, I changed the way I run my queries through mongodb, which yielded better looking code. ;-) I was trying to minimize the trips to the DB, but oh well hopefully this doesn't take a performance hit. My other attempt was to count the number of elements in the array, but quickly found out that wouldn't work since the $limit & $skip parameters would give ITS total number of docs.
$skip = (int)($docs_per_page * ($page - 1));
$limit = $docs_per_page;
$query = array("loc" => array('$near' => array('lat' => $latitude, 'lon' => $longitute) ),
"tags" => $tag, "seeking" => $this->session->userdata('gender'),
"gender" => $this->session->userdata('seeking'));
$fields = array("username", "zipcode", "tags", "birth_date");
$total_documents = $collection->find($query, array("_id"))->count();
$data['result'] = $collection->find($query, $fields)->limit($limit)->skip($skip);

Since the result of find()->limit()->skip() is a Mongo_Cursor you don't have to execute the actual query twice.
The following should work as well :
$skip = (int)($docs_per_page * ($page - 1));
$limit = $docs_per_page;
$query = array("loc" => array('$near' => array('lat' => $latitude, 'lon' => $longitute) ),
"tags" => $tag, "seeking" => $this->session->userdata('gender'),
"gender" => $this->session->userdata('seeking'));
$fields = array("username", "zipcode", "tags", "birth_date");
$cursor = $collection->find($query, $fields)->limit($limit)->skip($skip);
$total_documents = $cursor->count();
$data['result'] = $cursor;
btw I first misread your question, I thought you didn't know about limit & skip.

Yes you are doing right.
And you can run query in one shot.
Here is a paging example:
function printStudents(pageNumber, nPerPage) {
print("Page: " + pageNumber);
db.students.find().skip((pageNumber-1)*nPerPage).limit(nPerPage).forEach( function(student) { print(student.name + "<p>"); } );
}
Reference: Advanced Queries - MongoDB: http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-{{skip%28%29}}

Related

Mongodb php datetime filter not working properly

I use mongo db with php I need a last 1 hour data. I implement as like bellow.
{
"_id":{"$oid":"5ff42b30be00ec1eaf261db1"},
"logtype":"syslog",
"message":"Jan 4 06:51:56 4S-096 kernel: [70745743.387001] CPU7: Package temperature above threshold, cpu clock throttled (total events = 2955852254)",
"node_id":875,
"app_id":0,
"send_to_slack":1,
"created_date":{"$date":"2021-01-05T09:02:39.593Z"}
}
PHP CODE
$client = mongodb_connect();
$db = $client->$db_name;
$col = $db->selectCollection($collection_name);
$client->selectDatabase($db_name);
$criteria = array(
"created_date" => [$gte=> new \MongoDB\BSON\UTCDateTime(strtotime("-1 hour") * 1000)],
"logtype" => $logType,
"message" => trim($logdata),
"node_id" => $node_id,
);
$count = $col->count($criteria);
I need a count result. thanks in advance
I am not so familiar with PHP, but I think $client->$db_name is equal to $client->selectDatabase($db_name)
However -> does not work with variables, so $client->$db_name may fail.
Try this one:
$client = mongodb_connect();
$db = $client->selectDatabase($db_name);
$col = $db->selectCollection($collection_name);
$criteria = array(
"created_date" => [ '$gte' => new MongoDB\BSON\UTCDateTime(strtotime("-1 hour") * 1000)],
"logtype" => $logType,
"message" => trim($logdata),
"node_id" => $node_id,
);
$count = $col->count($criteria);
I never used strtotime, maybe you have to skip * 1000

How to LIMIT a Query which is already been selected?

I am trying to LIMIT a Query which is already been selected. I know I can directly do it in my query select, but due to some logics of the code, if I do that way I have to run the query selection twice which (I believe) will increase the computational time!
So, what I am trying is this:
$query1 = $mysqli->query("SELECT * FROM tableName WHERE someconditions");
then, I need to re-select a sub-selection of the $query1 for my Pagination. What I am trying to do is something like this;
$query2 = LIMIT($query1, $limit_bigin, $limit_end);
where $limit_bigin, $limit_end provide the LIMITING range (start and end respectively).
Could someone please let me know how I could do this?
P.S. I know I can do it directly by:
$query1 = $mysqli->query("SELECT * FROM tableName WHERE someconditions");
$query2 = $mysqli->query("SELECT * FROM tableName WHERE someConditions LIMIT $limit_bigin, $limit_end");
But this is running the query twice and slows down the process (I must run the first query without limits due to some logics of the program)
EDIT 1
As per the answers I tried using array_slice in PHP. BUT, since Query is an object it doesn't give the results that was expected. A NULL is resulted form
array_slice($query1, $start, $length, FALSE)
If you have already carried out the query and the result set has been returned to your PHP, you can not then LIMIT it. As you state, then running a second SQL execution of a subpart of the same query is wasteful and repetative.
Don't
Repeat
Yourself.
DRY.
As I said above, repetition causes issues with maintainability as you can easily forget about repetition, tweaking one SQL and forgetting to tweak the other.
Don't
Repeat
Yourself.
DRY.
Use PHP instead
Once you have executed the query, the result set is then passed back to PHP.
So assuming you have a $var with the contents of you SQL query, then you simply need to select the valid rows from the $var, not from the database [again].
You can do this using PHP numerous Array functions. Particularly array_slice().
So;
$query1 = $mysqli->query("SELECT * FROM tableName WHERE someconditions");
Now, to select the second page, say for example rows 10 to 20:
$query2 = array_slice($query1, (10-1), 10 );
This wil then "slice" the part of the array you want. Remember that the array counts will start at zero so to grab row 10 (of an index starting at 1, Typical of a MySQL Auto Increment Primary Key), then you will need to select X number of rows from row (10-1) .
Please also read the manual for PHP array_slice().
Further
As referenced in comments, there is no guarentee that your SQL will return the same values each time in the same order, so it is highly recommended to order your results:
$query1 = $mysqli->query("SELECT * FROM tableName
WHERE someconditions ORDER BY primary_key_column");
Example Data:
$query1 = $mysqli->query("SELECT * FROM tableName WHERE someconditions ORDER BY id");
/***
$query1 = array {
0 => array { 'id' => 1, 'user' => "Jim", 'colour' => "Blue" },
1 => array { 'id' => 2, 'user' => "Bob", 'colour' => "Green" },
2 => array { 'id' => 3, 'user' => "Tom", 'colour' => "Orange" },
3 => array { 'id' => 4, 'user' => "Tim", 'colour' => "Yellow" },
4 => array { 'id' => 5, 'user' => "Lee", 'colour' => "Red" },
5 => array { 'id' => 6, 'user' => "Amy", 'colour' => "Black" }
}
***/
$page = 2;
$size = 3; // number per page.
$start = ($page - 1) * $size; //page number x number per page.
// Select the second page of 3 results.
$query2 = array_slice($query1, $start, $size , FALSE);
/***
$query2 = array {
0 => array { 'id' => 4, 'user' => "Tim", 'colour' => "Yellow" },
1 => array { 'id' => 5, 'user' => "Lee", 'colour' => "Red" },
2 => array { 'id' => 6, 'user' => "Amy", 'colour' => "Black" }
}
***/
You can then use these in a foreach or other standard array manipulation technique.

Find all documents in mongodb

I want to display all documents (select *) with sub-documents in PHP.
I know how to query all find() but I have no idea how to do it when I have sub-documents. I don't know if there's something like find() or I need to make loops fo every sub-documents that I'd have.
This would be the code
$mongodatabase->insertOne(
['name' => 'Alex',
'surname' => 'Turner',
'country' => 'England',
'birth' => array(
'day' => 6,
'month' => 'january',
'year' => 1986
),
]);
Something easy, just to learn. When I try a var_dump of day I get Undefined index and NULL.
$client = new MongoDB\client;
$db = $client->database;
$mongodatabase = $db->document;
$document = $mongodatabase->find();
foreach ($document as $doc) {
var_dump($doc->day);
}
However, I'd like to query all.
Use $exists - It helps us in identifying the elements which are not empty
db.collection_name.find({
"birth.day" : {
$exists : true
}
});
If you need to check not null and empty, then we need to use $type together with $exists, $type can be passed with different values and 10 is for null check
db.collection_name.find({
"birth.day" : {
$not : { $type : 10 },
$exists : true
}
});
when u find the exactly data from mongoldb u can use the shelter to limit the field
eg:
db.xxxxx.find(
{'status':'DELIVRD'}
);

How to order joined results in Phalcon Framework

Let's pretend I'm working on a magazine, where a Category (like "sport", "art" an so on) can contain several Articles. Therefore I want to extract all articles for a specific category. In Phalcon I usually do:
$category = \Models\Category::findFirst(array(
'conditions' => 'id = ?1',
'bind' => array(1 => $id)
));
Then:
foreach ($category->Article as $article) {
// do something with $article
}
It works great, but I would like to sort those Articles - say - date wise, ascending. How could I accomplish that?
You should use get prefix in your for..loop statement, so your code should like this :
foreach ($category->getArticle(array('order' => 'date DESC')) as $article) {
// do something with $article
}
The main Docs explains more examples.
Try it & get your results.
M2sh answer is all important and actual, I just will post a secondary way, just using model designed for articles:
$page = 5; // eg. 5th page of results
$limit = 100; // eg. 100 results per page
$articles = \Models\Articles::find(array(
'conditions' => 'category_id = :cid:',
'bind' => array(
'cid' => $id
),
'order' => 'date DESC, id DESC',
'limit' => $limit,
'offset' => $page * $limit
));
It's possible to use such set of parameters in M2sh way aswell.
One more for completeness. I will borrow from yergo to illustrate the differences:
$page = 5;
$limit = 100;
$articles = \Models\Articles::query()
->where('category_id= :cid:', array('cid' => $id)
->orderBy('date DESC, id DESC')
->limit($limit)
->offset($page * $limit)
->query();

Is it possible to specify two limit values(from, offset) in one Phalcon\Mvc\Model::find() method?

For a moment i found following example:
$robots = Robots::find(array("limit" => 100));
Is it possible to define two values for limit, "from" and "offset"?
In Zend Framework it was possible with Db adapter function, that looked like this:
$db->select()->limit($from, $offset)
Try this:
$collection = Model::find(array(
"limit" => array('number' => $from, 'offset' => $offset)
));
You could always use PHQL which supports the OFFSET like so (in a controller):
$sql = 'SELECT * FROM Robots LIMIT 100 OFFSET 10';
$stmt = $this->modelsManager->createQuery($sql)
$results = $stmt->execute();
limit parameter can not only accept int values.
this example would work too:
$offset = 20;
$from = 10;
$collection = Model::find(array("limit" => $from . ',' . $offset));
You can just pass another element called offset and specify the value in it,refer to the given below example:
$orders=Orders::find(array(
"order" => "id DESC",
"limit" => 10,
"offset" => 0
));
You may refer to following link for detailed information:
https://docs.phalconphp.com/en/latest/reference/models.html
You can make your query pretty customizable doing this:
<?php
$notifications = \UserNotification::find([
"user_id = :user_id: ORDER BY date DESC LIMIT 5",
"bind" => [
"user_id" => 1
]
]);

Categories