Search code examples
ruby-on-railspostgresqlactiverecordrelational-division

Finding objects only where all of their has_many collections have a value for a specific column


I have an Album object that has many Tracks. Tracks have a youtube_uid column. I'd like to query albums where all of their tracks' youtube_uids are present. I know the technique to find albums with tracks where there's at least one track with a youtube_uid:

Album.left_outer_joins(:tracks).where.not(tracks: { youtube_uid: nil })

What would be the ideal query to find an album where every one of its' tracks has a youtube_uid?


Solution

  • As I understand your question, you want to find all albums that do not have a track with missing (empty) youtube-uid. So afaik you need a NOT EXISTS query.

    In sql I would write something like

     select * from albums a
     where not exists (select * from tracks where album_id = a.id and youtube_uid is null) 
    

    So how do we best translate this to activerecord? I see two possibilities:

     sql = <<-SQL
       select * from albums a
       where not exists (select * from tracks where album_id = a.id and youtube_uid is null) 
     SQL
    
     Album.find_by_sql(sql) 
    

    while this works, and for me being pretty at home in SQL, this feels fine, it is not very "rails-like", so can we improve this?

    There is a shorter form:

    Album.where("not exists (select * from tracks where album_id = albums.id and youtube_uid is null")
    

    but this still feels a little too verbose. Luckily there exists a more rails-like way. In rails 4 you can write:

      Album.where(Track.where("album_id = albums.id").where(youtube_uid: nil).exists.not)
    

    In rails 5/6 this is no longer possible, and you have to write:

      Album.where(Track.where("album_id = albums.id").where(youtube_uid: nil).arel.exists.not)
    

    you can easily verify this generates the good sql by adding to_sql at the end.