Count BelongToMany relationship in CakePHP 3.2 - cakephp

I have a Table A related many to many with a Table B and I want to know how many related rows I have in B with a given A entity.
In SQL it would be something like this:
SELECT COUNT(*) FROM AB WHERE A_id=1;
I thought that I should place it in the model of A with a Virtual Field that retrieves the count, but I didnĀ“t find a way to use the model itself to get the related B rows.
I tried with
$this->contain(['B'])->count()
But it didn't work because 'contain' is undefined. So, how should I get the count?

Make sure that u put the association in both of your models initialize method and dont call contain() on $this.
$query = $this->A->find();
numberOfRecords = $query->contain(['B'])->count;

Related

Laravel get value of column from relation through pivot on load() after all()

I have a problem retrieving values of a column from relations in Laravel.
I have a User - Model. This model has relation to a table btw. a model named Userhobbies.
For now we have:
User ::: hasMany >>> Userhobbies
Now with User::all()->load('hobbies') I'm getting right results like
{"id":"1","username":"jdoe","first_name":"Joe","last_name":"Doe","birth":"
1992-04-11","picture_id":"f3dca65323e876026b409b9ba3d49c56","hobbies":
[{"hobby_id":"1","user_id":"1"},{"hobby_id":"2","user_id":"1"},
{"hobby_id":"3","user_id":"1"},{"hobby_id":"4","user_id":"1"}]}
As you can see Userhobbies contains only primary-key relations between hobby - table (Hobby Model) and user - table (User Model).
(Hobby model also has hasMany relation to Userhobbies)
My question now is - how to retrieve all hobby-names (from hobby - table) in my call over (after load('hobbies') ) and is it possible without writting a lot of code?
For better understanding of my idea the result which I want to retrieve:
{"id":"1","username":"jdoe","first_name":"Joe","last_name":"Doe","birth":"
1992-04-11","picture_id":"f3dca65323e876026b409b9ba3d49c56","hobbies":
["golf", "cards", "games", "football"]}
EDIT:
If I try following (I tried with belongsToMany in User and Hobby):
User::with('hobbies')->get()->first()
And I'm getting the whole values from the hobbies - table:
{user-specific data ...
hobbies:[{"id":"1","name":"golf","created_at":"2015-04-07
14:15:02","updated_at":"2015-04-07 14:15:02","pivot":
{"user_id":"1","hobby_id":"1"}},
{"id":"2","name":"cards","created_at":"2015-04-07
14:15:02","updated_at":"2015-04-07 14:15:02","pivot":
{"user_id":"1","hobby_id":"2"}},
{"id":"3","name":"games","created_at":"2015-04-07
14:15:02","updated_at":"2015-04-07 14:15:02","pivot":
{"user_id":"1","hobby_id":"3"}},
{"id":"4","name":"football","created_at":"2015-04-07
14:15:02","updated_at":"2015-04-07 14:15:02","pivot":
{"user_id":"1","hobby_id":"4"}}]}
Same try with ->load('hobbies'). I really don't know how to go on.
To explain it a bit more what I need one could imagine such query as follows:
User::all(['id', 'name'])->load(array('hobbies.id','hobbies.name'))->get();
From my knowledge, I know that it's possible to use a closure to set constraints on the query that performs the load, like so:
User::all()->load(['hobbies' => function($query)
{
$query->select('id', 'name');
}]);
By doing it, when you cast it to array, it will produce a result near to what you want. You can even add 'pivot' to your $hidden property on your Hobby model to hide this information.

How to sort a Doctrine DQL Query by the number or members of a relation?

