Laravel4 ->whereIn: get all keywords per event - php

I have the following query:
public function getEventsByKeywordIds($data){
//Query events by dates
$query = DB::table('events')
->join('events_dates', function($join) use ($data){
$join->on('events.id', '=', 'events_dates.event_id')
->where('events_dates.start_date', "<=", date_format(date_create($data['date_end']), "Y-m-d"))
->where('events_dates.end_date', '>=', date_format(date_create($data['date_start']), "Y-m-d"));
});
//Query events by keywords
$query = $query->join('events_keywords', function($join) use ($data){
$join->on('events.id', '=', 'events_keywords.event_id');
})->whereIn('events_keywords.keyword_id', $data["keyword_ids"]);
//Query places
$query = $query->join('places', function($join) use ($data){
$join->on('events.place_id', '=', 'places.id');
});
//Set offset and limit
$query = $query
->take($data['limit'])
->offset($data['offset'])
->orderBy('events_dates.start_date', 'ASC')
->orderBy('events.name', 'ASC')
->groupBy('events.id');
$events = $query->get();
return $events;
}
I am interested in the part that mentions "Query events by keywords".
events_keywordsis a many-to-many table, linking event id's and keyword id's to each other.
The requirement exists to return the keywords per event to the client, but so far the query only returns 1 keyword per event that it returns.
Note: $data["keyword_ids"]is an array of keywords sent from the client, because only events related to certain keywords should be returned.
Do you have any suggestions to adapt this query in a simple way to meet the requirement?
Edit: what I require is actually the keyword names, which is stored in the keyword table (not the many-to-many.

I solved the problem by creating a many to many relationship through Laravel's belongsToMany relation.
foreach($events as $event){
$temp_event = EventModel::find($event->id);
$event->keywords = $temp_event->keywords;
}
return $events;
This is not the most efficient solution I'm sure, but it works.

Related

Problem in building relationships query in Laravel

Suppose we have 3 tables , User , Report and Job. In users table we having 2 columns to use, id and job_id, in report table , we have user_id and job_id'.
So I need all users with report detail, whose job_id and user_id matched User table. I want to do it with relationship.
I made that query.
Problem is how to write multiple where clause with report, (where user_id,job_id).
User:: select(*)->with("report")->paginate(10);
Try this
User model define
public function job()
{
return $this->belongsTo(Job::class);
}
Job model define
public function reports()
{
return $this->hasMany(Report::class);
}
Then use
User::select(*)->with("job.reports")->paginate(10);
Maybe you can do it like
User::whereHas('report', function ($q) use ($id, $sample) {
$q->where('id', $id)
->where('sample', $sample);
})->get();
$id are just sample variable you can pass on to closure, while whereHas able to check if there's existing relationship
or something like,
User::WhereHas('report', function ($q) use ($id, $sample) {
$q
->where('id', $id) // this part here your using where on reports table
->where('sample', $sample);
})
->where('id', $user_id); // the part here your using where on users table
->get();

List conversation between 2 users in the correct order

I want to create a chat system on which i could list all the chats between specific 2 persons
I have 2 tables users and chats
my chats table have 3 columns - user_id, friend_id and chat
my User.php model file is like this
public function chats() {
return $this->hasMany('App\Chat');
}
For eg:
I want to list all the chat between user 1 and 3 without changing the order of the conversation
I can simply do it by doing $chats = Auth::user()->chats->where('friend_id', '=', $id); but this will only give the authenticated (which is user 1 or 3) users chats. But I want the conversation between both of them.
So I have found an alternate way to do that by
$first = Chat::all()->where('user_id', '=', Auth::user()->id)->where('friend_id', '=', $id);
$second = Chat::all()->where('user_id', '=', $id)->where('friend_id', '=', Auth::user()->id);
$chats = $first->merge($second);
But this way has some problems. This will not get the chats in the correct order. I think it is impossible to order it correctly.
So my question is how can I list the conversation between two persons in the correct order easily?
If you want more details about my problem you can just ask.
You should be able to do it in one query with parameter grouping, rather than executing two separate queries and then merging them.
Chat::where(function ($query) use ($id) {
$query->where('user_id', '=', Auth::user()->id)
->where('friend_id', '=', $id);
})->orWhere(function ($query) use ($id) {
$query->where('user_id', '=', $id)
->where('friend_id', '=', Auth::user()->id);
})->get();
This might also return your results in the correct order, just because without any sort criteria specified, databases will often return rows in the order they were inserted. However, without adding something to your chat table to sort by, (either a timestamp or an autoincrement id), there's no way to guarantee it.
Try like this
$first = Chat::all()->where('user_id', '=', Auth::user()->id)
->where('friend_id', '=', $id)->get();
$second = Chat::all()->where('user_id', '=', $id)
->where('friend_id', '=', Auth::user()
->id)->get();
$chats = $first->merge($second)
->sortBy('created_at');//created_at is timing added change if other
First of all, you should not do all() before filtering. This is bad because fetches all the table data and then does the filtering in PHP.
You should consider doing this:
In your migration:
Schema::create("chat", function (Blueprint $table) {
//Other creation lines
$table->timestamps();
})
Then in your chat model:
public function scopeInvolvingUsers($query, $userId,$friendId) {
return $query->where([ ["user_id",$userId],["friend_id",$friendId] ])
->orWhere([ ["user_id",$friendId],["friend_id",$userId] ]);
}
Then you can do the following:
$chats = Chat::involvingUsers(\Auth::id(),$otherId)->latest()->get();
Note that latest or earliest requires the timestamps to be present on the table.
I will add timestamps in chat table which will ensure the order.
To add timestamp into chat table just add
$table->timestamps();
and the you can select the chat related to the user and sort it by created_at.
In laravel 5.3+ use
Chats::where(['user_id', '=', Auth::id()], ['friend_id', '=', $id])->orWhere(['user_id', '=', $id], ['friend_id', '=', Auth::id()])->sortBy('created_at');
Chat::whereIn('user_id', [$id, Auth->user()->id])
->whereIn('friend_id', [$id, Auth->user()->id])->get();

Error when use hasMany. I get non related objects

User relationship:
public function events() {
return $this->hasMany('Events', 'user_id');
}
Event relationship:
public function user() {
return $this->belongsTo('User');
}
I want to get all events for the current month except today's events, so I use:
$pets= Auth::user()->events()
->where(function($query) use($myYear, $myMonth, $myDay) {
$query->whereYear('start_date', '=', $myYear);
$query->whereMonth('start_date', '=', $myMonth);
$query->whereDay('start_date', '!=', $myDay);
})->orWhere(function($query) use($myYear, $myMonth, $myDay) {
$query->whereYear('end_date', '=', $myYear);
$query->whereMonth('end_date', '=', $myMonth);
$query->whereDay('end_date', '!=', $myDay);
})->get();
But this retrieves me all the events of all users. I need to add ->where("user_id", Auth::user()->id) before -get() and I don't know why.
Can someone help me solve this question?
The issue is with your or statement. Your query currently looks like this:
where relationship_condition AND start_date_condition OR end_date_condition
In the logical order of operations, the ANDs are performed before the ORs, so this is equivalent to:
where (relationship_condition AND start_date_condition) OR end_date_condition
This means that any records that match your end_date_condition will be returned, whether or not they match the relationship_condition. In order to correct this, you need to properly group your OR condition, so it looks like this:
where relationship_condition AND (start_date_condition OR end_date_condition)
So, your code should look something like:
$pets= Auth::user()->events()
->where(function ($query) use ($myYear, $myMonth, $myDay) {
return $query
->where(function ($query) use ($myYear, $myMonth, $myDay) {
return $query
->whereYear('start_date', '=', $myYear)
->whereMonth('start_date', '=', $myMonth)
->whereDay('start_date', '!=', $myDay);
})
->orWhere(function ($query) use($myYear, $myMonth, $myDay) {
return $query
->whereYear('end_date', '=', $myYear)
->whereMonth('end_date', '=', $myMonth)
->whereDay('end_date', '!=', $myDay);
});
})
->get();
You are not keeping reference of $query to pass in closure, Try like this.
$query = Auth::user()->events();
$query->where(function($query) use($myYear, $myMonth, $myDay) {
$query->whereYear('start_date', '=', $myYear);
$query->whereMonth('start_date', '=', $myMonth);
$query->whereDay('start_date', '!=', $myDay);
});
$query->orWhere(function($query) use($myYear, $myMonth, $myDay) {
$query->whereYear('end_date', '=', $myYear);
$query->whereMonth('end_date', '=', $myMonth);
$query->whereDay('end_date', '!=', $myDay);
})
$pets = $query->get();
How to solve: in mySQL (which I assume you're using if it's Lavavel) simply turn on the "general log". Here's a pretty decent StackOverflow on how to do that: How to enable MySQL Query Log?
This will log all the calls made to MySQL and you'll be able to see the construct.
This next bit is a guess: you'll probably find the query ends up as:
SELECT events FROM events
WHERE
userID = :userID
AND (not start date)
OR (not end date)
because ANDs are evaluated with higher priority (http://dev.mysql.com/doc/refman/5.7/en/operator-precedence.html) this is the same as
SELECT events FROM events
WHERE
(
userID = :userID
AND
(not start date)
)
OR
(not end date)
And therefore will include anyone in the second query, not just the user.
But what you need is
SELECT events FROM events
WHERE
userID = :userID
AND
(
(not start date)
OR
(not end date)
)
How you've "fixed" it, but by adding the additional "AND" at the end, you get
SELECT events FROM events
WHERE
userID = :userID
AND (not start date)
OR (not end date)
AND userID = :userID
Which is the same as
SELECT events FROM events
WHERE
(
userID = :userID
AND
(not start date)
)
OR
(
(not end date)
AND
userID = :userID
)
which does what you want, in a roundabout way...

laravel belongsToMany Filter

I have three tables as below:
users
id|name|username|password
roles
id|name
users_roles
id|user_id|role_id
These tables communicate via belongsToMany.
I would like to find a way to select all data in “users” table except ones that their user value of "role_id" is 5 in table “users_roles”.
How can I do it?
You should use whereDoesntHave() to select models that don't have a related model meeting certain criteria:
$users = User::whereDoesntHave('roles', function($q){
$q->where('role_id', 5);
})->get();
Use Laravel's Query Builder:
<?php
$users = DB::table('users')
->leftJoin('users_roles', 'user.id', '=', 'users_roles.user_id')
->where('users_roles.role_id', '!=', 5)
->get();
http://laravel.com/docs/4.2/queries
Or using Eloquent directly:
<?php
$users = User::whereHas('users_roles', function($q)
{
$q->where('role_id', '!=', 5);
})->get();
http://laravel.com/docs/4.2/eloquent#querying-relations
<?php
$users = User::whereHas('roles', function($query) {
$query->where('id', '<>', 5);
})
->orHas('roles','<', 1)
->get();
I think the correct answer is:
User::whereHas('roles', function ($query) {
$query->whereId(5)
}, '=', 0)->get();
This code should send a query that checks if the role with id=5 is related to the user or not.
Edit
While I think this should work but the #lukasgeiter answer is preferable.
In the end both methods use the has() to count the related models by using a subquery in the db query where clause but when you use the whereDoesntHave() it specifies the operator < and the count 1 itself.
You can var_dump(DB::getQueryLog()) in App::after()'s callback to see the actual query.

Laravel where also returns empty results

I'm using Laravel 4 to get all the persons that have a score for a certain event
This is the query i'm using
$event = Person::with(array('eventscore' => function($q){
$q->where('id', 3);
}))->get();
The problem is that it's also returning the persons that don't have a score in the eventscore table.
This is the output
Is there any way that i can return only the persons that have a score?
Thanks!
with() will not limit the Persons returned, it will only limit eventscores. If you want only Persons that have an event score, you use has or whereHas.
$event = Person::whereHas('eventscore', function($q) {
$q->where('id', 3);
})->get();

Categories