How about the direct approach: SELECT uid FROM resource WHERE uid NOT IN (SELECT resource_uid FROM event_participant, event WHERE event_participant.event_uid = event.uid AND event.shift_uid = :shift_uid AND event.date = :event_date)
or perhaps SELECT uid FROM resource WHERE NOT EXISTS (SELECT 1 FROM event_participant, event WHERE event_participant.event_uid = event.uid AND resource_uid = resource.uid AND event.shift_uid = :shift_uid AND event.date = :event_date) Is not the "right way to do it" the one that obtains the result required, not the one that uses a "checkbox" implementation? Using the "checkbox" approach, someone might be of the opinion that the "right way to do it" is entirely within a hand-coded StartIO channel program implemented for a 3390 disk controller, and that is unlikely to be implementable on a bitty-box. --- Theory is when you know everything but nothing works. Practice is when everything works but no one knows why. Sometimes theory and practice are combined: nothing works and no one knows why. >-----Original Message----- >From: sqlite-users-boun...@sqlite.org [mailto:sqlite-users- >boun...@sqlite.org] On Behalf Of Tristan Van Berkom >Sent: Saturday, 8 November, 2014 03:17 >To: sqlite-users@sqlite.org >Subject: [sqlite] What is the best alternative to this RIGHT OUTER JOIN ? > >Hi all, > >Today I've stumbled on a situation where I think I really need to use a >RIGHT OUTER JOIN, and looking at all the examples on the internet I >could find so far, I'm not finding a way to simulate it properly using >LEFT OUTER JOINs. > >So I thought, before I commit to an inefficient alternative I would >check to see if someone on this list has an idea of how I can form an >efficient query for this. > >I'll start by highlighting some of the schema which is relevant to the >query: >~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ >CREATE TABLE shift ( > uid INTEGER PRIMARY KEY AUTOINCREMENT, > > ... name and start time/duration of a shift ... >); > >CREATE TABLE resource ( > uid INTEGER PRIMARY KEY AUTOINCREMENT, > > ... details about the resource ... >); > >CREATE TABLE event ( > uid INTEGER PRIMARY KEY AUTOINCREMENT, > date INTEGER NOT NULL, /* unix timestamp */ > shift_uid INTEGER NOT NULL REFERENCES shift (uid), > > ... other details about the event ... >); > >CREATE TABLE IF NOT EXISTS event_participant ( > id INTEGER CHECK (id > 0), > event_uid INTEGER REFERENCES event (uid) ON DELETE CASCADE, > resource_uid INTEGER REFERENCES resource (uid), > > ... other details about this participant ... > > PRIMARY KEY (event_uid, id) >); >~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ > >Now my goal here, is to pull out every resource UID which is _not_ >assigned to any event on a given date and shift UID. > >My first attempt that gets the job done looks like this: >~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ >~ Statement 1 ~ >~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ >SELECT resource.uid > >/* All resources... */ >FROM resource > >/* Subtract from this set all the booked resources */ >LEFT JOIN ( > > /* All resources which are booked on the given date / shift_uid */ > SELECT participant.resource_uid AS uid > FROM event > JOIN event_participant > AS participant > ON (participant.event_uid = event.uid) > WHERE (event.date = 1411185600 AND > event.shift_uid = 1 AND > participant.resource_uid IS NOT NULL) > >) AS booked_resource ON (booked_resource.uid = resource.uid) > >WHERE booked_resource.uid IS NULL; > >~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ EXPLAIN SAYS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ >1|0|0|SEARCH TABLE event USING INDEX event_shift_uid_idx (shift_uid=?) >(~2 rows) >1|1|1|SEARCH TABLE event_participant AS participant USING INDEX >event_participant_pk_idx (event_uid=?) (~5 rows) >0|0|0|SCAN TABLE resource (~1000000 rows) >0|1|1|SEARCH SUBQUERY 1 AS booked_resource USING AUTOMATIC COVERING >INDEX (uid=?) (~2 rows) >~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ > >Now, this is not all that bad, except that I try to avoid nesting select >statements in cases like this as it prevents the query planner from >doing anything intelligent. > > >So I made another attempt which discards the nested statement: >~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ >~ Statement 2 ~ >~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ >SELECT resource.uid > >/* All Resources */ >FROM resource > >/* Get the cartesian product of event * resources, dangerously big, > * but hopefully trimmed down by constraints found in the final WHERE > * clause > */ >JOIN event > >/* Subtract from this set all the booked resources */ >LEFT JOIN event_participant > AS participant > ON (participant.event_uid = event.uid AND > participant.resource_uid = resource.uid) >WHERE event.date = 1411185600 AND > event.shift_uid = 1 AND > participant.resource_uid IS NULL; >~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ EXPLAIN SAYS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ >0|0|1|SEARCH TABLE event USING INDEX event_shift_uid_idx (shift_uid=?) >(~2 rows) >0|1|0|SCAN TABLE resource (~1000000 rows) >0|2|2|SEARCH TABLE event_participant AS participant USING INDEX >event_participant_resource_uid_idx (resource_uid=?) (~2 rows) >~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ > >It turns out that this query is almost semantically the same as the >first, however without the nested select, EXPLAIN QUERY PLAN seems to >indicate the performance is better (the SCAN on the 'resource' table >is still present, but the extra SEARCH SUBQUERY is not gone). > >Finally, after doing some more digging, I think I've found that the >correct way to make this query should really be with RIGHT OUTER JOIN as >follows: >~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ >~ Statement 3 ~ >~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ >SELECT resource.uid > >/* All events on the date specified in the final WHERE clause */ >FROM event > >/* Join participants by indexed event key, only those with assigned > * resources > */ >JOIN event_participant > AS participant > ON (participant.event_uid = event.uid AND > participant.resource_uid IS NOT NULL) > >/* Join in all the resources which do not appear in the preceding set */ >RIGHT OUTER JOIN resource > ON (resource.uid = participant.resource_uid) >WHERE event.DATE = 1411185600 AND > event.shift_uid = 1 AND > participant.resource_uid IS NULL; >~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ > >However, unlike other simple use cases of LEFT OUTER JOIN, this one >cannot simply be inverted to get the same performance/results as >with RIGHT OUTER JOIN (inverting the statement only works in the >very simple case where you really only have 2 tables to join). > >So, in closing, I have only been doing SQL for a couple of years now >and would consider myself a 'newbie' at this - and I was wondering if >someone more enlightened could point out something I've missed. > >If it's the case that RIGHT OUTER JOIN would be the correct way but >SQLite simply doesn't offer that (and there is no way to efficiently >work around this), I will gladly except that and probably go with a >variation of 'Statement 2' above. > >I would also be interested to know if 'Statement 1' may be superior >to 'Statement 2' due to the dangerously huge cartesian product of >the 'event' and 'resource' tables. > >Thank you all for your attention :) > >Best Regards, > -Tristan > > >_______________________________________________ >sqlite-users mailing list >sqlite-users@sqlite.org >http://sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-users _______________________________________________ sqlite-users mailing list sqlite-users@sqlite.org http://sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-users