Sunday, 16 December 2007

Complex find queries, with conditions in the associated model

I described my problem here:

http://groups.google.com/group/cake-php/browse_thread/thread/7059111f006ac694

I want to get all client records, where their associated invoiceitems
(client hasMany invoiceitems) have a particular condition.

For a while I thought this might not work, because even though the query I needed to modify would be run without the modifications, the query I wanted to do was impossible via normal SQL if I started with Clients as the first table in my FROM clause. This is because there are many invoice items for each client, so theres no 1:1 relationships for direct joining.

Instead, I tackled this from the other model, the model that belongsTo client.


/**
*returns a simple aray of Client ids and names,
*containing all clients that have invoiceitems without an invoice assigned to them.
*/
function getPendingClients()
{
$rtrnItems=array();
$this->restrict('Invoiceitem', 'Client');

$items=$this->findAll
(
array("`Invoiceitem`.`invoice_id`" => null),
$fields = array('DISTINCT Client.id', 'Client.name'),
$order = 'name'
);

if(!empty($items))
{
//simplify query results for a cleaner view
$newItems=Set::extract($items, '{n}.Client.id');
$ids = Set::extract($items, '{n}.Client.name');
$rtrnItems = array_combine($newItems, $ids);
}

return $rtrnItems;

//return $items;
}


As you can see, this uses distinct and binding to get just the data I need.

Semantically, I'd prefer to have this function working from the client model: after all, its a list of clients that I want, not invoice items!

Becuase of that I tried fiddling as suggested in the groups thread, by defining the join relationship on the fly, with code like this:


function getClientsForInvoicing()
{
/*
$this->restrict(
array('Client','Invoiceitem'=>array('conditions' => 'InvoiceItem.invoice_id IS NULL')
)
);*/

$ret= $this->find('all',
array
(
'restrict'=>
array('Client','Invoiceitem'=>
array('conditions' => 'InvoiceItem.invoice_id IS NULL')
)
)
);

return $ret;
}


With both calling restrict and using the restrict arg for find, I got different errors. Maybe my Cake version isnt ready for this new chaining of params to find, so theyre not ending up in the behaviour???
Frustrated, I gave up!! Grrr.

No more HABTMs?

I started using Cake 1.2s Bindable behavior by Mariano the other day.
It works just like it says on the tin, and will really make it easier to have fewer, simpler queries.

At the same time, Ive been looking into HABTMs.

Now, HABTMs are a bit clunky, (see http://groups.google.com/group/cake-php/msg/12598076103b0130) and before the 1.2 'with' addition, occasionally insufficient.
And I'm not blaming Cake, HABTMs aren't an easy thing to simplify, and I doubt its done much better anywhere else.

But now with the expects behaviour, what was previously a work around - i.e creating the two different relations, a hasOne and a reciprocal HasMany - might be the better solution all round.

I'll post more when I get a chance to try it again, with the expects behaviour...