I'm trying to create a query for retrieving objects from a Doctrine database, sorted by the number of members of a specific one-to-many relationship.
More specifically: I have two Entities: Person and Federation. A person can be a member of one federation (Person has 'federation' relationship), and a federation may have n people (Federation as 'people' relationship).
I would like to create a DQL Query that would return the list of Federations, ordered by how many people are members of that Federation. Something along these lines:
SELECT f FROM AcmeStatsBundle:Federation f ORDER BY [number of members of f.people]
That would be the first step. There is an additional second step, which I don't know if is possible to achieve with a single query, which would be filtering the members of the relation prior the counting. Like so:
SELECT f FROM AcmeStatsBundle:Federation f ORDER BY [number of (f.people p where p.attr = value)]
That second one would be the optimal result, but the first one satisfies my needs, if the second case is not feasibly in a single query.
Thanks in advance.
You could do something along the lines of:
public function getFederationsOrderedByNumberOfPeople()
{
return $this->createQueryBuilder('f')
->addSelect('COUNT(p) AS HIDDEN personCount');
->leftJoin('f.people', 'p');
->groupBy('f')
->orderBy('personCount', 'DESC');
}
The HIDDEN keyword was added in Doctrine 2.2, it means that the selected field won't be in the results, and in this case that means you just get your entities back instead of an array.
Reference to DQL SELECT documentation: http://doctrine-orm.readthedocs.org/en/latest/reference/dql-doctrine-query-language.html#dql-select-examples
There are 5 aggregate functions in DQL you can use (Doctrine 2.2): AVG, COUNT, MIN, MAX and SUM.
The following query should work:
SELECT f
FROM AcmeStatsBundle:Federation f
LEFT JOIN f.people p
GROUP BY f.id
ORDER BY COUNT(p)
WHERE p.attr = :some_value
For more DQL trickery I suggest you look up official Doctrine docs.

magento join sales_flat_quote_payment with sales_flat order tables

I am trying to show last4 of the CC and the Card type in a report, by going through the Database I have learned that inside sales_flat_quote_payment there is a field called additional information, and that field seems to have all the info I need, so I would need to extract if out of there. but the issue is that I am trying to find a way to join the sales_flat_quote_payment with sales_flat order tables so I can match the records by increment_id, but can't seem to find a way to do this.
I already tried:
orders->getSelect()->join(
array('p' => $orders->getResource()->getTable('sales/order_payment')),
'p.parent_id = main_table.entity_id',
array('cc_last4' => 'p.cc_last4', 'cc_type' => 'p.cc_type')
);
'OrderCreditCardLast4' => $order['cc_last4'],
but with no luck.
assuming you have the order number, do this:
select a.reserved_order_id, b.cc_last4
from sales_flat_quote a, sales_flat_quote_payment b
where b.quote_id=a.entity_id and a.reserved_order_id='YOUR ORDER NUMBER HERE';

CakePHP HABTM relationship, setting related records always adds new entry in jointable even if already exists

This is my code:
$charities = explode(',',$this->data['Charity']['charities']);
foreach ($charities as $key=>$charity){
$data['Charity'][$key] = $charity;
}
$this->Grouping->id = $this->data['Charity']['grouping_id'];
if ($this->Grouping->save($data)){
//Great!
}else{
//Oh dear
}
The code is from an action that gets called by AJAX. $this->data['Charity']['charities'] is a comma separated list of ids for the Charity model, which HABTM Grouping.
The code works fine, except that Cake doesn't seem to check whether that charity is already associated with that grouping, so I end up with lots of duplicate entries in the join table. I can see that this will be a pain later on so I'd like to get it right now.
What am I doing wrong?
Thanks
A few things I spotted:
A. Is $this->data['Charity']['charities'] an array with named keys corresponding to the table columns? Because inside the foreach() loop, you are taking the key and putting it in the $data['Charity'] array. The result could be a wrong format. The $data array for Model saves is commonly formatted as $data['Charity']['NAME_OF_COLUMN1], $data['Charity']['NAME_OF_COLUM2], and so on for each column that wants to be saved. So, if the keys are numbers, then you could be having something like: $data['Charity'][0], $data['Charity'][1], which is wrong.
B. HABTM Associations are saved differently. What I do recommend, is that you take a look at the book's section on saving HABTM. I save a HABTM relation like this (supposing a relation between users and coupons):
$this->data['Coupon'] = array('id' => 1, 'code' = 'BlaBla');
$this->data['User'] = array('id' => 33);
$this->Coupon->save($this->data, false);
So, as you can see, the $data array has a subarray named after the foreign model ('User') with the columns+values that I want to save. Then I save this array to the Coupon model.
C. Be aware that CakePHP will always delete old records and insert new ones within the join table when it is 'updating' HABTM relations. To avoid this, you set the 'unique' field to false in the Model's $HABTM configuration.

