Ecto - Выберите поле массива из базы данных postgresql - PullRequest
0 голосов
/ 07 августа 2020

У меня есть этот существующий запрос к базе данных, который принимает входные параметры поиска (массив shop_id s, содержащий продукт, и массив categories, который гарантирует, что возвращаются только продукты, которые имеют эти категории) и возвращает правильные продукты и их магазины:

  def create_unique_shop_query_no_keyword(categories, shop_ids) do
    products_shops_categories =
      from p in Product,
        join: ps in ProductShop,
        on: p.id == ps.p_id,
        join: s in Shop,
        on: s.id == ps.s_id,
        join: pc in ProductCategory,
        on: p.id == pc.p_id,
        join: c in Subcategory,
        on: c.id == pc.c_id,
        distinct: s.id,
        where: c.id in ^categories,
        where: s.id in ^shop_ids,
        group_by: [s.id, s.name],
        select: %{
          products:
            fragment(
              "json_agg( DISTINCT (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)) AS products",
              p.id,
              p.name,
              p.brand,
              p.description,
              p.image,
              p.rating,
              p.number_of_votes,
              ps.not_in_shop_count,
              ps.is_in_shop_count,
              ps.price,
              p.not_vegan_count,
              p.vegan_count
            ),
          shop:
            fragment(
              "json_agg( DISTINCT (?, ?, ST_X(?), ST_Y(?), ?, ?, ?, ?, ?)) AS shop",
              s.id,
              s.name,
              s.point,
              s.point,
              s.place_id,
              s.street,
              s.suburb,
              s.city,
              s.street_number
            )
        }
  end

Столбцы таблицы productCategory: id, p_id, c_id, которые представляют каждый продукт p_id, имеющий от 1 до многих категорий c_id. Я хочу добавить к результатам еще одно поле: массив категорий, которые есть у продукта. Я пробовал это ниже, но он говорит о синтаксической ошибке. Я вставлю ошибку в конец вопроса. Что я делаю не так?

def create_unique_shop_query_no_keyword(categories, shop_ids) do
    products_shops_categories =
      from p in Product,
        join: ps in ProductShop,
        on: p.id == ps.p_id,
        join: s in Shop,
        on: s.id == ps.s_id,
        join: pc in ProductCategory,
        on: p.id == pc.p_id,
        join: c in Subcategory,
        on: c.id == pc.c_id,
        distinct: s.id,
        where: c.id in ^categories,
        where: s.id in ^shop_ids,
        group_by: [s.id, s.name],
        select: %{
          products:
            fragment(
              "json_agg( DISTINCT (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)) AS products",
              p.id,
              p.name,
              p.brand,
              p.description,
              p.image,
              p.rating,
              p.number_of_votes,
              ps.not_in_shop_count,
              ps.is_in_shop_count,
              ps.price,
              p.not_vegan_count,
              p.vegan_count,
              fragment("json_agg((?)) AS categories", pc.c_id)
            ),
          shop:
            fragment(
              "json_agg( DISTINCT (?, ?, ST_X(?), ST_Y(?), ?, ?, ?, ?, ?)) AS shop",
              s.id,
              s.name,
              s.point,
              s.point,
              s.place_id,
              s.street,
              s.suburb,
              s.city,
              s.street_number
            )
        }
  end

error:

[error] #PID<0.516.0> running VepoWeb.Endpoint (connection
#PID<0.515.0>, stream id 1) terminated Server: 192.168.0.105:4000 (http) Request: GET /products?categories[]=1&categories[]=2&categories[]=3&keyword=null&latitude=-37.6739483&longitude=176.1662587&distanceFromLocationValue=10&distanceFromLocationUnit=%22kilometers%22
** (exit) an exception was raised:
    ** (Postgrex.Error) ERROR 42601 (syntax_error) syntax error at or near "AS"

