【问题标题】:How to convert this raw query to Active record query interface or arel?如何将此原始查询转换为活动记录查询接口或 arel?
【发布时间】:2016-05-09 01:37:49
【问题描述】:

我想找到至少有 1 个 variantvariant_type = 1 且没有任何 variantvariant_type = 2 的 courses

所以我的查询如下:

Course.where("id IN ( SELECT course_id
                     FROM course_variants
                     WHERE variant_type = 1
                   )
             AND id NOT IN (
                     SELECT course_id
                     FROM course_variants
                     WHERE variant_type = 2
                   )")

另外,一个course 有很多course_variants

我在 where 子句中使用原始查询,我想使用 Active Record 接口或 Arel 来改进它,有什么解决方案吗?

谢谢!

使用输入更新预期输出

输入

course: {id=1, course_variants: [{variant_type: 1}, {variant_type: 2}]}
course: {id=2, course_variants: [{variant_type: 1}, {variant_type: 3}]}
course: {id=3, course_variants: [{variant_type: 2}, {variant_type: 3}]}

输出

course: {id=2, course_variants: [{variant_type: 1}, {variant_type: 3}]}

【问题讨论】:

  • variant_type 可以取什么值?只有 1、2、3 或无限的数字?
  • variant_type 是一个枚举,范围值为 [1..4]
  • 当你试图用 2 排除 variant_type 时,试试这个查询 Course.includes(:course_variant).where.not(course_variants: {variant_type: 2})
  • 它不能正常工作,因为我还需要 variant_type = 1

标签: ruby-on-rails activerecord arel


【解决方案1】:

你可以稍微调整一下你的模型关联:

class Course < ActiveRecord::Base
  has_many :type_1_variants, class_name: "CourseVariant", -> { where(variant_type: 1) }
  has_many :non_type_3_variants, class_name: "CourseVariant", -> { where.not(variant_type: 3) }
end

Course.joins(:type_1_variants, :non_type_3_variants).group(:course_id).having('COUNT(course_variants.id) > 0').having('COUNT(non_type_3_variants_courses.id) > 0')

您可能需要将“non_type_3_variants_courses”替换为 ARel 在进行连接时生成的正确别名(我没有 Rails env atm)

【讨论】:

  • @HieuPham 这个查询真的返回了你的预期输出吗??
  • @HoanNguyen 可以为每个 variant_type 条件编写新的关联??
  • @VishalJAIN 我的想法是将条件抽象为可读的东西,让框架生成它认为最好的 SQL。遗憾的是 ActiveRecord 不支持 Django 提供的高级查询,因此我仍然不得不坚持上面的一些原始查询。回到您的问题,如果条件是动态的、不可预测的......,最好像 OP 那样生成原始 SQL。
  • @HoanNguyen 我认为您可以通过ActiveRecord 以上查询提供/更改它,以便我们可以在查询中更改variant 相关条件。你可以查看我的回复。
【解决方案2】:

您应该使用 joins 方法来连接两个表 coursescourse_variants 然后在 where 方法中定义条件,如下所示:

Course.joins("INNER JOIN course_variants on courses.id = course_variants.course_id")
      .where("course_variants.variant_type" => 1)

【讨论】:

  • 用预期的输入、输出查看我的更新,您的查询将返回 ID 在 [1, 2] 中的课程,这不是预期的结果
【解决方案3】:

试试这个:

Course.where(id: CourseVariant.where(variant_type: 1).pluck(:course_id)).where.not(id: CourseVariant.where(variant_type: 2).pluck(:course_id))

希望对您有所帮助。 :)

【讨论】:

  • 我希望它会运行一个查询!
【解决方案4】:

嘿,你可以试试这种方式,它是查找此类记录的优化方式,这里不需要在子句中触发两次:

你可以在 Rails pluck for second query 中这样做:

 Course.joins(:course_variants).where(:course_variants => {:variant_type => 1}).where("courses.id not in (?)",CourseVariant.where(:variant_type => 2).pluck(:course_id))

您可以在单个查询中在内部使用 in 查询,这比上面更快:

Course.joins(:course_variants).where(:course_variants => {:variant_type => 1}).where("courses.id not in (select course_id from course_variants where variant_type = 2)")

您可以通过在 mysql 的单个查询中使用 group 来做到这一点:

Course.joins(:course_variants).group("course_variants.course_id").having("SUM(CASE WHEN variant_type = 1 THEN 1 
    WHEN variant_type = 2 THEN 2 
    ELSE 0 
    END) = 1")

【讨论】:

  • 我们可以使用rails方式,但只使用一个查询吗?
  • 嘿,您可以使用group 检查我的最新回复,这两种方式都是我提到的 sql 方式,因为 in 查询在内部触发。没有办法像sql和rails
  • @HieuPham 这个工作适合你吗?如果有任何问题,请告诉我。
【解决方案5】:

如果您在 Course 和 CourseVarint 模型之间有关联,那么您可以执行以下操作:

Course.joins(:course_variants).where(course_variants: {variant_type: 1}).where.not(course_variants: {variant_type: 2})

【讨论】:

  • 这会做三个数据库查询,不是合适的答案。
  • @Sakshi:我希望对此有一个查询?谢谢你的回答,你能更新一下吗?
  • @HieuPham 我已经通过一些重构更新了答案。你可以试试看。
  • 仍然不正确,您能否检查我的输入/和预期输出以进一步更新?
  • 这里没有错误,我的查询运行良好,我正在寻找改进方法!
猜你喜欢
  • 2018-11-20
  • 1970-01-01
  • 2014-10-10
  • 1970-01-01
  • 1970-01-01
  • 2011-10-24
  • 1970-01-01
  • 2019-01-01
  • 2015-08-23
相关资源
最近更新 更多