Select All Events with Event->Schedule->Date between start and end dates in CakePHP

后端 未结 4 1963
野性不改
野性不改 2020-12-22 03:16

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

4条回答
  •  礼貌的吻别
    2020-12-22 04:01

    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:

    1. Retrieve all rows from table 'main'

      SELECT * FROM main
      
    2. 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])
      
    3. 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:

    1. 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).

    1. On the original results, there item D from Main is missing! That's because we used an 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:

      1. 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.
      2. If any of the tables you JOINed 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).
      3. Prevent Cake from running extra queries to get data we already retrieved on our main query. This is done by unbinding the proper models before running the find.

      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.

提交回复
热议问题