Laravel database query – select inside foreach - laravel

I've got a problem with a query in Laravel 5.3. I've got tables
attendees->attendee_tag<-tags (M:N relation)
and I would like to get attendees ordered by multiple tags, so I wrote this:
$query = Attendee::where('attendees.event_id', '=', $event_id);
$i = 1;
foreach($order_by as $column) {
$tag = $this->tagService->findTagById($column['tag_id']);
$sort_value = $tag->value_type == 'string' ? 'value_string_'.$i : 'value_int_'.$i;
$query->join('attendee_tag as at_'.$i, function ($join) use ($tag, $i) {
$join->on('attendees.id', '=', 'at_'.$i.'.attendee_id')
->where('at_'.$i.'.tag_id', '=', $tag->id);})
->select('at_'.$i.'.id as relationId_'.$i, 'at_'.$i.'.value_string as value_string_'.$i, 'at_'.$i.'.value_int as value_int_'.$i, 'attendees.id as id')
->orderBy($sort_value, $column['order']);
$i++;
}
$attendees = $query->get();
But I'm getting following sql error:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'value_string_1' in 'order clause' (SQL: select `at_2`.`id` as `relationId_2`, `at_2`.`value_string` as `value_string_2`, `at_2`.`value_int` as `value_int_2`, `attendees`.`id` as `id` from `attendees` inner join `attendee_tag` as `at_1` on `attendees`.`id` = `at_1`.`attendee_id` and `at_1`.`tag_id` = 3 inner join `attendee_tag` as `at_2` on `attendees`.`id` = `at_2`.`attendee_id` and `at_2`.`tag_id` = 4 where `attendees`.`event_id` = 1 order by `value_string_1` asc, `value_string_2` asc limit 1000 offset 0)
It seems, that laravel has some optimalization and passes the select on $join function only once, in last iteration (second iteration in my opinion, I got 2 tags in request)

I'm assuming that you have tags() relation within Attendee model!
I tested it and it's working fine
$attendees = Attendee::join('attendee_tag', 'attendee_tag.attendee_id', '=', 'attendees.id')
->join('tags', 'attendee_tag.tag_id', '=', 'tags.id')->where('event_id', $event_id);
$i = 1;
foreach ($order_by as $column) {
$tag = $this->tagService->findTagById($column['tag_id']);
$sort_value = $tag->value_type == 'string' ? 'value_string_' . $i : 'value_int_' . $i;
$attendees->orderBy('tags.' . $sort_value, $column['order']);
$i++;
}
$attendees->get();
PS: I don't know that $order_by is all about but I took the value of it from your code above, it should work just fine :) and I'm ready to tweak it for you if you want

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

Use Where in QueryBuilder - Laravel

I have a table Products(id,category_id,name);
I want to get a result like this query:
SELECT * FROM Products WHERE category_id=$category_id OR $category_id=0;
When I assign $category_id with 0 value => above query will return all records.
How do I write that query in Laravel?
I've try this:
Products::where(function($query) use ($category_id){
$query->where("category_id",$category_id)
->orWhere($category_id,0)
})->get();
But, Error:
It look like:
SELECT
*
FROM
`products`
WHERE
( `product_category_id` = 0 OR `0` = 0 )
And Print Error: Column not found: 1054 Unknown column '0' in 'where clause'
How to fix: '0' = 0 to 0 = 0?
Thanks!
Use when():
$products = Product::when($categoryId, function ($query, $categoryId) {
$query->whereCategoryId($categoryId);
})->get();
This method will call the function only if the condition is truthy. You could achieve the same thing with simple if statement:
$products = Product::query();
if ($categoryId) {
$products->whereCategoryId($categoryId);
}
$products = $products->get();
What you gain by using when() is that you don't break the chain, so you can use conditional query changes in one expression. It's useful if you want to just return it, for example.
use it
Products::where(function($query) use ($category_id){
$query->where("category_id",$category_id)
->orWhere("category_id",0);
})->get();
what's wrong with your code is this:
->orWhere($category_id,0)
you made the column part as variable.
try this code:
Products::where(function($query) use ($category_id){
return $query->where("category_id", $category_id)
->orWhere("category_id", 0);
})->get();
$product = new Products;
if ($category_id != 0) {
$product = $product->where("category_id", $category_id);
}
$products = $product->get();

How to sorting by current login user first in Laravel?

Below code is to divide list view by permission in the Job table.
public function index(Request $request)
{
$user_name = Auth::user()->name;
$jobs = Job::orderBy('created_at', 'desc') //#default
->where('is_trash', '==', 0)
->paginate(15);
if(!Auth::user()->hasPermissionTo('Admin')){ //check by permission
$jobs = Job::orderBy('created_at', 'desc')
->where('user_name', '=', $user_name)
->where('is_trash', '==', 0)
->orderby('updated_at', 'desc')
->orderBy('deleted_at', 'desc')
->paginate(15);
}
return view('jobs.index')
->with('jobs', $jobs);
}
For example, current login user name is C and there has A, B, D users.
Then I wish to show list as
------------------------------
C
A
B
D
------------------------------
How do I sort the currently logged in users first in the list?
Thank you in advance.:)
To place a row above all other rows and then order the remaining rows in ascending / descending order you need to do something like this SELECT * FROM table ORDER BY field = value ASC
So for your query something like this should work
$user_name = Auth::user()->name;
$jobs = Job::orderByRaw("user_name = ':name' ASC", ['name' => $user_name])
->orderBy('created_at', 'desc')
->where('is_trash', '==', 0)
->paginate(15);
I'm not entirely sure which query you wish to order and by which field but this example should give you enough to adapt on. It is untested but if :name isn't working as per the example try wrapping the query in \DB::raw() to add the binding $user_name.
You could use collections for that. Just tested, works perfectly:
list($user, $collection) = $collection->partition(function($user) {
return $user->id === auth()->id();
});
$collection = $user->merge($collection);

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