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.