Using whereRelation with a subquery - laravel

I have wrote a zip_code query that I am wanting to add to another query. The problem is I am not exactly sure how to combine these via whereRelation().
$zip_code = ZipCode::where('office', '=', function ($query) {
$query->selectRaw('office')->from('zip_codes')->where('zip_code', session()->get('zip_code'));
})->get();
$appointment = Appointment::with('customer', 'blocks', 'status', 'services.service')->whereRelation('customer', 'zip_code', $zip_code)->get();
The output of $appointment is:
select * from appointments where exists (select * from customers where appointments.customer_id = customers.id and zip_code = {"id":1,"zip_code":00000,"city":"City","office":"LOCATION"})
$zip_code is returning this query. I am just wanting those results to be called in the whereRelation() so all the zip_codes for that office are returned:
SELECT * FROM zip_codes
where office = (
select office
from zip_codes
where zip_code = '00000'
)

You can pluck the zip_code values from the $zip_code collection like so.
$zip_code = ZipCode::select('zip_code')
->where(
'office',
function($query) {
$query->select('office')
->from('zip_codes')
->where(
'zip_code',
session()->get('zip_code')
);
}
)->get();
$zip_code = $zip_code->pluck('zip_code')->all();
Then use that in the next query for filtering the relation.
$appointment = Appointment::with('customer', 'blocks', 'status', 'services.service')
->whereRelation(
'customer',
function($query) {
$query->whereIn('zip_code', $zip_code);
}
)->get();
Otherwise, you could query for the office with the given zip code, carry out a join of the customer and the zip codes table then filter out customers with zip codes pertaining to the office.
$offices = ZipCode::select('office')
->where('zip_code', session()->get('zip_code'))
->get();
$offices = $offices->pluck('office')->all();
$appointment = Appointment::with(
'customer',
'blocks',
'status',
'services.service'
)->whereHas('customer', function ($query) {
$query->join('zip_codes', function ($join) {
$join->on('zip_codes.zip_code', 'customers.zip_code');
$join->whereIn('zip_codes.office', $office);
});
});

Related

Laravel order by eagerly loaded column

