如何以 Elixir 的 Ecto 查询格式表示此查询?
How to represent this query in Elixir's Ecto Query format?
我遇到问题 "translating" 将以下 SQL 查询转换为有效的 Ecto 查询:
SELECT *
FROM file_modules
WHERE file_id =
(SELECT f.file_id
FROM files AS f
LEFT JOIN file_modules AS fm ON f.file_id = fm.file_id
WHERE f.storage_id = '20:1:0:86d:1591:c89c:512:de52' AND fm.name = 'bruteforce'
ORDER BY f.version DESC
LIMIT 1)
我认为要走的路是将此查询分成两部分。这是我尝试过的:
q1 =
File
|> where([f], f.storage_id == ^storage_id)
|> join(:left, [f], fm in FileModule, f.file_id == fm.file_id)
|> where([..., fm], fm.name == ^module_name)
|> order_by([..., fm], desc: fm.version)
|> select([fm], fm.file_id)
|> limit(1)
# q1 works and returns the expected file_id
q2 =
q1
|> subquery()
|> where([fm, fm2], fm.file_id == fm2.file_id) # Here's where I'm stuck
|> preload([..., m], [modules: m])
q1
有以下 Ecto.Query 结果:
#Ecto.Query<from f0 in Helix.Software.Model.File,
left_join: f1 in Helix.Software.Model.FileModule, on: f0.file_id == f1.file_id,
where: f0.storage_id == ^(storage_id),
where: f1.name == ^:bruteforce, order_by: [desc: f1.version], limit: 1,
select: f0.file_id>
我的问题似乎出在 q2
上。我应该如何格式化它才能使用
来自 q1
作为输入的结果?
提前致谢。
我认为这个问题与我的基本模式无关,但这里是:
schema "files" do
field :file_id, ID,
primary_key: true
field :name, :string
field :path, :string
field :software_type, Constant
field :file_size, :integer
field :storage_id, Storage.ID
field :crypto_version, :integer
field :full_path, :string
belongs_to :type, SoftwareType,
foreign_key: :software_type,
references: :software_type,
define_field: false
belongs_to :storage, Storage,
foreign_key: :storage_id,
references: :storage_id,
define_field: false
has_many :modules, FileModule,
foreign_key: :file_id,
references: :file_id,
on_replace: :delete
timestamps()
end
schema "file_modules" do
field :file_id, File.ID,
primary_key: true
field :name, Constant,
primary_key: true
field :version, :integer
belongs_to :file, File,
foreign_key: :file_id,
references: :file_id,
define_field: false,
on_replace: :update
end
正如 Jablanović 在评论中提到的,ORM 抽象有时可能会失败。在那种情况下,是否可以使用上面的原始 sql while:
1) 转换 File.ID 和 Storage.ID 类型,以避免难看的字符串连接?
2) 返回结果后,将此结果预加载或保存到模式中?
我想到的界面是这样的:
q = "SELECT * FROM files WHERE file_id = ", ^file_id
file = Repo.get(q) |> Repo.preload()
file.file_id # Returns file id
按照Ecto.Adapters.SQL.query/4的例子,好像可以做到1,那2呢?
我的最终目标是使用上面的嵌套 SQL 查询,同时安全地转换 file_id 和 storage_id,并正确使用 %File{} 模式,加载file.modules
协会。
请注意,存储过程或视图会给我一个更好的界面,我可以使用 fragment
正确地转换它,但我觉得 "preloading" 将数据导入架构。
我不打算回答我自己的问题,但嘿这是我想出的替代解决方案。如问题所述,如果我能找到一个不错的界面让我:
- 使用原始 SQL 查询
- 同时仍然利用 Ecto 类型转换
- 并且能够将关联数据预加载到架构中。
结合使用 Repo.load
和 Ecto.Adapters.SQL.query
,我能够做到这一点。如果对其他人有帮助,我会分享界面和代码。
sql = "
SELECT *
FROM file_modules
WHERE file_id =
(SELECT f.file_id
FROM files AS f
LEFT JOIN file_modules AS fm ON f.file_id = fm.file_id
WHERE f.storage_id = ##1::storage_id AND fm.name = ##2::module_name
ORDER BY f.version DESC
LIMIT 1)"
caster = fn type, value ->
case type do
:storage_id ->
Storage.ID.cast!(value) && to_string(value)
:module_name ->
Software.Module.exists?(value) && value || {:error, :bad_value}
_ ->
Hector.std_caster(type, value)
end
end
query = Hector.query(sql, [storage_id, module_name], caster)
loader = fn repo, {columns, rows} ->
rows
|> Enum.map(fn row ->
repo
|> apply(:load, [File, {columns, row}])
|> File.format()
end)
end
{:ok, entries} = Hector.get(Repo, query, loader)
# Where `entries` is a list of %File{}, a valid Ecto Schema.
Hector
是处理这个接口的库。上面的示例是最复杂的情况:我们需要一个自定义脚轮和一个自定义加载器。在大多数情况下,Hector 的默认加载器就可以正常工作,并且只要存在潜在的不安全输入,就需要自定义施法器。
可以找到代码here, with some extra examples on the tests。
当然,这远非最佳解决方案,即使我可能能够为这个或那个查询找出正确的 Ecto 语法,但为原始查询提供一个很好的 abstraction/interface 总是很方便.
我还没有测试过,但下面的查询应该会产生与原来相同的结果 SQL:
q1 = ... # Same as in the question.
q2 =
FileModule
|> join(:left, [fm], fid in subquery(q1), fm.file_id == fid)
|> select([fm], fm)
我遇到问题 "translating" 将以下 SQL 查询转换为有效的 Ecto 查询:
SELECT *
FROM file_modules
WHERE file_id =
(SELECT f.file_id
FROM files AS f
LEFT JOIN file_modules AS fm ON f.file_id = fm.file_id
WHERE f.storage_id = '20:1:0:86d:1591:c89c:512:de52' AND fm.name = 'bruteforce'
ORDER BY f.version DESC
LIMIT 1)
我认为要走的路是将此查询分成两部分。这是我尝试过的:
q1 =
File
|> where([f], f.storage_id == ^storage_id)
|> join(:left, [f], fm in FileModule, f.file_id == fm.file_id)
|> where([..., fm], fm.name == ^module_name)
|> order_by([..., fm], desc: fm.version)
|> select([fm], fm.file_id)
|> limit(1)
# q1 works and returns the expected file_id
q2 =
q1
|> subquery()
|> where([fm, fm2], fm.file_id == fm2.file_id) # Here's where I'm stuck
|> preload([..., m], [modules: m])
q1
有以下 Ecto.Query 结果:
#Ecto.Query<from f0 in Helix.Software.Model.File,
left_join: f1 in Helix.Software.Model.FileModule, on: f0.file_id == f1.file_id,
where: f0.storage_id == ^(storage_id),
where: f1.name == ^:bruteforce, order_by: [desc: f1.version], limit: 1,
select: f0.file_id>
我的问题似乎出在 q2
上。我应该如何格式化它才能使用
来自 q1
作为输入的结果?
提前致谢。
我认为这个问题与我的基本模式无关,但这里是:
schema "files" do
field :file_id, ID,
primary_key: true
field :name, :string
field :path, :string
field :software_type, Constant
field :file_size, :integer
field :storage_id, Storage.ID
field :crypto_version, :integer
field :full_path, :string
belongs_to :type, SoftwareType,
foreign_key: :software_type,
references: :software_type,
define_field: false
belongs_to :storage, Storage,
foreign_key: :storage_id,
references: :storage_id,
define_field: false
has_many :modules, FileModule,
foreign_key: :file_id,
references: :file_id,
on_replace: :delete
timestamps()
end
schema "file_modules" do
field :file_id, File.ID,
primary_key: true
field :name, Constant,
primary_key: true
field :version, :integer
belongs_to :file, File,
foreign_key: :file_id,
references: :file_id,
define_field: false,
on_replace: :update
end
正如 Jablanović 在评论中提到的,ORM 抽象有时可能会失败。在那种情况下,是否可以使用上面的原始 sql while:
1) 转换 File.ID 和 Storage.ID 类型,以避免难看的字符串连接?
2) 返回结果后,将此结果预加载或保存到模式中?
我想到的界面是这样的:
q = "SELECT * FROM files WHERE file_id = ", ^file_id
file = Repo.get(q) |> Repo.preload()
file.file_id # Returns file id
按照Ecto.Adapters.SQL.query/4的例子,好像可以做到1,那2呢?
我的最终目标是使用上面的嵌套 SQL 查询,同时安全地转换 file_id 和 storage_id,并正确使用 %File{} 模式,加载file.modules
协会。
请注意,存储过程或视图会给我一个更好的界面,我可以使用 fragment
正确地转换它,但我觉得 "preloading" 将数据导入架构。
我不打算回答我自己的问题,但嘿这是我想出的替代解决方案。如问题所述,如果我能找到一个不错的界面让我:
- 使用原始 SQL 查询
- 同时仍然利用 Ecto 类型转换
- 并且能够将关联数据预加载到架构中。
结合使用 Repo.load
和 Ecto.Adapters.SQL.query
,我能够做到这一点。如果对其他人有帮助,我会分享界面和代码。
sql = "
SELECT *
FROM file_modules
WHERE file_id =
(SELECT f.file_id
FROM files AS f
LEFT JOIN file_modules AS fm ON f.file_id = fm.file_id
WHERE f.storage_id = ##1::storage_id AND fm.name = ##2::module_name
ORDER BY f.version DESC
LIMIT 1)"
caster = fn type, value ->
case type do
:storage_id ->
Storage.ID.cast!(value) && to_string(value)
:module_name ->
Software.Module.exists?(value) && value || {:error, :bad_value}
_ ->
Hector.std_caster(type, value)
end
end
query = Hector.query(sql, [storage_id, module_name], caster)
loader = fn repo, {columns, rows} ->
rows
|> Enum.map(fn row ->
repo
|> apply(:load, [File, {columns, row}])
|> File.format()
end)
end
{:ok, entries} = Hector.get(Repo, query, loader)
# Where `entries` is a list of %File{}, a valid Ecto Schema.
Hector
是处理这个接口的库。上面的示例是最复杂的情况:我们需要一个自定义脚轮和一个自定义加载器。在大多数情况下,Hector 的默认加载器就可以正常工作,并且只要存在潜在的不安全输入,就需要自定义施法器。
可以找到代码here, with some extra examples on the tests。
当然,这远非最佳解决方案,即使我可能能够为这个或那个查询找出正确的 Ecto 语法,但为原始查询提供一个很好的 abstraction/interface 总是很方便.
我还没有测试过,但下面的查询应该会产生与原来相同的结果 SQL:
q1 = ... # Same as in the question.
q2 =
FileModule
|> join(:left, [fm], fid in subquery(q1), fm.file_id == fid)
|> select([fm], fm)