How do I join two tables in a third n..n (hasAndBelongsToMany) relationship in CakePHP?

I have a n...n structure for two tables, makes and models. So far no problem.
In a third table (products) like:
id
make_id
model_id
...
My problem is creating a view for products of one specifi make inside my ProductsController containing just that's make models:
I thought this could work:
var $uses = array('Make', 'Model');
$this->Make->id = 5; // My Make
$this->Make->find(); // Returns only the make I want with it's Models (HABTM)
$this->Model->find('list'); // Returns ALL models
$this->Make->Model->find('list'); // Returns ALL models
So, If I want to use the list to pass to my view to create radio buttons I will have to do a foreach() in my make array to find all models titles and create a new array and send to the view via $this->set().
$makeArray = $this->Make->find();
foreach ($makeArray['Model'] as $model) {
$modelList[] = $model['title'];
}
$this->set('models', $models)
Is there any easier way to get that list without stressing the make Array. It will be a commom task to develops such scenarios in my application(s).
Thanks in advance for any hint!
Here's my hint: Try getting your query written in regular SQL before trying to reconstruct using the Cake library. In essence you're doing a lot of extra work that the DB can do for you.
Your approach (just for show - not good SQL):
SELECT * FROM makes, models, products WHERE make_id = 5
You're not taking into consideration the relationships (unless Cake auto-magically understands the relationships of the tables)
You're probably looking for something that joins these things together:
SELECT models.title FROM models
INNER JOIN products
ON products.model_id = models.model_id
AND products.make_id = 5
Hopefully this is a nudge in the right direction?
Judging from your comment, what you're asking for is how to get results from a certain model, where the condition is in a HABTM related model. I.e. something you'd usually do with a JOIN statement in raw SQL.
Currently that's one of the few weak points of Cake. There are different strategies to deal with that.
Have the related model B return all ids of possible candidates for Model A, then do a second query on Model A. I.e.:
$this->ModelB->find('first', array('conditions' => array('field' => $condition)));
array(
['ModelB'] => array( ... ),
['ModelA'] => array(
[0] => array(
'id' => 1
)
)
Now you have an array of all ids of ModelA that belong to ModelB that matches your conditions, which you can easily extract using Set::extract(). Basically the equivalent of SELECT model_a.id FROM model_b JOIN model_a WHERE model_b.field = xxx. Next you look for ModelA:
$this->ModelA->find('all', array('conditions' => array('id' => $model_a_ids)));
That will produce SELECT model_a.* FROM model_a WHERE id IN (1, 2, 3), which is a roundabout way of doing the JOIN statement. If you need conditions on more than one related model, repeat until you have all the ids for ModelA, SQL will use the intersection of all ids (WHERE id IN (1, 2, 3) AND id IN (3, 4, 5)).
If you only need one condition on ModelB but want to retrieve ModelA, just search for ModelB. Cake will automatically retrieve related ModelAs for you (see above). You might need to Set::extract() them again, but that might already be sufficient.
You can use the above method and combine it with the Containable behaviour to get more control over the results.
If all else fails or the above methods simply produce too much overhead, you can still write your own raw SQL with $this->Model->query(). If you stick to the Cake SQL standards (naming tables correctly with FROM model_as AS ModelA) Cake will still post-process your results correctly.
Hope this sends you in the right direction.
All your different Make->find() and Model->find() calls are completely independent of each other. Even Make->Model->find() is the same as Model->find(), Cake does not in any way remember or take into account what you have already found in other models. What you're looking for is something like:
$this->Product->find('all', array('conditions' => array('make_id' => 5)));
Check out the Set::extract() method for getting a list of model titles from the results of $this->Make->find()
The solution can be achieved with the use of the with operation in habtm array on the model.
Using with you can define the "middle" table like:
$habtm = " ...
'with' => 'MakeModel',
... ";
And internally, in the Model or Controller, you can issue conditions to the find method.
See: http://www.cricava.com/blogs/index.php?blog=6&title=modelizing_habtm_join_tables_in_cakephp_&more=1&c=1&tb=1&pb=1

Resources