Ecto의 "or_where"함정을 주의하십시오.
or_where
기능을 사용할 때 발생할 수 있는 오류를 강조하고 싶습니다. 이것은 Ecto의 결함이 아닙니다. 단지 이 함수가 Ecto.Query 함수와 함께 작동하는 방식에 대해 잘못된 결론에 도달할 수 있는 방법일 뿐입니다.때때로 나는 특정 리소스를 필터링하기 위해 양식을 작성하는 자신을 발견합니다. 이 양식을 사용하면 특정 속성이 특정 값과 같은 레코드(
select
생각) 또는 특정 속성이 in
값 목록( multi-select
생각)인 레코드를 필터링할 수 있습니다.예를 들어, 정제인 모든 약을 반환하고 메스꺼움이나 두통을 치료합니다. 즉, 태블릿이 아닌 레코드는 반환되지 않아야 합니다. 그리고 그들이 치료할 수 있는 조건 목록에 "메스꺼움"또는 "두통"이 없는 정제는 반환되지 않아야 합니다. 모든 쿼리에
AND
절을 추가하고 이것이 어떻게 중단되는지 강조하기 위해 부울 필드 available
도 추가합니다. 모든 쿼리에 대해 사용할 수 없는 레코드를 필터링하는 절을 포함합니다.다음은 스키마의 관련 부분입니다.
schema "medicines" do
...
field :type, :string
field :conditions, {:array, :string}
field :available, :boolean, default: false
end
첫 번째 부분은 쉽습니다.
type
이 tablet
인 레코드를 반환합니다.defmodule MedicineCupboard.MedicineFilter do
import Ecto.Query
def type_filter(query, %{"type" => type}) do
where(query, [m], m.type == ^type)
end
def type_filter(query, _no_type), do: query
end
available 절부터 시작하여 다음 SQL을 얻습니다.
SELECT m0."id", m0."name", m0."type", m0."conditions", m0."available"
FROM "medicines" AS m0 WHERE
(m0."available" = TRUE) AND (m0."type" = $1) ["tablet"]
두 번째 필터는 조금 더 복잡합니다. 조건 필터로 전달된 값 중 하나가
conditions
문자열 배열에 있는 의약품을 반환하려고 합니다.이 두 번째 필터를 아래와 같이 작성하는 것이 좋습니다.
defmodule MedicineCupboard.MedicineFilter do
...
# This does not build the intended query!
def condition_filter(query, %{"condition" => conditions_list}) do
conditions_list
|> Enum.reduce(query, fn condition, query ->
or_where(query, [m], ^condition in m.conditions)
end)
end
end
이 쿼리의 문제점은 빌드하는 SQL이 다음과 같다는 것입니다.
SELECT m0."id", m0."name", m0."type", m0."conditions", m0."available"
FROM "medicines" AS m0 WHERE
((m0."available" = TRUE))
OR ($1 = ANY(m0."conditions"))
OR ($2 = ANY(m0."conditions")) ["nausea", "headache"]
Ecto 쿼리는 다음과 같습니다.
#Ecto.Query<from m0 in MedicineCupboard.Medicine, where: m0.available == true,
or_where: ^"nausea" in m0.conditions, or_where: ^"headache" in m0.conditions>
사용 가능 여부에 관계없이 조건 필드에 두통이 포함된 모든 약은 반환됩니다. 가용성에 대한 조항은 조건에 대한 조항과 함께
OR
ed입니다.우리가 원하는 것은
available = true
AND nausea in conditions, or headache in conditions
입니다.Ecto.Query
or_where/3
함수는 이전의 모든 조건에 OR
절을 추가하여 효과적으로 다음과 같은 결과를 얻습니다.지금까지의 모든 조건을 충족하거나 이 새로운
or_where
절만 충족하십시오.이 문제를 해결한 방법은
subquery
을 사용하는 것입니다.defmodule MedicineCupboard.MedicineFilter do
import Ecto.Query
def type_filter(query, %{"type" => type}) do
where(query, [m], m.type == ^type)
end
def type_filter(query, _no_type), do: query
def condition_filter(query, %{"condition" => condition_list}) do
conditions_query = condition_list
|> Enum.reduce(Medicine, fn condition, query ->
or_where(query, [m], ^condition in m.conditions)
end)
|> select([:id])
where(query, [m], m.id in subquery(conditions_query))
end
end
이것은 하나의 하위 쿼리에서 다양한
or_where
절을 그룹화한 다음 들어오는 쿼리에 AND
where 절을 추가합니다. 여기에서 조건 하위 쿼리에서 ID가 반환되는 레코드만 반환합니다.결과 SQL은 다음과 같습니다.
SELECT m0."id", m0."name", m0."type", m0."conditions",
m0."available" FROM "medicines" AS m0 WHERE
(m0."available" = TRUE)
AND
(m0."id" IN
(SELECT sm0."id" FROM "medicines" AS sm0 WHERE
($1 = ANY(sm0."conditions")) OR ($2 = ANY(sm0."conditions"))
)
)
["nausea", "headache"]
Ecto 쿼리는 다음과 같습니다.
#Ecto.Query<from m0 in MedicineCupboard.Medicine, where: m0.available == true,
where: m0.id in subquery(#Ecto.Query<from m0 in MedicineCupboard.Medicine, or_where: ^"nausea" in m0.conditions, or_where: ^"headache" in m0.conditions, select: [:id]>)>
마지막으로 이제 유형 및 조건 필터를 결합하고 사용 가능 및 유형 필터가
AND
과 결합되고 OR
절이 포함된 조건 하위 쿼리도 AND
을 사용하여 기존 쿼리에 추가되는지 확인할 수 있습니다.params = %{"type" => "tablet", "conditions" => ["nausea", "headache"]}
Medicine
|> where([m], m.available == true)
|> MedicineFilter.type_filter(params)
|> MedicineFilter.condition_filter(params)
|> Repo.all()
그러면 다음과 같은 외부 쿼리가 제공됩니다.
#Ecto.Query<from m0 in MedicineCupboard.Medicine, where: m0.available == true,
where: m0.type == ^"tablet",
where: m0.id in subquery(#Ecto.Query<from m0 in MedicineCupboard.Medicine, or_where: ^"nausea" in m0.conditions, or_where: ^"headache" in m0.conditions, select: [:id]>)>
그리고 SQL:
SELECT m0."id", m0."name", m0."type", m0."conditions",
m0."available" FROM "medicines" AS m0
WHERE (m0."available" = TRUE)
AND (m0."type" = $1)
AND (m0."id" IN (
SELECT sm0."id" FROM "medicines" AS sm0 WHERE
($2 = ANY(sm0."conditions")) OR ($3 = ANY(sm0."conditions"))
)
) ["tablet", "fever", "headache"]
누군가에게 도움이 되기를 바라며 개선 사항에 대한 의견이 있으면 언제든지 공유해 주세요.
코드를 가지고 놀고 싶다면 여기 github의 샘플 프로젝트: https://github.com/Ivor/medicine_cupboard
Reference
이 문제에 관하여(Ecto의 "or_where"함정을 주의하십시오.), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/ivor/beware-ectos-orwhere-pitfall-50bb텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)