Laravel Eloquent Combine Queries For Activity Feed - php

Hello so I have an application in Laravel in which a lot of user data is stored in separate tables across several models. I now have a requirement to create an activity feed, which means ordering the various data across tables by date.
For illustrative purposes, imagine I have two models, Comment and Like.
I want a feed that combines both by date. merge() is not an option because they may have the same id.
Therefore I could UNION them, but my problem is I won't know what came from what.
My likes table looks like this:
+------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| user_id | int(11) | NO | | NULL | |
| asset_id | int(11) | NO | | NULL | |
| created_at | timestamp | YES | | NULL | |
| updated_at | timestamp | YES | | NULL | |
+------------+------------------+------+-----+---------+----------------+
My comments table looks like this:
+------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| asset_id | int(11) | NO | | NULL | |
| content | longtext | NO | | NULL | |
| user_id | int(11) | NO | | NULL | |
| created_at | timestamp | YES | | NULL | |
| updated_at | timestamp | YES | | NULL | |
+------------+------------------+------+-----+---------+----------------+
My issue is if I just union the two queries, I would only know they are different based on the presence of the content column, which could be in another model, such as Blurb or whatever.
Basically, how do I get multiple queries across models while keeping straight what belongs where, because in my activity feed I want to say, 10 minutes ago you commented, 5 minutes ago you liked, etc.
I don't want to do multiple queries because of inefficiency and I don't want to store all the activities (likes and comments, etc.) in one table either. Is there some kind of alias I can use where instead of renaming a column I insert data using the query for the purposes of the query, so for example a comment selection would add "comment" in a temporary field so that I can access it like
$data->type? I could put a type in all of the tables but then I'd have space being taken up needlessly, as obviously I know a comment is a comment if its in the comment table when that is my only query, but now I am rethinking my structure given I need one query to span multiple tables.

you can use following code to get user activity feed.
$userId = Auth::id(); //or whatever you want.
$activity = DB::table('comment as ac')
->select(DB::raw('ac.user_id , ac.asset_id , ac.comment , ac.created_at , ac.updated_at , "comment" as activity_type'))
->where("ac.user_id", $userId)
->union(
DB::table('like as al')
->select(DB::raw('al.user_id , al.asset_id , NULL as comment , al.created_at , al.updated_at , "like" as activity_type'))
->where("al.user_id", $userId)
)
->latest()
->get();

When performing your query, select an addition raw value based on the table name. For example, in raw SQL:
SELECT likes.*, '' AS content, 'like' AS type
FROM likes
WHERE likes.user_id = 1
UNION
SELECT comments.*, 'comment' AS type
FROM comments
WHERE likes.user_id = 1
ORDER BY created_at DESC
The Laravel code (untested) will look something like:
$activity = DB::table('comments')
->select("comments.*, 'comment' AS type")
->where('comments.user_id', $user->id)
->union(
DB::table('likes')
->select("likes.*, '' AS content, 'like' AS type")
->where('likes.user_id', $user->id)
)
->orderBy('created_at', 'ASC')
->get();

Related

How can i update the Records included in another query using SUM and GROUP By in mysql

I am having a mysql table
content_votes_tmp
+------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+------------------+------+-----+---------+----------------+
| up | int(11) | NO | MUL | 0 | |
| down | int(11) | NO | | 0 | |
| ip | int(10) unsigned | NO | | NULL | |
| content | int(11) | NO | | NULL | |
| datetime | datetime | NO | | NULL | |
| is_updated | tinyint(2) | NO | | 0 | |
| record_num | int(11) | NO | PRI | NULL | auto_increment |
+------------+------------------+------+-----+---------+----------------+
surfers can vote up or vote down on posts i.e. content, a record gets inserted everytime a vote is given same as rating , in the table along with other data like ip , content id
Now i am trying to create cronjob script in php which will SUM(up) and SUM(down) of votes
like this,
mysqli_query($con, "SELECT SUM(up) as up_count, SUM(down) as down_count, content FROM `content_votes_tmp` WHERE is_updated = 0 GROUP by content")
and then by using while loop in php i can update the main table for the specific content id,
but i would like to set the records which are part of SUM to be marked as updated i.e. SET is_updated = 1, so the same values wont get summed again and again.
How can i achieve this ? using mysql query ? and work on same data set as , every second/milisecond the records are getting inserted in the table ,.
i can think of another way of achieving this is by getting all the non-updated records and doing sum in the php and then updating every record.
The simplest way would probably be a temporary table. Create one with the record_num values you want to select from;
CREATE TEMPORARY TABLE temp_table AS
SELECT record_num FROM `content_votes_tmp` WHERE is_updated = 0;
Then do your calculation using the temp table;
SELECT SUM(up) as up_count, SUM(down) as down_count, content
FROM `content_votes_tmp`
WHERE record_num IN (SELECT record_num FROM temp_table)
GROUP by content
Once you've received your result, you can set is_updated on the values you just calculated over;
UPDATE `content_votes_tmp`
SET is_updated = 1
WHERE record_num IN (SELECT record_num FROM temp_table)
If you want to reuse the connection to do the same thing again, you'll need to drop the temporary table before creating it again, but if you just want to do it a single time in a page, it will disappear automatically when the database is disconnected at the end of the page.

