tinytds sqlserver rails conectar con sql ruby-on-rails ruby-on-rails-4 rails-activerecord mysql2

sqlserver - IZQUIERDA EXTERIOR ÚNASE en Rails 4



rails sql server (11)

Tengo 3 modelos:

class Student < ActiveRecord::Base has_many :student_enrollments, dependent: :destroy has_many :courses, through: :student_enrollments end class Course < ActiveRecord::Base has_many :student_enrollments, dependent: :destroy has_many :students, through: :student_enrollments end class StudentEnrollment < ActiveRecord::Base belongs_to :student belongs_to :course end

Deseo consultar una lista de cursos en la tabla de Cursos, que no existen en la tabla de Inscripciones de Alumnos que están asociadas con un determinado alumno.

Descubrí que tal vez Left Join es el camino a seguir, pero parece que joins () en rails solo acepta una tabla como argumento. La consulta SQL que creo que haría lo que quiero es:

SELECT * FROM Courses c LEFT JOIN StudentEnrollment se ON c.id = se.course_id WHERE se.id IS NULL AND se.student_id = <SOME_STUDENT_ID_VALUE> and c.active = true

¿Cómo ejecuto esta consulta en Rails 4 way?

Cualquier entrada es apreciada.


Agregando a la respuesta anterior, para usar includes , si quiere un OUTER JOIN sin hacer referencia a la tabla en donde (como id es nulo) o la referencia está en una cadena, puede usar references . Eso se vería así:

Course.includes(:student_enrollments).references(:student_enrollments)

o

Course.includes(:student_enrollments).references(:student_enrollments).where(''student_enrollments.id = ?'', nil)

http://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html#method-i-references


De hecho, hay un "Camino de Rails" para hacer esto.

Podría usar Arel , que es lo que Rails usa para construir consultas para ActiveRecrods

Me gustaría envolverlo en el método para que pueda llamarlo bien y pasar cualquier argumento que desee, algo así como:

class Course < ActiveRecord::Base .... def left_join_student_enrollments(some_user) courses = Course.arel_table student_entrollments = StudentEnrollment.arel_table enrollments = courses.join(student_enrollments, Arel::Nodes::OuterJoin). on(courses[:id].eq(student_enrollments[:course_id])). join_sources joins(enrollments).where( student_enrollments: {student_id: some_user.id, id: nil}, active: true ) end .... end

También existe la forma rápida (y ligeramente sucia) que muchos usan

Course.eager_load(:students).where( student_enrollments: {student_id: some_user.id, id: nil}, active: true )

eager_load funciona muy bien, solo tiene el "efecto secundario" de cargar modelos en la memoria que quizás no necesites (como en tu caso)
Por favor, consulte Rails ActiveRecord :: QueryMethods .eager_load
Hace exactamente lo que estás preguntando de una manera ordenada.


Ejecutaría la consulta como:

Course.joins(''LEFT JOIN student_enrollment on courses.id = student_enrollment.course_id'') .where(active: true, student_enrollments: { student_id: SOME_VALUE, id: nil })



He estado luchando con este tipo de problema durante bastante tiempo, y decidí hacer algo para resolverlo de una vez por todas. Publiqué un Gist que aborda este problema: https://gist.github.com/nerde/b867cd87d580e97549f2

Creé un pequeño truco de AR que usa Arel Table para construir dinámicamente las combinaciones de la izquierda para usted, sin tener que escribir SQL sin procesar en su código:

class ActiveRecord::Base # Does a left join through an association. Usage: # # Book.left_join(:category) # # SELECT "books".* FROM "books" # # LEFT OUTER JOIN "categories" # # ON "books"."category_id" = "categories"."id" # # It also works through association''s associations, like `joins` does: # # Book.left_join(category: :master_category) def self.left_join(*columns) _do_left_join columns.compact.flatten end private def self._do_left_join(column, this = self) # :nodoc: collection = self if column.is_a? Array column.each do |col| collection = collection._do_left_join(col, this) end elsif column.is_a? Hash column.each do |key, value| assoc = this.reflect_on_association(key) raise "#{this} has no association: #{key}." unless assoc collection = collection._left_join(assoc) collection = collection._do_left_join value, assoc.klass end else assoc = this.reflect_on_association(column) raise "#{this} has no association: #{column}." unless assoc collection = collection._left_join(assoc) end collection end def self._left_join(assoc) # :nodoc: source = assoc.active_record.arel_table pk = assoc.association_primary_key.to_sym joins source.join(assoc.klass.arel_table, Arel::Nodes::OuterJoin).on(source[assoc.foreign_key].eq( assoc.klass.arel_table[pk])).join_sources end end

Espero eso ayude.



Puedes usar la gema left_joins , que left_joins método left_joins de Rails 5 for Rails 4 y 3.

Course.left_joins(:student_enrollments) .where(''student_enrollments.id'' => nil)


Si alguien vino aquí buscando una forma genérica de hacer una combinación externa izquierda en Rails 5, puede usar la función #left_outer_joins .

Ejemplo de unión múltiple:

Rubí:

Source. select(''sources.id'', ''count(metrics.id)''). left_outer_joins(:metrics). joins(:port). where(''ports.auto_delete = ?'', true). group(''sources.id''). having(''count(metrics.id) = 0''). all

SQL:

SELECT sources.id, count(metrics.id) FROM "sources" INNER JOIN "ports" ON "ports"."id" = "sources"."port_id" LEFT OUTER JOIN "metrics" ON "metrics"."source_id" = "sources"."id" WHERE (ports.auto_delete = ''t'') GROUP BY sources.id HAVING (count(metrics.id) = 0) ORDER BY "sources"."id" ASC


Si desea OUTER JOINs sin todos los objetos ActiveRecord con .pluck(:id) carga, use .pluck(:id) después de .eager_load() para abortar la carga ansiosa mientras conserva el OUTER JOIN. Usar .pluck(:id) frustra la carga ansiosa porque los alias de nombre de columna ( items.location AS t1_r9 , por ejemplo) desaparecen de la consulta generada cuando se usan (estos campos con nombre independiente se utilizan para crear una instancia de todos los objetos ActiveRecord cargados con impaciencia).

Una desventaja de este enfoque es que luego necesita ejecutar una segunda consulta para obtener los objetos ActiveRecord deseados identificados en la primera consulta:

# first query idents = Course .eager_load(:students) # eager load for OUTER JOIN .where( student_enrollments: {student_id: some_user.id, id: nil}, active: true ) .distinct .pluck(:id) # abort eager loading but preserve OUTER JOIN # second query Course.where(id: idents)


También puede pasar una cadena que es el join-sql. por ejemplo, joins("LEFT JOIN StudentEnrollment se ON c.id = se.course_id")

Aunque usaría el nombre de mesa estándar de los rieles para mayor claridad:

joins("LEFT JOIN student_enrollments ON courses.id = student_enrollments.course_id")


Use Squeel :

Person.joins{articles.inner} Person.joins{articles.outer}