Use PostgreSQL array_to_string in Phoenix - postgresql

I have 2 n-n relationships between posts and tags tables. This is my query in Postgres:
SELECT t0.*, array_to_string(array_agg(t2.tag), ', ')
FROM "posts" AS t0
INNER JOIN "posts_tags" AS t1 ON (t0.id = t1.post_id)
INNER JOIN "tags" AS t2 ON (t1.tag_id = t2.id)
GROUP BY t0.id
I tried to use something similar in Ecto:
Repo.all(
from p in Post,
join: a in Post_Tag, on: p.id == a.post_id,
join: t in Tag, on: a.tag_id == t.id,
select: {p, array_to_string(array_agg(t.tag),', ')},
limit: ^limit,
offset: ^offset,
group_by: p.id
)
But I get this error:
(Ecto.Query.CompileError) `array_to_string(array_agg(t.tag()), ', ')` is not a valid query expression.

You will need to use a fragment/1 for the array_to_string(...) portion of your query. I have not tested it, but it should look something like:
fragment("array_to_string(array_agg(?), ', ')", t.tag)

Related

How to rewrite query?

I can easily select rows which I should update.
select
p.id,
(regexp_match( p.name, '\d+'))[1] as renum,
pd.quantity
from package p
left join package_detail pd on
pd.package_id = p.id and resource_type_id is null
where p.name like '%Bit%';
But how to write query to update quantity by renum from the result above?
I am not looking for the query. I am looking the rule to complete this task.
You can find background in the docs (https://www.postgresql.org/docs/current/sql-update.html ) but if you've got a query that gives the result you want, you can use that query as a correlated subquery in the update command:
UPDATE table1 t1 SET (col1, col2, col3) = (select t2.val1, t2.val2, t2.val3 from table2 t2 where t2.table1_id = t1.id)
WHERE t1.col1 IS NULL
In your case, this might take the form or something similar to:
UPDATE package p2 SET (quantity) = (
select ((regexp_match( p.name, '\d+'))[1])::integer + pd.quantity
from package p
left join package_detail pd on pd.package_id = p.id and resource_type_id is null
where p.name like '%Bit%'
and p2.id = p.id
and ((regexp_match( p.name, '\d+'))[1])::integer + pd.quantity IS NOT NULL )
WHERE p2.name like '%Bit%'

Strange Behaviour on Postgresql query

We created a view in Postgres and I am getting strange result.
View Name: event_puchase_product_overview
When I try to get records with *, I get the correct result. but when I try to get specific fields, I get wrong values.
I hope the screens attached here can explain the problem well.
select *
from event_purchase_product_overview
where id = 15065;
select id, departure_id
from event_puchase_product_overview
where id = 15065;
VIEW definition:
CREATE OR REPLACE VIEW public.event_puchase_product_overview AS
SELECT row_number() OVER () AS id,
e.id AS departure_id,
e.type AS event_type,
e.name,
p.id AS product_id,
pc.name AS product_type,
product_date.attribute AS option,
p.upcomming_date AS supply_date,
pr.date_end AS bid_deadline,
CASE
WHEN (pt.categ_id IN ( SELECT unnest(tt.category_ids) AS unnest
FROM ( SELECT string_to_array(btrim(ir_config_parameter.value, '[]'::text), ', '::text)::integer[] AS category_ids
FROM ir_config_parameter
WHERE ir_config_parameter.key::text = 'trip_product_flight.product_category_hotel'::text) tt)) THEN e.maximum_rooms
WHEN (pt.categ_id IN ( SELECT unnest(tt.category_ids) AS unnest
FROM ( SELECT string_to_array(btrim(ir_config_parameter.value, '[]'::text), ', '::text)::integer[] AS category_ids
FROM ir_config_parameter
WHERE ir_config_parameter.key::text = 'trip_product_flight.product_category_flight'::text) tt)) THEN e.maximum_seats
WHEN (pt.categ_id IN ( SELECT unnest(tt.category_ids) AS unnest
FROM ( SELECT string_to_array(btrim(ir_config_parameter.value, '[]'::text), ', '::text)::integer[] AS category_ids
FROM ir_config_parameter
WHERE ir_config_parameter.key::text = 'trip_product_flight.product_category_bike'::text) tt)) THEN e.maximum_bikes
ELSE e.maximum_seats
END AS departure_qty,
CASE
WHEN now()::date > pr.date_end AND po.state::text = 'draft'::text THEN true
ELSE false
END AS is_deadline,
pl.product_qty::integer AS purchased_qty,
pl.comments,
pl.price_unit AS unit_price,
rp.id AS supplier,
po.id AS po_ref,
po.state AS po_state,
po.date_order AS po_date,
po.user_id AS operator,
pl.po_state_line AS line_status
FROM event_event e
LEFT JOIN product_product p ON p.related_departure = e.id
LEFT JOIN product_template pt ON pt.id = p.product_tmpl_id
LEFT JOIN product_category pc ON pc.id = pt.categ_id
LEFT JOIN purchase_order_line pl ON pl.product_id = p.id
LEFT JOIN purchase_order po ON po.id = pl.order_id
LEFT JOIN purchase_order_purchase_requisition_rel prr ON prr.purchase_order_id = po.id
LEFT JOIN purchase_requisition pr ON pr.id = prr.purchase_requisition_id
LEFT JOIN res_partner rp ON rp.id = po.partner_id
LEFT JOIN ( SELECT p_1.id AS product_id,
pav.name AS attribute
FROM product_product p_1
LEFT JOIN product_attribute_value_product_product_rel pa ON pa.prod_id = p_1.id
LEFT JOIN product_attribute_value pav ON pav.id = pa.att_id
LEFT JOIN product_attribute pat ON pat.id = pav.attribute_id
WHERE pat.name::text <> ALL (ARRAY['Date'::character varying, 'Departure'::character varying]::text[])) product_date ON product_date.product_id = p.id
WHERE (p.id IN ( SELECT DISTINCT mrp_bom_line.product_id
FROM mrp_bom_line)) AND p.active
ORDER BY e.id, pt.categ_id, p.id;
If I add new event_event or new product_product I'll get a new definition of row_number in my view, then the column ID of my view is not stable.
at least you can't use row_number as Id of the view,
If you insist to use row_number, you can use the Order By "creation DATE" by this way all new records will be as last lines in the view and this will not change the correspondency between ID (row_number) and other columns.
Hope that helps !
Very likely the execution plan of your query depends on the columns you select. Compare the execution plans!
Your id is generated using the row_number window function. Now window functions are executed before the ORDER BY clause, so the order will depend on the execution plan and hence on the columns you select.
Using row_number without an explicit ordering doesn't make any sense.
To fix that, don't use
row_number() OVER ()
but
row_number() OVER (ORDER BY e.id, pt.categ_id, p.id)
so that you have a reliable ordering.
In addition, you should omit the ORDER BY clause at the end.

How to avoid duplicates in the STRING_AGG function

My query is below:
select
u.Id,
STRING_AGG(sf.Naziv, ', ') as 'Ustrojstvena jedinica',
ISNULL(CONVERT(varchar(200), (STRING_AGG(TRIM(p.Naziv), ', ')), 121), '')
as 'Partner',
from Ugovor as u
left join VezaUgovorPartner as vup
on vup.UgovorId = u.Id AND vup.IsDeleted = 'false'
left join [TEST_MaticniPodaci2].dbo.Partner as p
on p.PartnerID = vup.PartnerId
left join [dbo].[VezaUgovorUstrojstvenaJedinica] as vuu
on vuu.UgovorId = u.Id
left join [TEST_MaticniPodaci2].hcphs.SifZavod as sf
on sf.Id = vuu.UstrojstvenaJedinicaId
left join [dbo].[SifVrstaUgovora] as vu
on u.VrstaUgovoraId = vu.Id
group by u.Id, sf.Naziv
My problem is that I can have more sf.Naziv and also only one sf.Naziv so I have to check if there is one and then show only one result and if there is two or more to show more results. But for now the problem is when I have only one sf.Naziv, query returns two sf.Naziv with the same name because in first STRING_AGG i have more records about p.Naziv.
I have no idea how to implement DISTINCT into STRING_AGG function
Any other solutions are welcome, but I think it should work with DISTINCT function.
It looks like distinct won't work, so what you should do is put your whole query in a subquery, remove the duplicates there, then do STRING_AGG on the data that has no duplicates.
SELECT STRING_AGG(data)
FROM (
SELECT DISTINCT FROM ...
)
I like this format for distinct values:
(d is required but you can use any variable name there)
SELECT STRING_AGG(LoadNumber, ',') as LoadNumbers FROM (SELECT DISTINCT LoadNumber FROM [ASN]) d
A sample query to remove duplicates while using STRING_AGG().
WITH cte AS (
SELECT DISTINCT product
FROM activities
)
SELECT STRING_AGG(product, ',') products
FROM cte;
Or you can use the following query. The result is same -
SELECT STRING_AGG(product, ',') as products
from (
SELECT product
FROM Activities
GROUP BY product
) as _ ;

Postgres Lateral Join Multiple Tables to Limit Results

I have a question regarding lateral joins in Postgres.
My use case is I want to return a dataset that combines multiple tables but limits the number of publications and reviews returned. The simplified table schema is below
Table Author
ID
NAME
Table Review
ID
AUTHOR_ID
PUBLICATION_ID
CONTENT
Table Publication
ID
NAME
Table AuthorPublication
AUTHOR_ID
PUBLICATION_ID
So for my initial query I have this:
SELECT
a.id,
a.name
json_agg (
json_build_object (
'id', r.id,
'content', r.content
)
) AS reviews,
json_agg (
json_build_object(
'id', p.id,
'name', p.name
)
) AS publications
FROM
public.author a
INNER JOIN
public.review r ON r.author_id = a.id
INNER JOIN
public.author_publication ap ON ap.author_id = a.id
INNER JOIN
public.publication p ON p.id = ap.publication_id
WHERE
a.id = '1'
GROUP BY
a.id
This returns the data I need, for example I get the author's name, id and a list of all of their reviews and publications they belong to. What I want to be able to do is limit the number of reviews and publications. For example return 5 reviews, and 3 publications.
I tried doing this with a lateral query but am running into an issue where if I do a single lateral query it works as intended.
so like:
INNER JOIN LATERAL
(SELECT r.* FROM public.review r WHERE r.author_id = a.id LIMIT 5) r ON TRUE
This returns the dataset with only 5 reviews - but if I add a second lateral query
INNER JOIN LATERAL
(SELECT ap.* FROM public.author_publication ap WHERE ap.author_id = a.id LIMIT 5) r ON TRUE
I now get 25 results for both reviews and publications with repeated/duplicated data.
So my question is are you allowed to have multiple lateral joins in a single PG query and if not what is a good way to go about limiting the number of results from a JOIN?
Thanks!
You must change your query to something like this:
SELECT
a.id,
a.name,
(
SELECT
json_agg ( r )
FROM (
SELECT
json_build_object (
'id', r.id,
'content', r.content
) AS r
FROM public.review r
WHERE r.author_id = a.id
ORDER BY r.id
LIMIT 5
) AS a
) AS reviews,
(
SELECT
json_agg (p)
FROM (
SELECT
json_build_object(
'id', p.id,
'name', p.name
) AS p
FROM public.author_publication ap
INNER JOIN public.publication p ON p.id = ap.publication_id
WHERE ap.author_id = a.id
ORDER BY p.id
LIMIT 3
) AS a
) AS publications
FROM
public.author a
WHERE
a.id = '1'

Providing subquery in query parameters

I need to implement the following query with Ecto, however it doesn't compile saying the subquery statement is not a valid expression.
query = from p in Passphrase,
left_join: pi in PassphraseInvalidation, on: p.id == pi.target_passphrase_id,
join: u in User, on: p.user_id == u.id,
where: p.passkey == ^passkey and
is_nil(pi.inserted_at) and
p.inserted_at > ago(5, "month") and
p.inserted_at > subquery(from pr in PasswordReset,
where: pr.user_id == u.id,
select: max(pr.inserted_at)),
select: {u, p}
user = Repo.one!(query)
(Ecto.Query.CompileError) subquery(from(pr in PasswordReset, where: pr.user_id() == ^u.id(), select: max(pr.inserted_at()))) is not a valid query expression.
The equivalent PgSQL query would be something like:
SELECT u.*, p.*
FROM passphrases p
LEFT OUTER JOIN passphrase_invalidations pi ON p.id = pi.target_passphrase_id
INNER JOIN users u ON p.user_id = u.id
WHERE p.passkey = '/* some passkey */' AND
pi.inserted_at IS NULL AND
u.id = 2 AND
p.inserted_at > (SELECT max(pr.inserted_at)
FROM password_resets pr
WHERE pr.user_id = u.id)
Is there a way to implement that, or am I missing something?
As of Ecto 2.1.0, according to https://hexdocs.pm/ecto/Ecto.Query.html#subquery/1, subqueries cannot be use in where:
Subqueries are currently only supported in the from and join fields.
You can use fragment and put the entire subquery in it for now:
p.inserted_at > fragment("(SELECT max(pr.inserted_at) FROM password_resets pr WHERE pr.user_id = u.id)")
where just became available (Ecto 3.4.3)
https://hexdocs.pm/ecto/Ecto.Query.html#subquery/2