ORM thật tuyệt vời, cho đến khi chúng rò rỉ . Tất cả đều làm, cuối cùng. Ecto còn trẻ (tức là nó chỉ đạt được khả năng OR
nơi các mệnh đề kết hợp với nhau 30 ngày trước
), vì vậy nó chỉ đơn giản là chưa đủ trưởng thành để phát triển một API xem xét các bước chuyển đổi SQL nâng cao.
Khảo sát các tùy chọn khả thi, bạn không đơn độc trong yêu cầu. Không thể hiểu danh sách trong các phân đoạn (cho dù là một phần của order_by
hoặc where
hoặc bất kỳ nơi nào khác) đã được đề cập trong Ecto issue # 1485
, trên StackOverflow
, trên Diễn đàn Elixir
và điều này bài đăng trên blog
. Sau đó là hướng dẫn cụ thể. Thêm vào đó trong một chút. Trước tiên, hãy thử một số thử nghiệm.
Thử nghiệm số 1: Trước tiên, người ta có thể thử sử dụng Kernel.apply/3
để chuyển danh sách tới fragment
, nhưng điều đó sẽ không hoạt động:
|> order_by(Kernel.apply(Ecto.Query.Builder, :fragment, ^ids))
Thử nghiệm số 2: Sau đó, có lẽ chúng ta có thể xây dựng nó bằng thao tác chuỗi. Làm thế nào về việc cung cấp fragment
một chuỗi được tạo sẵn khi chạy với đủ chỗ dành sẵn để nó lấy từ danh sách:
|> order_by(fragment(Enum.join(["FIELD(id,", Enum.join(Enum.map(ids, fn _ -> "?" end), ","), ")"], ""), ^ids))
Cái nào sẽ tạo ra FIELD(id,?,?,?)
đã cho ids = [1, 2, 3]
. Không, điều này cũng không hoạt động.
Thử nghiệm số 3: Tạo toàn bộ, SQL cuối cùng được xây dựng từ id, đặt các giá trị ID thô trực tiếp vào chuỗi đã soạn. Ngoài việc kinh khủng, nó cũng không hoạt động:
|> order_by(fragment(Enum.join(["FIELD(id,", Enum.join(^ids, ","), ")"], "")))
Thử nghiệm số 4: Điều này đưa tôi đến với bài đăng trên blog mà tôi đã đề cập. Trong đó, tác giả hack xung quanh việc thiếu or_where
bằng cách sử dụng một tập hợp các macro được xác định trước dựa trên số lượng điều kiện để kết hợp với nhau:
defp orderby_fragment(query, [v1]) do
from u in query, order_by: fragment("FIELD(id,?)", ^v1)
end
defp orderby_fragment(query, [v1,v2]) do
from u in query, order_by: fragment("FIELD(id,?,?)", ^v1, ^v2)
end
defp orderby_fragment(query, [v1,v2,v3]) do
from u in query, order_by: fragment("FIELD(id,?,?,?)", ^v1, ^v2, ^v3)
end
defp orderby_fragment(query, [v1,v2,v3,v4]) do
from u in query, order_by: fragment("FIELD(id,?,?,?)", ^v1, ^v2, ^v3, ^v4)
end
Mặc dù điều này hoạt động và sử dụng ORM "với hạt" có thể nói như vậy, nhưng nó yêu cầu bạn phải có một số lượng trường có sẵn hữu hạn, có thể quản lý được. Đây có thể là một yếu tố thay đổi cuộc chơi.
Khuyến nghị của tôi:đừng cố gắng tung hứng những rò rỉ của ORM. Bạn biết truy vấn tốt nhất. Nếu ORM không chấp nhận nó, hãy viết nó trực tiếp bằng SQL thô và ghi lại lý do tại sao ORM không hoạt động. Che chắn nó sau một chức năng hoặc mô-đun để bạn có thể bảo lưu quyền thay đổi cách triển khai nó trong tương lai. Một ngày nào đó, khi ORM bắt kịp, bạn có thể viết lại nó một cách độc đáo mà không ảnh hưởng đến phần còn lại của hệ thống.