expresssqlunionqueryrails方法

expresssqlunionqueryrails方法,sql,ruby-on-rails,postgresql,activerecord,arel,Sql,Ruby On Rails,Postgresql,Activerecord,Arel,我得到了一个运行良好的查询,但它是用SQL表示的。我希望使用ActiveRecord查询接口表达相同的查询(Arel也可以)。查询最好返回ActiveRecord::Relation,或者至少其结果应可转换为一系列客户模型 目标是获取公司的客户没有与远程类型=“帐户”关联的导入日志,以及客户拥有导入日志与远程类型=“帐户”和状态=“待定” 客户可以根本没有关联的导入日志,或者每个远程类型都有一个导入日志,或者仅针对某些远程类型。只有一个与特定的远程\u类型值关联的导入\u日志 这反映了客户可以作

我得到了一个运行良好的查询,但它是用SQL表示的。我希望使用ActiveRecord查询接口表达相同的查询(Arel也可以)。查询最好返回ActiveRecord::Relation,或者至少其结果应可转换为一系列客户模型

目标是获取
公司的
客户
没有与
远程类型=“帐户”
关联的
导入日志
,以及
客户
拥有
导入日志
远程类型=“帐户”
状态=“待定”

客户
可以根本没有关联的
导入日志
,或者每个
远程类型
都有一个
导入日志
,或者仅针对某些
远程类型
。只有一个与特定的
远程\u类型
值关联的
导入\u日志

这反映了
客户
可以作为
帐户
联系人
或两者都导入的要求,并且
导入日志
跟踪导入的状态

尽管
导入日志
客户
存在多态关联,但这与任务无关

现有查询:

