Union select with CakePHP - php

This is a two part question. I found something similar, but it was more complicated with more tables and used joins making it much more complex and difficult to translate to my more simplified situation. Also, it doesn't cover the second part of my question.
This takes place in my OrdersController. I have also have ArchivedordersControler and ArchivedOrder model
I'm trying to search for an email address in two different tables(orders and archived_orders). I don't need to join anything (at least I don't think I have to). Both tables have the exact same structure, one is just for archived values.
With MySQL I'd just do something like
select * from orders where orders.email = '$email'
Union
select * from archived_orders where archived_orders.email = '$email'
How can I add some sort of identifier to know which table it was selected from? The email can appear in both tables but the options displayed based on which table it was pulled from will be different.

You'll want to create a Model for both tables, Order and ArchivedOrder. That way you can easily find the data you are looking for in both tables by:
// From OrdersController
$this->Order->find('first', array('conditions' => array('email' => $email)));
// From ArchivedOrdersController
$this->ArchivedOrder->find('first', array('conditions' => array('email' => $email)));
If you want to fetch the archived data from the original OrdersController, you can load the model from there as well, like:
$this->loadModel('ArchivedOrder');
$this->ArchivedOrder->find('first', array('conditions' => array('email' => $email)));
That way, you don't need a separate controller for it. It will return the data as an array that looks like:
array(
'Order' => array(
'id' => 12,
'email' => 'customer#example.com'
// And other data...
)
)
So from the Order you can tell it was selected from the original table. Otherwise, it will be ArchivedOrder.

Related

cakephp - get results from two unrelated tables

I've been googling this for a while but to no avail. I'm finding the possible answers confusing and hoped someone could clear it up for me.
I've two tables (tasks and installs) which contain similar data, but not the same, and there's no relationship between the two tables, other than the fact they both belong to the same branch. So for example:
Tasks Table
id
branch_id
task_name
to_be_billed
created
Installs Table
id
branch_id
install_details
to_be_billed
created
I'm trying to figure out how to get a result set which would show each record from either table, arranged by date created order and only where the 'to_be_billed' column is '1'.
Can anyone give me some pointers please?
Thanks
I'm assuming that you want to get the results using the branch_id and these two tables (Tasks and Install) have some relationship with the BranchTable.
I'm also assuming the Tasks and Installs Table's have multiple records for a Branch.
BranchTable->find()
->contain([
'Tasks' => [
'sort' => ['Tasks.created' => 'ASC']
]
])
->contain([
'Installs' => [
'sort' => ['Installs.created' => 'ASC']
]
])
->matching('Tasks', function ($q){
return $q->andWhere(['Tasks.to_be_billed' => 1]);
})
->matching('Installs', function ($q){
return $q->andWhere(['Installs.to_be_billed' => 1]);
})
->where(['Branch.id' => $foo]);
If your doubt does not use these assumptions let me know.
If you are trying to get the data using one DB query then you would need to use the UNION operator.
In that case you would need these two queries to have the same columns, so for example:
select
id,
branch_id,
task_name,
NULL as install_details,
'task' as type,
to_be_billed,
created
from
tasks_table
UNION
select
id,
branch_id,
NULL as task_name,
install_details,
'install' as type,
to_be_billed,
created
from
install_table
but that's a rather dirty solution.
If I knew what exactly you are trying to achieve, maybe I could suggest a better answer.

select sql many-to-many relationship into nested php object

problem
I have two data tables SEQUENCES and ORGANISMS whose many-to-many-relationship is mappend in the table SOURCES. There is also a 1-m relationshipt between SOURCES and ENTRIES. I will append a detailed structure.
What i want to achieve, is the display of all sequences with all associated organisms and entries, where a condition within the sequences table is met. I have some ideas on how to achieve this, but i need the solution with the best performance, as each of these contains 50k+ entries.
idea one
Select all organisms that belong to the same sequence as a concatenated string in sql, and split it in PHP. I have no idea though, how to do the concatenation in SQL.
idea two
select same sequences with different organisms as distinct records, order by organism, and join them later in php. though this somehow feels just wrong.
idea three
use views. ANY idea on this one appreciated
structure
SEQUENCES
SEQUENCE_ID
DESCRIPTION
ORGANISMS
ORGANISM_ID
NAME
SOURCES
SOURCE_ID
SEQUENCE_ID FK to SEQUENCES.SEQUENCE_ID
ORGANISM_ID FK to ORGANISMS.ORGANISM_ID
ENTRIES
SOURCE_ID FK to SOURCES.SOURCE_ID
ENTRY_VALUE
desired outcome
array(
array(
"SEQUENCE_ID" => 4,
"DESCRIPTION" => "Some sequence",
"SOURCES" => array(
array(
"ORGANISM_ID" => 562,
"ORGANISM_NAME" => "Escherichia coli",
"ENTRIES" => array(
"some entry",
"some other entry"
),
array(
"ORGANISM_ID" => 402764,
"ORGANISM_NAME" => "Aranicola sp. EP18",
"ENTRIES" => array()
)
)
),
array(
"SEQUENCE_ID" => 5,
.....
)
)
PHP5 and FIREBIRD2.5.1
You can't fetch a nested array like that directly from a flat table structure. But if I get you right, what you want to do is not that hard to achieve.
I don't understand why you would concatenate things and then split them again, that's hard to maintain and probably slow.
I see two approaches here:
Fetch everything at once as flat table using JOIN and loop through it in PHP. This approach creates a lot of duplication but it's fast because you can fetch all data in one query and then process it with PHP.
Fetch every entity separately, loop and fetch the next hierarchy level as you go. This approach will be slower. It takes complexity away from the SQL query and doesn't fetch redunant data. It also gives you more freedom as to how you loop through your data and what you do with it.
Alternatively you might want to actually store hierarchical data in a no-sql way, where you could already store the array structure you mentioned.

Choosing field names while finding results from two tables

I have two tables (which happen to be in two different databases). "clients" and "domains", clients can have multiple domains.
This is the code i am using:
$this->Domain->find('all', array(
'order' => 'domain ASC',
'fields' => array(
'Domain.id',
'Domain.domain',
'Server.name',
'Client.id',
'Client.name'
)
));
When i return all the fields by not using the 'fields' => array() everything works fine, as soon as i ask for specific fields, it says:
SQL Error: 1054: Unknown column
'Client.id' in 'field list'
Everything also works fine if i just remove the two Client columns (The Client model is the only model which is on another database.
If clients hasMany domains, So your models should be called like $this->Domain->find('all'); explicitly passing fields Client.id will show error, as its not the part of domains table, enable sql dumping using debug=2 and see how queries are being run.
You models should be
// in client.php model - having structure - id, name
$hasMany = 'Domain';
// in domain.php model - having structure - id, name, client_id
$belongsTo = 'Client';
This should work like this
$this->Domain->recursive = 1;
$data = $this->Domain->find('all');
// $data = Array ( 'Domain' => ********, 'Client' => ****** )
If your two tables are in different databases, you're really making your life difficult. AFAIK, Cake doesn't support joining two tables (or enforcing relationships) between two different databases. Why do you have the client table in a separate db?
If you can't move your table, I think you're going to have to write some custom code inside your Domain model, so that it will use the (default) db connection string for domains, but will instantiate and connect another db resource to the other database. See http://bakery.cakephp.org/articles/doze/2010/03/12/use-multiple-databases-in-one-app-based-on-requested-url for how to do it -- skip ahead to the section entitled, "Select Correct Database Dynamically".
HTH,
Travis

Why is CakePHP duplicating my queries? (this is not the same "too many queries" problem that is usually asked)

I've defined these relationships in my models:
Lead hasMany Job
Job HABTM Employee
Job HABTM Truck
I'm trying to do a find('all') from my Truck model, and limit the results to:
All Trucks,
all jobs associated with those trucks that have a certain pickup date,
the employees assigned to those jobs,
and the lead associated with the job.
Here is my find operation:
// app/models/truck.php
$this->find('all', array(
'contain' => array(
'Job' => array(
'Employee',
'Lead',
'conditions' => array(
'Job.pickup_date' => $date
)
)
)
));
For some reason, Cake does the query to find Employees TWICE. This leads to having all employees represented two times for each job. Here is the SQL dump:
SELECT `Truck`.`id`, `Truck`.`truck_number`
FROM `trucks` AS `Truck`
WHERE 1 = 1;
SELECT `Job`.`id`, `Job`.`lead_id`, `Job`.`city`,
`JobsTruck`.`id`, `JobsTruck`.`job_id`, `JobsTruck`.`truck_id`
FROM `jobs` AS `Job`
JOIN `jobs_trucks` AS `JobsTruck` ON (`JobsTruck`.`truck_id` IN (2, 3)
AND `JobsTruck`.`job_id` = `Job`.`id`)
WHERE `Job`.`pickup_date` = '2010-10-06'
SELECT `Lead`.`id`, `Lead`.`name`, `Lead`.`created` FROM `leads` AS `Lead`
WHERE `Lead`.`id` = 4
SELECT `Employee`.`id`, `Employee`.`name`, `Employee`.`created`,
`EmployeesJob`.`id`, `EmployeesJob`.`employee_id`,
`EmployeesJob`.`job_id`
FROM `employees` AS `Employee`
JOIN `employees_jobs` AS `EmployeesJob`
ON (
`EmployeesJob`.`job_id` = 1 AND
`EmployeesJob`.`employee_id` = `Employee`.`id`
)
SELECT `Lead`.`id`, `Lead`.`name`, `Lead`.`created` FROM `leads` AS `Lead`
WHERE `Lead`.`id` = 4
SELECT `Employee`.`id`, `Employee`.`name`, `Employee`.`created`,
`EmployeesJob`.`id`, `EmployeesJob`.`employee_id`,
`EmployeesJob`.`job_id`
FROM `employees` AS `Employee`
JOIN `employees_jobs` AS `EmployeesJob`
ON (
`EmployeesJob`.`job_id` = 1 AND
`EmployeesJob`.`employee_id` = `Employee`.`id`
)
Notice that the last two queries are duplicates. Did I do something wrong that I'm missing?
UPDATE
It seems Cake sends a duplicate query for every truck. Now that I have 15 records in the trucks table, the queries to leads and employees are duplicated 15 times each.
I don't know why there are two queries duplicated but maybe this behavior can help:
https://github.com/Terr/linkable
UPDATE
This kind of problem is well known:
Duplicate Queries in
MODEL->HABTM->HABTM->HasMany
relationship
Duplicate Queries Problem with
Containable
Ticket
There is nothing to join the found Job/s to a specific Truck.
(I hope my explanation isn't too hard to understand, but CakePHP can be that way sometimes! imho)
The Jobs are being attributed to the Trucks in an almost arbitrary way (my memory of Cake is that this can happen); the nature of the HABTM call attaches the Job/s to each of the 15 Trucks. This seems to be the current process from my point of view;
Get all trucks.
Get all jobs for those trucks where the date is x.
[Problem 1] Attach the found Jobs to each Truck (that is your 15 trucks), but attached to every Truck.
[Problem 2] Get all Leads/Employees related to that Job, again for each Truck.
Problem 1: The source of the issue. You can see in the second query (SELECT Job...), where it uses the correct truck_id's in the ON statement, but Cake cannot "join" these Jobs back into the right Truck, because it is a different query! So it joins the found jobs to each Truck.
Problem 2: This is the 'real' problem, for Cake does not construct long JOIN statements, so there is no way to find the Employees/Leads only for those Trucks that you want. That is why it finds them for each Truck, this is because you are doing a FindAll on Truck.
I hope that makes sense. You need to do a find all on Jobs, since that is the 'center' of the query (pickup_date).
$this->loadModel('Job');
$whatev = $this->Job->find('all', array(
'contain' => array(
'Job' => array(
'Truck',
'Employee',
'Lead',
'conditions' => array(
'Job.pickup_date' => $date
)
)
)
));
CakePHP queries really only work when you find one/all of a certain model, it is better to start in the middle and work either side if you have a double HABTM relationship. If you wish to 'sort' by Truck, then you might have to write you're own query (model method) to accomplish the task yourself. In raw SQL this query may be easy, in abstracted PHP super-cake-ness this is difficult for CakePHP to allow.
Happy baking, as they say!
You want LEFT JOIN instead of JOIN.
Don't ask me how to do it in cakephp, i'm happy my code currently works. ^^

construct a complex SQL query (or queries)

As part of a larger web-app (using CakePHP), I'm putting together a simple blog system. The relationships are exceedingly simple: each User has a Blog, which has many Entries, which have many Comments.
An element I'd like to incorporate is a list of "Popular Entries." Popular Entries have been defined as those with the most Comments in the last month, and ultimately they need to be ordered by the number of recent Comments.
Ideally, I'd like the solution to stay within Cake's Model data-retrieval apparatus (Model->find(), etc.), but I'm not sanguine about this.
Anyone have a clever/elegant solution? I'm steeling myself for some wild SQL hacking to make this work...
Heh, I was just about to come back with essentially the same answer (using Cake's Model::find):
$this->loadModel('Comment');
$this->Comment->find( 'all', array(
'fields' => array('COUNT(Comment.id) AS popularCount'),
'conditions' => array(
'Comment.created >' => strtotime('-1 month')
),
'group' => 'Comment.blog_post_id',
'order' => 'popularCount DESC',
'contain' => array(
'Entry' => array(
'fields' => array( 'Entry.title' )
)
)
));
It's not perfect, but it works and can be improved on.
I made an additional improvement, using the Containable behaviour to extract the Entry data instead of the Comment data.
Shouldn't be too bad, you just need a group by (this is off the type of my head, so forgive syntax errors):
SELECT entry-id, count(id) AS c
FROM comment
WHERE comment.createdate >= DATE_SUB(CURDATE(), INTERVAL 1 MONTH)
GROUP BY entry-id
ORDER BY c DESC
If you weren't fussed about the time sensitive nature of the comments, you could make use of CakePHP's counterCache functionality by adding a "comment_count" field to the entries table, configuring the counterCache key of the Comment belongsTo Entry association with this field, then call find() on the Entry model.
You probably want a WHERE clause to get just last 30 days comments:
SELECT entry-id, count(id) AS c
FROM comment
WHERE comment_date + 30 >= sysdate
GROUP BY entry-id
ORDER BY c DESC

Categories