How can I join two tables on multiple columns in CakePHP 3? - php

I'm using CakePHP v3
I have a table that looks like this:
Document:
id | section | paragraph
-------------------------
1 2 4
Text:
id | section | paragraph | theText
---------------------------------------
12 2 4 Blah blah
So in SQL I could do something like this;
SELECT * FROM document
INNER JOIN text
ON document.section=text.section
AND document.paragraph=text.paragraph
How can I do something like this in CakePHP using the ORM? The Primary key in both tables is set up to be the id column.
I've looked into foreignKey and binidingKey in Cake's docs, but I can't see how to use multiple columns in those.
http://book.cakephp.org/3.0/en/orm/associations.html.
FWIW, here is a sample of code that shows how I want to access them.
$cond = [
'contain' => ['text']
];
$docs = $this->Documents->find('all',$cond);

Yes, it is possible. Just use arrays to express the columns that should be matched:
$this->belongsTo('Things', [
'bindingKey' => ['key1', 'ke2'],
'foreignKey' => ['fk1', 'fk2']
]);
That will match key1 = fk1 and key2 = fk2

Related

Cakephp 4: FIND_IN_SET not returning result

I have an array, $submenus, in my app that I implode to a delimited string:
$subs = implode(',', $submenus);
The string will look something like this: 'ml_,nc_,msr_'. These values are stored in a field called group_prefix in my submenus table. Each submenu row has a unique group_prefix.
The following code builds menus and submenus from a database:
$menus = $this->Menus->find('all', [
'order' => ['Menus.display_order ASC'],
'conditions' => $conditions,
'contain' => [
'Submenus' => [
'conditions' => [
'Submenus.status' => 1,
'FIND_IN_SET("' . $subs . '", Submenus.group_prefix)'
],
]
]
]);
$this->set('menus', $menus);
It works fine until I add the FIND_IN_SET condition on Submenus. When I do, I get no submenus returned, just the main menus. Debug confirms that the string is formatted propery. Doesn't error out, I just get no resultset.
When I run the submenus query in MySQL, it works.
set #prefixes = 'ml_,nc_,msr_';
SELECT `id`, `name` FROM `submenus` WHERE `status` = 1 AND FIND_IN_SET(`submenus`.`group_prefix`, #prefixes);
+----+---------------------------+
| id | name |
+----+---------------------------+
| 4 | Mission Lessons Module |
| 5 | MSR Module |
| 8 | Work Authorization Module |
+----+---------------------------+
What am I missing?
Answer was to reverse the order of arguments in FIND_IN_SET.

CakePHP : Get Table with many conditions

I have a very specific problem. Even with the great CakePHP doc, I still don't know how to fix my pb.
I'm currently web developping using the CakePHP framework. Here is my situation :
I have a Table "TableA" which contains parameters "name", "type"(1 to 6) and "state"(OK and NOT OK) . What I want is getting all the Table lines which are type 5 OR 6 and which have not a same name line with "state" OK.
There are different lines of the table which have the same "name". I'm interesting to the lines from the same name where there is no OK state.
For example, there are :
name : example1 state : NOT OK
name : example1 state : NOT OK
name : example1 state : NOT OK
And there is no example1 with the state OK and this is this kind of line I want to get.
I would like to do this with the cakePHP syntax, with conditions in the TableRegistry::get function.
Thanks for helping. Waiting for your return.
PS:
What I achieved now is not the best solution :
$tablea_NOTOK = TableRegistry::get("TableA")->find('all', array(
'conditions' => array(
'OR' => array(
array('TableA.type' => 5),
array('TableA.type' => 6),
),
'Etudes.state' => 'NOT OK'
)
));
$this->set(compact('tablea_NOTOK'));
$tablea_OK = TableRegistry::get("TableA")->find('all', array(
'conditions' => array(
'OR' => array(
array('TableA.type' => 5),
array('TableA.type' => 6),
),
'Etudes.state' => 'OK'
)
));
$this->set(compact('tablea_OK'));
And then in my view, i compared each line of the tablea_OK with the tablea_NOTOK. But there is a lot of data so the code is not perfect and slow
You may consider creating a view table in your database which holds the combination of data needed. Since the data will all be from a single table, you wouldn't need to loop through the data and compare it.
I don't know all your table relationships, but I made a simple table with these fields and data:
id name type state
1 Harry 5 OK
2 Harry 6 NOT OKAY
3 Harry 6 NOT OKAY
4 John 5 NOT OKAY
Then I wrote a query which would group by name and count the state values:
SELECT `name`, `type`, `state`,
(SELECT COUNT(state) FROM TableA as TableA1 WHERE `state` = 'OK' AND TableA.name = TableA1.name) as okay_count,
(SELECT COUNT(state) FROM TableA as TableA2 WHERE `state` = 'NOT OKAY' AND TableA.name = TableA2.name) as not_okay_count
FROM TableA
GROUP BY name;
The results look like this:
name type state okay_count not_okay_count
Harry 5 OK 1 2
John 5 NOT OKAY 0 1
You can adjust the query as needed and create your database view table and then call that in CakePHP.
$my_view_table = TableRegistry::get("MyViewTable")->find('all');
You can learn more about MySQL view tables here

Fetching row data as column names and set as boolean if exists or not

I have access to a database similar to this:
users:
id | name
====|========
1 | Tom
2 | Dick
3 | Harry
4 | Sally
exlusions:
user_id | exclusion
========|==============
1 | French only
3 | No magazine
4 | English only
1 | No magazine
Is it possible to query the database to get a result like this?
$users = [
[
'id' => 1,
'name' => 'Tom',
'english_only' => false, // unset or null would be okay
'french_only' => true,
'no_magazine' => true,
],
// . . .
];
I've been playing around with things like GROUP_CONCAT, PIVOT, and other examples and can't figure out how or if any of it applies.
SQL Fiddle -- thought I could modify this to match my scenario. I didn't get all that far, and as you can see I have no idea what I am doing...
You can use IF in your select to make your 3 columns out of the 1 based on their values.
SELECT id, name,
IF(exclusion='English only',true,false) as english_only
IF(exclusion='French only',true,false) as french_only
IF(exclusion='No magazine',true,false) as no_magazine
FROM users, exclusions
WHERE users.id=exclusions.user_id
I started with #RightClick's answer, but had to tweak it a bit for my SQL server.
SELECT User.id, User.name,
CASE WHEN Ex.exclusion = 'English Only' THEN 1 ELSE 0 END as english_only,
CASE WHEN Ex.exclusion = 'French Only' THEN 1 ELSE 0 END as french_only,
CASE WHEN Ex.exclusion = 'No magazine' THEN 1 ELSE 0 END as no_magazine
FROM users as User
LEFT JOIN exclusions Ex on User.id = Ex.user_id;
So much simpler than what I thought it was going to be after googling and searching SO all day...

Merge meta table with main table

I have two tables, a main one, and one that supports the main table, very very similar to what wordpress has, posts and posts_meta.
Main table:
id
title,
content
id | title | content
1 | one | content one
2 | two | content two
Meta table:
id
item_id
key
value
id | item_id | key | value
1 | 1 | template | single
2 | 1 | group | top
1 | 2 | template | page
2 | 2 | group | bottom
And my goal is, in the end, have an array with the data from the main table, merged with the meta table. example:
$data = array(
array(
'id' => 1,
'title' => 'one',
'content' => 'content one',
'template' => 'single',
'group' => 'top'
),
array(
'id' => 2,
'title' => 'two',
'content' => 'content two',
'template' => 'page',
'group' => 'bottom'
)
);
What is the best way to achieve this in a way that preforms good?
I am using PDO to connect to my database, and how Im doing right now is, I first query the data on the first table, and then for each result, i query the meta table, I use prepared statements for this, since it's suposed to be fast, but even so, it's harming the performance of my script.
Thank you
Instead of querying meta table for each result from first query
you should extract the ids from the first result:
$rows = q('SELECT * FROM posts');
$byIds = [];
foreach ($rows as &$row)
{
$byIds[$row['id']] =& $row;
}
and run second query:
$rows2 = q('SELECT * FROM posts_meta WHERE item_id IN (' . implode(',', array_keys($byIds)) . ')');
Then loop the results in PHP and merge with first query results.
foreach ($rows2 as $row2)
{
$byIds[$row2['item_id']][$row2['key']] = $row2['value'];
}
You have your merged results in $rows variable now:
var_dump($rows);
This way you will have only 2 db requests.
Please note that i have used $byIds as array of references so i dont have to search row with specific id in second loop. This way order of elements in $rows are preserved.

CakePHP Many to One - single table

I have a Members table:
Members(id, name, gender, head_id)
What I want is to make family relationships based around the head of household (who is a member).
So its like: One member belongs to one Family (defined by a member)
Maybe I could split it into 2 tables, a Members and Families table:
Families(id, head_id)
and the Members table would have a family_id instead of head_id.
The main problem would be on adding new members and modifying the relationships.
EDIT:
All the answers were great.
I ended up doing it manually. Dave's solution is what I was looking for, but didn't workout exactly the way I was hoping.
See "Multiple Relations to the Same Model"
"It is also possible to create self associations as shown below:"
class Post extends AppModel {
public $belongsTo = array(
'Parent' => array(
'className' => 'Post',
'foreignKey' => 'parent_id'
)
);
public $hasMany = array(
'Children' => array(
'className' => 'Post',
'foreignKey' => 'parent_id'
)
);
}
From a Database point of view, I consider you should have 3 tables:
persons (id, passport_number, name, dob, ...)
families (id, father_id, mother_id, surname, number_of_members,... )
families_persons (family_id, person_id)
A family is defined as the union of two persons and might have some other common fields such as surname.
Anyway, if you do it in your way, you can do it with one only table. (father with head_id set to 0, and the rest of the family members with head_id referring to his id.
In case you want to use two tables, controllers can use more than one Model, so it is not a problem to deal with more table in the save action.
You should have two tables. That is the best way, and will offer you the most flexibility and ease of use within your application.
Database Tables
// families
id | name
------------------------------
1 | Smith
2 | Jones
3 | Davis
// members table
id | family_id | name | gender
-----------------------------------
1 | 2 | James | M
2 | 3 | Christine | F
3 | 1 | David | M
4 | 2 | Mark | M
5 | 1 | Simon | M
6 | 1 | Lucy | F
CakePHP Models
Then you just need to define your models so they have the correct relationships.
// app/Model/Family.php
class Family extends AppModel {
public $hasMany = array('Member');
}
// app/Model/Member.php
class Member extends AppModel {
public $belongsTo = array('Family');
}
Then you can retrieve your families like this:
CakePHP Controller
// Find all members that belong to Family 1
$family = $this->Member->find('all', array(
'conditions' => array('family_id' => 1)
));
OR
// Find Family 1 and get all its members
$family = $this->Family->find('first', array(
'conditions' => array('family_id' => 1),
'contain' => array('Member')
));
You shouldn't have any problems with adding new members or modifying the relationships, like you are worried about, but if you run into any specific problems we can likely help you. This kind of model relationship is extremely common.

Categories