Customer.find_by_sql(
  <<-SQL
    SELECT
      customers.*
    FROM
      customers
    WHERE
      company_id = #{@company.id}
      AND NOT EXISTS
          ( SELECT *
            FROM import_logs
            WHERE import_logs.importable_id = customers.id
              AND import_logs.importable_type = 'Customer'
              AND import_logs.remote_type = 'account'
          )
    UNION
    SELECT
      customers.*
    FROM
      customers,
      import_logs
    WHERE
      import_logs.importable_id = customers.id AND
      import_logs.importable_type = 'Customer' AND
      company_id = #{@company.id} AND
      import_logs.remote_type = 'account' AND
      import_logs.status = 'pending';
  SQL
)
Customer.find\u by\u sql(
合并
关联 事实上,只有一个关联是由查询常量驱动的

"customers"."company_id" = #{@company.id}
这与:

.merge(@company.customers)
…这看起来更安全、更明智

Arel表 我们很快就会需要的

customers = Customer.arel_table
不存在…
子查询 Arel可以做到这一点,唯一不那么明显的是如何引用外部表格:

ne_subquery = ImportLog.where(
                importable_type: Customer.to_s,
                  importable_id: customers[:id],
                    remote_type: 'account'
              ).exists.not
这将产生大量Arel AST,我们可以将其提供给Rails的
where
-语句

现在这两个问题变得显而易见: 这几乎是一对一的转换

联合 这是一个棘手的部分,我发现的唯一解决方案语法非常难看,并输出一个稍微不同的查询。给定
a union B
,我们只能从(a union B)X
中构建
select X.*。效果相同

好了,让我们开始吧:

Customer.from(
  customers.create_table_alias(
    first.union(second),
    Customer.table_name
  )
)
当然,要使此查询更具可读性,您应该:

  • 将其作为范围放置在
    Customer
    类中
  • 将可重用部件划分为范围和关联

根据@D-side建议的代码,我能够找到一个有效的解决方案。以下是最初建议的代码:

customers = Customer.arel_table

ne_subquery = ImportLog.where(
  importable_type: Customer.to_s,
  importable_id: customers['id'],
  remote_type: 'account'
).exists.not

first  = @company.customers.where(ne_subquery)
second = @company.customers.joins(:import_logs).merge(
  ImportLog.where(
    importable_type: Customer.to_s,
    remote_type: 'account',
    status: 'pending'
  )
)

Customer.from(
  customers.create_table_alias(
    first.union(second),
    Customer.table_name
  )
)
运行它会导致以下错误:

PG::ProtocolViolation: ERROR:  bind message supplies 0 parameters, but prepared statement "" requires 1

: SELECT "customers".* FROM ( SELECT "customers".* FROM "customers"  WHERE "customers"."company_id" = $1 \
AND (NOT (EXISTS (SELECT "import_logs".* FROM "import_logs"  WHERE "import_logs"."importable_type" = 'Customer' \
AND "import_logs"."importable_id" = "customers"."id"))) UNION SELECT "customers".* FROM "customers" \
INNER JOIN "import_logs" ON "import_logs"."importable_id" = "customers"."id" \
AND "import_logs"."importable_type" = 'Customer' WHERE "customers"."company_id" = $1 \
AND "import_logs"."importable_type" = 'Customer' AND "import_logs"."remote_type" = 'contact' \
AND "import_logs"."status" = 'pending' ) "customers"
我认为这个错误是目前尚未解决的一个表现。因为这个问题与参数绑定有关,所以使绑定更明确会有帮助。下面是一个有效的解决方案:

customers = Customer.arel_table

ne_subquery = ImportLog.where(
  importable_type: Customer.to_s,
  importable_id: customers['id'],
  remote_type: 'account'
).exists.not

first  = Customer.where(ne_subquery).where(company_id: @company.id)
second = Customer.joins(:import_logs).merge(
  ImportLog.where(
    importable_type: Customer.to_s,
    remote_type: 'account',
    status: 'pending'
  )
).where(company_id: @company.id)

Customer.from(
  customers.create_table_alias(
    first.union(second),
    Customer.table_name
  )
)

注意
。其中(company_id:@company.id)
被明确应用,第一次和第二次查询开始时没有范围。

我觉得你们联盟中的第二次查询很奇怪。可能缺少一个连接。或者
存在
。我不知道它是否真的存在。第二次查询得到了
客户
导入日志
存在。它的顶部
是什么RE
条件与
内部连接同义,因此它的风格不同。它确实按预期工作。只是Rails通常通过
内部连接
s获取关联。好吧,我看到了它背后的逻辑。唯一的问题是
联合
。我必须考虑一下。同时,检查一下might给出一些关于从何处开始的提示。Scuttle.io在此查询中出现问题。这是我在发布之前检查的内容。感谢您的努力!这一切都可以实现联合。此时,它会出现以下消息:
ActiveRecord::StatementInvalid:PG::ProtocolViolation:ERROR:bind消息提供了0个参数,但准备好了状态"需要1
。显然,
公司id
没有绑定,我还没有找到解决方法。一个肮脏的方法是在
where
调用中对
公司id
自己应用条件,而不是将其委托给关联。好吧,那会起作用。我只是不喜欢那种风格。我尝试了,但没有成功工作。也许我做错了什么,你能举个例子吗?我可以试试。导致此错误的SQL是什么?嗯……这很奇怪,两个
$1
s。看起来你必须自己绑定一个值。我不确定如何做,但我会看看。可能会工作的是一个带有条件字符串的单独where子句。试试这个我出去了。
Customer.from(
  customers.create_table_alias(
    first.union(second),
    Customer.table_name
  )
)
customers = Customer.arel_table

ne_subquery = ImportLog.where(
  importable_type: Customer.to_s,
  importable_id: customers['id'],
  remote_type: 'account'
).exists.not

first  = @company.customers.where(ne_subquery)
second = @company.customers.joins(:import_logs).merge(
  ImportLog.where(
    importable_type: Customer.to_s,
    remote_type: 'account',
    status: 'pending'
  )
)

Customer.from(
  customers.create_table_alias(
    first.union(second),
    Customer.table_name
  )
)
PG::ProtocolViolation: ERROR:  bind message supplies 0 parameters, but prepared statement "" requires 1

: SELECT "customers".* FROM ( SELECT "customers".* FROM "customers"  WHERE "customers"."company_id" = $1 \
AND (NOT (EXISTS (SELECT "import_logs".* FROM "import_logs"  WHERE "import_logs"."importable_type" = 'Customer' \
AND "import_logs"."importable_id" = "customers"."id"))) UNION SELECT "customers".* FROM "customers" \
INNER JOIN "import_logs" ON "import_logs"."importable_id" = "customers"."id" \
AND "import_logs"."importable_type" = 'Customer' WHERE "customers"."company_id" = $1 \
AND "import_logs"."importable_type" = 'Customer' AND "import_logs"."remote_type" = 'contact' \
AND "import_logs"."status" = 'pending' ) "customers"
customers = Customer.arel_table

ne_subquery = ImportLog.where(
  importable_type: Customer.to_s,
  importable_id: customers['id'],
  remote_type: 'account'
).exists.not

first  = Customer.where(ne_subquery).where(company_id: @company.id)
second = Customer.joins(:import_logs).merge(
  ImportLog.where(
    importable_type: Customer.to_s,
    remote_type: 'account',
    status: 'pending'
  )
).where(company_id: @company.id)

Customer.from(
  customers.create_table_alias(
    first.union(second),
    Customer.table_name
  )
)