frioux / DBIx-Class-Helpers

https://metacpan.org/pod/DBIx::Class::Helpers
20 stars 38 forks source link

Add EXISTS/NOT EXISTS support #94

Closed mzealey closed 4 years ago

mzealey commented 5 years ago

Per the POD this adds $rs->exists( $subq, { join cond } ) and ->not_exists(...) support

coveralls commented 5 years ago

Coverage Status

Coverage increased (+0.02%) to 98.693% when pulling 1254666cfffbe3128cf99f4b990df8ad12b9f332 on mzealey:add-exists-subquery into ba9ab1bee7e9af1f6a849bb562ad45550f47388e on frioux:master.

frioux commented 5 years ago

Have you see https://metacpan.org/pod/DBIx::Class::Helper::ResultSet::CorrelateRelationship ?

mzealey commented 5 years ago

I had not but I don't think it provides the full exists / not exists functionality eg spanning multiple cols via join criteria?On 29 Dec 2018 17:15, fREW Schmidt notifications@github.com wrote:Have you see https://metacpan.org/pod/DBIx::Class::Helper::ResultSet::CorrelateRelationship ?

—You are receiving this because you authored the thread.Reply to this email directly, view it on GitHub, or mute the thread.

frioux commented 5 years ago

Sure, you can do multi-column relationships in DBIC; here's an example (using ::Candy and ::DWIM to shorten the definition:)

belongs_to alarm_channel => '::AlarmChannel', {
   'foreign.user'       => 'self.user',
   'foreign.site'       => 'self.serial',
   'foreign.alarm'      => 'self.alarm',
   'foreign.alarmstate' => 'self.alarmstate',
};
mzealey commented 5 years ago

@frioux Sure you can do joins like that. But lets say I want to search for items which dont't have a particular tag

SELECT *
FROM items
WHERE NOT EXISTS (SELECT 1 FROM item_tags WHERE items.id = item_tags.item_id AND some other criteria LIMIT 1)

I could do this as a left join checking for null but a) that is often much more intensive on the DB side (certainly in MySQL it can't optimize so well) and b) returns all the joined rows for items which have multiple tags which would later need filtering. I could also do this as corrolated with count and a having but again the correct way (and one which database can optimize much more simply) is to do the (NOT) EXISTS filters. If you have a join in the query the (NOT) EXISTS also allows you to reference multiple tables as well which is very powerful.

frioux commented 5 years ago

Sorry I should have given a more complete example; the following should do what you just said:

$rs->search({
   -not_exists => $rs->correlate('item_tags')
      ->search({ some other criteria }, { rows => 1 })->as_query
 })

Honestly I'm ok with your PR on the face of it, I just think it reimplements some stuff that is already implemented; Just trying to make sure I'm not missing something.

mzealey commented 5 years ago

@frioux OK I see what you mean now. I wasn't aware that could be done (both the corrolate and the -exists) and I can't see it documented anywhere as such - perhaps you could add exists/not exists to a cookbook somewhere so it is more obvious when searching? Only advantage that my code brings is that you can do dynamic joins but I'm sure there are other ways to do that and generally you shouldn't be doing that within dbic.

frioux commented 5 years ago

Yeah honestly I was scouring the docs to link you to them and couldn't find anything. I fully agree that it should be documented and found.

I actually think dynamic joins are really useful! When I was trying to find examples of where I'd used -exists I found dynamic joins in my own code because it explicitly had no relationships because it was an initial migration.

Here's an idea: update your docs (there are some parts that are no longer correct, like the alias thing) and add some explanations of -exists and -not_exists; even if we get a patch into the cookbook, more examples in relevant places tend to be useful.

mzealey commented 5 years ago

Would you be up for me writing something like ResultSet/Exists.pod based around the correlate functionality so people searching can find it more easily?

frioux commented 5 years ago

Sure!