【问题标题】:How to create Rails query that (I think) requires PostgreSQL subquery?如何创建(我认为)需要 PostgreSQL 子查询的 Rails 查询?
【发布时间】:2020-04-19 14:27:18
【问题描述】:

不确定如何执行此操作,因此标题可能不正确。

每个User 都有一个字符串类型的字段country

给定一个 user_id 数组,国家元组用于查询,找到所有匹配的记录。必须找到每个用户都有自己的国家/地区。

例如,这里是元组数组。

[1, 'us'],
[2, 'mexico'],
[3, 'us']

如果它存在并且它的国家是'us',这将返回User 1。
如果它存在并且它的国家是'mexico',它也应该返回User 2。

查询应该返回所有匹配的结果。

Rails 4.2

【问题讨论】:

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


    【解决方案1】:
    class User < ApplicationRecord
      def self.query_from_tuples(array_of_tuples)
        array_of_tuples.inject(nil) do |scope, (id, country)|
          if scope
            scope.or(where(id: id, country: country))
          else
            where(id: id, country: country) # this handles the initial iteration
          end
        end
      end
    end
    

    结果查询是:

    SELECT "users".* FROM "users" 
    WHERE (("users"."id" = $1 AND "users"."country" = $2 OR "users"."id" = $3 AND "users"."country" = $4) OR "users"."id" = $5 AND "users"."country" = $6) 
    LIMIT $7
    

    您还可以通过以下方式调整 kamakazis WHERE (columns) IN (values) 查询:

    class User < ApplicationRecord
      def self.query_from_tuples_2(array_of_tuples)
        # just a string of (?,?) SQL placeholders for the tuple values
        placeholders = Array.new(array_of_tuples.length, '(?,?)').join(',')
        # * is the splat operator and turns the tuples (flattened) into
        # a list of arguments used to fill the placeholders
        self.where("(id, country) IN (#{placeholders})", *array_of_tuples.flatten)
      end
    end
    

    这会导致以下查询不那么冗长:

    SELECT "users".* FROM "users" 
    WHERE ((id, country) IN ((1,'us'),(2,'mexico'),(3,'us'))) LIMIT $1
    

    如果您在 [id, country] 上有一个复合索引,也可以表现得更好。

    【讨论】:

      【解决方案2】:

      我知道这适用于纯 SQL:例如

      SELECT * FROM user
      WHERE (id, country) IN ((1, 'us'), (2, 'mexico'), (3, 'us'))
      

      现在我不知道 Rails 将如何处理绑定参数,如果它是一个对列表(每个包含两个元素的列表)。也许这会奏效。

      【讨论】:

        【解决方案3】:

        您可以构造原始 sql 并使用活动记录。像这样的:

        def self.for_multiple_lp(arr=[])
          # Handle case when arr is not in the expected format.
          condition = arr.collect{|a| "(user_id = #{a[0]} AND country = #{a[1]})"}.join(" OR ")
          where(condition)
        end
        

        编辑:改进的解决方案

        def self.for_multiple_lp(arr=[])
          # Handle case when arr is not in the expected format.
          condition = arr.collect{|a| "(user_id = ? AND country = ?)"}.join(" OR ")
          where(condition, *(arr.flatten))
        end
        

        这应该可行。

        【讨论】:

        • 我认为这应该有效。谢谢!您可能希望删除第一个答案,因为它允许 SQL 注入。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-11-23
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多