I have a table with messages. I want to group these messages into conversations, checking if there is unread messages and previewing the latest message.
My current ORM looks like this:
$text = new Model_Text;
$text->select(DB::expr('min(`text`.`read`) AS `read_all`'))
->where('time', '>', $time_offset);
->order_by('read', 'DESC')->order_by('time', 'ASC')
->group_by('contact')->with('user')->find_all();
But as you know MySQL groups before it orders, meaning I don't get the latest message, while the min('text'.'read') trick makes sure I always know if the conversation has unread messages.
To get the latest message in each conversation, I would do a MySQL query like this:
SELECT min(q1.`read`) as read_all, q2.* FROM texts q1
INNER JOIN (
SELECT * FROM texts ORDER BY time DESC
) q2 ON(q1.contact = q2.contact)
GROUP BY q1.contact
ORDER BY q2.`time` DESC, q2.`read` ASC
But I am completely lost as for how to optimally implement this query with ORM.
My best guess would be to execute the above query directly with Kohana's DB class and load an ORM object for each row returned. But, this will cause one database hit for each and every conversation loaded into ORM, for data that I already retrieved - stupid!
Let me know your thoughts!
Thanks.
There is not a good way to do this with the ORM class, even if you could select the data properly, you wouldn't be able to read it back, as your Model_Text won't have the read_all property.
I think the best way to do this is with two queries, The first would be to get the conversations that you are interested in, select contact, min(read) as read_all ... where time > $time_offset and group by contact. Once you have the contact and read_all, you can make another query the get just the latest text for each conversation and load that into your Model_Text as suggested by Carl: DB::query(DATABASE::SELECT, $sql)->as_object('Model_Text')->execute()
Related
I'm using CakePHP 4 to build an application that shows an inventory of documents and the latest status for each document.
The tables are fairly simple:
documents: This contains a list of documents, each of which has a unique id, a human-friendly name, a filename etc.
statuses: This includes a list of about 10 different statuses that a document can go through within the application (e.g. "uploaded", "review requested", "reviewed", "rejected" etc). Each of these has a unique id and name (the name being the text of the status, such as "uploaded", "review requested" etc).
documents_statuses: This is a history table that contains all of the statuses that a document has gone through. Any given document (documents.id) can appear multiple times in this table (using a foreign key of documents_statuses.document_id referring to the relevant documents.id). It also has a documents_statuses.status_id corresponding to a statuses.id in the statuses table mentioned above. Each row here has CakePHP's conventional created timestamp so we know when rows were inserted. This is enough to tell us dates/times about when the document got to a particular status.
What I'm trying to do is output a list of documents and show the most recent status from documents_statuses in my table. The HTML structure of the table is simple and contains 3 headings:
Document Name
Filename
Last status
Writing a query to get the data for the first 2 columns is easy as the data for that belongs in the documents table:
// src/Controller/DocumentsController.php
public function index()
{
$documents = $this->Documents->find();
$documents = $documents->paginate($document);
$this->set('documents', $documents);
}
In my template I can then reference $documents->name and $documents->filename to output the respective columns from the documents table.
I understand that I need some extra logic in this query which will JOIN to the documents_statuses table and then order the records in descending order with a LIMIT of 1 to get the most recent status per document. I know I also need to do a further JOIN such that documents_statuses.status_id returns the corresponding statuses.name.
I know that I can adapt my query to contain documents_statuses and statuses:
$documents = $this->Documents->contain(['DocumentsStatuses', 'Statuses'])->find();
But I don't know how to loop through the records in documents_statuses in this query and do the ->orderDesc->limit(1) to get the most recent record. Furthermore I also know that to obtain the statuses.name I would need to get this query to join the documents_statuses.status_id and statuses.id to return statuses.name (e.g. "uploaded", "review requested" etc).
The application has been bake'd and the models associations are defined correctly.
Might something equivalent be described in the CakePHP docs?
Edit - Raw SQL
The following SQL is equivalent to what I'm trying to write using the ORM. The problem isn't particularly understanding the SQL involved, it's writing it using CakePHP's ORM syntax. Equally, if there is a "better" way to write this query I'm interested but the purpose of this question is how to make this work using CakePHP's ORM.
SELECT
documents.name,
documents_statuses.created,
statuses.name
FROM documents
LEFT JOIN
(SELECT documents_id, MAX(created) AS created
FROM documents_statuses
GROUP BY documents_id
) recent_statuses
ON documents.id = recent_statuses.documents_id
LEFT JOIN documents_statuses
ON documents.id = documents_statuses.documents_id AND recent_statuses.created = documents_statuses.created
LEFT JOIN statuses
ON documents_statuses.status_id = statuses.id
It can be done with a slightly complex JOIN; see the [groupwise-max] tag or my Groupwise-Max
Alternatively, you could use a different schema pattern:
Current -- this always has the latest 'status' (plus other info). There would be one row per document.
History -- essentially as you have it now. But this is not looked at to find the "current status". This table has many rows per document.
Your code would need to INSERT INTO History and UPDATE Current to update the status. (Actually the Update could be a IODKU if you need to Insert when the row does not exist yet.)
The query in question would be simply SELECT ... FROM Current ... -- no Join needed.
$documents = $this->Documents->contain(['DocumentsStatuses'=>function(Query $q){ return $q->contain(['Statuses'])->orderDesc->limit(1);}])->find();
book.cakephp.org
Am trying to query messages and group them by conversations and get the last message in each conversation, the structure of my table is
Table Messages(id(int), user_from(varchar), user_to(varchar), message(text), read(bool), chat_id(int), created_at(time_stamp), updated_at(time_stamp))
After searching for quite sometime here is what i think may solve my problem but i dont know how to implement this in Laravel 5.5 with eloquent or Query Builder
SELECT * FROM messages WHERE id IN(
SELECT MAX(id)
FROM messages
GROUP BY chat_id
)
I know it has been a while and you must have a solution for it. But I met a similar problem and just spent over 1 hour on it, so I think it worthy to confirm the solution here.
Yes, using Max could deal with the situation here. I used query builder, it could be like:
DB::table('messages')
->selectRaw('max(id) as id, chat_id') // you may add other fields
->groupby('chat_id')
->orderby('id') // you may use other fields to sort
->get();
I need to know how to do i do a subquery type selection with phalcon models?
for example i want to select all the users who viewed me, they are stored in the UserView table with columns 'id','user_from','user_to' (mapped by User table user_id to either user_from or user_to)
so i want to select all the users who has a user_to as with the current user, and group by user_to make sure i only get one recorded, I wrote below function to do this but there is fundamental two problems
1. Is how to do sub-query using phalcon models
2. Is my logic correctly applied on the back-end of the DB (as i cant see real executed query)
public function getUserWithViewedMe($limit=1000000){
return User::query()
->rightJoin("XYZ\Models\UsersView")
->andWhere(" XYZ\Models\UsersView.user_from IN :user_id: ",
array('user_id' => $this->user->user_id) )
->group('user_id')
->order("XYZ\Models\UsersView.id DESC ")
->limit($limit)
->execute();
}
This returns empty set...
So far it is not possible to model subqueries in Phalcon. There is also topic according to standard implementation issues.
To query params according to other table, here is an answer.
To query IN you can use queryBuilder
$this->modelsManager->createBuilder()
// ...
->inWhere('column', $array);
I am trying to make a social networking site in PHP/MySQL. I am currently developing status update and comments on status's system. I am trying to show all status of mine and comments on certain status. For doing that I have two tables: comment and user_status.
I have used this MySQL query,
SELECT * FROM user_status LEFT JOIN
comment ON id_status = comment.status_id
WHERE sender_id = '$id2'
OR receive_id = '$id2'
/* $id2 is my id */
I have successfully showed status and one comment. But the problem is, when the number of comments are more than one, then the status shows more than one times. How much same status will be showed depends on how much comments available on certain status. But I would like to be able to display same status only one time, and display more than one comments (if available) on certain status.
This isn't so much a PHP problem as it is confusion about how SQL joins work.
It sounds as if what you really want is not so much a join but a distinct set of records from two tables. Until your SQL skils develop a little more, consider simplifying things by making two queries -- one each for comment and user_status. Also consider requesting just the specific fields you're interested rather than using SELECT *.
Here is a visual explanation of different SQL joins, in case you want to pursue this with a single query.
I assume that you are not displaying the raw results from the query, but rather are piping them to an html page to display. Display only the most recent status in a textbox. then display thin a table or list an ordered list of the comments.
Your query is correct.
Here i need help with joins.
I have two tables say articles and users.
while displaying articles i need to display also the user info like username, etc.
So will it be better if i just use joins to join the articles and user tables to fetch the user info while displaying articles like below.
SELECT a.*,u.username,u.id FROM articles a JOIN users u ON u.id=a.user_id
OR can this one in php.
First i get the articles with below sql
SELECT * FROM articles
Then after i get the articles array i loop though it and get the user info inside each loop like below
SELECT username, id FROM users WHERE id='".$articles->user_id."';
Which is better can i have explanation on why too.
Thank you for any reply or views
There is a third option. You could first get the articles:
SELECT * FROM articles
Then get all the relevant user names in one go:
SELECT id, username FROM users WHERE id IN (3, 7, 19, 34, ...)
This way you only have to hit the database twice instead of many times, but you don't get duplicated data. Having said that, it seems that you don't have that much duplicated data in your queries anyway so the first query would work fine too in this specific case.
I'd probably choose your first option in this specific case because of its simplicity, but if you need more information for each user then go with the third option. I'd probably not choose your second option as it is neither the fastest nor the simplest.
It depends how much data the queries are returning - if you'll be getting a lot of duplicate data (i.e. one user has written many articles) you are better off doing the queries separately.
If you don't have a lot of duplicated data, joins are always preferable as you only have to make one visit to the database server.
The first approach is better if applicable/possible:
SELECT a.*, u.username, u.id
FROM articles a
JOIN users u ON u.id = a.user_id
You have to write less code
There is no need to run multiple queries
Using joins is ideal when possible
Get the articles with one query, then get each username once and not every time you display it (cache them in an array or whatever).