I'm trying to return a JSON object from postgres that looks something like this:
[
{id: 1, name: "some organisation name", alias: [{alias:"alt name"}, {alias:"another name"}]}
...]
My query below works fine, except that I want to add a where condition, referencing the org_aliases table
SELECT json_build_object('id', table1.id, 'name', table1.name, 'alias', a.alias) as json
FROM orgs table1
CROSS JOIN LATERAL (
SELECT json_agg(agg) AS alias
FROM (
SELECT table2.alias as name
FROM org_aliases table2
WHERE
table2.org_id = table1.id
) agg
) a
WHERE
table1.name ilike 'nspcc'
or table2.alias ilike 'nspcc';
It fails on the last line (missing from condition on table2). I can see why it doesn't allow me to do this, as I'm referencing something inside a sub query.
My question, is what's the best way to handle this?
My only idea is that I need to join the org_aliases again so I can add a where condition. But if anyone has a better idea for how I structure the query to avoid duplication, that would be amazing.
Not sure if it's exactly what you're looking for, but it does give you the required result for your example:
SELECT
json_build_object('id', orgs.id
, 'name', name
, 'alias', json_agg(
json_build_object('name', alias)
)
)
FROM orgs
LEFT JOIN org_aliases ON orgs.id = org_aliases.org_id
WHERE name ILIKE '%dave%'
OR alias ILIKE '%dave%'
GROUP BY
orgs.id, orgs.name;
Edit: I would use a CTE to first find my target id and then get alle the data that I need. This makes it easy to understand (and debug):
WITH target AS (
SELECT orgs.id
FROM orgs
LEFT JOIN org_aliases ON orgs.id = org_aliases.id
WHERE name ILIKE '%dave%'
OR alias ILIKE '%dave%'
)
SELECT
json_build_object('id', orgs.id
, 'name', name
, 'alias', json_agg(
json_build_object('name', alias)
)
)
FROM target
JOIN orgs ON target.id = orgs.id
LEFT JOIN org_aliases ON orgs.id = org_aliases.org_id
GROUP BY
orgs.id, orgs.name;
Related
From here, and here I have figured out that if I want to aggregate a set of related rows into an array of objects I have to use this syntax:
(select to_json(C) from ( /* subquery */ ) C)
So, if I have three tables: user, creature and their junction table user_creature:
And I want to retrieve each user, and each creature that belongs to this user, I would have to do something like this:
select to_json(T)
from (
select "user".id as user_id,
(select to_json(C) -- !!! There it is
from (
select name, height
from creature
inner join "user_creature" uc on creature.id = "uc".creature_id
inner join "user" u on "uc".user_id = u.id
where u.id = user_id
) C) as "creatures" -- !!! There it is
from "user"
) T;
This query successfully retrieves a list of users and their related creatures:
Is there a way to drop select and from keywords from the query, so that I can write my query like this:
select to_json(T)
from (
select "user".id as user_id,
to_json( -- !!! Calling to_json directly on select statement
select name, height
from creature
inner join "user_creature" uc on creature.id = "uc".creature_id
inner join "user" u on "uc".user_id = u.id
where u.id = user_id
) as "creatures"
from "user"
) T;
It is possible to use a subquery as the argument to to_json, but not practical:
You need to wrap the subquery in a grouping parenthesis: to_json( (SELECT … FROM …) )
The subquery must return exactly one row (but that's normal)
The subquery must return exactly one column. This is a bit harder - you can return a record, but if you build it dynamically (e.g. from a selection of columns, you can hardly control the field names)
(See a demo here).
Instead, use json_build_object if you want to write a single SELECT query only:
SELECT json_build_object(
'user_id', u.id,
'creatures', (
SELECT json_build_object(
'name', c.name,
'height', c.height
)
FROM creature c
INNER JOIN "user_creature" uc ON c.id = uc.creature_id
WHERE uc.user_id = u.id
)
)
FROM "user" u;
And, if you want to be able to retrieve multiple rows use SELECT json_agg(json_build_object(…)) FROM … or ARRAY(SELECT json_build_object(…) FROM …):
SELECT json_build_object(
'user_id', u.id,
'creatures', (
SELECT json_agg(json_build_object(
'name', c.name,
'height', c.height
))
FROM creature c
INNER JOIN "user_creature" uc ON c.id = uc.creature_id
WHERE uc.user_id = u.id
)
)
FROM "user" u;
SELECT *
FROM car_t
JOIN ( SELECT driver_id, gender, first_name,last_name
FROM driver_t
WHERE gender = 'Male');
I get an error for not using Alias. Can someone help me with that ? Thank you!
I need to do a join statement with a subquery.
It is a common requirement in SQL that every derived table must have an alias (although some databases are lax about it).
Your subquery produces a derived table, that you hence need to alias:
SELECT *
FROM car_t c
JOIN (
SELECT driver_id, gender, first_name,last_name FROM driver_t WHERE gender = 'Male'
) d ON ???
--^ table alias
Notes:
your query is clearly missing a JOIN condition to relate the two tables; it should appear after the ON keyword, that I added to the query
the subquery is actually not necessary here; you could just join the table, then select the needed columns
SELECT
c.*,
d.driver_id,
d.gender,
d.first_name,
d.last_name
FROM car_t c
JOIN driver_t d ON ???
WHERE d.gender = 'Male'
Well the error messages refers to your "derived table" (alias "subquery") which requires a name
SELECT *
FROM car_t
JOIN (
SELECT driver_id, gender, first_name,last_name
FROM driver_t
WHERE gender = 'Male'
) as x --<< the alias
But that will just lead you to the next error because you have no join condition.
So you need something like:
SELECT *
FROM car_t
JOIN (
SELECT driver_id, gender, first_name,last_name
FROM driver_t
WHERE gender = 'Male'
) as x on x.??? = car_t.???
You need to replace the ??? with the columns that link the two tables together.
But you don't really need the derived table anyway. You can simplify this to
SELECT
FROM car_t
JOIN driver_t as x on x.??? = car_t.???
WHERE x.gender = 'Male';
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 _ ;
I have this SQL:
select * from `posts`
where `posts`.`deleted_at` is null
and `expire_at` >= '2017-03-26 21:23:42.000000'
and (
select count(distinct tags.id) from `tags`
inner join `post_tag` on `tags`.`id` = `post_tag`.`tag_id`
where `post_tag`.`post_id` = `posts`.`id`
and (`tags`.`tag` like 'PHP' or `tags`.`tag` like 'pop' or `tags`.`tag` like 'UI')
) >= 1
Is it possible order the results by number of tags in posts?
Maybe add there alias?
Any information can help me.
Convert your correlated subquery into a join:
select p.*
from posts p
join (
select pt.post_id,
count(distinct t.id) as tag_count
from tags t
inner join post_tag pt on t.id = pt.tag_id
where t.tag in ('PHP', 'pop', 'UI')
group by pt.post_id
) pt on p.id = pt.post_id
where p.deleted_at is null
and p.expire_at >= '2017-03-26 21:23:42.000000'
order by pt.tag_count desc;
Also, note that I changed the bunch of like and or to single IN because you are not matching any pattern i.e. there is no % in the string. So, better using single IN instead.
Also, if you have defined your table names, column names etc keeping keywords etc in mind, you shouldn't have the need to use the backticks. They make reading a query difficult.
I'm making select with a union.
SELECT * FROM table_1
UNION
SELECT * FROM table_2
Is it possible to filter query results by column values?
Yes, you can enclose your entire union inside another select:
select * from (
select * from table_1 union select * from table_2) as t
where t.column = 'y'
You have to introduce the alias for the table ("as t"). Also, if the data from the tables is disjoint, you might want to consider switching to UNION ALL - UNION by itself works to eliminate duplicates in the result set. This is frequently not necessary.
A simple to read solution is to use a CTE (common table expression). This takes the form:
WITH foobar AS (
SELECT foo, bar FROM table_1
UNION
SELECT foo, bar FROM table_2
)
Then you can refer to the CTE in subsequent queries by name, as if it were a normal table:
SELECT foo,bar FROM foobar WHERE foo = 'value'
CTEs are quite powerful, I recommend further reading here
One tip that you will not find in that MS article is; if you require more than one CTE put a comma between the expression statements. eg:
WITH foo AS (
SELECT thing FROM place WHERE field = 'Value'
),
bar AS (
SELECT otherthing FROM otherplace WHERE otherfield = 'Other Value'
)
If you want to filter the query based on some criteria then you could do this -
Select * from table_1 where table_1.col1 = <some value>
UNION
Select * from table_2 where table_2.col1 = <some value>
But, I would say if you want to filter result to find the common values then you can use joins instead
Select * from table_1 inner join table_2 on table_1.col1 = table_2.col1