I am using laravel eager loading to load data on the jquery datatables. My code looks like:
$columns = array(
0 => 'company_name',
1 => 'property_name',
2 => 'amenity_review',
3 => 'pricing_review',
4 => 'sqft_offset_review',
5 => 'created_at',
6 => 'last_uploaded_at'
);
$totalData = Property::count();
$limit = $request->input('length');
$start = $request->input('start');
$order = $columns[$request->input('order.0.column')];
$dir = $request->input('order.0.dir');
$query = Property::with(['company','notices']);
$company_search = $request->columns[0]['search']['value'];
if(!empty($company_search)){
$query->whereHas('company', function ($query) use($company_search) {
$query->where('name','like',$company_search.'%');
});
}
$property_search = $request->columns[1]['search']['value'];
if(!empty($property_search)){
$query->where('properties.property_name','like',$property_search.'%');
}
if(!Auth::user()->hasRole('superAdmin')) {
$query->where('company_id',Auth::user()->company_id);
}
$query->orderBy($order,$dir);
if($limit != '-1'){
$records = $query->offset($start)->limit($limit);
}
$records = $query->get();
With this method I received error: Column not found: 1054 Unknown column 'company_name' in 'order clause' .
Next, I tried with following order condition:
if($order == 'company_name'){
$query->orderBy('company.name',$dir);
}else{
$query->orderBy($order,$dir);
}
However, it also returns similar error: Column not found: 1054 Unknown column 'company.name' in 'order clause'
Next, I tried with whereHas condition:
if($order == 'company_name'){
$order = 'name';
$query->whereHas('company', function ($query) use($order,$dir) {
$query->orderBy($order,$dir);
});
}else{
$query->orderBy($order,$dir);
}
But, in this case also, same issue.
For other table, I have handled this type of situation using DB query, however, in this particular case I need the notices as the nested results because I have looped it on the frontend. So, I need to go through eloquent.
Also, I have seen other's answer where people have suggested to order directly in model like:
public function company()
{
return $this->belongsTo('App\Models\Company')->orderBy('name');
}
But, I don't want to order direclty on model because I don't want it to be ordered by name everytime. I want to leave it to default.
Also, on some other scenario, I saw people using join combining with, but I am not really impressed with using both join and with to load the same model.
What is the best way to solve my problem?
I have table like: companies: id, name, properties: id, property_name, company_id, notices: title, slug, body, property_id
The issue here is that the Property::with(['company','notices']); will not join the companies or notices tables, but only fetch the data and attach it to the resulting Collection. Therefore, neither of the tables are part of the SQL query issued and so you cannot order it by any field in those tables.
What Property::with(['company', 'notices'])->get() does is basically issue three queries (depending on your relation setup and scopes, it might be different queries):
SELECT * FROM properties ...
SELECT * FROM companies WHERE properties.id in (...)
SELECT * FROM notices WHERE properties.id in (...)
What you tried in the sample code above is to add an ORDER BY company_name or later an ORDER BY companies.name to the first query. The query scope knows no company_name column within the properties table of course and no companies table to look for the name column. company.name will not work either because there is no company table, and even if there was one, it would not have been joined in the first query either.
The best solution for you from my point of view would be to sort the result Collection instead of ordering via SQL by replacing $records = $query->get(); with $records = $query->get()->sortBy($order, $dir);, which is the most flexible way for your task.
For that to work, you would have to replace 'company_name' with 'company.name' in your $columns array.
The only other option I see is to ->join('companies', 'companies.id', 'properties.company_id'), which will join the companies table to the first query.
Putting it all together
So, given that the rest of your code works as it should, this should do it:
$columns = [
'company.name',
'property_name',
'amenity_review',
'pricing_review',
'sqft_offset_review',
'created_at',
'last_uploaded_at',
];
$totalData = Property::count();
$limit = $request->input('length');
$start = $request->input('start');
$order = $columns[$request->input('order.0.column')];
$dir = $request->input('order.0.dir');
$query = Property::with(['company', 'notices']);
$company_search = $request->columns[0]['search']['value'];
$property_search = $request->columns[1]['search']['value'];
if (!empty($company_search)) {
$query->whereHas(
'company', function ($query) use ($company_search) {
$query->where('name', 'like', $company_search . '%');
});
}
if (!empty($property_search)) {
$query->where('properties.property_name', 'like', $property_search . '%');
}
if (!Auth::user()->hasRole('superAdmin')) {
$query->where('company_id', Auth::user()->company_id);
}
if ($limit != '-1') {
$records = $query->offset($start)->limit($limit);
}
$records = $query->get()->sortBy($order, $dir);

Convert Raw SQL Query to Laravel Eloquent using eager loading

I'm having great difficulty in trying to convert the following query into an Eloquent one.
SELECT Sum(t.amount) AS amount,
m.name
FROM transactionsv2 t
JOIN channels c
ON t.entityid = c.uuid
JOIN merchants m
ON c.sender = m.uuid
WHERE t.paymenttype = 'DB'
AND t.status = 1
AND t.processing_time >= '2019-01-01'
AND t.processing_time < '2019-01-21'
GROUP BY m.name;
This is what I have but the result set is not right...
public function getTransactionsVolumeReport()
{
$report = Transaction::select(DB::raw('sum(amount) as amount, entityId'))
->where('paymentType', '=', 'DB')
->where('status', 1)
->where('processing_time', '>=', '2019-01-01 00:00:00')
->where('processing_time', '<=', '2019-01-21 23:59:59')
->with(['channel' => function ($q) {
$q->select('uuid', 'name', 'sender');
}])
->with(['channel.merchant' => function ($q) {
$q->select('uuid', 'name')
->groupBy('name');
}])
->get();
echo $report;
}
These are the queries that Laravel debugbar is showing...
Below are my Eloquent relationships...
Transaction Model
protected $with = ['channel', 'statusPayment'];
public function channel() {
return $this->hasOne(Channel::class, 'uuid', 'entityId');
}
-----
Channel Model
protected $with = ['merchant', 'attachedMerchantAccount'];
public function merchant() {
return $this->hasOne('App\Merchant', 'uuid', 'sender');
}

