Sıfırdan büyük ilişkilendirme sayısına sahip tüm kayıtları bulun


100

Basit olacağını düşündüğüm bir şey yapmaya çalışıyorum ama öyle görünmüyor.

Çok sayıda boş pozisyonu olan bir proje modelim var.

class Project < ActiveRecord::Base

  has_many :vacancies, :dependent => :destroy

end

En az 1 boş pozisyonu olan tüm projeleri almak istiyorum. Bunun gibi bir şey denedim:

Project.joins(:vacancies).where('count(vacancies) > 0')

ama diyor ki

SQLite3::SQLException: no such column: vacancies: SELECT "projects".* FROM "projects" INNER JOIN "vacancies" ON "vacancies"."project_id" = "projects"."id" WHERE ("projects"."deleted_at" IS NULL) AND (count(vacancies) > 0).

Yanıtlar:


68

joinsvarsayılan olarak bir iç birleştirme kullanır, bu nedenle kullanmak Project.joins(:vacancies), yalnızca ilişkili bir boşluğu olan projeleri döndürür.

GÜNCELLEME:

@ Mackskatz'ın yorumda belirttiği gibi, bir groupmadde olmadan , yukarıdaki kod birden fazla boş kontenjan olan projeler için yinelenen projeleri döndürecektir. Yinelenenleri kaldırmak için kullanın

Project.joins(:vacancies).group('projects.id')

GÜNCELLEME:

@Tolsee'nin belirttiği gibi, kullanabilirsiniz distinct.

Project.joins(:vacancies).distinct

Örnek olarak

[10] pry(main)> Comment.distinct.pluck :article_id
=> [43, 34, 45, 55, 17, 19, 1, 3, 4, 18, 44, 5, 13, 22, 16, 6, 53]
[11] pry(main)> _.size
=> 17
[12] pry(main)> Article.joins(:comments).size
=> 45
[13] pry(main)> Article.joins(:comments).distinct.size
=> 17
[14] pry(main)> Article.joins(:comments).distinct.to_sql
=> "SELECT DISTINCT \"articles\".* FROM \"articles\" INNER JOIN \"comments\" ON \"comments\".\"article_id\" = \"articles\".\"id\""

1
Bununla birlikte, bir grup maddesini uygulamadan bu, birden fazla Boş Pozisyonu olan Projeler için birden çok Proje nesnesi döndürür.
mackshkatz

1
Yine de verimli bir SQL ifadesi oluşturmaz.
David Aldridge

İşte bu sizin için Rails. Bir sql yanıtı sağlayabilirseniz (ve bunun neden verimli olmadığını açıklayabilirseniz), bu çok daha yararlı olabilir.
jvnill

Ne düşünürsün Project.joins(:vacancies).distinct?
Tolsee

1
@Tolsee btw: D
Tolsee

172

1) En az 1 boş pozisyonu olan Proje almak için:

Project.joins(:vacancies).group('projects.id')

2) Birden fazla boş kontenjan olan Proje almak için:

Project.joins(:vacancies).group('projects.id').having('count(project_id) > 1')

3) Veya Vacancymodel sayaç önbelleği ayarlarsa:

belongs_to :project, counter_cache: true

o zaman bu da işe yarayacak:

Project.where('vacancies_count > ?', 1)

Çekim kuralı için manuelvacancy olarak belirtilmesi gerekebilir mi?


2
Bu olmamalı Project.joins(:vacancies).group('projects.id').having('count(vacancies.id) > 1')mı? Proje kimlikleri yerine boş pozisyonların sayısını sorgulamak
Keith Mattix

2
Hayır, @KeithMattix, olmamalı . O olabilir , o size daha iyi okur ancak, olması; bu bir tercih meselesi. Sayım, birleştirme tablosundaki her satırda bir değer olması garanti edilen herhangi bir alanla yapılabilir. En anlamlı adaylar projects.id, project_idve vacancies.id. Saymayı seçtim project_idçünkü birleştirmenin yapıldığı alandır; eklemenin omurgası. Ayrıca bana bunun bir birleştirme tablosu olduğunu hatırlatıyor.
Arta

38

Evet, vacanciesbirleşim alanı değil. İstediğine inanıyorum:

Project.joins(:vacancies).group("projects.id").having("count(vacancies.id)>0")

16
# None
Project.joins(:vacancies).group('projects.id').having('count(vacancies) = 0')
# Any
Project.joins(:vacancies).group('projects.id').having('count(vacancies) > 0')
# One
Project.joins(:vacancies).group('projects.id').having('count(vacancies) = 1')
# More than 1
Project.joins(:vacancies).group('projects.id').having('count(vacancies) > 1')

6