query: SELECT DISTINCT ON (s2."id") json_agg( DISTINCT (p0."id", p0."name", p0."brand", p0."description", p0."image", p0."rating", p0."number_of_votes", p1."not_in_shop_count", p1."is_in_shop_count", p1."price", p0."not_vegan_count", p0."vegan_count", json_agg((p3."c_id")) AS categories)) AS products, json_agg( DISTINCT (s2."id", s2."name", ST_X(s2."point"), ST_Y(s2."point"), s2."place_id", s2."street", s2."suburb", s2."city", s2."street_number")) AS shop FROM "products" AS p0 INNER JOIN "product_shops" AS p1 ON p0."id" = p1."p_id" INNER JOIN "shops" AS s2 ON s2."id" = p1."s_id" INNER JOIN "product_categories" AS p3 ON p0."id" = p3."p_id" INNER JOIN "subcategories" AS s4 ON s4."id" = p3."c_id" WHERE (s4."id" = ANY($1)) AND (s2."id" = ANY($2)) GROUP BY s2."id", s2."name"
    (ecto_sql 3.4.5) lib/ecto/adapters/sql.ex:593: Ecto.Adapters.SQL.raise_sql_call_error/1
    (ecto_sql 3.4.5) lib/ecto/adapters/sql.ex:526: Ecto.Adapters.SQL.execute/5
    (ecto 3.4.5) lib/ecto/repo/queryable.ex:192: Ecto.Repo.Queryable.execute/4
    (ecto 3.4.5) lib/ecto/repo/queryable.ex:17: Ecto.Repo.Queryable.all/3
    (vepo 0.1.0) lib/vepo_web/services/product_service.ex:392: VepoWeb.ProductService.get_products_from_search/1
    (vepo 0.1.0) lib/vepo_web/controllers/product_controller.ex:1: VepoWeb.ProductController.action/2
    (vepo 0.1.0) lib/vepo_web/controllers/product_controller.ex:1: VepoWeb.ProductController.phoenix_controller_pipeline/2
    (phoenix 1.5.4) lib/phoenix/router.ex:352: Phoenix.Router.__call__/2
    (vepo 0.1.0) lib/vepo_web/endpoint.ex:1: VepoWeb.Endpoint.plug_builder_call/2
    (vepo 0.1.0) lib/plug/debugger.ex:132: VepoWeb.Endpoint."call (overridable 3)"/2
    (vepo 0.1.0) lib/vepo_web/endpoint.ex:1: VepoWeb.Endpoint.call/2
    (phoenix 1.5.4) lib/phoenix/endpoint/cowboy2_handler.ex:65: Phoenix.Endpoint.Cowboy2Handler.init/4
    (cowboy 2.8.0) /Users/benjaminfarquhar/Api/vepo/deps/cowboy/src/cowboy_handler.erl:37: :cowboy_handler.execute/2
    (cowboy 2.8.0) /Users/benjaminfarquhar/Api/vepo/deps/cowboy/src/cowboy_stream_h.erl:300: :cowboy_stream_h.execute/3
    (cowboy 2.8.0) /Users/benjaminfarquhar/Api/vepo/deps/cowboy/src/cowboy_stream_h.erl:291: :cowboy_stream_h.request_process/3
    (stdlib 3.13) proc_lib.erl:226: :proc_lib.init_p_do_apply/3

Попытка ответа Алексея Матюшкина:

def create_unique_shop_query_no_keyword(categories, shop_ids) do
    products_shops_categories =
      from p in Product,
        join: ps in ProductShop,
        on: p.id == ps.p_id,
        join: s in Shop,
        on: s.id == ps.s_id,
        join: pc in ProductCategory,
        on: p.id == pc.p_id,
        join: c in Subcategory,
        on: c.id == pc.c_id,
        distinct: s.id,
        where: c.id in ^categories,
        where: s.id in ^shop_ids,
        group_by: [s.id, s.name],
        select: %{
          products:
            fragment(
              "json_agg( DISTINCT (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)) AS products, json_agg((?)) AS categories",
              p.id,
              p.name,
              p.brand,
              p.description,
              p.image,
              p.rating,
              p.number_of_votes,
              ps.not_in_shop_count,
              ps.is_in_shop_count,
              ps.price,
              p.not_vegan_count,
              p.vegan_count,
              pc.c_id
            ),
          shop:
            fragment(
              "json_agg( DISTINCT (?, ?, ST_X(?), ST_Y(?), ?, ?, ?, ?, ?)) AS shop",
              s.id,
              s.name,
              s.point,
              s.point,
              s.place_id,
              s.street,
              s.suburb,
              s.city,
              s.street_number
            )
        }
  end

