Laravel order by eagerly loaded column - laravel

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);

Related

Laravel Join with where not in gives "Column xxxxx in IN/ALL/ANY subquery is ambiguous"

I have two tables: leads and hts_patients. Both table has phone column and I want to list all leads that has no record on hts_patients table(foreign key is phone).
So far, I have tried this and it works but taking so much long time.
$countries = ['DE','TR'];
$dateS = Carbon::now()->startOfMonth()->subMonth(4);
$dateE = Carbon::now()->startOfMonth();
$leads = Lead::whereBetween('created_at',[$dateS,$dateE])->whereIn('visitor_country',$countries)
->select('id','name','phone','visitor_country','source','description','contact','created_at')->get();
$patient = HtsPatient::pluck('phone');
foreach ($leads as $key=>$item){
if (isset($item->phone)){
$data =$patient->where('phone',$item->phone)->first();
if (!empty($data)){
$leads->forget($key);
}
}
}
return view('poll.leadSecond',['leads' => $leads]);
Thing, that I want is probably something like this;
$countries = ['DE','TR'];
$dateS = Carbon::now()->startOfMonth()->subMonth(4);
$dateE = Carbon::now()->startOfMonth();
$leads = DB::table('leads')->join('hts_patients','hts_patients.phone', '=', 'leads.phone')
->whereBetween('leads.created_at',[$dateS,$dateE])->whereIn('leads.visitor_country',$countries)
->whereNotIn('phone', function($query) {
$query->select('phone')
->from('hts_patients');
})
->select('leads.id','leads.name','leads.phone','leads.visitor_country','leads.source','leads.description','leads.contact','leads.created_at')->get();
But it gives an error says
SQLSTATE[23000]: Integrity constraint violation: 1052 Column 'phone'
in IN/ALL/ANY subquery is ambiguous
Any help?
you have tow tables with column called 'phone', in your whereNotIn clause you should specify witch on you wants.
I think the result should be like:
->whereNotIn('leads.phone', function($query) {
$query->select('hts_patients.phone');
// ->from('hts_patients'); you do not need this 'from'
})

How to get data based in comparing value in pivote table with value in required table in laravel 5.6?

I am learning larvel 5.6 so I am trying to retrieve number of messages that have id larger than last_seen_id in pivot table
I have user table which have the default columns generated by:
php artisan make:auth
and messages tables which have the following columns:
id, from, message_content, group_id
and the group table have the columns:
id,type
now there is many to many relation between the users and groups table through the custom pivot table which have the columns:
group_id,user_id,last_id_seen
now if I want to retrieve the messages which belong to same group and have larger id than last_id_seen in the pivot table how to do it?
I think you are looking for something like this:
$groupId = 1; // set to whatever you want
$lastSeenId = \Auth::user()->groups()
->where('group_id', $groupId)
->first()->pivot->last_id_seen;
$messages = Message::where('id', '>', $lastSeenId)->get();
A more robust version, which does not fail when your user does not have an entry for the group yet, would be:
$groupId = 1; // set to whatever you want
$group = \Auth::user()->groups()->where('group_id', $groupId)->first();
$lastSeenId = $group ? $group->pivot->last_id_seen : null;
$messages = Message::when($lastSeenId, function($query, $id) {
$query->where('id', '>', $id);
})->get();
Note: normally you'd use optional() in the second snippet, but Laravel 5.2 does not come with this helper...
If you want both the count() of the results and the results themselves, you can store the query in a variable and perform two queries with it. You then don't have to rewrite the same query twice:
$groupId = 1; // set to whatever you want
$group = \Auth::user()->groups()->where('group_id', $groupId)->first();
$lastSeenId = $group ? $group->pivot->last_id_seen : null;
$query = Message::when($lastSeenId, function($query, $id) {
$query->where('id', '>', $id);
});
$count = $query->count();
$messages = $query->get();

Laravel Repositories whereHas -- multiple

I have the following repository Products and each product can have many Categories and many Bidders
What I am trying to achieve is the following (Without Repository)
$products = Products::whereHas('categories', function ($category) {
})->whereHas('bidders', function ($bidder) {
})->get();
This works fine, however, I am trying to make it so that repositories are in place and you can still do the whereHas query, so in my repository I created a method:
public function whereHas($attribute, \Closure $closure = null)
{
return $this->model->whereHas($attribute, $closure);
}
This works well, but only if I am using one of them in my main query, whereas if I use multiple:
$products = $this->products->whereHas('categories', function ($category) {
$category->where('id', '=', 1);
})->whereHas('bidders', function($bidders) {
})->get();
I am getting the following error:
Unknown column 'has_relation'
Column not found: 1054 Unknown column 'has_relation' in 'where
clause' (SQL: select * from products where exists (select * from
categories inner join products_categories on categories.id =
products_categories.categories_id where products.id =
products_categories.products_id and id = 1) and (has_relation
= sections))
The issue I'm seeing is that its returning a collection of items on the first whereHas which means it cannot compute the second one. Any ideas to where I am going wrong?
You can pass a closure with multiple relations to whereHas
$products = $this->products->whereHas(['categories', function
($category) {
$category->where('id', '=', 1);
},'bidders' => function($bidders) {
}])->get();
It will work as expected.

where clause is ambiguous on a query

