Soru En son kayıtlarla sınırlı ilişkiyi ilişkilendirme ile raylar?


class User 
has_many :books

Geri dönen bir sorguya ihtiyacım var:

En son kitabı olan kullanıcılar: complete => true. Örneğin, bir kullanıcının en yeni kitabı varsa: complete => false, I sonuçta onları istemiyorum.

Şimdiye kadar sahip olduğum şey

User.joins(:books).merge(Book.where(:complete => true))

Bu umut verici bir başlangıçtır ama bana ihtiyacım olan sonucu vermez. Eklemeye çalıştım .order("created_on desc").limit(1)
Yukarıdaki sorgunun sonuna kadar, ancak daha fazla beklediğimde sadece bir sonuçla sonuçlanırım.

Teşekkürler!


25
2018-03-09 15:19


Menşei


sağ. kontrol etmeyi deniyordu: complete => true? Her kullanıcı için sadece en yeni kitap kaydında ama açıkçası işe yaramadı ... - istan


Cevaplar:


@ Rubyprince'nin ruby ​​çözümü ile gitmeyecekseniz, bu aslında ActiveRecord'un bir alt sorgu gerektirmesi nedeniyle en basit biçiminde işleyebileceğinden daha karmaşık bir DB sorgusudur. Bunu tamamen bir sorguyla nasıl yapacağım:

SELECT   users.*
FROM     users
         INNER JOIN books on books.user_id = users.id
WHERE    books.created_on = ( SELECT  MAX(books.created_on)
                              FROM    books
                              WHERE   books.user_id = users.id)
         AND books.complete = true
GROUP BY users.id

Bunu ActiveRecord'a dönüştürmek için şunu yapardım:

class User
  scope :last_book_completed, joins(:books)
    .where('books.created_on = (SELECT MAX(books.created_on) FROM books WHERE books.user_id = users.id)')
    .where('books.complete = true')
    .group('users.id')
end

Sonunda, tamamlanmış bir kitabı olan tüm kullanıcıların listesini aşağıdakileri yaparak alabilirsiniz:

User.last_book_completed

35
2018-03-10 17:21



Bunu denediğimde sqlite sözdizimi hatası "Around 'completed'” oluyordu. Yine de umut verici görünüyor! - istan
Oh, değişkeni 'tamamlandı' yerine 'tamamlandı' olarak adlandırmış olabilirsiniz. Sadece değişken adını değiştirin. - Pan Thomakos
Cevabımı, tamamlanmış yerine tamamlanmış olarak güncelledim. - Pan Thomakos
@ pan. 'books.complete = true' be 'books.complete = 1' 'veya 'books.complete = ?', true - rubyprince
Sabit ve kullanıcı olmayan bir girişse, 'books.complete =?', True ile dezenfekte etmek gerekli değildir. 1 veya true her ikisi de, en azından MySQL'de çalışmalıdır. - Pan Thomakos


Bu, biraz daha fazla ek yük getirir, ancak karmaşıklığı azaltır ve önemli olduğunda daha sonra hızı artırır.

Kitaplara "en iyi" sütun ekleyin. Bir dizin eklediğinizden emin olun.

class AddMostRecentToBooks < ActiveRecord::Migration

  def self.change
    add_column :books, :most_recent, :boolean, :default => false, :null => false
  end
  add_index :books, :most_recent, where: :most_recent  # partial index

end

Ardından, bir kitabı kaydettiğinizde, en_yönünü güncelleyin

class Book < ActiveRecord::Base

  on_save :mark_most_recent

  def mark_most_recent
    user.books.order(:created_at => :desc).offset(1).update_all(:most_recent => false)
    user.books.order(:created_at => :desc).limit(1).update_all(:most_recent => true)
  end

end

Şimdi, sorgunuz için

class User < ActiveRecord::Base

  # Could also include and preload most-recent book this way for lists if you wanted
  has_one :most_recent_book, -> { where(:most_recent => true) }, :class_name => 'Book'

  scope :last_book_completed, -> { joins(:books).where(:books => { :most_recent => true, :complete => true })
end

Bu, bunu böyle yazmanıza olanak tanır ve sonuç diğer kapsamlarla kullanılacak bir İlişkidir.

User.last_book_completed

14
2017-09-08 06:10



Bu en açık cevap - Sean Szurko


Yakın zamanda benzer bir soruna rastladım ve işte bunu çözdüm:

most_recent_book_ids = User.all.map {|user| user.books.last.id }
results = User.joins(:books).where('books.id in (?) AND books.complete == ?', most_recent_book_ids, true).uniq

Bu şekilde sadece ActiveRecord yöntemlerini kullanıyoruz (ekstra SQL yok) ve kullanıcılar için herhangi bir alt kümeyi (ilk, son, son n kitaplar, vb.) Göz önünde bulundurarak yeniden kullanabiliriz. Son 'uniq' sebebine ihtiyacınız var, aksi halde her kullanıcı iki kez görünecektir.


2
2017-11-08 01:05





Tek bir sorguda yapmanın bir yolunu düşünemiyorum ama yapabilecekleriniz:

User.all.select { |user| user.books.order("created_at desc").limit(1).complete }

2
2018-03-09 18:57



Bu benim hayata geçirdiğim şeydi ama ben orada gibi hissediyorum şart db sorgusuyla bunu yapmanın daha temiz bir yolu olun. Sadece bana basit bir istek gibi geliyor. - istan
Çok kullanıcınız varsa bu çok gelişir - MattyB
Ben adam biliyorum, benim çözümüm çok kötü ve (n + 1) sorgu üretir. Ama o zaman daha iyi bir çözüm bulamadım. Bu yüzden @pan'dan bu soruya bir göz atmasını istedim ve gerçekten de güzel ve doğru bir cevap buldu. - rubyprince
Bu aslında uygulamanızda istemediğiniz N + 1 problemi yaratır. - eveevans


Burada kapsamları kullanmalısınız - hayatınızı çok basitleştireceklerdir (bu bir rails3 örneğidir)

Book.rb modelinizde

scope :completed, where("complete = ?", true)
scope :recent, order("created_on ASC")

Sonra arayabilirsin User.books.recent.completed istediğini almak için


0
2018-03-09 15:48



error: undefined method 'books' için # <Class: ...> - Bir kullanıcıya değil tüm kullanıcılara karşı sorgulamam gerekiyor - istan
@ john..Sen arayamıyorum .books Kullanıcı modeli üzerinde ... sadece bunun gibi bir örnek olarak diyebilirsiniz @user = User.first - rubyprince
@rubyprince yep, tamamen doğru. yapamazsın - John Beynon
Bu OP'nin kriterlerini karşılamayacaktır: "Eğer bir kullanıcının en son kitabında varsa: tam => yanlış, sonuçta onları istemiyorum." - Brent Dillingham


dayalı Pan'ın cevabı ancak bir iç birleştirme yerine sol birleşim ve gruplandırma yok:

scope :with_last_book_complete, -> {
  subquery = Book.select(:id)
                 .where("books.user_id = users.id")
                 .order(created_at: :desc).limit(1)

  joins(<<-SQL).where(books: { complete: true })
    LEFT JOIN books
      ON books.user_id = users.id
      AND books.id = (#{subquery.to_sql})
  SQL
}

0
2017-09-21 18:48