I am trying to join the following two tables in a controller:
artifacts_publications
----------------------
id
artifact_id
publication_id
and
authors_publications
--------------------
id
author_id
publication_id
Other tables which have associations like belongsTo and HasMany set up easily join using contain(). For these tables, suppose no association is already defined. In the end, I want to join another table to the join of these tables but for now I cannot get these two joined.
The Controller uses
namespace App\Controller;
use App\Controller\AppController;
use Cake\ORM\TableRegistry;
use Cake\Datasource\ConnectionManager;
In the index() action of the controller, I have
$options['joins'] = [
[
'table' => 'authors_publications',
'alias' => 'j',
'type' => 'LEFT OUTER',
'conditions' => [
'authors_publications.publication_id = artifacts_publications.publication_id',
],
],
];
$query = TableRegistry::getTableLocator()->get('artifacts_publications')->find('all', $options);
$query->limit(2);
debug($query);
debug($query->toArray());
The resulting SQL query is somewhat like this, but I have shortened long identifiers:
SELECT id, artifact_id, publication_id
FROM artifacts_publications
LIMIT 2;
Executing that query also shows no join. I tried to follow cakePHP table joining two tables issue but I still cannot join. Hand-written SQL worked but it is not elegant. What am I missing here?
#Utsav
Try to use the Containable behaviour of CakePHP by switching the 'join' with 'contain'.
$options = [
'fields' => [
'ArtifactsPublication.fieldsYouWant'
],
'conditions' => [
'ArtifactsPublication.id' => $artifactId
],
'contain' => [
'AuthorsPublication'
]
];
You can also set the recursive to define if you want to get the contained model's linked models. Alternativly you can specify in the fields option what fields you want from the artifacts_publications Model and then just contain the authors_publications Model.
Hope this helps as I have had bad experiences with manual joins in CakePHP, also the CakePHP standard is to use contain. Just note a contain cannot retrieve distant joined data.
Cheers mate :)
Related
I try to use contain to make an full join of 2 Tables but i don't know how to change the condition:
$select= $this->Friends->find('all')
->order(['Friends.id' => 'ASC'])
->contain([
'Animals'
])
->where(['animal1_id'=> $animalsid,
'confirmed'=>'1'
]);
$this->set(compact('select'));
And so look the SQL:
SELECT
*
FROM
friends Friends
INNER JOIN animals Animals ON Animals.id = (Friends.animal2_id)
WHERE
(
animal1_id = 4
AND confirmed = 1
)
ORDER BY
Friends.id ASC
The problem is that this Friends.animal2_id i want to change with Friends.animal1_id but i don't know how? Maybe there another methods? I tried to use join but i get just one table not both.
I am considering that you have belongsTo relationship between the two model friends and animals.
So you just have to change the foreignKey name in the association, so update the association in your Friends model like
$this->belongsTo('Animals', [
'foreignKey' => 'animal1_id', //Here the column name you want to use for join
'className'=>'Animals',
'joinType' => 'INNER'
]);
EDIT(for comment query):-
If you want to select other tables, you can achieve this in two ways
In controller
$this->loadModel('OtherModelName'); //This will load your model to $this object
$other_table_result = $this->OtherModelName->find('all')->toArray();
Method to use raw SQL queries,
$sql = "SELECT * FROM users";
$query = $this->connection->prepare($sql);
$query->execute();
$result = $query->fetchAll();
I am joining multiple tables like the following.
$recommend_logs = $this->RecommendingProductLog->find('all', array(
'recursive' => 2,
'fields' => array('Product.ProductName', 'Product.Gender', 'Product.Price', 'RecommendingProductLog.preference', 'Brand.BrandName'),
'conditions' => array('RecommendingProductLog.user_id' => $user_id),
'contain' => array('Product', 'Product.Brand')
));
I am getting this query from log.
SQL Query: SELECT `Product`.`ProductName`, `Product`.`Gender`, `Product`.`Price`, `RecommendingProductLog`.`preference`, `Brand`.`BrandName`, `Product`.`id` FROM `database`.`recommending_Product_log` AS `RecommendingProductLog` LEFT JOIN `database`.`Products` AS `Product` ON (`RecommendingProductLog`.`Product_id` = `Product`.`id`) WHERE `RecommendingProductLog`.`user_id` = 32
Even though 'Product' table is a child table of 'Brand' table, somehow I don't see 'Brand' table in the query. That's why I am getting an error 'Unknown column 'Brand.BrandName' in 'field list'.
I specified 'Brand' in the Perfume model as 'belongsTo' and 'Perfume' in RecommendingPerfumeLog model and 'Perfume' as hasMany in Brand model.
can somebody point where the problem is?
thanks.
Have you correctly put the foreign keys on the respective tables of the relation? In this case Product have to have a a key with the name brand_id and it must be a foreign key to the brand id field.
Also check this and try to put your joins.
I don't remenber this 'contain' field from the docs. Are you using the latest version?
Sorry if this is obvious - I'm trying to do a find() on a join table and failing, whats the correct syntax to do this?
Basically I have expenseCode table with a HABTM releationship with the expense table.
In my expenseCode model I have:
public function beforeDelete($cascade = false) {
$count = $this->Expense->find("count", array(
'conditions' => array('expense_code_id' => $this->id)
));
if ($count == 0) {
return true;
} else {
//$this->Session->setFlash('Record cannot be deleted as it has ' . $count . 'number of expenses attached to it');
return false;
}
}
If I uncomment the setFlash() I get the error.
Where am I going wrong? Thanks in advance.
Unfortunately it's not possible to perform such a query on the HABTM join table from one of the parent models without some extra work. I'm assuming that Expense is the parent model and ExpensesCode the join model?
A common way is to modelise the HABTM join table. Say you have an expenses and codes table which are joined by expenses_codes:
$this->Expense->bindModel(array('hasOne' => array('ExpensesCode')));
$this->Expense->find('all', array(
'fields' => array('Expense.*'),
'conditions' => array('ExpensesCode.expense_code_id' => $this->id)
));
However, Cake also auto-iniatlises a model for the join table when a HABTM association is defined (see the manual, and the "with" key in the list of possible keys).
So this syntax would let you directly query the join table:
$this->Expense->ExpensesCode->find('all', array(
'conditions' => array('ExpensesCode.expense_code_id' => $this->id)
));
The query above will net you with an array containing only the results from the join table, as it doesn't perform a join like the first procedure. So you would have to perform a second find() on the Expense model to find the expenses related to the expense_code_id from ExpensesCode.
EDIT:
It's a framework convention that HABTM join tables should be underscored and alphabetically ordered. So if the HABTM join table is called codes_expenses, it's auto-modelised as CodesExpense.
You can also work in a simpler way if you create another model with two hasMany relations instead of one with HABTM.
HasAndBelongsToMany between two models is in reality shorthand for three models associated through both a hasMany and a belongsTo association.
I found it is one of the simplest ways to save and retrieve data.
For more information:
http://book.cakephp.org/2.0/en/models/saving-your-data.html#what-to-do-when-habtm-becomes-complicated
I have a stores and store_details table and now a store_details_stores table.
The Store model has the following:
public $hasAndBelongsToMany = array('StoreDetail');
The StoreDetail model has the following:
public $hasAndBelongsToMany = array('Store');
When I attempt the below query in the stores_controller, I receive an sql error. For some reason the store_details table is not being joined naturally. Is this expected behaviour? Do I need to join this table manually?
$this->Store->find('all', array('conditions' => array('StoreDetail.name' => 'Parking')));
This is expected behavior. You need to set up a bindModel call with your conditions, or you can create a model for your join table and query that directly.
See:
CakePHP Book - HABTM
Modelizing HABTM Join Tables
$this->Store->bindModel(array(
'hasAndBelongsToMany' => array(
'StoreDetail' => array('conditions'=>array('StoreDetail.name' => 'Parking')
))));
$this->Store->find('all');
I would recommend applying the condition to the association before you do your find, using the bindModel method.
/**
* Apply a condition to the association
*/
$this->Store->bindModel(array(
'hasAndBelongsToMany' => array(
'StoreDetail' => array('conditions' => array('StoreDetail.name'=>'Parking'))
)));
/**
* Find all Stores that have an associated StoreDetail.name of 'Parking'
*/
$this->Store->find('all');
For an e-commerce app that I'm building I am using CakePHP.
I have created the db and afterwards baked my app with cake bake.
My models are all linked up properly as follows:
Product hasMany CategoryProduct,ImagesProduct
Category hasMany CategoryProduct
CategoryProduct belongsTo Product,Category
Image hasMany ImagesProduct
ImagesProduct belongsTo Product,Image
I have tried in various ways to obtain a paginated view of products dependent of the category_id with no succes.
I have managed to obtain the array I wanted with
$this->CategoryProducts->findByCategoryId($id), but I can't use the built-in paginator from cakePHP with the resultant array.
Could somebody tell me how to properly formulate the $options['joins'] array to pass on to the paginate function in order to get the same result, but in a paginated way?
The SQL for what I want to do would be something like this:
SELECT p . * , i.filename
FROM products p
LEFT JOIN (
category_products cp, categories c, images_products ip, images i
) ON ( cp.product_id = p.id
AND c.id = cp.category_id
AND c.id =2
AND ip.product_id = p.id
AND ip.image_id = i.id )
This is a question that perplexed me for quite sometime. You shouldn't have to associate either of your join models (CategoryProduct, ImagesProduct) directly to your models if you're using a HABTM association with CakePHP. cake bake may not have picked it up the HABTM association correctly if you didn't have the table names quite right. The join tables should be categories_products and images_products, which would make th join models CategoriesProduct and ImagesProduct.
Either way though, the following should get you going on filtering by categories:
//in products_controller.php:
//Fake a HasOne association, setting the reset parameter to false since pagination
//requires two queries to complete (one for the count, one for the data)
$this->Product->bindModel(array(
'hasOne' => array(
'CategoriesProduct
)
), false);
//Group by products since the HasOne will return multiple rows for each product
$options = array(
'group' => 'Product.id',
'conditions' => array(
'CategoriesProduct.category_id' => $categories
)
);
//To paginate the result:
$this->paginate = $options;
$products = $this->paginate();
In this example $categories can be either a single category_id or an array containing category_ids (i.e. array('1', '2', '3')). The result will be an 'OR' of categories. If you're looking to filter by an 'AND' type condition, check http://edblogs.cal.msu.edu/devteam/2011/02/08/cakephp-habtm-searches/.
Hope this helps.