views:

334

answers:

3

I have 3 models: Books, Notifications, and NotificationTypes. Books have notifications as the Notification model has a book_id. Notifications have one notification_type as the Notification model has one notification_type_id

I want all Books created between date1 and date2

books_set1 = Book.find :all, :conditions => ["created_at <= ? AND show_time >= ?", max_date, min_date]

But I do not want books that have notifications of notification.notification_type_id = 1 and ideally i would like to say this by referring to notification_type.name so i would not want books that have notifications of notification.notification_type.name = 'type1'

If there has been a notification of type1 created for a book already, I do not want it returned in the set (because i am going to create notifications of that type with the returned set).

I am not sure if there is a way to do this in one query, I am thinking I need 2 queries with and INTERSECT - the first I already included in this post and the second i am not sure about. But in pseudo-code, i think this is what it needs to do:

notification_type_id = Notification.find_by_name('type1')
get all notifications where notification_id = notification_type_id
set2 = get the associated book set from the notification set (since each notification has one book)

then i do set1 - set2

UPDATE

Thanks to some help I have written two queries that get me the desired results. I would love for this to be in 1 query if anyone knows how to do it:

books_in_range = Book.find :all, :conditions => ["created_at <= ? AND created_at >= ?", max_date, min_date]
books_without_these_notifications = Book.find(:all, :joins => { :notifications => :notification_type }, :conditions => ["notification_types.name = ?","type1"] )
books_to_consider = books_in_range - books_without_these_reminders

Again, the idea is to get all books that do not have a notification of type1 created and fall within a specific date range.

A: 
SELECT books.* FROM books WHERE books.created_at <= ? AND books.show_time >= ? AND 
  notifications.name <> 'type1'
LEFT OUTER JOIN notifications ON notifications.book_id = books.id AND 
  notification.name = 'type1'

That would work, I would imagine.

Ben Hughes
+3  A: 

The following should work:

Book.scoped(:conditions => ["created_at <= ? AND show_time >= ?", max_date,
  min_date]).find(:all, :joins => { :notifications => :notification_types },
  :conditions => ["notification_type.name <> ?", "type1"])

Extract some of the arguments into named_scopes or local variables for extra clarity.

Updated to reflect the negation.

molf
hmmm... trying to disect this. one issue is a book doesn't have many reminders (it does theoretically, but i made users have many reminders since that is how i will generally access them). also wouldn't this return all books that HAVE notification type1? i want to end up with books that do not have notification type1
Tony
reminders=notifications
Tony
@Tony, updated. Sorry, I missed the negation.
molf
i think you meant to put an open bracket before the :conditions? looks like there is a syntax error....still testing the query but this is starting to make a lot of sense
Tony
Will this not exclude notifications of type1 from the associated notifications but still return all books?
Shadwell
@Shadwell, ah, yes. This changed in Rails 2 when :include no longer generates a JOIN, but a second query. We should use :joins instead.
molf
i haven't gotten this to work yet in the console...still workin on it. but it looks like it will do what i want. im assuming the second find operates on all books that satisfy the date conditions. then the actual operation is to get all books from that set where notification_type.name != 'type1'. just guessing at this point
Tony
@molf I thought I was going mad then - missed the edit to joins. I think you then need to add :select => 'distinct books.*' to ensure you don't return duplicates.However, does it return books that have no notifications at all?
Shadwell
still working on getting this to work but yes, it should return books that have no notifications at all...because they don't have notification of type1.
Tony
just a quick update...i still think there is a syntax error. should only have one parentheses at the end, no closing bracket. i also keep getting an error "Association named 'notification' was not found"...i did add "has_many :notifications" to Book model which did not solve the problem. still investigating
Tony
@Tony: fixed the syntax error, and the join arguments should probably be plural (updated).
molf
@molf thank you. the plural arguments are not the issue.
Tony
@molf the error was two fold on my part, i forgot a belongs_to statement in notifications, and i forgot to reload the console to load in the new model relationships...still testing the query. thanks!
Tony
@molf ok finally got this working, but it doesn't return me the books that have no notifications at all. i could do Book.find(:all) - some version of your query but i worry that it will be very inefficient. @Shadwell, you were right. any idea on how to include the Books that have no notifications at all? also, the correct query was this: Book.scoped(:conditions => ["created_at <= ? AND show_time >= ?", max_date, min_date]).find(:all, :joins => { :notifications => :notification_type }, :conditions => ["notification_types.name <> ?", "type1"]) ...notice the changes in plurality.
Tony
hmmm...also if a book has notification of type1 and notification of type2, this query will still grab it. it should not grab it since notification of type1 already exists for this book
Tony
+2  A: 

You could do it with NOT EXISTS but I'm not sure how efficient it would be:

Book.find(
  :all, 
  :conditions => 
    ['created_at <= ? AND show_time >= ? AND 
      NOT EXISTS 
        (SELECT * FROM notifications 
         INNER JOIN notification_types ON notifications.notification_type_id = notification_types.id 
         WHERE notification_types.name = ? AND notifications.book_id = books.id)',
     max_date, min_date, 'type1'
    ])

You might be able to come at it from a different direction and keep track of those which have had a notification of type1 sent out by adding a boolean to books or some other object joined directly to books. Then your query could be:

Book.find(:all, 
  :conditions => 
    ['created_at <= ? AND show_time >= ? AND 
      type1_notification_sent = ?', 
     max_date, min_date, false
    ])
Shadwell
i would but there may be like 30 types of notifications by the end of this project
Tony
Fair enough. Worth a try!
Shadwell