Perform order by relationship field in Eloquent

I want to create product filter with Eloquent.
I start like this
$query = Product::whereHas('variants')
->with('variants')
->with('reviews')
$query = $this->addOrderConstraints($request, $query);
$products = $query->paginate(20);
Where
private function addOrderConstraints($request, $query)
{
$order = $request->input('sort');
if ($order === 'new') {
$query->orderBy('products.created_at', 'DESC');
}
if ($order === 'price') {
$query->orderBy('variants.price', 'ASC');
}
return $query;
}
However, that doesn't work, cause Eloquent is performing this query like this (information from Laravel DebugBar)
select count(*) as aggregate from `products` where exists
(select * from `variants` where `products`.`id` = `variants`.`product_id`)
select * from `products` where exists
(select * from `variants` where `products`.`id` = `variants`.`product_id`)
select * from `variants` where `variants`.`product_id` in ('29', '30', '31', '32', '33', '34', '35', '36', '37', '38', '39', '40', '41', '42', '43', '44', '45', '46', '47', '48')
And so on
So when I try to use sorting by price it just obvious error
Unknown column 'variants.price' in 'order clause' (SQL: select * from
`products` where exists (select * from `variants` where `products`.`id` =
variants.product_id) order by variants.price asc limit 20 offset 0)
So is it possible to perform relationship ordering with Eloquent or not?
This will sort the subquery. Not the "first query (the product query)".
Basically, your subquery will be:
select * from variants where product_id in (....) order by price, and that is not what you want, right?
<?php
// ...
$order = $request->sort;
$products = Product::whereHas('variants')->with(['reviews', 'variants' => function($query) use ($order) {
if ($order == 'price') {
$query->orderBy('price');
}
}])->paginate(20);
If you want to sort product +/or variant you need to use join.
$query = Product::select([
'products.*',
'variants.price',
'variants.product_id'
])->join('variants', 'products.id', '=', 'variants.product_id');
if ($order == 'new') {
$query->orderBy('products.created_at', 'DESC');
} else if ($order == 'price') {
$query->orderBy('variants.price');
}
return $query->paginate(20);
If you want to sort product and variants, you don't need joins, because you won't have the related model loaded (like $product->variants), just all the fields of the variants table.
To sort models by related submodels, we can use Eloquent - Subquery Ordering.
To order the whole model by a related model, and NOT the related model itself, we can do it like this:
return Product::with('variants')->orderBy(
Variants::select('price')
// This can vary depending on the relationship
->whereColumn('variant_id', 'variants.id')
->orderBy('price')
->limit(1)
)->get();

Laravel: multiple where clauses in a many-to-many relationship

I am not able to create this sql query on laravel:
select * from `events_theatre` where
event_id IN (
select id from events where (`events`.`id` = `events_theatre`.`event_id` and `user_id` = 1 and `event_type` ='theatre') and `events`.`deleted_at` is null
)
or event_id IN (
select id from events inner join events_managers on events.id = events_managers.event_id and events_managers.user_id = 1
)
and `events_theatre`.`deleted_at` is null
The Events_Theatre model has this relationship:
public function event()
{
return $this->hasOne('App\MyEvent', 'id');
}
While the MyEvent model has this relationship:
public function managers()
{
return $this->belongsToMany('App\User', 'events_managers', 'event_id', 'user_id');
}
Pratically an Events_Theatre model is an Event model, and an Event model may have many managers but just 1 admin.
I need to get that Events_Theatre (and their MyEvent) whose user is an admin or one of the managers.
I tried with this eloquent query:
$list = App\Events_Theatre::whereIn('event_id', function ($query) {
$query
->where(['user_id' => Auth::id(), 'event_type' => 'theatre'])
->orWhereHas('managers', function ($query) {
$query->where('user_id', Auth::id());
});
});
But I get this sql query:
select * from `events_theatre` where
exists (
select * from `events` where (
`events`.`id` = `events_theatre`.`event_id` and `user_id` = 1 and `event_type` = 'theatre'
) or exists (
select * from `users` inner join `events_managers` on `users`.`id` = `events_managers`.`user_id` where `events_managers`.`event_id` = `events`.`id` and `user_id` = 1
)
and `events`.`deleted_at` is null
) and `events_theatre`.`deleted_at` is null
But its wrong. How can I solve this?
Thanks
UPDATED
I got the answer. This is the eloquent code I used:
$list = Events_Theatre::whereHas('event', function ($query) {
$query
->where(['user_id' => Auth::id(), 'event_type' => 'theatre']);
})->orWhereHas('event', function ($query) {
$query
->whereHas('managers', function ($query) {
$query->where('user_id', Auth::id());
});
})->get();
You can try has method
App\Events_Theatre::has('event')->orhas('managers')->get();

