How to do scope with subquery - laravel

I have a model Partner_deal which has lots of fields but the only one you really need to know about is called quantity which is an integer that specifies how many times the deal can be redeemed.
I then have another model Partner_deal_redemption to keep track of the redemptions. This has a partner_deal_id column and a user_id column to record which users have redeemed which deal.
I want to create a scope in my Partner_deal model so that it will only return deals where the number of redemptions is less than the quantity field.
I know how to do this in MySql by doing a subquery that counts how many redemptions each deal has had and uses a HAVING clause to filter out the ones where the number of redemptions = quantity.
I have no idea where to begin doing this in eloquent, this is my best attempt:
function scopeNotRunOut($query)
{
return $query->having('quantity', '>', function($q)
{
$q->from('partner_deal_redemptions')
->selectRaw('count(*)')
->where('partner_deal_id', '=', 'id');
});
}

You can probably use the has() function of Eloquent:
http://laravel.com/docs/5.0/eloquent#querying-relations
function scopeNotRunOut($query)
{
return $query->has('redemptions', '<', DB::raw('quantity'));
}
To use this function you need to define the redemptions function in your Partner_deal model, which will represent the relation between the Partner_deal and the Partner_deal_redemption models.

MrShibby was correct but then I realised I needed to check if quantity was null as well so I modified his solution slightly to the following:
function scopeNotRunOut($query)
{
return $query->where(function($q) {
$q->orHas('redemptions', '<', DB::raw('quantity'))
->orWhere('quantity', '=', null);
});
}

Related

Sort the results returned by model relation laravel

I have 2 table:
products: id, name
auctions: id, product_id, expired_time
Model product
function getAuctions(){
return $this->hasOne('App\Models\Auction', 'product_id', 'id');
}
How can I do the same as below(auctions.expired_time not working):
Controller
function getProductAuctions(){
return \App\Models\Product::with('getAuctions')->whereHas('getAuctions', function(){})->orderBy('auctions.expired_time', 'asc');
}
Thanks everyone.
You can do it with sortBy() method. That method applies to collections only.
function getProductAuctions(){
$products = \App\Models\Product::all();
return $products->sortBy(function ($product, $key) {
return $product->getAuctions->expired_time;
})
}
This should now sort your products collection by the auctions expired time, if you want reversed order, you can just use sortByDesc() method, rest is the same. Also, in your product model, if you are having hasOne() relationship, rename the function name to getAuction() or even better auction() (singular).
you can use one of these to get sort your result
in your controller
function getProductAuctions(){
return \App\Models\Product::with('getAuctions')->whereHas('getAuctions', function($query){
$query->orderBy('expired_time', 'asc');
})->get();
}
and if you want to sort in your model then just use this
function getAuctions(){
return $this->hasOne('App\Models\Auction', 'product_id', 'id')->orderBy('expired_time', 'asc');
}
and thats all no need to do any thing...your result is already sorted
Thank everyone. I found the answer here and I find it correct: https://laracasts.com/discuss/channels/eloquent/order-by-on-relationship
"When you use the with method the Eloquent makes another query in database to retrieve the related data using whereIn. This is why we can't order by a relation field."

Eloquent Removing Columns

I'm having a really strange issue with my eloquent query. I have a table called Calls which I am joining to Contacts and Companies. I am trying to reference the column calls.id but it has been replaced with the id for Companies.
Here is my query:
$calls=DB::table('calls')
->leftJoin('contacts','calls.contact_id','=','contacts.id')
->leftJoin('companies','calls.company_id','=','companies.id')
->where('completed','=',false)
->orderBy('call_on','asc')
->get();
return $calls;
I have seen on Github that this seems to be a known bug but no-one has put forward a workaround.
Can anyone point me in the right direction?
The most direction solution to your immediate question is to add a select to your Eloquent query:
$calls=DB::select('calls.* from calls')
->leftJoin('contacts','calls.contact_id','=','contacts.id')
->leftJoin('companies','calls.company_id','=','companies.id')
->where('completed','=',false)
->orderBy('call_on','asc')
->get();
return $calls;
Instead of the default select *, explicitly dictate what is returned. However, this can be done a lot more cleanly with Eloquent using models:
Calls::whereHas('companies', function (Builder $query) {
$query->where('completed', false);
})->orderBy('call_on', 'asc')->get();
In order for this to work you need to setup the relationship on the model level:
// App\Calls model:
public function companies() {
return $this->belongsTo(App\Companies::class);
}
// App\Companies model:
public function calls() {
return $this->hasMany(App\Calls::class);
}

Eloquent query all the latest records for each id