Has_many tablosuna bir iç birleştirme gerçekleştirmek, bir groupveya uniqpotansiyel olarak çok verimsizdir ve SQL'de bu, EXISTSilişkili bir alt sorgu ile kullanılan bir yarı birleştirme olarak daha iyi uygulanacaktır .

Bu, sorgu iyileştiricisinin boş pozisyonlar tablosunu doğru proje kimliğine sahip bir satırın varlığını kontrol etmesine olanak tanır. Bu proje kimliğine sahip bir satır veya bir milyon olması fark etmez.

Bu, Rails'de o kadar kolay değil, ancak şunlarla sağlanabilir:

Project.where(Vacancies.where("vacancies.project_id = projects.id").exists)

Benzer şekilde, boş pozisyonu olmayan tüm projeleri bulun:

Project.where.not(Vacancies.where("vacancies.project_id = projects.id").exists)

Düzenleme: son Rails sürümlerinde exists, arel için yetkilendirilmeye güvenmemenizi söyleyen bir kullanımdan kaldırma uyarısı alırsınız . Bunu şu şekilde düzeltin:

Project.where.not(Vacancies.where("vacancies.project_id = projects.id").arel.exists)

Düzenleme: Ham SQL'den rahatsızsanız şunları deneyin:

Project.where.not(Vacancies.where(Vacancy.arel_table[:project_id].eq(Project.arel_table[:id])).arel.exists)

Kullanımını gizlemek için sınıf yöntemleri ekleyerek bunu daha az karmaşık hale getirebilirsiniz arel_table, örneğin:

class Project
  def self.id_column
    arel_table[:id]
  end
end

... yani ...

Project.where.not(
  Vacancies.where(
    Vacancy.project_id_column.eq(Project.id_column)
  ).arel.exists
)

bu iki öneri işe yaramıyor gibi görünüyor ... alt sorgu Vacancy.where("vacancies.project_id = projects.id").exists?ya ya trueda sonucunu verir false. Project.where(true)bir ArgumentError.
Les Nightingill

Vacancy.where("vacancies.project_id = projects.id").exists?çalıştırılmayacak - bir hata oluşacaktır çünkü projectsilişki sorguda olmayacaktır (ve yukarıdaki örnek kodda da soru işareti yoktur). Yani bunu iki ifadeye ayırmak geçerli değildir ve işe yaramaz. Son zamanlarda Rails Project.where(Vacancies.where("vacancies.project_id = projects.id").exists)bir kullanımdan kaldırma uyarısı veriyor ... Soruyu güncelleyeceğim.
David Aldridge

4

Rails 4+ ile aynı yanıtı almak için include veya eager_load da kullanabilirsiniz :

Project.includes(:vacancies).references(:vacancies).
        where.not(vacancies: {id: nil})

Project.eager_load(:vacancies).where.not(vacancies: {id: nil})

4

Bence daha basit bir çözüm var:

Project.joins(:vacancies).distinct

1
Ayrıca "farklı" kullanmak da mümkündür, örneğin Project.joins (: boş pozisyonlar) .distinct
Metaphysiker

Haklısın! #Uniq yerine #distinct kullanmak daha iyidir. #uniq tüm nesneleri belleğe yükler, ancak #distinct bir veritabanı tarafında hesaplamalar yapar.
Yuri Karpovich

3

Çok fazla Rails sihri olmadan şunları yapabilirsiniz:

Project.where('(SELECT COUNT(*) FROM vacancies WHERE vacancies.project_id = projects.id) > 0')

Bu tür koşullar, işin çoğu doğrudan DB tarafında yapıldığından tüm Rails sürümlerinde çalışacaktır. Ayrıca, zincirleme .countyöntemi de iyi çalışacaktır. Daha önce olduğu gibi sorgular tarafından yakıldım Project.joins(:vacancies). Tabii ki, DB agnostik olmadığı için artıları ve eksileri var.


1
Bu, her proje için 'select count (*) ..' alt sorgusu yürütüldüğünden, birleştirme ve grup yönteminden çok daha yavaştır.
YasirAzgar

@YasirAzgar Join ve group yöntemi "var" yönteminden daha yavaştır çünkü bir milyon bile olsa tüm alt satırlara erişmeye devam edecektir.
David Aldridge

0

Tablodan tüm sütunları seçmek yerine EXISTSile de kullanabilirsiniz :SELECT 1vacancies

Project.where("EXISTS(SELECT 1 from vacancies where projects.id = vacancies.project_id)")

-6

Hata, size boş pozisyonların temelde projelerde bir sütun olmadığını söylüyor.

Bu çalışmalı

Project.joins(:vacancies).where('COUNT(vacancies.project_id) > 0')

7
aggregate functions are not allowed in WHERE
Kamil Lelonek
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.