In database table I have one row :
users
id|email|is_deleted
1|test#test.com|1
I have this code :
User::where('email', 'test#test.com')
->orWhere('email', 'test2#test2.com')
->get();
and this query is generated :
select * from users where email = 'admin#myzone.com' or email = 'asdasdas'
with one result. Now I want apply where is_deleted = 0
If I do like this :
User::where('email', 'test#test.com')
->orWhere('email', 'test2#test2.com')
->where('is_deleted', 0)
->get();
Generated query is :
select * from "users" where "email" = ? or "email" = ? and "users"."deleted_at" is null
So far everything works as expected, this query returns one result, but I want only not deleted users, I can do following :
User::where(function($query){
$query->where('email', 'test#test.com')
->orWhere('email', 'test2#test2.com')
})->where('is_deleted', 0)
->get();
and this will work, but in my code I already have returned builder :
function applyNotDeleted(Builder $builder){
//here I want to filter only not deleted users,
//but this is already triggered on builder $query->where('email', 'test#test.com')->orWhere('email', 'test2#test2.com')
//currently generated query on builder is select * from users where email = 'admin#myzone.com' or email = 'asdasdas'
//but at this stage I want to create query which will look like select * from "users" where "email" = ? or "email" = ? and "users"."deleted_at" is null
//something like this
$builderNew = $builderNew->where(function($query){
$query->applyAllLogicFromCurrentBuilder($builder)
})
->where('is_deleted', 0)
->get();
}
any idea?
Personally I would use query scope to just obtain non deleted records
public function scopeNotDeleted(Builder $query): Builder
{
return $query->where('is_deleted', 0);
}
and then use it when fetching records
User::notDeleted()->where(function(Builder $query) {
$query->where('email', 'test#test.com')->orWhere('email', 'test2#test2.com');
})->get();
If you are using applyAllLogicFromCurrentBuilder method, you can also extract it to the query scope and just chain it with your call like so:
User::allLogicFromCurrentBuilder()->notDeleted()->where(function(Builder $query) {
$query->where('email', 'test#test.com')->orWhere('email', 'test2#test2.com');
})->get();
You can also keep your applyNotDeleted method as is, but without the call to get() method - this way you can append any further statements to it if need to. I would probably convert it to a public static method so you can call it without instantiating model:
User::applyNotDeleted(User::where(function(Builder $query) use ($email) {
$query->where('email', 'test#test.com')->orWhere('email', 'test2#test2.com');
}))->get();
Personally I would prefer the scope approach as it seems a bit cleaner.
Related
I am trying to get two different results from single query but the problem is both conditions get applied on last query.
for example
$getUser = User::join('role_user','role_user.user_id','users.id')
->select('users.id','users.name','users.email','users.identity_status','users.email_verified_at','role_user.role_id')
->where('role_user.role_id',2)
->whereNotNull('users.email_verified_at');
$newMailUsers = $getUser->where('new_mail',0)->get();
$topSellingMailUsers = $getUser->where('topselling_mail',0)->get();
but when i checked sql query of $topSellingMailUsers i saw that both the conditions of new_mail and topselling_mail applied in $topSellingMailUsers query what i want is it should not consider mail condition in query.
how can i get two different results of $newMailUsers, $topSellingMailUsers based on both conditions separately.
Every time you use where() method you are mutating $getUser query builder.
You can chain your query builder with clone() method and this will return another query builder with the same properties.
$getUser = User::join('role_user','role_user.user_id','users.id')
->select('users.id','users.name','users.email','users.identity_status','users.email_verified_at','role_user.role_id')
->where('role_user.role_id',2)
->whereNotNull('users.email_verified_at');
$newMailUsers = $getUser->clone()->where('new_mail',0)->get();
$topSellingMailUsers = $getUser->clone()->where('topselling_mail',0)->get();
you need to clone Builder object to reuse it
$selectFields = [
'users.id',
'users.name',
'users.email',
'users.identity_status',
'users.email_verified_at',
'role_user.role_id'
];
/** #var Illuminate\Database\Eloquent\Builder */
$getUser = User::join('role_user', 'role_user.user_id', 'users.id')
->select($selectFields)
->where('role_user.role_id', 2)
->whereNotNull('users.email_verified_at');
$newMailUsers = (clone $getUser)->where('new_mail', 0)->get();
$topSellingMailUsers = (clone $getUser)->where('topselling_mail', 0)->get();
Well, if you have relations set up on models will be easier, but still can do it:
// Let's say you pass sometimes the role_id
$role_id = $request->role_id ?? null;
$getUser = User::join('role_user','role_user.user_id','users.id')
->select('users.id','users.name','users.email','users.identity_status','users.email_verified_at','role_user.role_id')
// ->where('role_user.role_id',2) // replaced with ->when() method
->when($role_id, function ($query) use ($role_id){
$query->where('role_user.role_id', $role_id);
})
->whereNotNull('users.email_verified_at');
This way it will return users with ALL ROLES or if ->when(condition is TRUE) it will return users with the role id = $role_id.
You can use multiple ->when(condition, function(){});
Have fun!
Instead of firing multiple queries you can do it in a single query as well by using collection method as like below.
$users = User::join('role_user','role_user.user_id','users.id')
->select('users.id','users.name','users.email','users.identity_status','users.email_verified_at','role_user.role_id', 'new_mail', 'topselling_mail')
->where('role_user.role_id',2)
->where(function($query) {
$query->where('new_mail', 0)->orWhere('topselling_mail', 0);
})
->whereNotNull('users.email_verified_at')
->get();
$newMailUsers = $users->where('new_mail',0)->get();
$topSellingMailUsers = $user->where('topselling_mail',0)->get();
I'm trying to fetch some data with a subquery using Eloquent but dding returns nothing. Separately, this
$discountArticles = $discountTableItemIdIn
->where('recipient_type', '=', 'article')
->toArray();
or this
$discountArticles = $discountTableItemIdIn
->where('recipient_id', '=', $articleId)
->toArray();
work fine.
However when I try something like this, it fails (or rather, returns nothing):
$discountArticles = $discountTableItemIdIn->where(function ($subQuery) {
$subQuery
->where('recipient_type', '=', 'article')
->where('recipient_id', '=', $articleId);
})->toArray();
I know I can do separate queries on the same collection and do an array_merge but I'd like to get this way working instead. Not sure what's happening.
So $discountTableItemIdIn is a collection of the entire table? That means you're gonna need a different function, as the ->where() logic on a collection is different from how it functions on a builder (eloquent) instance.
Try using filter():
$discountArticles = $discountTableItemIdIn->filter(function ($item) use($articleId) {
return $item->recipient_type == "article" && $item->recipient_id == $articleId;
})->toArray();
What this will do is filter your $discountTableItemIdIn collection for records that have a type of article and a recipient_id of whatever $articleId contains, return a new collection and convert that to an array.
Just a note, this is quite inefficient; you should try to avoid loading the whole table into a collection and just query the table directly using the subquery logic in your question.
In Laravel we can setup relationships like so:
class User {
public function items()
{
return $this->belongsToMany('Item');
}
}
Allowing us to to get all items in a pivot table for a user:
Auth::user()->items();
However what if I want to get the opposite of that. And get all items the user DOES NOT have yet. So NOT in the pivot table.
Is there a simple way to do this?
Looking at the source code of the class Illuminate\Database\Eloquent\Builder, we have two methods in Laravel that does this: whereDoesntHave (opposite of whereHas) and doesntHave (opposite of has)
// SELECT * FROM users WHERE ((SELECT count(*) FROM roles WHERE user.role_id = roles.id AND id = 1) < 1) AND ...
User::whereDoesntHave('Role', function ($query) use($id) {
$query->whereId($id);
})
->get();
this works correctly for me!
For simple "Where not exists relationship", use this:
User::doesntHave('Role')->get();
Sorry, do not understand English. I used the google translator.
For simplicity and symmetry you could create a new method in the User model:
// User model
public function availableItems()
{
$ids = \DB::table('item_user')->where('user_id', '=', $this->id)->lists('user_id');
return \Item::whereNotIn('id', $ids)->get();
}
To use call:
Auth::user()->availableItems();
It's not that simple but usually the most efficient way is to use a subquery.
$items = Item::whereNotIn('id', function ($query) use ($user_id)
{
$query->select('item_id')
->table('item_user')
->where('user_id', '=', $user_id);
})
->get();
If this was something I did often I would add it as a scope method to the Item model.
class Item extends Eloquent {
public function scopeWhereNotRelatedToUser($query, $user_id)
{
$query->whereNotIn('id', function ($query) use ($user_id)
{
$query->select('item_id')
->table('item_user')
->where('user_id', '=', $user_id);
});
}
}
Then use that later like this.
$items = Item::whereNotRelatedToUser($user_id)->get();
How about left join?
Assuming the tables are users, items and item_user find all items not associated with the user 123:
DB::table('items')->leftJoin(
'item_user', function ($join) {
$join->on('items.id', '=', 'item_user.item_id')
->where('item_user.user_id', '=', 123);
})
->whereNull('item_user.item_id')
->get();
this should work for you
$someuser = Auth::user();
$someusers_items = $someuser->related()->lists('item_id');
$all_items = Item::all()->lists('id');
$someuser_doesnt_have_items = array_diff($all_items, $someusers_items);
Ended up writing a scope for this like so:
public function scopeAvail($query)
{
return $query->join('item_user', 'items.id', '<>', 'item_user.item_id')->where('item_user.user_id', Auth::user()->id);
}
And then call:
Items::avail()->get();
Works for now, but a bit messy. Would like to see something with a keyword like not:
Auth::user()->itemsNot();
Basically Eloquent is running the above query anyway, except with a = instead of a <>.
Maybe you can use:
DB::table('users')
->whereExists(function($query)
{
$query->select(DB::raw(1))
->from('orders')
->whereRaw('orders.user_id = users.id');
})
->get();
Source: http://laravel.com/docs/4.2/queries#advanced-wheres
This code brings the items that have no relationship with the user.
$items = $this->item->whereDoesntHave('users')->get();
I have an application with a basic forum system where users can "like" a topic multiple times. My models extend Eloquent and I'm trying to get the sum of votes a user has for a specific topic... Basically, I'm trying to accomplish something like:
$votes = Auth::user()
->votes->has('topic_id', '=', $topic->id)
->sum('votes');
However, when executing this, I get the following error...
Call to a member function sum() on a non-object
I've also tried
public function show($forumSlug, $topicSlug)
{
$topic = Topic::whereSlug($topicSlug)->first();
$votes = Topic::whereHas('votes', function ($q) use ($topic)
{
$q->where('topic_id', '=', $topic->id)->sum('votes');
});
dd($votes);
}
However, with that I receive an error stating:
Unknown column 'ideas.id' in 'where clause' (SQL: select sum(votes)
as aggregate from votes where votes.idea_id = ideas.id and
idea_id = 1)`
You may try something like this (Not sure about your relationship but give it a try):
$topic = User::with(array('topics' => function ($query) use ($topic_id) {
// $query = Topic, so it's: Topic::with('votes')
$query->with('votes')->where('topics.id', $topic_id);
}))->find(Auth::user()->id)->topics->first();
// Count of total votes
dd($topic->votes->count());
P/S: If it doesn't work then please post your model's relationship methods.
I managed to get it working, though I'm not sure I like this approach. I'd love to hear if anyone knows of a better way of doing this...
Basically, I used my relationships to filter() the votes and then used sum() on the filtered collection.
public function show($forumSlug, $topicSlug)
{
$userId = is_null(Auth::user()) ? false : Auth::user()->id;
$topic = Topic::whereSlug($topicSlug)->first();
$votes = $topic->votes->filter(function ($votes) use ($userId)
{
return $votes->user_id == $userId;
})->sum('votes');
return View::make('forums.topics.show', compact('topic', 'votes'));
}
I have a query builder that works:
$article = Page::where('slug', '=', $slug)
->where('hide', '=', $hidden)
->first();
But I want to only add the second where statement if hidden is equal to 1. I've tried the code below which shows the logic of what I'm trying to do, but it doesn't work.
$article = Page::where('slug', '=', $slug);
if ($hidden == 1) {
$article->where('hide', '=', 1);
}
$article->first();
I'm using Laravel 4, but I think the question still stands with Laravel 3.
Yeah there's a little "gotcha" with Eloquent and the query builder. Try the code below ;)
$query = Page::where('slug', '=', $slug);
if ($hidden == 1) {
$query = $query->where('hide', '=', 1);
}
$article = $query->first();
Note the assigning of $query within the conditional. This is becuase the first where (statically called) returns a different object to the query object within the conditional. One way to get around this, I believe due to a recent commit, is like so:
$query = Page::where('slug', '=', $slug)->query();
This will return the query object and you can do what you want as per normal (Instead of re-assigning $query).
Hope that helps.