SELECT id FROM table WHERE id=$_GET['id'] AND user1=$user OR user2=$user

I'm trying to build a similar facebook style messaging system (conversations).
This is the conversation table.
DESCRIBE conversation;
+----------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+----------------+
| c_id | int(11) | NO | PRI | NULL | auto_increment |
| user_one | int(11) | NO | | NULL | |
| user_two | int(11) | NO | | NULL | |
| ip | varchar(30) | NO | | NULL | |
| time | int(11) | NO | | NULL | |
+----------+-------------+------+-----+---------+----------------+
Now before the user can read a conversation, I need to check if the conversation (c_id) exists, and if the user is the owner of the given conversation id. What is the best possible way to write this query?
Example of what I have, which is not working:
$cid = intval($_GET['cid']);
$conv = $this->db->fetchRow('SELECT c_id FROM `conversation` WHERE
user_one=? OR
user_two=? AND
c_id=?',
array($this->user->id, $this->user->id, $cid));
if ($conv) {
// get the conversation replies etc..
}
I see a couple of problems.
One is that you seem to have overlooked that AND has a higher precedence than OR. So the logic of your condition works as if you had written it this way:
WHERE user_one=? OR (user_two=? AND c_id=?)
Whereas I would guess that you intended the logic to work this way:
WHERE (user_one=? OR user_two=?) AND c_id=?
But if that's how you intended it to work, I wonder why you need to search for the user id's at all, since the condition on c_id=? will select only one row (or zero rows if there's no match), because it's searching for one specific primary key value.

Conditional Join Statement in MySQL using IF-ELSE

I'm making a notification scheme for my social networking app. I've different kind of notification which are categorized in two groups: Friends-related and Events-related. Currently, my database schema is like this:
+---------------------+------------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------------------+------------------------+------+-----+---------+----------------+
| notification_id | int(11) | NO | PRI | NULL | auto_increment |
| notification_type | enum('event','friend') | NO | | NULL | |
| notification_date | datetime | NO | | NULL | |
| notification_viewed | bit(1) | NO | | NULL | |
| user_id | int(11) | NO | MUL | NULL | |
+---------------------+------------------------+------+-----+---------+----------------+
Now, I've two different tables fro event-related notification and friend-related notification. Below is schema for event-related notification table:
+-------------------------+----------------------------------------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------------------------+----------------------------------------------------+------+-----+---------+-------+
| notification_id | int(11) | NO | PRI | NULL | |
| event_id | int(11) | NO | MUL | NULL | |
| event_notification_type | enum('added','kicked','new-message','info-edited') | NO | | NULL | |
+-------------------------+----------------------------------------------------+------+-----+---------+-------+
And again I've 4 more tables for each kicked, added, new-message, info-edited type of notification, since each requires to have it different kind of property (for example kicked requires a reason).
Now, I want to write a conditional SQL query such that it joins the notification with event_notification if notification_type is event otherwise different.
SELECT * FROM notification_table t WHERE t.seen = FALSE AND t.user_id = ? INNER JOIN event_notification en ON(t.notification_type='event' AND en.notification_id = t.notification_id) INNER JOIN .....
There is going to be so many inner joins is there any better way of doing it? I think my query is not very optimized either, would appreciate if any help could be provided.
You can use the joins. However, you want to create the query using left outer joins rather than inner joins:
SELECT *
FROM notification_table t
WHERE t.seen = FALSE AND t.user_id = ? left JOIN
event_notification en
ON(t.notification_type='event' AND en.notification_id = t.notification_id) left JOIN ...
Don't worry about the proliferation of joins. If your tables have proper indexing, they will perform fine.
Do consider changing the data structure so you have only one table for the different notification types. Having a few fields that are not used does not add much performance overhead, especially when you consider the complications of having so many joins and the additional management overhead of having more tables.

json data in mysql

I have a mysql table called "Data",
+---------+------------------+------+-----+-------------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------+------------------+------+-----+-------------------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| data | text | YES | | NULL | |
| created | timestamp | NO | MUL | CURRENT_TIMESTAMP | |
+---------+------------------+------+-----+-------------------+----------------+
The field "data" has values like this:
606 | {"first_name":"JOHN","last_name":"SLIFKO","address":"123 main AVE","city":"LAKEWOOD","state":"OH","zip":"20190","home_phone":2165216359,"email":"john#gmail.com",} | 2012-12-04 16:37:23 |
So, it is saving the records in a JSON Format from a PHP Script that I have.
THIS IS THE THING:
How can I structure this table to make faster searchs or consults by every single field like doing searches or queries like:
SELECT * FROM Data WHERE first_name = john;
how can I do this???
Help please......
Yikes. Not a good design. About the best you could do is use the like keyword
Select * from Data Where data like '%"first_name":"JOHN"%'

Complex sorting on MySQL database

I'm facing the following situation.
We've got an CMS with an entity with translations. These translations are stored in a different table with a one-to-many relationship. For example newsarticles and newsarticle_translations. The amount of available languages is dynamically determined by the same CMS.
When entering a new newsarticle the editor is required to enter at least one translation, which one of the available languages he chooses is up to him.
In the newsarticle overview in our CMS we would like to show a column with the (translated) article title, but since none of the languages are mandatory (one of them is mandatory but i don't know which one) i don't really know how to construct my mysql query to select a title for each newsarticle, regardless of the entered language.
And to make it all a little harder, our manager asked for the possibilty to also be able to sort on title, so fetching the translations in a separate query is ruled out as far as i know.
Anyone has an idea on how to solve this in the most efficient way?
Here are my table schema's it it might help
> desc news;
+-----------------+----------------+------+-----+-------------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------------+----------------+------+-----+-------------------+----------------+
| id | int(10) | NO | PRI | NULL | auto_increment |
| category_id | int(1) | YES | | NULL | |
| created | timestamp | NO | | CURRENT_TIMESTAMP | |
| user_id | int(10) | YES | | NULL | |
+-----------------+----------------+------+-----+-------------------+----------------+
> desc news_translations;
+-----------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------------+------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| enabled | tinyint(1) | NO | | 0 | |
| news_id | int(1) unsigned | NO | | NULL | |
| title | varchar(255) | NO | | | |
| summary | text | YES | | NULL | |
| body | text | NO | | NULL | |
| language | varchar(2) | NO | | NULL | |
+-----------------+------------------+------+-----+---------+----------------+
PS: i've though about subqueries and coalesce() solutions but those seem rather dirty tricks, wondering if something better is know that i'm not thinking of?
This is not a fast approach, but I think it gives you what you want.
Let me know how it works, and we can work on speed next :)
select nt.title
from news n
join news_translations nt on(n.id = nt.news_id)
where nt.title is not null
and nt.language = (
select max(x.language)
from news_translations x
where x.title is not null
and x.new_id = nt.news_id)
order
by nt.title;
Assuming I've read your problem aright, you want to get a list of titles for articles, preferring the "required" language? A query for that might go along the lines of ...
SELECT * FROM (
SELECT nt.`title`, nt.news_id
FROM news n
INNER JOIN news_translations nt ON (n.id = nt.news_id)
WHERE title != ''
ORDER BY
CASE
WHEN nt.language = 'en' THEN 3
WHEN nt.language = 'jp' THEN 2
WHEN nt.language = 'de' THEN 1
ELSE 0 END DESC
) AS t1
GROUP BY `news_id`
This example prefers a title in English (en) if available, Japanese (jp) as a second preference, and German (de) as a third, but will display the first 'other' entry if none of the requested languages are available.

Categories