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