Error when use hasMany. I get non related objects - php

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...

Related

Get last record on GROUP BY using Laravel & MySql

I have two table (users and messages) .. I wrote a query to get all messages that users sent to me or I sent, using JOIN .. to get all the users I have contacted or they did.
as in the code below:
$users = Message::join('users', function ($join) {
$join->on('messages.sender_id', '=', 'users.id')
->orOn('messages.receiver_id', '=', 'users.id');
})
->where(function ($q) {
$q->where('messages.sender_id', Auth::user()->id)
->orWhere('messages.receiver_id', Auth::user()->id);
})
->orderBy('messages.created', 'desc')
->groupBy('users.id')
->paginate();
The problem here is when records grouped, I'm getting the old message not the new one according to its created_at .. So, I want to get the last record of the grouped records.
It seems like it would make more sense to make use of Eloquent's relationships here so that you can eager load the relationships instead of having to use join and group by:
$messages = Message::with('sender', 'receiver')
->where(function ($query) {
$query->where('sender_id', auth()->id())
->orWhere('receiver_id', auth()->id())
})
->orderByDesc('created') // is this meant to be 'created_at'?
->paginate();

Laravel add function to join

I have 2 tables, a product table and a user table.
In my users table, there's a last_login column, which returns a datetime.
In this query, I'd like to be able to create a function that would allow me to only get products if the user hasn't been online for a certain amount of time.
I was thinking of using joins for this but I'm not overly familiar with them.
Something like...
$products = Product::where("price", "<=", $maxBudget)
->where('active', 1)
...
->join('users', function ($join) {
$join->on('products.created_by', '=', 'users.id')
->where('last_login', '>', 2592000);
})
->get()
except this wouldn't work because last_login is a datetime so I'd need to put in a function in there like:
if ($user->last_login->diffInSeconds(Carbon::now() > 2592000) {
do the thing
}
How could I do this?
If you're trying to join the tables then you should be able to do something like:
// min seconds
$threshold = 123456;
Product::query()
->join('users', 'products.created_by', '=', 'users.id')
->where('products.active', 1)
->where('users.last_login', '<=', now()->subSeconds($threshold)->toDateTimeString())
->select(['products.*', 'users.last_login'])
->get();
Otherwise if it's based on the logged in user's last_login:
// Get the user's last login attribute.
$lastLogin = auth()->user()->last_login;
// whatever the minimum time since their last login should be.
$threshold = 12345;
$exceeded = now()->diffInSeconds(Carbon::parse($lastLogin))->gte($threshold);
if ($exceeded) {
Product::where(...)->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();

How to order my query by most duplicates in Laravel?

On my website, users can post images.
Images can have tags.
There's 4 tables for this, the images table, the images_tag pivot table, the tag table, and of course the users table.
A user can have multiple images with the same tag(s).
I can pull up the tags a user has used across all his images with this query:
$userTags = Tag::whereHas('images', function($q) use($user) {
$q->where('created_by', $user->id);
})->get();
However, I want to make it so that I can order these tags based on how frequently a user uses them. In other words, I want to order by duplicates. Is this possible?
To achieve this, you're going to need to join the images_tags and images tables, count the number of tags, and order by those tags.
$tags = Tag::selectRaw('tags.*, COUNT(images.id) AS total')
->join('images_tags', 'tags.id', '=', 'images_tags.tag_id')
->join('images', 'images.id', '=', 'images_tags.image_id')
->where('images.created_by', $user->id)
->groupBy('tags.id')
->orderBy('total', 'desc')
->get();
The above query will only work in MySQL if the only_full_group_by option is disabled. Otherwise, you're going to need to either rewrite this to use a sub query, or do the ordering in the returned Laravel Collection. For example:
$tags = Tag::selectRaw('tags.*, COUNT(images.id) AS total')
->join('images_tags', 'tags.id', '=', 'images_tags.tag_id')
->join('images', 'images.id', '=', 'images_tags.image_id')
->where('images.created_by', $user->id)
->groupBy('tags.id')
->get();
$tags = $tags->sortByDesc(function ($tag) {
return $tag->total;
});
If you want to add this to your user model, per your comment, create a function similar to the following:
public function getMostUsedTags($limit = 3)
{
return Tag::selectRaw('tags.*, COUNT(images.id) AS total')
->join('images_tags', 'tags.id', '=', 'images_tags.tag_id')
->join('images', 'images.id', '=', 'images_tags.image_id')
->where('images.created_by', $this->id)
->groupBy('tags.id')
->orderBy('total', 'desc')
->limit($limit)
->get();
}

Laravel Eloquent query get users from another model

I have another table called tableb and it has a user relationship defined through the user_id field.
I want to run a query against tableb where a certain date is within a certain range but then I want to grab the user table associated with that row but I only want it to grab the user if it's not been grabbed yet. I'm trying to do this all in 1 DB query. I have most of it done, but I'm having trouble with the unique part of it.
Here's what I have right now:
$tableB = TableB::select('users.*')
->join('users', 'tableb.user_id', '=', 'users.id')
->where('tableb.start_date', '>', date('Y-m-d'))
->get();
So right now I have 3 entries in tableB from the same user, and ideally I'd like to only get 1 entry for that user.
How would I go about doing this?
Since you're selecting only users data, just add a groupBy clause in your query.
$tableB = TableB::select('users.*')
->join('users', 'tableb.user_id', '=', 'users.id')
->where('tableb.start_date', '>', date('Y-m-d'))
->groupBy('users.id')
->get();
You should just add groupBy like this :
$tableB = TableB::select('users.*')
->join('users', 'tableb.user_id', '=', 'users.id')
->where('tableb.start_date', '>', date('Y-m-d'))
->groupBy('users.id')
->get
Try This Code
App/user.php
public function getrelation(){
return $this->hasMany('App\tableB', 'user_id');
}
In Your Controller
Controller.php
use App/user;
public funtion filterByDate(user $user)
{
$date = '2016-02-01';
$result = $user->WhereHas('getrelation', function ($query) use($date) {
$query->whereDate('tableb.start_date', '>', $date)
->first();
});
}

Categories