Это данные в моей таблице productCategories, где P - p_id, а c - c_id:

ID  P   C
1   1   5
2   2   3
3   2   4
4   2   5
5   3   1
6   3   2
7   3   3

Ответы [ 2 ]

1 голос
/ 07 августа 2020

Я хотел бы предоставить вам похожие варианты использования. Здесь я начинаю с создания subquery, который вернет мне parent_uuid, который будет p.id в вашем варианте использования.

  1. В подзапросе я также возвращаю список categories которые я хочу добавить для каждого продукта.

  2. Затем я присоединяюсь к этому подзапросу с Organization, который в вашем случае использования будет Product через идентификатор продукта

  3. Тогда я могу предоставить categories в самом фрагменте.

  def product_with_categories do
    from o in Organization,
      inner_join: po in Organization, on: o.platform_uuid == po.parent_uuid,
      group_by: [po.parent_uuid],
      select: %{
        product_uuid: po.parent_uuid,
        categories: fragment("SELECT json_agg(DISTINCT(?, ?)) as categories", o.platform_uuid, o.name)
      }
  end

  def query do
  from pos in subquery(product_with_categories),## Product Category Subquery
      join: o in Organization, ## Join with product with uuid returned in subquery
      on: o.parent_uuid == pos.product_uuid,
      join: c in Course, ## This is join with shop
      on: c.organization_uuid == o.platform_uuid,
      group_by: [o.platform_uuid],
      select: %{
        products: fragment("SELECT json_agg(DISTINCT(?, ?, cast(? as text)::jsonb))",
          o.platform_uuid, o.name, pos.categories),
        shop_of_courses: fragment("json_agg(DISTINCT(?, ?))", c.platform_uuid, c.name)
      }
  end

Результат имеет ту же структуру, что и вы


   products: [
      %{
        "f1" => "e73e8953-7cdd-4a01-a5da-46e0c0ee6b55",
        "f2" => "Another Product",
        "f3" => [
          %{
            "f1" => "7971ca8b-98b4-4c41-aad7-486bf761d8b6",
            "f2" => "Another category"
          }
        ]
      }
    ],
    shop_of_courses: [
      %{"f1" => "f415b90f-8aa9-4f89-b2c0-2544fe7792eb", "f2" => "3-D Design SHOP"},
      %{
        "f1" => "fcd4df71-2730-4b06-bf2e-066308ab2d06",
        "f2" => "2-D Design and Color SHOP"
      }
    ]
  }
]

Поскольку тестировать без запуска env сложно, я также предоставляю псевдокод, который упростит вам реализацию на вашей стороне.

PSEUDOCODE

  def product_with_categories do
    from p in Product,
      join: pc in ProductCategory,
      on: pc.p_id == p.id,
      group_by: [p.id],
      select: %{
        product_id: p.id,
        categories: fragment("SELECT json_agg(DISTINCT(?, ?)) as categories", pc.id, pc.name) ## if pc has a name or add as per the requirement
      }
  end

  ## Than do the join with this subquery in your existing query.
  join: pcategories in subquery(product_with_categories),
     on: pcategories.id == p.id


  ### and than in select query
  products:
            fragment(
              "json_agg( DISTINCT (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, cast(? as text)::jsonb)) AS products",
              p.id,
              p.name,
              p.brand,
              p.description,
              p.image,
              p.rating,
              p.number_of_votes,
              ps.not_in_shop_count,
              ps.is_in_shop_count,
              ps.price,
              p.not_vegan_count,
              p.vegan_count,
              pcategories.categories
            )

1 голос
/ 07 августа 2020

Нельзя вкладывать фрагменты, также я сомневаюсь, что это было первоначальным намерением. В запросе products у вас в настоящее время есть

fragment(
  "json_agg( DISTINCT (...)) AS products",
    p.id,
    ...,
    fragment("json_agg((?)) AS categories", pc.c_id)
)

Я думаю, вы хотели вместо этого

fragment(
  "json_agg(DISTINCT (...)) AS products, json_agg((?)) AS categories",
    p.id,
    ...,
    pc.c_id
)

Если бы вы предоставили MCVE , я бы, вероятно, проверил его, но он, вероятно, должен работать именно так, или в любом случае это хорошая отправная точка для дальнейшей настройки.

...