Beginners Alias - postgresql

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';

Related

Where condition inside json_agg in postgres

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;

Converting select statement directly into json array

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;

MariaDB - order by with more selects

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.

How should I add fields without adding them to a GROUP BY?

I have a SQL statement that works as-is. I get an area name and the minimum value within that area. next, I need to add in a key so I can actually do something with the results. The key is necessary since names and values are unlikely to be unique.
select g.name, min(g.rndval) from
(
select p.rndval, a.name, p.id
from points p, areas a
where ST_WITHIN(p.geom, a.geom)
) AS g
group by g.name
When I add the Id field to the group by, the query returns multiple rows for each area, as expected since it's grouping by the name and id combination, and the results are no longer what I need. How should I add in the id field (p.id in the inner select)?
You can try:
WITH cte AS
( select p.rndval, a.name, p.id
from points p, areas a
where ST_WITHIN(p.geom, a.geom)
), cte_aggregated AS
(
SELECT name, min(rndval) AS min_value
FROM cte
GROUP BY name
)
SELECT DISTINCT c.rndval, c.name, c.id
FROM cte c
JOIN cte_aggregated ca
ON c.rndval = ca.min_value
AND c.name = ca.name;
You can solve this quite elegantly with a window function:
select name, rndval as min, id
from (
select a.name, p.rndval, p.id, rank() over (partition by a.name order by p.rndval) as rnk
from points p
join areas a on ST_Within(p.geom, a.geom)) as g
where rnk = 1;

Filter union result

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