Multiple hasMany() associations with different foreign keys - orm

I am implementing a simple system that allows recording transactions between users. For that I have two tables transactions and users.
Every transaction is between two users and records the flow from one user to the other in the fields from_user_id and to_user_id which are foreign keys to users.id.
Now I would like to get all transactions for an user (in reverse chronologically order). I'd love to have something like this:
class UsersTable extends Table {
public function initialize(array $config) {
...
$this->hasMany('Transactions', [
'foreignKey' => [
'OR' => [
'Users.id = Transactions.from_user_id',
'Users.id = Transactions.to_user_id'
]
],
'finder' => [
...
]
]);
...
}
}
Of course this does not work out (SQLSTATE[42S22]: Column not found: 1054 Unknown column 'Transactions.Array' in 'where clause') and setting foreignKey to false and using a custom queryBuilder is not allowed for hasMany associations.
What does work is using two different associations for both foreign keys (one for negative and one for positive transactions) and then merging the two, but I would rather not go this way. This is the current approach:
$this->hasMany('NegativeTransactions', [
'className' => 'Transactions',
'foreignKey' => 'from_user_id'
]);
$this->hasMany('PositiveTransactions', [
'className' => 'Transactions',
'foreignKey' => 'to_user_id'
]);
Has anybody any ideas?

Related

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

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.

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

CakePHP retrieving when related model NULL

I have a null-able relation model, and I am using this standard code to retrieve my records:
$this->paginate = [
'contain' => ['Clients']
];
$coupons = $this->paginate($this->Coupons);
I am getting just the records that have client associated.
what is the best practice to make the contain work like OR, and not AND
EDIT: the relationship is seted as the following:
$this->belongsTo('Clients', [
'foreignKey' => 'client_id',
'joinType' => 'INNER'
]);
I have solved it by setting the joinType to LEFT:
$this->belongsTo('Clients', [
'foreignKey' => 'client_id',
'joinType' => 'LEFT'
]);

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