cakephp4 get 1st record from containing table with order by a field - orm

I have cakephp4 project
having 1 to many relationship between Portfolios and PSnaps
I want to show all 'Portfolios' with one associated record from PSnaps where its PSnaps.status=1 and order=>['PSnap.order_at'=>'ASC']
I tried many things but getting the correct result
below is giving 90% correct result only ordering on PSnaps.order_at is not working.
along with hasmany() i have created hasOne() association as shown in below model
Model
class PortfoliosTable extends Table
{
public function initialize(array $config): void
{
parent::initialize($config);
$this->setTable('portfolios');
$this->setDisplayField('id');
$this->setPrimaryKey('id');
$this->hasOne('FirstPSnaps', [
'className' => 'PSnaps',
'foreignKey' => 'portfolio_id',
'strategy' => 'select',//also tried join
//'joinType'=>'LEFT',//also tried inner,left,right
//'sort' => ['FirstPSnaps.order_at' => 'ASC'], //*******this is not working
'conditions' => function (\Cake\Database\Expression\QueryExpression $exp, \Cake\ORM\Query $query) {
$query->order(['FirstPSnaps.order_at' => 'ASC']);//*******also not working
return [];
}
])
;
$this->hasMany('PSnaps', [
'foreignKey' => 'portfolio_id',
]);
}
Controller
$pfolios = $this->Portfolios->find('all')
->select(['Portfolios.id','Portfolios.client','Portfolios.country','Portfolios.url'])
->where(['Portfolios.status'=>1])
->order(['Portfolios.order_at'=>'asc','Portfolios.id'=>'asc'])
->limit(8)
->contain([
'FirstPSnaps'=>function($q){
return $q
->select(['FirstPSnaps.portfolio_id','FirstPSnaps.snap'])
//->where(['FirstPSnaps.status'=>1])
//->order(['FirstPSnaps.order_at'=>'asc'])
;
}
])
->toArray();
it is returning correct porfolios with 1 p_snap record but ordering/sorting is not correct as I need first p_snap something like where p_snap.status=1 and p_span.portfolio_id= portfolios.id limit 1.

Related

Query efficiency -> merge 2 queries with a join or union

I need some serious help/direction. I have two tables:
students
-id
-site_id
-name
-enter_date
-exit_date
student_meals
-id
-site_id
-student_id
-meal_type_id (1, 2, or 3)
-date_served
I need two arrays:
All students enrolled on the requested 'serviceDate' ('serviceDate is between their enter_date and exit_date) that DO NOT have a meal_type_id of the requested mealType on the rquested serviceDate.
All students enrolled on the requested 'serviceDate' ('serviceDate is between their enter_date and exit_date) that DO have a meal_type_id of the requested mealType on the requested serviceDate.
I got it to work with the following:
'unservedStudents' => Auth::user()->site
->students()
->where('enter_date', '<=',Request::only( 'serviceDate') )
->where('exit_date', '>=',Request::only( 'serviceDate') )
->OrderByName()
->filter(Request::only('search', 'serviceDate', 'mealType'))
->get()
->map(fn ($students) => [
'id' => $students->id,
'name' => $students->name,
]),
'servedStudents' => Auth::user()->site
->student_meals()
->with('student')
->where('meal_type_id', Request::only( 'mealType'))
->where('date_served', Request::only( 'serviceDate'))
->orderBy('created_at', 'DESC')
->get()
->map(fn ($served_students) => [
'id' => $served_students->id,
'student' => $served_students->student ? $served_students->student->only('id','name') : null,
]),
//Filter for students
public function scopeFilter($query, array $filters)
{
$mealType = $filters['mealType'] ?? null;
$serviceDate = $filters['serviceDate'] ?? null;
$search = $filters['search'] ?? null;
$query
->when($search, function ($query) use ($search) {
$query->where( fn ($query) =>
$query->where('first_name', 'like', '%'.$search.'%')
})
->when( $mealType, function ($query) use ($mealType, $serviceDate) {
$query->whereDoesntHave('student_meals', fn ($query) =>
$query->where('meal_type_id', $mealType )
->where('date_served', $serviceDate));
});
When I seeded my database ites that have more than 400 students or so gets really slow. I'm pretty sure I need to condense the two queries above, but I can't figure out the logic.
Below is an attempt, but it gives me an error 'Method Illuminate\Database\Eloquent\Collection::getBindings does not exist'.
$students = Auth::user()->site
->students()
->join('student_meals as m', 'm.student_id', '=', 'students.id')//this is my attempt to get the same columns as the table to union....
->where('enter_date', '<=',Request::only( 'serviceDate') )
->where('exit_date', '>=',Request::only( 'serviceDate') )
->where('date_served', '=',Request::only( 'serviceDate') )
->filter(Request::only('search', 'serviceDate', 'grade', 'hr'))
->select('students.id as studentId', 'first_name', 'students.site_id as siteId', 'm.id as mealId', 'm.meal_type_id', )
->get()
->map(fn ($students) => [
'id' => $students->studentId,
'name' => $students->first_name,
'siteId' => $students->site_id,
'mealId' => $students->mealId,
'mealType' => $students->meal_type_id,
]),
'student_meals' => Auth::user()->site
->student_meals()
->join('students as s', 's.id', '=', 'student_meals.student_id')
->where('date_served', '>=',Request::only( 'serviceDate') )
->where('meal_type_id', '>=',Request::only( 'mealType') )
->select('s.id as studentId', 'first_name',
's.site_id as siteId', 'student_meals.id as mealId', 'meal_type_id')
->union($students)
->map(fn ($students) => [
'id' => $students->studentId,
'name' => $students->first_name,
'siteId' => $students->site_id,
'mealId' => $students->mealId,
'mealType' => $students->meal_type_id,
]),
If you're up for it, I'd really appreciate any insight/help/pointers/tips.
I think that your problem is very simple if you use the collections
//Relation name should be meals instead of student_meals because is redundant that a student has many student meals
$students = Student::with([
'meals' => function ($query) use ($request) {
$query->where('date_served', $request['serviceDate']);
}
])
->where('site_id', $request->user()->site_id)
->where('enter_date', '<=', $request['serviceDate'])
->where('exit_date', '>=', $request['serviceDate'])
->get();
At this point you have all students that has the requested serviceDate between enter_date and exit_date and belongs to the same site_id of the current user (lazy loading all the meals of the student that belongs to the requested serviceDate), so, all you have to do is spread them in two different collections.
//Students with requested meal type
$swrmt = collect();
//Students without requested meal type
$swtrmt = collect();
foreach ($students as $student) {
//If student contains at least one meal with the requested mealType
if ($student->contains('meals.meal_type_id', $request['mealType'])) {
$swrmt->push($student);
} ese {
$swtrmt->push($student);
}
}
So you only have one query, and only need to be worried if the result is greater than 2000 students, if that happens would be necesary to change the with for a load using chunk of 2000 for preventing limit param query error. (Sorry if there is any type mistake, i write all of this on my cellphone), and don't forget to add your name filter at the main query with the same when that you alredy use.

How to link Yii's Active Record model table with other table using JOIN

I am using Yii 2.
I have model User with table in DB.
I tried link this table with table auth_assignment which has columns user_id, item_name and created_at using LEFT JOIN clause. I wrote this:
$model = User::find()->leftJoin('auth_assignment AS a', '`a`.`user_id` = `user`.`id`');
Then I attach $model to ActiveDataProvider:
$dataProvider = new ActiveDataProvider([
'query' => $model,
'pagination' => [
'pageSize' => 5,
],
]);
But when I run application it prints only Users columns: screenshot
What I do wrong?
In User model you can't get attributes of another table(auth_assignment ).
You can do it by two ways.
Define relationship between User and auth_assignment table in User model using hasOne()
Example : ( use your model name for auth_assignment table)
public function getAuth()
{
return $this->hasOne(Authassignment::className(), ['id' => 'user_id']);
}
Than you can use auth_assignment table attributes as $model->auth->user_id;
Generate query as array as below, in this scenario you don't need to define relationship in model.
$model = User::find()
->select('user.*,a*')
->leftJoin('auth_assignment AS a', 'a.user_id= user.id')
->asArray();
$dataProvider = new ActiveDataProvider([
'query' => $model,
'pagination' => [
'pageSize' => 5,
],
]);
Now you can use attributes as array variable i.e.
$model['user_id'] etc.

Left Join CakePHP3

I'm trying to do a LEFT JOIN in CakePHP3.
But all I get is a "is not associated"-Error.
I've two tables BORROWERS and IDENTITIES.
In SQL this is what I want:
SELECT
identities.id
FROM
identities
LEFT JOIN borrowers ON borrowers.id = identities.id
WHERE
borrowers.id IS NULL;
I guess this is what I need:
$var = $identities->find()->select(['identities.id'])->leftJoinWith('Borrowers',
function ($q) {
return $q->where(['borrowers.id' => 'identities.id']);
});
But I'm getting "Identities is not associated with Borrowers".
I also added this to my Identities Table:
$this->belongsTo('Borrowers', [
'foreignKey' => 'id'
]);
What else do I need?
Thanx!
The foreign key cannot just be 'id', that's not a correct model association. You'd need to put a 'borrower_id' field in identities, and declare it like this in the Identities model:
class Identities extends AppModel {
var $name = 'Identities';
public $belongsTo = array(
'Borrower' => array (
'className' => 'Borrower',
'foreignKey' => 'borrower_id'
)
);
Note the capitalization and singular/plural general naming conventions which your example doesn't follow in the least - ignoring those will get you some really hard to debug errors..
Yup. It was an instance of \Cake\ORM\Table, due to my not well chosen table name (Identity/Identities). I guess it's always better not to choose those obstacles, for now I renamed it to Label/Labels.
This query now works perfectly:
$var = $identities
->find('list')
->select(['labels.id'])
->from('labels')
->leftJoinWith('Borrowers')
->where(function ($q) {
return $q->isNull('borrowers.id');
});

Filter by a field that's a foreign key

Using Yii2.
I have a table called 'calificacion' that has a foreign key 'alumno-id' that points to field id in table alumno.
The column defined in the GridView widget is:
[
'header' => 'Alumno',
'attribute' => 'alumno.id',
'value' => 'alumno.name'
],
And it's showing perfectly, but the filter, in the header of the column, is not appearing. I want to have a textbox for writing the name of the alumno and get filtered. How can I achieve that?
EDIT: Here're the files https://dl.dropboxusercontent.com/u/7059378/Desktop.zip
First declare an attribute in your SearchModel.
public $alumno_name;
In your search model's rule add:
[['alumno_name'], 'safe'],
Join with alumno relation in search method (supponsinbly there is a alumno relation in your Calificacion model):
$query = Calificacion::find()
->joinWith(['alumno alumno']);
To sort with $alumno_name add:
$dataProvider->sort = [
'attributes' => [
//Other attributes here
'alumno_name' => [
'asc' => ['alumno.name' => SORT_ASC],
'desc' => ['alumno.name' => SORT_DESC],
],
]
];
To filter this you have to add:
$query->andFilterWhere(['like', 'alumno.name', $this->alumno_name]);
Finally in your grid view add:
[
'attribute' => 'alumno_name',
'value' => 'alumno.name'
],
You can find more info here and here.
Also instead of header use 'label' => 'Alumno'.
You must first define a relation in Model of that table 'calificacion', like
public function getAlumno()
{
return $this->hasOne(Alumno::className(), ['id' => 'alumno_id']);
}
Than in the search model of Calificacion set that you are joining tables after validation, like
$query->joinWith('alumno');
Than set search like
$query->andFilterWhere([
'alumno.id' => $this.alumno_id
]);

CakePHP - HABTM find() don't make the JOIN to other tables

My title will look like naive but I have to say I read/searched/tested everything possible, but my find() method don't implement the JOIN to related tables in the SQL query. I used it several times in other projects without problems but here...
Here my 2 models (nothing special but the manual definition of the related model) :
class Pflanzen extends AppModel {
public $useTable = 'pflanzen';
public $hasAndBelongsToMany = array(
'Herbar' => array(
'order'=>'Herbar.order ASC',
'joinTable' => 'herbar_pflanzen',
'foreignKey' => 'pflanzen_id',
'associationForeignKey' => 'herbar_id')
);
}
class Herbar extends AppModel {
public $useTable = 'herbar';
public $hasAndBelongsToMany = array(
'Pflanzen' => array('joinTable' => 'herbar_pflanzen',
'foreignKey' => 'herbar_id',
'associationForeignKey' => 'pflanzen_id')
)
}
Here my query in the "Herbar" controller (can't be more normal...) :
$pflanzen = $this->Herbar->Pflanzen->find('all',array(
'fields'=>array('Herbar.name','Pflanzen.linkplatter'),
'conditions' => array('Pflanzen.linkplatter' => true),
'order' => 'Herbar.name',
'limit' => 10,
'recursive'=>2)
);
$this->set('pflanzen',$pflanzen);
and the resulting error in the view :
Error: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'Herbar.name' in 'field list'
SQL Query: SELECT `Herbar`.`name`, `Pflanzen`.`linkplatter`, `Pflanzen`.`id` FROM `burgerbib`.`platter_pflanzen` AS `Pflanzen` WHERE `Pflanzen`.`linkplatter` = '1' ORDER BY `Herbar`.`name` ASC LIMIT 10
You can see that their is no JOIN in the SQL. Why ?? What do I wrong ?
I would really appreciate your help as I'm searching for hours and do no more see any solutions and didn't find nothing using google. Thanks in advance !!
HABTM doesn't make joined queries, it makes a query for all base records and more queries as needed for each relationship to fill the array. Your condition assumes a join, hence the error.
You can force a join using the 'joins' parameter. http://book.cakephp.org/1.2/en/view/872/Joining-tables
In the End, the better way of doing this is using the containable behaviour. Force Join is only useful when the containable behavior don't respond to the need :
http://book.cakephp.org/2.0/fr/core-libraries/behaviors/containable.html#using-containable