I'm looking to do something like this, but with Eloquent: get latest record for each ID
Personally, I have three tables: Game, Assignments, Users.
Games get assigned to Users through the Assignments table. However, when a game needs to be assigned to a new user, I just make a new Assignment, and go off of the latest assignment for each game.
My end goal is to be able to grab a collection of games that any given User has assigned to them. However, to do that, I first need to be able to query Assignments and filter out any assignment that isn't the most recent for that given Game id.
Any thoughts? I've been trying some things (see below) but not getting anywhere.
Game function (works):
public function latestAssignment() {
return $this->hasOne('App\Models\Game_Assignment', 'game_id')->latest();
}
Game_Assignment function:
public function isLatestAssignment() {
if($this->game->latestAssignment()->id == $this->id) {
return true;
} else {
return false;
}
}
User function (throws error):
public function current_game_assignments() {
return $this->hasMany('App\Models\Game_Assignment', 'statistician_id')->where('isLatestAssignment', true);
}
Let me know what y'all think I can do!
What you can do is select your games with the id of the latest assigned user in a subquery. You can then use a special relation that utilizes this subquery column to join to the users table:
class Game extends Model
{
public function latestUser()
{
return $this->hasOne(User::class, 'id', 'latest_user_id');
}
}
$games = Game::query()
->select('*') // necessary to avoid overrides by selectSub()
->selectSub(
Assignment::query()
->whereColumn('game_assignments.game_id', 'games.id') // table prevents ambiguity
->latest()
->select('game_assignments.user_id')
->take(1),
'latest_user_id'
)
->with('latestUser')
->get();
After re-reading your question, I come to a different solution. If you want all the games for a specific user of which the user is the latest assigned user, you can use the following query. It uses a little hack with the wrapping, but without this it doesn't allow to filter on the subquery:
// only for demonstration purposes
$user = User::find(1);
$games = Game::query()
->fromSub(
Game::query()
->select('*') // necessary to avoid overrides by selectSub()
->selectSub(
Assignment::query()
->whereColumn('game_assignments.game_id', 'games.id')
->latest()
->select('game_assignments.user_id')
->take(1),
'latest_user_id'
),
'games'
)
->where('latest_user_id', $user->id)
->get();
Please note that the subquery alias (second argument) must be the table name of your games.

Eloquent select with() based on foreign key

I have a table with user data (users) and a table with prices (prices).
My prices table can contain multiple prices pr. user since I want to keep historical data.
I've defined my relation as a one-to-one
$this->hasOne("App\Model\Price","userid","id")->orderBy("id","desc")->take(1);
to allow me to see the users current price.
What I want to do now, is to select every user that has a current price of 100, but how do I do this? I know I could go for a left join, but as I read the documentation, it should be possible without a left join.
I've built a pseudo-query to explain what I'm after;
User::with("price")->where("prices.price","100")->get();
I've read through the documentation (Eloquent: Querying relationships), but that doesn't seem to be useful to my question.
I've also read several questions here on SO but unfortunately to no avail.
You may try this:
$currentPrice = 100;
$users = User::whereHas('price', function($query) use ($currentPrice) {
$query->where('price', $currentPrice); // price is the field name
})
->with("price")->get();
Since you have more than a single price for per user then you may also declare another relationship method to get all the price models instead of one and you may do it using something like this:
// In User model
public function prices()
{
return $this->hasMany("App\Model\Price", "userid", "id");
}
In this case, with::price will give you the last single record and with::prices will give you all the related prices. So, if you want then you may write something like the following to get all users with their all related prices who has the (latest/current) price of 100:
$currentPrice = 100;
$users = User::whereHas('price', function($query) use($currentPrice) {
$query->where('price', $currentPrice); // price is the field name
})
->with("prices") // with all prices
->get();
You can use the combination of whereHas() and with() as:
$users = User::whereHas("price", function($q) use ($currentPrice) {
$q->where("price", $currentPrice);
})
->with(["price" => function ($q) {
$query->where("price", $currentPrice);
})
->get();

Laravel / Eloquent: Search for rows by value in polymorphed table

I'm stuck at the moment and hope someone can give me a hand. I'm using a polymorphic relation and want to search my database for rows that fulfill conditions in the "parent" and the "child" table.
To get concrete, one small example. Given the following structure I e.g. want to look for a property with price "600" and rooms "3". Is there a way to do that with eloquent?
Tables
Table properties (parent)
id
price
details_type [can be "Apartment" or "Parcel"]
details_id
Table apartments (child)
id
rooms
Table parcels (child)
id
... (does not have a "rooms" column)
Relationships
Class Property
public function details() {
return $this->morphTo();
}
Classes Apartment + Parcel
public function property() {
return $this->morphMany('Property', 'details')
}
What I tried
A lot, really. But somehow I'm always doing something wrong or missing something. The solutions that, in my opinion should work are either:
Property::with(array('details' => function($query) {
$query->where('rooms', 3);
}));
or
Property::with('details')
->whereHas('details', function($query) {
$query->where('rooms', '=', '3');
});
But in both cases I get the following FatalError.:
Class name must be a valid object or a string
Has anyone of you already had a similar problem? Thank you very much for any kind of hint.
Let's start with your naming convention:
public function detail() // It relates to a single object
{
return $this->morphTo();
}
And
public function properties() // It relates to several objects
{
return $this->morphMany('Property', 'details')
}
Then you would be able to do this:
$properties = Property::whereHas('details', function($q)
{
$q->where('rooms', '=', '3');
})
->where('price', '=', 600)
->get();
Please note that this will never return a Parcel, since there isn't a parcel with a room.

Resources