I built a search module to get results form different params ! it"s work but when i when to export the result in csv i'm getting problems with my join table. for exemple when i search with a catg_licence_id i get an exception like :
SQLSTATE[23000]: Integrity constraint violation: 1052 Column
'catg_licence_id' in where clause is ambiguous
here my controller to get the result and generate the file with the join tables to get the value from the other tables and not simple ids . hope someone could help me. thanks a lot in advance :)
public function exportLicencesExcelWithParam(Request $request){
$type_licence = Type_licence::pluck('lb_type' , 'id');
$activite = ActiviteLicencie::pluck('lb_activite' , 'id');
$catg_licence = CatgLicence::pluck('lb_catg_lic' , 'id');
$structure = Structure::select('num_structure', 'nom_structure' , 'id')
->get()
->mapWithKeys(function($i) {
return [$i->id => $i->num_structure.' - '.$i->nom_structure];
});
$query = Licencies::query();
$filters = [
'type_licence' => 'type_licence_id',
'activite_licencie' => 'activite_licencie_id',
'assurance' => 'lb_assurance_etat',
'catg_licence' => 'catg_licence_id',
'structure' => 'structure_id',
];
foreach ($filters as $key => $column) {
if ($request->has($key)) {
$query->where($column, $request->{$key});
}
}
$action = Input::get('action', 'none');
if($action =='send'){
//HERE I WANT TO GENERATE THE CSV FILE BUT I NEED TO GET THE JOIN TABLES TO DISPLAY THE RESULT
$licencies = $query->join('activite_licencie', 'activite_licencie.id', '=', 'licencies.activite_licencie_id')
->join('saisons', 'saisons.id', '=', 'licencies.saison_id')
->join('pays', 'pays.id', '=', 'licencies.pays_naissance_id')
->join('type_licence', 'type_licence.id', '=', 'licencies.type_licence_id')
->join('structures', 'structures.id', '=', 'licencies.structure_id')
->join('civilite', 'civilite.id', '=', 'licencies.civilite_id')
->join('catg_licence', 'catg_licence.id', '=', 'licencies.catg_licence_id')
->select('num_licence', 'civilite.lb_civilite', 'lb_nom', 'lb_prenom', 'dt_naissance', 'pays.fr as pays', 'activite_licencie.lb_activite', 'catg_licence.lb_catg_lic', 'type_licence.lb_type', 'saisons.lb_saison', 'lb_surclassement', 'structures.nom_structure', 'structures.num_structure', 'lb_assurance', 'cd_dept_naissance', 'lb_ville_naissance', 'lb_adresse', 'tel_fix_licencie', 'tel_port_licencie', 'adresse_email')
->get();
$licencies->map(function ($licencie) {
$licencie['dt_naissance'] = \Carbon\Carbon::parse($licencie['dt_naissance'])->format('d/m/Y');
$licencie['lb_nom'] = strtoupper($licencie['lb_nom']);
$licencie['lb_prenom'] = ucfirst(strtolower($licencie['lb_prenom']));
if ($licencie['num_structure'] == 662883) {
$licencie['lb_activite'] = 'Super League';
} elseif ($licencie['num_structure'] == 311197) {
$licencie['lb_activite'] = 'ChampionShip';
} else {
//do nothing
}
if ($licencie['lb_activite'] == 'Tricolore LER' or $licencie['lb_activite'] == 'Tricolore - Autres Divisions') {
$licencie['lb_activite'] = 'Tricolore';
}
if ($licencie['lb_type'] == 'Membre') {
$licencie['lb_catg_lic'] = '';
}
return $licencie;
});
$date = Carbon::now('Europe/Paris')->format('d-m-Y h:m:s');
$file = Excel::create('' . $date . '', function ($excel) use ($licencies) {
$excel->sheet('Excel', function ($sheet) use ($licencies) {
$sheet->fromArray($licencies);
});
})->string('csv');
Storage::disk('local')->put('licencies_export_'.$date.'.csv' , $file);
return back()->with('status', "Fichier Exporté");
}else{
}
return view('export/licences' , compact('type_licence' , 'structure' , 'structures' , 'licencies' , 'activite' , 'catg_licence'));
}
here the full exception:
SQLSTATE[23000]: Integrity constraint violation: 1052 Column
'type_licence_id' in where clause is ambiguous (SQL: select
num_licence, civilite.lb_civilite, lb_nom, lb_prenom,
dt_naissance, pays.fr as pays,
activite_licencie.lb_activite, catg_licence.lb_catg_lic,
type_licence.lb_type, saisons.lb_saison, lb_surclassement,
structures.nom_structure, structures.num_structure,
lb_assurance, cd_dept_naissance, lb_ville_naissance,
lb_adresse, tel_fix_licencie, tel_port_licencie, adresse_email
from licencies inner join activite_licencie on
activite_licencie.id = licencies.activite_licencie_id inner
join saisons on saisons.id = licencies.saison_id inner join
pays on pays.id = licencies.pays_naissance_id inner join
type_licence on type_licence.id = licencies.type_licence_id
inner join structures on structures.id =
licencies.structure_id inner join civilite on civilite.id =
licencies.civilite_id inner join catg_licence on
catg_licence.id = licencies.catg_licence_id where
type_licence_id = 4 and catg_licence_id = 1)
When it says it's ambiguous, what it means is that the mysql is joining tables and that specific field (catg_licence_id) is found on another table. So what happens is when you're joining something to this field, he doesn't know what table to join with. A solution would be to place the table name before, something like #user3154557 just said
->join('tablename', 'tablename.field', 'othertablename.field')
You're not joining the 'licencies' table anywhere.
->join('catg_licence', 'catg_licence.id', '=', 'licencies.catg_licence_id')
That line is your problem.
You might also get the same error in your select. It's better to put the table.property in the select rather than the property when you're joining a bunch of tables.

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