find all parent records where all child records have a given value (but not just some child records)

跟風遠走 提交于 2019-11-30 16:18:48

问题


An event has many participants. A participant has a field of "status".

class Event < ActiveRecord::Base
  has_many :participants
end

class Participant < ActiveRecord::Base
  belongs_to :event
end

I need to find all events except the following ones: events where every one of its participants has a status of 'present'.

I can find all events where some of its participants have a status of 'present' with the following AR code:

Event.joins(:participants).where
 .not(participants: {status: 'present'})
  .select("events.id, count(*)")
   .group("participants.event_id")
    .having("count(*) > 0")

That creates SQL like:

SELECT events.id, participants.status as status, count(*) 
FROM `events` INNER JOIN `participants` 
ON `participants`.`event_id` = `events`.`id` 
WHERE (`participants`.`status` != 'present') 
GROUP BY participants.event_id HAVING count(*) > 0

This almost works. The problem is that if one of the participant's rows (within the scope of @participant.event_id) has a status of something other like "away", the event will still get fetched, because at least some of the sibling records are of a status equal to something other than "present".

I need to ensure that I am filtering out every event record with all participants of a status of "present".

I am open to ActiveRecord or SQL solutions.


回答1:


If I get it right your problem can be classified as relational division. There are basically two ways to approach it:

1a) Forall x : p(x)

which in SQL has to be translated to:

1b) NOT Exists x : NOT p(x)

For your problem that would be something like:

SELECT e.* 
FROM events e
WHERE NOT EXISTS (
    SELECT 1 
    FROM PARTICIPANTS p
    WHERE p.status <> 'present'
      AND p.event_id = e.event_id
)

i.e. any given event where there does not exist a participant such that status != 'present'

The other principle way of doing it is to compare the number of participants with the number of participants with status present

SELECT e.id 
FROM events e
JOIN participants p 
    ON p.event_id = e.id 
GROUP BY e.event_id 
HAVING count(*) = count( CASE WHEN p.status = 'present' then 1 end )

Both solutions are untested so there might be errors in there, but it should give you a start




回答2:


I really like Lennarts examples

I made a simple modification to the first example which will only return EVENT parent records which have Participation Child records, and is much faster at processing than finding the counts for each.

SELECT e.* 
FROM events e
INNER JOIN participants p ON p.event_id = e.event_id
WHERE NOT EXISTS (
  SELECT 1 
  FROM PARTICIPANTS p
  WHERE p.status <> 'present'
  AND p.event_id = e.event_id
)
GROUP BY e.event_id



回答3:


What if you try to come at the query by finding the ids of the events where a person has a status other than "present" and then find all the unique event where that is the case?

unique_event_ids = Participant.where.not(status: "present").pluck(:event_id).uniq
events_you_want = Event.where(unique_event_ids)



回答4:


You could use a subselect to filter out events that have participants that aren't present. It's probably not the most efficient way to do it, though.

SELECT events.id, participants.status as status, count(*) 
FROM `events` INNER JOIN `participants` 
ON `participants`.`event_id` = `events`.`id` 
WHERE (`participants`.`status` != 'present')
AND events.id NOT IN (SELECT DISTINCT event_id FROM participants WHERE participants.status != 'present')
GROUP BY participants.event_id HAVING count(*) > 0



回答5:


I need a solution for this and the other answers didn't work for me, but here is my solution. I wrote two functions one to get the total number of child records and another to get the total number of child records that meet a particular condition in my case (true). Then I compared both functions. If the resulting arithmetic/evaluation equal zero it means all records meet the true criteria. Quite straightforward.

Select p.pid, p.Name, p.Group, udfn_TotalChildrenRecords(p.pid), udfn_TotalChildrenRecordsThatAreTrue(p.pid) 
From Parent AS p INNER JOIN Child AS c ON Parent.pid = child.pid
GROUP BY p.pid, p.Name, p.Group
HAVING udfn_TotalChildrenRecords(p.pid) - udfn_TotalChildrenRecordsThatAreTrue(p.pid) = 0


来源:https://stackoverflow.com/questions/23707292/find-all-parent-records-where-all-child-records-have-a-given-value-but-not-just

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