Trying to figure out how to build a query in CakePHP where I can select all Events that are between X and Y dates (user-entered dates).
The problem lies in that the
Dave, you have a reasonably complicated problem to solve, which requires more than basic Cake. You must understand what's going on in order to be able to solve it. I'm assuming you don't have much experience with SQL, and don't know much 'under-the-hood' Cake. So I'll try to explain the basics here.
Consider you have two tables, called 'main' and 'related':
main related id | val id | main_id | val 1 | A 1 | 1 | Foo 2 | B 2 | 1 | FooBar 3 | C 3 | 2 | Bar 4 | D 4 | 3 | BarFoo
In Cake, you'll have models Main and Related to deal with them. Main hasMany Related
, and Related belongsTo Main
. Now you do the following (from a method inside Main):
$data = $this->find('all', array(
'recursive' => 1
));
Here is what Cake will do behind the scenes:
Retrieve all rows from table 'main'
SELECT * FROM main
With the results, Cake will build an array of IDs, which will then be used to get the data for the associated model Related. This data will be fetched from MySQL using a query like this:
SELECT * FROM related WHERE main_id IN ([comma_separated_list_of_ids_here])
Finally, Cake will loop through results array from Main, and add the related data to each row as applicable. When it finishes, it returns the "decorated" array.
Sometimes, depending on the type of association, Cake will do an extra SQL query for every row retrieved for the main model. That can be really slow. The solution would be to use single query to get data from both tables, and that's what JOINs are for. The problem with that is data repetition. For example:
SELECT Main.*, Related.*
FROM main as Main
INNER JOIN related AS Related
ON Related.main_id = main.id
Results:
Main.id | Main.val | Related.id | Related.main_id | Related.val 1 | A | 1 | 1 | Foo 1 | A | 2 | 1 | FooBar 2 | B | 3 | 2 | Bar 3 | C | 4 | 3 | BarFoo
Things to notice here:
We have 2 rows for Main.id = 1. The difference between them is at Related.id and Related.val. If you remove those columns from the SELECT clause, the repetition will go away. This is very useful if you need to add conditions on the related table. For example:
SELECT DISTINCT Main.*
FROM main as Main
INNER JOIN related AS Related
ON Related.main_id = main.id
WHERE Related.val LIKE '%Foo%'
Gives:
Main.id | Main.val 1 | A 3 | C
There are actually 2 rows on related that match on our conditions (Foo and FooBar), but A shows up only once in the results beacause we didn't ask SQL to display Related.val, and also told it to ignore exact duplicates (with DISTINCT
).
INNER JOIN
, which limits the results to rows from Main that also have one or more corresponding rows on Related. If we used a LEFT JOIN
, the results would have an extra line, as below:Main.id | Main.val | Related.id | Related.main_id | Related.val 1 | A | 1 | 1 | Foo 1 | A | 2 | 1 | FooBar 2 | B | 3 | 2 | Bar 3 | C | 4 | 3 | BarFoo 4 | D | NULL | NULL | NULL
(if you need more details on INNER vs. LEFT JOINs, see here). (EDIT: link updated)
Back to the duplicates: it's easy to clean them up with a simple foreach
loop in PHP. It's simple when there are just 2 tables involved, but becomes more and more complex for every extra table you add to the query (if the new table has a one-to-many relationship with either main or related).
But you do have lots of tables and associations involved. So, the solution I suggested above is somewhat a compromise between performance and code simplicity. Let me try to explain my line of thought when I wrote it.
You need to deal with 13 tables to get all the data you want. You need to display data that comes from most of those tables, and need to filter events based on quite a few tables too.
Cake alone can't understand what you want, and will return too much data, including stuff you expected it to have filtered out.
There are some 1-n and n-n relashionships involved. If you jast add all 13 to a single query with JOINs, the result will have too many dupes, and will be unmanageable.
So I decided to try a mixed approach: start by getting a filtered list of events, with no dupes, then let Cake 'decorate' it with data from some associated models. To do that, you must:
JOIN
all tables which need conditions applied to them. This will allow us to retrieve our final list of events, considering all conditions, with a single query.JOIN
ed can cause duplicates, do not include their fields in the SELECT
clause (or Cake's fields
list). If the associations are properly setup, Cake will run an extra query later to get the associated data (since we used recursive=2
).If this is still returning fields you don't want, and such fields come from associated models, you must use Containable to tell Cake which fields you want from each of those models.
I know it might sound complicated, but you won't be able to solve this on your own unless you understand what Cake does, and how SQL works. I hope this helps.