【问题标题】:SQL query to Rails 4对 Rails 4 的 SQL 查询
【发布时间】:2021-04-12 06:13:31
【问题描述】:

我很难在 Rails 中转换此查询。我有两个模型CategoryProduct。每个类别都有许多产品。我们可以通过product.category_id 获取特定产品的类别。有人可以帮我将此查询转换为 rails 4 吗?谢谢。


select * from category
where not exists 
(
 select from product
 where product.category_id = category.id 
 and product.validity_time >= now() - interval '1 month'
)

【问题讨论】:

    标签: ruby postgresql ruby-on-rails-4 arel


    【解决方案1】:

    Rails 便捷的查询方法非常适合许多用例,但它们无法提供任何给定数据库可以做的全部范围的暴露。 Rails 团队不断改进这个顶级 DSL,为更多使用的功能提供更好的可见性(例如 Rails 5 实现了where.not,Rails 6 添加了where.missing)。

    也就是说,对于更复杂的查询,您需要比ActiveRecord::QueryMethods 提供的便捷方法更深入一点才能实现您的目标。

    要实现这种类型的查询,您将不得不使用StringArel(rails 的底层查询组装器)。我不喜欢 Rails 中的String SQL,所以我将在此处提供Arel 解决方案。

    如果您想要确切的 SQL:

    product_table = Product.arel_table
    category_table = Category.arel_table
    Category.where(
      Arel::Nodes::Not.new(
        Arel::Nodes::Exists.new(
          product_table.project(Arel.star).where(
            product_table[:category_id].eq(category_table[:id]).and(
              product_table[:validity_time].gteq(
                Arel::Nodes::Subtraction.new(
                  Arel::Nodes::SqlLiteral.new('now()'),
                  Arel::Nodes::SqlLiteral.new("interval '1 month'")
            )))
          )))
    )
    

    其他选项:这些选项应该产生相同的结果(使用不同的 SQL),并且实现和组装更简单,更容易理解。

    1. 不在 () 中
    product_table = Product.arel_table
    category_table = Category.arel_table
    Category.where(
      category_table[:id].not_in(
        product_table.project(product_table[:category_id])
          .where(
              product_table[:validity_time].gteq(Time.now - 1.month)
          )
      )
    )
    

    生成的 SQL 应该类似于:

    SELECT categories.*
    FROM categories
    WHERE 
      categories.id NOT IN ( 
         SELECT products.category_id
         FROM products
         WHERE 
           products.validity_time >= [RUN TIME - 1 MONTH]
      )
    
    1. 多条件连接
    product_table = Product.arel_table
    category_table = Category.arel_table
    join = category_table.join(product_table).on(
      products_table[:category_id].eq(category_table[:id]).and(
        product_table[:validity_time].gteq(
          Arel::Nodes::Subtraction.new(Arel::Nodes::SqlLiteral.new('now()'),
            Arel::Nodes::SqlLiteral.new("interval '1 month'")))))
    Category.joins(join.join_sources).where(products: {id: nil }).distinct
    

    SQL

    SELECT DISTINCT categories.*
    FROM 
      categories
      LEFT OUTER JOIN products ON products.category_id = categories.id
        AND products.validity_time >= now() - interval '1 month'
    WHERE 
      products.id IS NULL
    

    【讨论】:

      猜你喜欢
      • 2015-09-04
      • 1970-01-01
      • 2019-02-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-03-14
      • 1970-01-01
      • 2015-02-13
      相关资源
      最近更新 更多