How to select only those records that have at least one associated record?

痞子三分冷 提交于 2019-12-12 03:24:09

问题


I have the following associations: Orders belongsTo Sites

I execute the following query:

$sites = $this->Sites->find('all')
->select(['id', 'name'])
->contain([
        'Users' => function ($q) {
                return $q->select(['id', 'owner_id', 'firstname', 'lastname']);
        },
        'Orders' => function ($q) {
                return $q->where(['status >' => 0]);
        },
        'Orders.Agplans'
])
->matching('Users', function ($q) use($owner_id)
{
    return $q->where([
        'Users.owner_id' => $owner_id
    ]);
})
->all();

But most of the sites have no orders so I get some kind of results:

(int) 100 => object(App\Model\Entity\Site) {
    'id' => (int) 7966,
    'name' => 'Site example',
    'orders' => [],
    'users' => [
        (int) 0 => object(App\Model\Entity\User) {

    ...

Is it possible to specify in the query that I only want sites with not empty orders?


回答1:


Since what you have there is a hasMany association, you'll have to take measures additionally to containing Orders.

Use an inner join

You could use an INNER join, that would select only those rows where a matching order is present. For filtering purposes, this can easily be done using Query::innerJoinWith(), which is similar to Query::matching(), but it won't load any results.

$sites = $this->Sites
    ->find('all')
    ->select(['id', 'name'])

    // ...

    ->innerJoinWith('Orders', function ($q) {
        return $q->where(['Orders.status >' => 0]);
    })
    ->group(['Sites.id']) // may or may not be necessary
    ->all();

Use matching like you already do

You are already using Query::matching() for Users, so I'd assume that you know what it does... filtering. Adding a further matching for Orders is an option too, it will however fetch additional data.

$sites = $this->Sites
    ->find('all')
    ->select(['id', 'name'])

    // ...

    ->matching('Orders', function ($q) {
        return $q->where(['Orders.status >' => 0]);
    })
    ->group(['Sites.id']) // may or may not be necessary
    ->all();

Last but not least, use a counter cache

Using a properly indexed counter cache is probably the best performing option, as filtering would be done via a column in the same table.

You'd define it in your Orders model with the proper conditions, and then just use where() in your query, like

$this->addBehavior('CounterCache', [
    'Sites' => [
        'active_orders_count' => [
            'conditions' => ['Orders.status >' => 0]
        ]
    ]
]);
$sites = $this->Sites
    ->find('all')
    ->select(['id', 'name'])

    // ...

    ->where([
        'active_orders_count >' => 0
    ])
    ->all();

See also

  • Cookbook > Database Access & ORM > Query Builder > Using innerJoinWith
  • Cookbook > Database Access & ORM > Query Builder > Filtering by Associated Data
  • Cookbook > Database Access & ORM > Behaviors > Counter Cache


来源:https://stackoverflow.com/questions/34528298/how-to-select-only-those-records-that-have-at-least-one-associated-record

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!