I am having problems with a query that uses values from multiple form inputs, and each one is optional.
The idea is to find the applications of an ISP (ie. technical services, installations, etc) assigned to a technician.
Due to the inputs being optional, I'm using a ->when() function to avoid queries with NULL values.
But also I need to find the applications using the ID of the technician, this ID is stored in a pivot table with the related application ID.
This is the code in the controller
$finalizadas = Solicitud::whereHas('tecnicos')
->when($desde, function ($query) use ($desde, $hasta) {
return $query->whereBetween('sol_fecha_finalizada', [$desde, $hasta])->where('sol_estado', 4);
})
->when($tipo, function ($query) use ($tipo) {
return $query->where('sol_tipo_solicitud', $tipo)->where('sol_estado', 4);
})
->when($tecnico, function ($query) use ($tecnico) {
return $query->where('tecnico_tec_id', $tecnico)->where('sol_estado', 4);
})
->when($cliente, function ($query) use ($cliente) {
return $query->where('sol_cliente', $cliente)->where('sol_estado', 4);
})->get();
return view('solicitudes.listar_finalizadas')->with('finalizadas', $finalizadas);
sol_estado = 4 stands for application finished.
tecnico_tec_id is the ID of the technician in the pivot table solicitud_tecnico
The problem is when I try to search apps by technician, it gives the next error.
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'tec_id' in 'where clause' (SQL:
SELECT * FROM solicitudes
WHERE EXISTS (SELECT* FROM tecnicos
INNER JOIN solicitud_tecnico
ON tecnicos.tec_id = solicitud_tecnico.tecnico_tec_id
WHERE solicitudes.sol_id = solicitud_tecnico.solicitud_sol_id)
AND tec_id = 8 AND sol_estado = 4)
This statement, altought is inside the relationship, it doesn't work
->when($tecnico, function ($query) use ($tecnico) {
return $query->where('tecnico_tec_id', $tecnico)->where('sol_estado',4);
})
but this one works like a charm
$finalizadas = Solicitud::whereHas('tecnicos', function ($query) use ($tecnico) {
$query->where('tecnico_tec_id', $tecnico)->where('sol_estado', 4);
})->get();
Model Solicitud (Application)
<?php
namespace OPyME2;
use Illuminate\Database\Eloquent\Model;
class Solicitud extends Model
{
// Nombre de la tabla
protected $table = 'solicitudes';
// Primary key
protected $primaryKey = 'sol_id';
// Marcas de fecha
public $timestamps = false;
// Columnas
protected $fillable = ['sol_id','sol_fecha_creacion','sol_fecha_traslado','sol_fecha_retiro','sol_fecha_finalizada','sol_horario','sol_cliente','sol_estructura', 'sol_plan', 'sol_escalera', 'sol_tecnico_asignado', 'sol_estado', 'sol_motivo', 'sol_zona_gps', 'sol_telefono_2', 'sol_domicilio_traslado', 'sol_creacion', 'sol_tipo_solicitud', 'sol_pedido_material
'];
// Pivot
public function tecnicos()
{
return $this->belongsToMany('\OPyME2\Tecnico', 'solicitud_tecnico')
->withPivot('solicitud_sol_id');
}
}
Model Tecnico (Technician)
<?php
namespace OPyME2;
use Illuminate\Database\Eloquent\Model;
class Tecnico extends Model
{
// Nombre de la tabla
protected $table = 'tecnicos';
// Primary key
protected $primaryKey = 'tec_id';
// Marcas de fecha
public $timestamps = false;
// Columnas
protected $fillable = ['tec_id', 'tec_nombre', 'tec_activo', 'tec_movil'];
// Pivot
public function solicitudes()
{
return $this->belongsToMany('\OPyME2\Solicitud', 'solicitud_tecnico')
->withPivot('tecnico_tec_id');
}
public function moviles()
{
return $this->belongsToMany('\OPyME2\Movil', 'movil_tecnico')
->withPivot('tecnico_tec_id');
}
}
I can't figure out what the error is.
I think this might be caused because the tecnico_tec_id field is part of the pivot table. Have you tried querying it inside the whereHas closure?
$finalizadas = Solicitud::where('sol_estado', 4)
->when($tecnico, function ($query) use ($tecnico) {
return $query->whereHas('tecnicos', function ($query) use ($tecnico) {
$query->where('tecnico_tec_id', $tecnico);
});
}, function ($query) {
return $query->has('tecnicos');
})
->when($desde, function ($query) use ($desde, $hasta) {
return $query->whereBetween('sol_fecha_finalizada', [$desde, $hasta]);
})
->when($tipo, function ($query) use ($tipo) {
return $query->where('sol_tipo_solicitud', $tipo);
})
->when($tecnico, function ($query) use ($tecnico) {
return $query->where('tecnico_tec_id', $tecnico);
})
->when($cliente, function ($query) use ($cliente) {
return $query->where('sol_cliente', $cliente);
})
->get();
Finally the next did the trick
$finalizadas = Solicitud::whereHas('tecnicos')
->when($tecnico, function ($query) use ($tecnico) {
return Solicitud::whereHas('tecnicos', function ($query) use ($tecnico) {
return $query->where('tecnico_tec_id', $tecnico)->where('sol_estado', 4);
});
})
->when($cliente, function ($query) use ($cliente) {
return $query->where('sol_cliente', $cliente)->where('sol_estado', 4);
})
->when($desde, function ($query) use ($desde, $hasta) {
return $query->whereBetween('sol_fecha_finalizada', [$desde, $hasta])->where('sol_estado', 4);
})
->when($tipo, function ($query) use ($tipo) {
return $query->where('sol_tipo_solicitud', $tipo)->where('sol_estado', 4);
})
->get();
I've nested the relationship query inside the main query, in that way and it works, even using all search conditions. PD: technicians (tecnicos) relationship has to be always first, otherwise the query excludes the dates, type of application and/or client. Thank you #IGP
Related
How can I make this query in Laravel eloquent. Please, no DB Record solution.
SELECT slo.batch,
slo.type,
(SELECT Count(sl1.id)
FROM sync_log sl1
WHERE sl1.status = 1
AND sl1.batch = slo.batch) AS success,
(SELECT Count(sl2.id)
FROM sync_log sl2
WHERE sl2.status = 0
AND sl2.batch = slo.batch) AS failed,
slo.created_at
FROM sync_log slo
GROUP BY batch
ORDER BY slo.created_at
Below is the database table.
Try something like :
$result=DB::table('sync_log as slo')
->select('slo.batch','slo.type', 'slo.created_at', DB::raw(('SELECT Count(sl1.id) FROM sync_log sl1 WHERE sl1.status=1 AND sl1.batch = slo.batch) AS success'), DB::raw(('SELECT Count(sl2.id) FROM sync_log sl2 WHERE sl2.status = 0 AND sl2.batch = slo.batch) AS failed')
->groupBy('batch')
->orderBy('slo.created_at')
->get();
Without any idea on your structure or models. guessing a manyToMany relation wetween batch and type where sync_log is the pivot table between them.
$batchs = Batch::withCount([
'types as success' => function ($query) {
$query->where('status', 1);
},
'types as failed' => function ($query) {
$query->where('status', 0);
}])
->get();
Using Eloquent ORM it can be tricky but I guess will work, you can define hasMany relation(s) in same model which will relate to same model using batch attribute/key like
class SyncLog extends Model
{
public function success_batches()
{
return $this->hasMany(SyncLog::class, 'batch', 'batch')->where('status',1);
}
public function failed_batches()
{
return $this->hasMany(SyncLog::class, 'batch', 'batch')->where('status',0);
}
}
Then using your model you can get count for these relations using withCount
$bacthes = SyncLog::withCount(['success_batches','failed_batches'])
->select(['batch','type'])
->distinct()
->orderBy('created_at')
->get();
If you don't want to define it twice based on where clause then you can follow the approach explained in #N69S's answer like
class SyncLog extends Model
{
public function batches()
{
return $this->hasMany(SyncLog::class, 'batch', 'batch');
}
}
$bacthes = SyncLog::withCount([
'batches as success' => function ($query) {
$query->where('status', 1);
},
'batches as failed' => function ($query) {
$query->where('status', 0);
}])
->select(['batch','type'])
->distinct()
->orderBy('created_at')
->get();
Im using laravel for a project i am wanting to create an endpoint which filters and searches a Film model and returns a collection of films that match the chosen filter options which will be Options, Location and Age Rating picked by the user.
I have created a query with how i would initially join the tables in symfony(hopefully its correct) but im unsure on how i adjust this to be more Laravel or eloquent.
Film Model
public function location(): BelongsTo
{
return $this->belongsTo(Location::class);
}
public function options(): BelongsToMany
{
return $this->belongsToMany(
Option::class,
'film_options',
'film_id',
'option_id'
);
}
public function ageRatings(): BelongsToMany
{
return $this->belongsToMany(
AgeRating::class,
'film_age_ratings',
'film_id',
'age_rating_id'
);
}
Query in Doctrine
Select *
From films f
Join film_options fo on f.id = fo.film_id
Join options o on o.id = fo.option_id
Join film_age_ratings fa on f.id = fa.film_id
Join age_ratings a on a.id = fa.age_rating_id
Join location l on l.id = p.location_id
How would i write this query within laravel?
To achieve what you're after you can use a mixture of when() and whereHas():
$films = Film
::when($request->input('first'), function ($query, $first) {
$query->whereHas('options', function ($query) use ($first) {
$query->where('first', $first);
});
})
->when($request->input('second'), function ($query, $second) {
$query->whereHas('options', function ($query) use ($second) {
$query->where('second', $second);
});
})
->when($request->input('age'), function ($query, $age) {
$query->whereHas('ageRatings', function ($query) use ($age) {
$query->where('age', $age);
});
})
->when($request->input('country'), function ($query, $country) {
$query->whereHas('locations', function ($query) use ($country) {
$query->where('country', $country);
});
})
->when($request->input('city'), function ($query, $city) {
$query->whereHas('locations', function ($query) use ($city) {
$query->where('city', $city);
});
})
->orderBy('updated_at', 'desc')
->get();
Essentially, what the above is doing is saying when you have a non-empty value submitted it will add a where clause to the query for that relationship
e.g. is main is submitted it will limit the films to only ones that have categories when main is the submitted value.
Also, just an FYI, you don't need to add the from(...) method when using Eloquent.
A simple way to limit the number of lines would be to add scopes to your Film model e.g.
In your Film model:
public function scopeHasMain($query, $main)
{
$query->whereHas('options', function ($query) use($first) {
$query->where('first', $first);
});
}
Then your when() method would be:
when($request->input('first'), function ($query, $first) {
$query->hasFirst($first);
})
You can use the whereHas method as shown in the docs: https://laravel.com/docs/5.8/eloquent-relationships#querying-relationship-existence
Example:
use Illuminate\Database\Eloquent\Builder;
//...
$optionIds = [/* Option ids to filter by */];
$films = Film::whereHas('options', function (Builder $query) use ($optionIds) {
$query->whereIn('options.id', $optionIds);
})->get();
I have the following tables and I want to click delete at Pak button which can delete all 3 tables relations.
How can I achieve that?
table Pak : id_pak, pak_name/////
table Church : id_church, church_name, id_pak/////
table Member : id_member, name_member, id_church////
public function actionDelete($id)
{
$this->findModel($id);
$select = Church::find()
->select('church_name')
->where(['id_pak' => $id])
->all();
$a3 = Church::find()
->select('id_church')
->where(['id_pak' => $id])
->all();
$select2 = Member::find()
->select('member_name')
->where(['id_church'=> $a3])
->all();
Church::find()->where(['id_pak' => $id])->one()->delete();
Pak::find()->where(['id_pak' =>$id])->one()->delete();
Member::find()->where(['id_church'=> $a3])->one()->delete();
return $this->redirect(['index','select'=>$select,'select2'=>$select2]);
}
Using Constraints with innoDB Engine
If you are using InnoDB and have added the constraints on delete cascade correctly and defined the respective relations in the models you don't need to worry about the related records in the other tables you just need to find the model in the Pak and delete it.
public function actionDelete($id)
{
$this->findModel($id)->delete();
return $this->redirect(['index']);
}
protected function findModel( $id ) {
if ( ($model = Pak::findOne ( $id )) !== null ) {
return $model;
}
throw new NotFoundHttpException ( 'The requested page does not exist.' );
}
Removing Manually
Or if you are not using innoDB or not using constraints for any reason then you can override the beforeDelete() in the ActiveRecord Model for Pak and remove all the child rows for the Pak Model in the Church and override beforeDelete() inside the Church to delete all child rows in Member model and return true from there to continue deleting the actual record in the Pak model
I assume that you have the following relations defined in the Pak model
public function getChurch(){
return $this->hasOne(Church::className(), ['id_pak'=>'id_pak']);
}
and the following inside the Church model
public function getMember(){
return $this->hasOne(Member::className(),['id_church'=>'id_church']);
}
Then override the beforeDelete() in the Pak model
public function beforeDelete() {
$this->church->delete();
return parent::beforeDelete ();
}
and override the beforeDelete() in the Church Model
public function beforeDelete() {
$this->member->delete();
return parent::beforeDelete ();
}
and in your actionDelete() just find the model and call delete
public function actionDelete($id)
{
$this->findModel($id)->delete();
return $this->redirect(['index']);
}
There is also a nice article on implementing recursiveDelete() method in a parent model here.
Following code may help you to solve your problem.
public function actionDelete($id)
{
$select = Church::find()
->select('church_name')
->where(['id_pak' => $id])
->all();
$a3 = Church::find()
->select('id_church')
->where(['id_pak' => $id])
->all();
$select2 = Member::find()
->select('member_name')
->where(['id_church'=> $a3])
->all();
// ---------- start ---------------
$park = Park::find()->where(['id_pak' => $id])->one();
if ( $park->delete() ){
Pak::deleteAll('id_pak = :id', [':id' => $id]);
foreach ($a3 as $value) {
Member::deleteAll('id_church = :id', [':id' => $value->id_church ]);
}
}
// ---------- end ---------------
return $this->redirect(['index','select'=>$select,'select2'=>$select2]);
}
In controller i have:
public function actionGetItems()
{
$model = new \app\models\WarehouseItems;
$items = $model->find()->with(['user'])->asArray()->all();
return $items;
}
In WarehouseItem model i have standard (created by gii) relation declaration:
public function getUser()
{
return $this->hasOne('\dektrium\user\models\User', ['user_id' => 'user_id']);
}
How can i control which column data do i get from "user" relation? I currently get all columns which is not good as that data is being sent to Angular in JSON format.
Right now i have to loop trough $items and filer out all columns i dont want to send.
You should simply modify the relation query like this :
$items = \app\models\WarehouseItems::find()->with([
'user' => function ($query) {
$query->select('id, col1, col2');
}
])->asArray()->all();
Read more : http://www.yiiframework.com/doc-2.0/yii-db-activequerytrait.html#with()-detail
Your code should go this way.
public function actionGetItems()
{
$items = \app\models\WarehouseItems::find()
->joinWith([
/*
*You need to use alias and then must select index key from parent table
*and foreign key from child table else your query will give an error as
*undefined index **relation_key**
*/
'user as u' => function($query){
$query->select(['u.user_id', 'u.col1', 'u.col2']);
}
])
->asArray()
->all();
return $items;
}
Inside WarehouseItem model
/**
* #return ActiveQuery
*/
public function getUser()
{
$query = User::find()
->select(['id', 'col1', 'col2'])
->where([
'id' => $this->user_id,
]);
/**
* Default hasOne, setup multiple for hasMany
* $query->multiple = true;
*/
return $query;
}
I'm trying to figure out how to get all rows except few (A and B) in Eloquent ORM modal.
User Model
public function notifications()
{
return $this->hasMany('notification','listener_id','id');
}
Model Notification
public function scopeFriendship($query)
{
return $query->where('object_type', '=', 'Friendship Request');
}
public function scopeSent($query)
{
return $query->where('verb', '=', 'sent');
}
Here how can I get all notifications of a user except other than (Friendship and Sent) scope.
Something like:- all rows except !(Friendship AND Sent)
It is possible to use scopes in combination with eager loading. Like that:
User::with(['notifications' => function($q){
$q->friendship();
}])->get();
However we now need to invert the scope somehow. I can think of two ways to solve this.
1. Add negative scopes
public function scopeNotFriendship($query){
return $query->where('object_type', '!=', 'Friendship Request');
}
public function scopeNotSent($query){
return $query->where('verb', '!=', 'sent');
}
User::with(['notifications' => function($q){
$q->notFriendship();
$q->notSent();
}])->get();
2. Optional parameter
Or you could introduce an optional parameter to your current scopes. Something like this:
public function scopeFriendship($query, $is = true)
{
return $query->where('object_type', ($is ? '=' : '!='), 'Friendship Request');
}
public function scopeSent($query, $is = true)
{
return $query->where('verb', ($is ? '=' : '!='), 'sent');
}
This way you would only have to pass in false:
User::with(['notifications' => function($q){
$q->friendship(false);
$q->sent(false);
}])->get();
Edit
You can even gain more control by adding a second parameter for the boolean (AND or OR of the where:
public function scopeFriendship($query, $is = true, $boolean = 'and')
{
return $query->where('object_type', ($is ? '=' : '!='), 'Friendship Request', $boolean);
}
And if you wanted either scope to be true:
$q->friendship(true, 'or');
$q->sent(true, 'or');
2nd Edit
This one finally worked (from the chat)
Notification::where('listener_id', $user_id)
->where(function($q){
$q->friendship(false)
$q->sent(false, 'or')
})
->get();