2017-10-09 9 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)結果を返すと、プリロードまたはこの結果をスキーマに保存しますか?私が考えている

インターフェースのようなものです:Ecto.Adapters.SQL.query/4の例を1として

q = "SELECT * FROM files WHERE file_id = $1", ^file_id 
file = Repo.get(q) |> Repo.preload() 

file.file_id # Returns file id 

私は1どのように約2を達成することができますように、それが見えますか?

私の最終目標は、file_idとstorage_idを安全にキャストし、%File {}スキーマを正しく使用し、ロードされた関連付けをfile.modulesにして、上記のネストされたSQLクエリを使用することです。

fragmentを使用して正しくキャストできる、より良いインターフェースがストアドプロシージャまたはビューから得られることに注意してください。しかし、私はまだそのデータをスキーマに「プリロードする」ことに問題があると感じています。

+0

は、なぜあなたは別に2つのクエリを実行しませんか?あなたは多くを失うことはありません。 –

+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は、このインターフェイスを処理するライブラリです。上記の例は、最も複雑なケースです。カスタムキャスターとカスタムローダーが必要な場合です。ほとんどの場合、ヘクターのデフォルトローダーはうまくいくでしょうし、潜在的に危険な入力があるときはいつでもカスタムキャスターが必要になります。

コードはhereで、testsにいくつかの余分な例があります。

もちろんこれは最適な解決策ではありませんが、このクエリまたはそのクエリの正しいEcto構文を理解することはできますが、生のクエリに対して素晴らしい抽象化/インターフェイスを持たせると便利です。

関連する問題