Ecto - select all fields from all tables in a 5 table join - ecto
I have a products table, a categories table, and a shops table, as well as joining tables product_shops, and product_categories.
I want to select all products that have a category which is in the conn.query_params, that are in shops that are within a certain distance which is specified in the conn.query_params. And I want to return pretty much every field of the product, category, and shop that was selected.
I have this:
get "/products" do
query = conn.query_params
point = %Geo.Point{coordinates: {String.to_float(query["longitude"]), String.to_float(query["latitude"])}, srid: 4326}
shops = within(Shop, point, String.to_float(query["distanceFromPlaceValue"]) * 1000) |> order_by_nearest(point) |> select_with_distance(point) |> Api.Repo.all
categories = Enum.map(query["categories"], fn(x) -> String.to_integer(x) end)
shop_ids = Enum.map(shops, fn(x) -> x.id end)
query1 = 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 Category, on: c.id == pc.c_id,
where: Enum.member?(categories, c.id),
where: Enum.member?(shop_ids, s.id),
select: p, c, s,
group_by s,
order_by s.distance
In the code above, the shops variable holds all the shops that are within the specified distance.
this is inside shop.ex to get all shops within distance, ordered from the closest shop:
def within(query, point, radius_in_m) do
{lng, lat} = point.coordinates
from(shop in query, where: fragment("ST_DWithin(?::geography, ST_SetSRID(ST_MakePoint(?, ?), ?), ?)", shop.point, ^lng, ^lat, ^point.srid, ^radius_in_m))
end
def order_by_nearest(query, point) do
{lng, lat} = point.coordinates
from(shop in query, order_by: fragment("? <-> ST_SetSRID(ST_MakePoint(?,?), ?)", shop.point, ^lng, ^lat, ^point.srid))
end
def select_with_distance(query, point) do
{lng, lat} = point.coordinates
from(shop in query, select: %{shop | distance: fragment("ST_Distance_Sphere(?, ST_SetSRID(ST_MakePoint(?,?), ?))", shop.point, ^lng, ^lat, ^point.srid)})
end
This is the shop schema and the distance field gets populated when the select_with_distance variable is called.
#derive {Poison.Encoder, only: [:name, :place_id, :point]}
schema "shops" do
field :name, :string
field :place_id, :string
field :point, Geo.Point
field :distance, :float, virtual: true
timestamps()
end
My current error is in the select: p, c, s line as I'm unsure how to select the whole lot:
== Compilation error on file lib/api/router.ex ==
** (SyntaxError) lib/api/router.ex:153: syntax error before: c
(elixir) lib/kernel/parallel_compiler.ex:117: anonymous fn/4 in Kernel.ParallelCompiler.spawn_compilers/1
Also unsure if the group_by should be the shop. I just know I want to return all fields and only unique fields, ordered by proximity of the shop. I feel most of the way there but a bit stuck on the select, group_by, and order_by.
EDIT: Thanks Dogbert for fixing those two issues in the comments.
currently code is:
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 Category, on: c.id == pc.c_id,
where: c.id in ^categories,
where: s.id in ^shop_ids,
select: {p, c, s}
group_by s,
order_by s.distance
I have this error now:
** (Ecto.Query.CompileError) `order_by(s.distance())` is not a valid query expression.
* If you intended to call a database function, please check the documentation
for Ecto.Query to see the supported database expressions
* If you intended to call an Elixir function or introduce a value,
you need to explicitly interpolate it with ^
expanding macro: Ecto.Query.group_by/2
lib/api/router.ex:155: Api.Router.do_match/4
(elixir) lib/kernel/parallel_compiler.ex:117: anonymous fn/4 in Kernel.ParallelCompiler.spawn_compilers/1
Related
How do I perform a subquery in cosmosdb where statement?
I'm new to cosmosdb so bear with me. Here's what I'm trying to do. I have a document that has an array value. Here's the query select value c.NodeIds from c where c.PartitionKey = 'contentlink' and c.id = '123' I want to use this result in my where statement, but I'm having trouble understanding how to do this. Here's what I tried to come up with. SELECT c.* FROM c where c.PartitionKey = 'node' and c.id in (select value n from n in c.NodeIds where c.PartitionKey = 'contentlink' and c.id = '123')
TableAlias doesn't work with multiple joins
TableAlias isn't working with multiple joins. The query: var q = Db.From<Blog>(Db.TableAlias("b")) .LeftJoin<Blog, BlogToBlogCategory>((b,btb)=> b.Id == btb.BlogId, Db.TableAlias("btbc")) .Join<BlogToBlogCategory, BlogCategory>((bt,bc)=>bt.BlogCategoryId == bc.Id, Db.TableAlias("cats")) .GroupBy(x => x.Id); .Select("b.*, json_agg(cats) as BlogCategoriesJson"); var results = Db.Select<BlogQueryResponse>(q); Generates this SQL: SELECT b.*, json_agg(cats) as BlogCategoriesJson FROM "blog" "b" LEFT JOIN "blog_to_blog_category" "btbc" ON ("b"."id" = "btbc"."blog_id") INNER JOIN "blog_category" "cats" ON ("blog_to_blog_category"."blog_category_id" = "cats"."id") GROUP BY "b"."id" This causes error because it is referencing "blog_to_blog_category" instead of btbc
The Db.TableAlias() only provides an alias for the target join table, your inner join does not specify the alias to use for the source table so it references the full table name as expected. You can use Sql.TableAlias() in your LINQ Expression to reference a table alias, e.g: var q = Db.From<Blog>(Db.TableAlias("b")) .LeftJoin<Blog, BlogToBlogCategory>((b,btb)=> b.Id == btb.BlogId, Db.TableAlias("btbc")) .Join<BlogToBlogCategory, BlogCategory>((bt,bc)=> Sql.TableAlias(bt.BlogCategoryId, "btbc") == bc.Id, Db.TableAlias("cats")) .GroupBy(x => x.Id); .Select("b.*, json_agg(cats) as BlogCategoriesJson");
Having() count of id's on joined table
I am trying to make this query in OrmLite: select b.* from blog b join blog_to_blog_category btbc on b.id = btbc.blog_id join blog_category bc on btbc.blog_category_id = bc.id where b.url like '%.com' and bc.id in (100, 583) group by b.id having count(distinct bc.id ) = 1 I can't figure out how to get the Having() method structured. I can see there is a Sql.CountDistinct() method but I can't figure out how to use it with Having(). I figure I need to do something along the lines of: var q = db .From<Blog>() .LeftJoin<BlogToBlogCategory>() .Join<BlogToBlogCategory, BlogCategory>() .Where<BlogCategory>(x => Sql.In(x.Id, 100, 583)) .GroupBy<Blog>(bc => bc.Id) .Having(x => Sql.CountDistinct("blog_category.id") == "2") This gives error: 42883: operator does not exist: bigint = text I can't see how to type it to take a table column name and return a number for comparison. Is this query possible? EDIT I got around it by setting having expression explicitly q.HavingExpression = $"having count(distinct {q.Table<BlogCategory>()}.{q.Column<BlogCategory>(bc => bc.Id)}) = 2"; I am still curious though if it is possible to do this with fluent api.
I've just added multiple typed table overloads for Having() in the latest v5.11.1 that's now available on MyGet which will allow you to reference a joined table properties in a typed expression, e.g: var q = db .From<Blog>() .LeftJoin<BlogToBlogCategory>() .Join<BlogToBlogCategory, BlogCategory>() .Where<BlogCategory>(x => Sql.In(x.Id, 100, 583)) .GroupBy<Blog>(bc => bc.Id) .Having<BlogCategory>(x => Sql.CountDistinct(x.Id) == 2)
HYBRIS Flexible Query to get all the products from Order Model
My requirement is to get the list of customers who have ordered an old product. Here for the old product, we are using an attribute "endproduct". I am able to get all the customers who have placed orders. BUT I don't know how to create a query to get products from Order Model. I have run this query : SELECT distinct {c:uid},{aeo:product} from {customer as c JOIN order as o on {c:pk}={o:user}JOIN AbstractOrder as ao on {o:pk}={ao:pk} JOIN AbstractOrderEntry as aeo on {ao:pk}={aeo:pk}} Because AbstractOrderEntryModel has a product attribute.
Try like SELECT distinct {u:uid},{p:name} FROM { Order AS o JOIN OrderEntry AS oe ON {o.pk} = {oe.order} JOIN Product AS p ON {p.pk} = {oe.product} and {p.endproduct} = '1' JOIN User AS u ON {o.user} = {u.pk}} Change endproduct condition as per your requirement.
Try below query, it should give expected results. select {c.uid},{p.code} from {Customer as c}, {Order as o}, {Product as p}, {AbstractOrderEntry as ao} where {o.user} = {c.pk} and {o.pk} = {ao.order} and {ao.product} = {p.pk}
JSON encode GEO.Point from geo library as human readable form
I have this schema which has a Geo Geo.Point: defmodule Api.Shop do use Ecto.Schema import Ecto.Changeset import Api.Repo import Ecto.Query #derive {Poison.Encoder, only: [:name, :place_id, :geo_json, :distance]} schema "shops" do field :name, :string field :place_id, :string field :point, Geo.Point field :geo_json, :string, virtual: true field :distance, :float, virtual: true timestamps() end def encode_model(shop) do %Api.Shop{shop | geo_json: Geo.JSON.encode(shop.point) } end defimpl Poison.Encoder, for: Api.Shop do def encode(shop, options) do shop = Api.Shop.encode_model(shop) Poison.Encoder.Map.encode(Map.take(shop, [:id, :name, :geo_json]), options) end end def changeset(shop, params \\ %{}) do shop |> cast(params, [:name, :place_id, :point]) |> validate_required([:name, :place_id, :point]) |> unique_constraint(:place_id) end...... end And when I return the shop.point field in a query: def create_query_no_keyword(categories, shop_ids) do products_shops_categories = from p in Product, distinct: p.id, 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, where: c.id in ^categories, where: s.id in ^shop_ids, group_by: [p.id, p.name, p.brand], select: %{product: p, categories: fragment("json_agg( DISTINCT (?, ?)) AS category", c.id, c.name), shops: fragment("json_agg( DISTINCT (?, ?, ?)) AS shop", s.id, s.name, s.point)} end What gets returned is actually 0101000020E6100000A3BDB0EB0DD9654030AC2C1BE76D42C0 which is the wrong format - WKB. I'm looking to encode as WKT which has readable coordinates. How do I get s.point to be WKT format and thus have coordinates, when it is returned by the query?
I found this Stack Exchange GIS answer to be the solution: use this for point object: SELECT ST_AsText(the_geom) FROM myTable; and viewing X,Y and geom object: SELECT ST_X(the_geom), ST_Y(the_geom), ST_AsText(the_geom) FROM myTable; The Geo library is using PostGIS and the solution was PostGIS specific. You need to select the column using ST_AsText, or ST_X and ST_Y from PostGIS. My select statement changed to this: select: %{product: p, categories: fragment("json_agg( DISTINCT (?, ?)) AS category", c.id, c.name), shops: fragment("json_agg( DISTINCT (?, ?, ST_X(?), ST_Y(?))) AS shop", s.id, s.name, s.point, s.point)}