Laravel merge multiple rows as multiple columns - php

So I have four tables:
experiments (tables) - id
feature_classes (columns) - id, experiment_id, title
entities (rows) - id, experiment_id
features (cells) - id, entity_id, feature_class_id, value
I need to construct a table from these four tables.
I tried this:
$experiment_id = $request->input('experiment_id');
$feature_classes = FeatureClass::where('experiment_id', $experiment_id)->select('title', 'id')->get();
$select = [
'entities.id',
'entities.prediction',
'entities.result'
];
foreach ($feature_classes as $f) {
$select[] = $f->id . ".value AS " .$f->id;
}
$entities = DB::table('entities')
->where('experiment_id', $experiment_id);
foreach ($feature_classes as $f) {
$entities = $entities->leftJoin('features AS ' . $f->id, function ($join) use ($f){
$join->on($f->id . '.entity_id', '=', 'entities.id')
->where($f->id . '.feature_class_id', $f->id);
});
}
return $entities
->select($select)
->get();
But my efforts are rewarded with this error message SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '1 ? left joinfeaturesas2on2.entity_id=entities.idand2.fe' at line 1 (SQL: select entities.id, entities.prediction, entities.result, 1.value as 1, 2.value as 2 from entities left join features as 1 on 1.entity_id = entities.id and 1.feature_class_id 1 left join features as 2 on 2.entity_id = entities.id and 2.feature_class_id 2 where experiment_id = 1) `

I'd argue that you shouldn't have to. You should approach this by adding the many to many relations in the model only, then using eloquent to do all the rest. For example, in the features model:
public function Classes(){
return $this->belongsToMany('App\Classes', 'feature_classes', 'feature_class_id', 'id');
}
Then something similar that defines the relationship between classes and entities linking by experiment_id. Then you should be able to access the data you need by using the native eloquent features like
$entities = Features::where('experiment_id', $experiment_id)->Classes->Entities;
return $entities
More information here:
https://laravel.com/docs/5.3/eloquent-relationships#many-to-many

Related

how to convert this SQL query to eloquent in Laravel

I am trying to convert this SQL query to Eloquent in Laravel
Convert SQL code to Eloquent
SELECT
session_id,
SUM(points) AS total_points
FROM
(
SELECT
session_id,
spent_points AS points
FROM
session_details
WHERE
session_id IN
(
" - Meagevy6y9ukbmFXvB7",
" - Meak6dG9iqvHWfAGQvy"
)
UNION ALL
SELECT
session_id,
price_points
FROM
template_sales
WHERE
session_id IN
(
" - Meagevy6y9ukbmFXvB7",
" - Meak6dG9iqvHWfAGQvy"
)
)
t
GROUP BY
session_id
my code in Laravel but not working
$ids = ["-Meagevy6y9ukbmFXvB7","-Meak6dG9iqvHWfAGQvy"];
$query = DB::table('session_details')
->select('session_id',DB::raw('SUM(points) AS total_points FROM ( SELECT session_id, spent_points AS points FROM session_details
WHERE session_id IN ("'.$ids.'") UNION ALL SELECT session_id,price_points FROM template_sales WHERE session_id IN ("'.$ids.'") ) t GROUP BY session_id'))
->get();
I'd advise you to use Eloquent models & Eloquent relationships to make the query more readable.
Execute the following in your terminal to create a new model:
php artisan make:model SessionDetail
Open the file that Laravel has generated for you in /app/Models (or whatever folders your models are in), and set the table in the model by putting the following property into the model class: public $table = "session_details";
If your model does not use or have Laravel timestamps which are usually created_at & updated_at, you can also use this property to disable them in the model: public $timestamps = false;
After that, create generate another model by execute the following command in your terminal:
php artisan make:model TemplateSale
Follow the same instructions again but this time change the table name to template_sales
After you have done that, head into your SessionDetail model and make a relationship to the TemplateSale model using the following code (this must be in the model class beneath the properties):
public function template_sales() {
return $this->hasMany(TemplateSale::class);
}
After that, you can replace your query with this line of code:
$query = \App\Models\SessionDetail::select("session_id", "SUM(points) as total_points")->whereIn("session_id", $ids)->get();
To get the template sales from that query, you have to use $query->template_sales;
If I got anything wrong, please tell me & I'll fix it ASAP
There is documentation available for all the operations in your query.
For selected columns use select('column1', 'column2', ...)
For selected aggregate columns use selectRaw('sum(column) as column')
For WHERE column IN (...) use whereIn('column', $array)
For subquery tables, use Closures or Builder classes (DB::table(fn($q) => ... , alias) or DB::table($builder, alias))
For UNION ALL use unionAll() with the same syntax as subquery tables.
Option 1: Closures
$ids = ["-Meagevy6y9ukbmFXvB7","-Meak6dG9iqvHWfAGQvy"];
$query = DB::table(function ($sub) use ($ids) {
$sub->select('session_id', 'spent_points as points')
->from('session_details')
->whereIn('session_id', [1,2])
->unionAll(function ($union) use ($ids) {
$union->select('session_id', 'price_points')
->from('template_sales')
->whereIn('session_id', $ids);
});
}), 't')
->select('session_id')
->selectRaw('sum(points) as total_points')
->groupBy('session_id')
->get();
Option 2: Builder (or translating the subqueries from the inside-out)
$ids = ["-Meagevy6y9ukbmFXvB7","-Meak6dG9iqvHWfAGQvy"];
$union = DB::table('template_sales')
->select('session_id', 'price_points')
->whereIn('session_id', $ids);
$sub = DB::table('session_details')
->select('session_id', 'spent_points as points')
->whereIn('session_id', $ids)
->unionAll($union);
$query = DB::table($sub, 't')
->select('session_id')
->selectRaw('sum(points) as total_points')
->groupBy('session_id')
->get();
Pick whichever you prefer. Both evaluate to the same query you posted.

Laravel reducing queries

I use Laravel Eloquent and I have this code:
<?php
$bulk = Bulk::where('id', 'bulkid1');
echo $bulk->info;
foreach($bulk->models as $model) {
echo $model->info;
$lastreport = $model->reports()->getQuery()->orderBy('created_at', 'desc')->first();
echo $lastreport->message;
}
?>
What I want to achieve is that the $lastreport is preloaded. In this piece of code, the query will be executed too many times (every bulk has 1000 models, resulting in 1000 subqueries).
While in plain sql I could just do:
SELECT * FROM bulk
LEFT JOIN models ON bulk.id = models.bulk_id
LEFT JOIN ( SELECT *, MAX(created_at) AS created_at
FROM
reports
GROUP BY
model_id )
lastreport ON models.id = lastreport.model_id
WHERE bulk.id = 'bulkid1'
Database pseudocode:
TABLE bulks
id, info
TABLE models
id, bulk_id, info
TABLE reports
id, model_id, message
This is the N+1 selects problem The laravel solution for this problem is eager loading
In your case you'd do:
<?php
$bulk = Bulk::with(['models', 'models.reports' => function ($query) {
return $query->orderBy('created_at', 'desc');
}
])->where('id', 'bulkid1')->first();
echo $bulk->info;
foreach($bulk->models as $model) {
echo $model->info;
$lastreport = $model->reports->first();
echo $lastreport->message;
}
This should ensure that (a) all models are loaded with only 1 additional query and (b) all model reports are loaded with another additional query. The downside with this is that there are more data being loaded than necessary because of the orderBy clause which can't really be represented as a query time condition.

Adding raw query to eloquent relationship

I am developing an application in Laravel 5.6 I'm having a simple table with following columns:
company_id project_id company_role_id company_specialisation_id
And this represents the model AssociateCompanies, which has relation of company, project, role, specialisation now I am having some query to get the attribute:
$companies = AssociateCompany::whereHas('company', function ($q) use ($request) {
$q->whereHas('projectOwners', function ($q) use($request) {
$q->where('slug', $request->slug);
});
})->groupBy('company_id', 'company_specialisation_id')->with('company', 'role', 'specialisation');
I want to collect all unique fields with their counts from two columns company_id and specialisation_id, but groupBy is not giving me proper the results so I am unbale to proceed further:
SQLSTATE[42000]: Syntax error or access violation: 1055 Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'conxn.project_associate_company.id' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by (SQL: select * from project_associate_company where exists (select , (select count() from project_associate_company where companies.id = project_associate_company.company_id and project_associate_company.deleted_at is null) as associated_projects_count from companies where project_associate_company.company_id = companies.id and exists (select * from projects inner join project_owner_relation on projects.id = project_owner_relation.project_id where companies.id = project_owner_relation.company_id and slug = lodha-patel-estate-tower-a-b-mumbai and projects.deleted_at is null) and companies.deleted_at is null) and project_associate_company.deleted_at is null group by company_id, company_specialisation_id)"
So I tried running raw queries like this:
$companies = AssociateCompany::whereHas('company', function ($q) use ($request) {
$q->whereHas('projectOwners', function ($q) use($request) {
$q->where('slug', $request->slug);
});
})->selectRaw(DB::raw('COUNT(*) AS count GROUP BY company_id , company_specialisation_id'))
->with('company', 'companyRole', 'specialisation')->get();
SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'GROUP BY company_id , company_specialisation_id from project_associate_company' at line 1 (SQL: select COUNT(*) AS count GROUP BY company_id , company_specialisation_id from project_associate_company where exists (select , (select count() from project_associate_company where companies.id = project_associate_company.company_id and project_associate_company.deleted_at is null) as associated_projects_count from companies where project_associate_company.company_id = companies.id and exists (select * from projects inner join project_owner_relation on projects.id = project_owner_relation.project_id where companies.id = project_owner_relation.company_id and slug = lodha-patel-estate-tower-a-b-mumbai and projects.deleted_at is null) and companies.deleted_at is null) and project_associate_company.deleted_at is null)"
Suggest me better way to get this. Thanks.
Initially I want to notice you can not "group by" inside the "select" statement.
So you can not aggregate the non-aggregated columns. This means is grouped fields can be has got multiple "role" so you can not "load" the "roles". The query should be like the one of the follows:
AssociateCompany::whereHas('company', function ($q) use ($request) {
$q->whereHas('projectOwners', function ($q) use($request) {
$q->where('slug', $request->slug);
});
})->select('company_id', 'company_specialisation_id', \DB::raw('COUNT(*) as cnt'))
->groupBy('company_id', 'company_specialisation_id')
->with('company', 'specialisation');
Or :
AssociateCompany::whereHas('company', function ($q) use ($request) {
$q->whereHas('projectOwners', function ($q) use($request) {
$q->where('slug', $request->slug);
});
})->select('company_id', 'company_specialisation_id', 'company_role_id', \DB::raw('COUNT(*) as cnt'))
->groupBy('company_id', 'company_specialisation_id', 'company_role_id')
->with('company', 'specialisation', 'role');
My suggestions are like this but I think you can solve it with raw MySQL queries rather than use to Eloquent.

How can I execute native queries in my Model?

I have a requirement to define composite foreign keys in my Model.Looks it is not supported currently. So, i try to run native queries in my model.
I have two tables(vwAlarm, vwYfUserToSiteMappings) Both has two columns.
CompanyId,SiteCode
I want to return single row, by joining both columns from two tables.
Here is my Model;
class Alarm extends Model
{
protected $table = 'vwAlarm';
protected $primaryKey = 'AlarmId';
..
public function Site()
{
$rec = \DB::table('vwAlarm')
->join('vwYfUserToSiteMappings', 'vwAlarm.SiteCode', '=', 'vwYfUserToSiteMappings.SiteCode')
->join('vwYfUserToSiteMappings','vwAlarm.CompanyId', '=', 'vwYfUserToSiteMappings.CompanyId')
->first();
return $rec;
}
Im getting
QueryException
SQLSTATE[42000]: [Microsoft][ODBC Driver 13 for SQL Server][SQL Server]The objects "vwYfUserToSiteMappings" and "vwYfUserToSiteMappings" in the FROM clause have the same exposed names. Use correlation names to distinguish them. (SQL: select top 1 * from [vwAlarm] inner join [vwYfUserToSiteMappings] on [vwAlarm].[SiteCode] = [vwYfUserToSiteMappings].[SiteCode] inner join [vwYfUserToSiteMappings] on [vwAlarm].[CompanyId] = [vwYfUserToSiteMappings].[CompanyId])
How can I correct my query?
You should try this:
$rec = \DB::table('vwAlarm')
->join('vwYfUserToSiteMappings AS vwSiteCode', 'vwSiteCode.SiteCode', '=', 'vwAlarm.SiteCode')
->join('vwYfUserToSiteMappings AS vwCompanyId','vwCompanyId.CompanyId', '=', 'vwAlarm.CompanyId')
->first();
Set alias name for table name like below
$rec = \DB::table('vwAlarm')
->join('vwYfUserToSiteMappings AS vw1', 'vwAlarm.SiteCode', '=', 'vw1.SiteCode')
->join('vwYfUserToSiteMappings AS vw2','vwAlarm.CompanyId', '=', 'vw2.CompanyId')
->first();

Yii2 - viaTable join returns error

I am trying to write hasMany relationship, but I am getting this error:
SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax;
check the manual that corresponds to your MySQL server version for the right syntax to use near
'[[licences.id = userKeys.licence_id]] = `licences`.`0` LEFT JOIN `userKeys` `use' at line 1
The SQL being executed was:
SELECT COUNT(*) FROM `activityLogsUserActivity`
LEFT JOIN `users` ON `activityLogsUserActivity`.`user_id` = `users`.`id`
LEFT JOIN `licences` ON `activityLogsUserActivity`.[[licences.id = userKeys.licence_id]] = `licences`.`0`
LEFT JOIN `userKeys` `userKeys` ON `licences`.`user_id` = `userKeys`.`user_id`
The code:
public function getKeys()
{
return $this->hasMany(UserKeys::classname(), ['user_id' => 'user_id'])
->select('licences.licenceName, userKeys.*')
->from(['userKeys' => UserKeys::tableName()])
->viaTable(Licences::tableName(), ['licences.id = userKeys.licence_id']);
}
What am I doing wrong?
['licences.id = userKeys.licence_id']
should be a key-value pair, like
['id' => 'licence_id']
and it should not be necessary to declare the table name there, have a look at the docs
http://www.yiiframework.com/doc-2.0/yii-db-activequery.html#viaTable()-detail
The link between the junction table and the table associated with $primaryModel. The keys of the array represent the columns in the junction table, and the values represent the columns in the $primaryModel table.

Categories