2017-10-09 36 views
0

我无法“翻译”下面的SQL查询到有效的外生查询:如何用Elixir的Ecto Query格式表示该查询?

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:

1)为了避免丑陋的字符串连接而强制转换File.ID和Storage.ID类型?

2)返回结果后,将该结果预加载或保存到模式中?

我心目中的界面是一样的东西:

q = "SELECT * FROM files WHERE file_id = $1", ^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正确投射,但是我觉得在将数据“预加载”到模式中仍然存在问题。

+0

你为什么不单独执行两个查询?你不会损失太多。 –

+0

这就是我现在正在做的,作为解决方法..但是如果不能使用子查询,我最终会受到极大的限制。我确定Ecto支持,所以这是我想出它的工作原理。但是到目前为止,即使在阅读了几篇关于Ecto子查询的文章和指南后,我仍然无法理解它。 –

+1

不幸的是,在过去使用多个ORM之后,我可以说每个人迟早会试图将SQL查询翻译成ORM表达式(命中墙)(https://stackoverflow.com/a/41429995/82592) 。有时,将SQL保持原样,不管是字符串还是某种形式的数据库视图,都可以为您和未来的维护者节省大量浪费时间。 –

回答

0

我不打算回答我自己的问题,但嘿,这是我提出的另一种解决方案。关于这一问题的注意,我会同样高兴,如果我能找到一个漂亮的界面,将让我:

  • 使用原始的SQL查询
  • ,同时还采取外生型铸造的优势
  • 并且是能够将关联数据预加载到模式中。

有了Repo.loadEcto.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,在tests上有一些额外的例子。

当然,这远不是最好的解决方案,即使我能够找出这个或那个查询的正确Ecto语法,但对于原始查询来说,有一个很好的抽象/接口总是很方便。