Laravel how do I get the row number of an object using Eloquent?

I'd like to know the position of a user based on its creation date. How do I do that using Eloquent?
I'd like to be able to do something like this:
User::getRowNumber($user_obj);
I suppose you want MySQL solution, so you can do this:
DB::statement(DB::raw('set #row:=0'));
User::selectRaw('*, #row:=#row+1 as row')->get();
// returns all users with ordinal 'row'
So you could implement something like this:
public function scopeWithRowNumber($query, $column = 'created_at', $order = 'asc')
{
DB::statement(DB::raw('set #row=0'));
$sub = static::selectRaw('*, #row:=#row+1 as row')
->orderBy($column, $order)->toSql();
$query->remember(1)->from(DB::raw("({$sub}) as sub"));
}
public function getRowNumber($column = 'created_at', $order = 'asc')
{
$order = ($order == 'asc') ? 'asc' : 'desc';
$key = "userRow.{$this->id}.{$column}.{$order}";
if (Cache::get($key)) return Cache::get($key);
$row = $this->withRowNumber($column, $order)
->where($column, '<=',$this->$column)
->whereId($this->id)->pluck('row');
Cache::put($key, $row);
return $row;
}
This needs to select all the rows from the table till the one you are looking for is found, then selects only that particular row number.
It will let you do this:
$user = User::find(15);
$user->getRowNumber(); // as default ordered by created_at ascending
$user->getRowNumber('username'); // check order for another column
$user->getRowNumber('updated_at', 'desc'); // different combination of column and order
// and utilizing the scope:
User::withRowNumber()->take(20)->get(); // returns collection with additional property 'row' for each user
As this scope requires raw statement setting #row to 0 everytime, we use caching for 1 minute to avoid unnecessary queries.
$query = \DB::table(\DB::raw('Products, (SELECT #row := 0) r'));
$query = $query->select(
\DB::raw('#row := #row + 1 AS SrNo'),
'ProductID',
'ProductName',
'Description',
\DB::raw('IFNULL(ProductImage,"") AS ProductImage')
);
// where clauses
if(...){
$query = $query->where('ProductID', ...));
}
// orderby clauses
// ...
// $query = $query->orderBy('..','DESC');
// count clause
$TotalRecordCount = $query->count();
$results = $query
->take(...)
->skip(...)
->get();
I believe you could use Raw Expresssions to achieve this:
$users = DB::table('users')
->select(DB::raw('ROW_NUMBER() OVER(ORDER BY ID DESC) AS Row, status'))
->where('status', '<>', 1)
->groupBy('status')
->get();
However, looking trough the source code looks like you could achieve the same when using SQLServer and offset. The sources indicates that if you something like the following:
$users = DB::table('users')->skip(10)->take(5)->get();
The generated SQL query will include the row_number over statement.
[For Postgres]
In your model
public function scopeWithRowNumber($query, $column = 'id', $order = 'asc'){
$sub = static::selectRaw('*, row_number() OVER () as row_number')
->orderBy($column, $order)
->toSql();
$query->from(DB::raw("({$sub}) as sub"));
}
In your controller
$user = User::withRowNumber()->get();

Resources