PostgreSQL - Count Function - postgresql

I've got two Tables Called Manu and Cars
Manufacturer | Employees | id
Toyota | 102346 | 1
Subaru | 284608 | 2
Kia | 268244 | 3
Suzuki | 228624 | 4
The second table Cars
Car | id
Corolla | 1
camry | 1
alto | 4
vitara | 4
forester | 2
impreza | 2
xv | 2
cerato | 3
celica | 1
Now the table Cars references back to table Manu through Id
Im trying to return manufacturers that have produced 2 or more models of cars.
So far what I have tried is
Select m.id, m.manufacturer
from Manu m
inner join Cars n on m.id = n.id
group by m.id having count(n.id) >= 2;
it tells me that column m.id must appear in group by clause or be used in an aggregate function. Very confused.
Thanks

First we'll get the number of cars by manufacturer:
SELECT id,COUNT(id) FROM cars GROUP BY id;
Next, we'll use that data to get the manufacturers that make more than two models:
SELECT s.id,s.count,m.manufacturer FROM
(SELECT id,COUNT(id) FROM cars GROUP BY id) s
JOIN manu m USING (id) WHERE s.count >= 2;

Actually you have already give the answer. just you didnt group by m.manufactorer;
Select m.id, m.manufactorer
from Manu m
inner join Cars n using(id)
group by m.id,m.manufactorer
having count(*) >= 2;

Related

How to get flat aggregation of two calls to json_agg

I have the following tables:
products
id | name
----+-------------
1 | Shampoo
2 | Conditioner
productOptions
id | name | productId
----+-------------+-----------
1 | Hair Growth | 1
2 | Frizzy Hair | 1
images
id | fileName | productOptionId
----+-----------+-----------------
1 | bee.png | 1
2 | fancy.png | 2
3 | soap.png | 2
products have many productOptions, and productOptions have many images.
Following from this question, I have aggregated images.fileName twice to get an aggregated list of the fileNames for each product:
SELECT p.name, o.options, o.images
FROM products p
LEFT JOIN (
SELECT "productId", array_agg(name) AS options, json_agg(i.images) AS images
FROM "productOptions" o
LEFT JOIN (
SELECT "productOptionId", json_agg(i."fileName") AS images
FROM images i
GROUP BY 1
) i ON i."productOptionId" = o.id
GROUP BY 1
) o ON o."productId" = p.id;
name | options | images
-------------+-------------------------------+------------------------------------------
Shampoo | {"Hair Growth","Frizzy Hair"} | [["bee.png"], ["fancy.png", "soap.png"]]
Conditioner | |
I am wondering how to flatten the second json_agg so that the list of images is flat, and if my overall approach makes sense.
I didn't have to json_agg inside the inner-most JOIN, instead I can call array_agg(i.images) at the same point array_agg(name) AS options is called, to get a flat list of images:
SELECT p.name, o.options, o.images
FROM products p
LEFT JOIN (
SELECT "productId", array_agg(DISTINCT name) AS options, array_agg(i.images) AS images
FROM options o
LEFT JOIN (
SELECT "optionId", i."fileName" AS images
FROM images i
) i ON i."optionId" = o.id
GROUP BY 1
) o ON o."productId" = p.id;
name | options | images
-------------+-------------------------------+------------------------------
Shampoo | {"Frizzy Hair","Hair Growth"} | {bee.png,fancy.png,soap.png}
Conditioner |
A different approach:
I used DISTINCT in function json_agg() (you can use array_agg() instead), so as not to repeat the name of the product Options.
SELECT
p.name,
json_agg(DISTINCT po.name) AS options,
json_agg(i."fileName") AS images
FROM products p
LEFT JOIN "productOptions" po ON p.id = po."productId"
LEFT JOIN images AS i ON po.id = i."productOptionId"
GROUP BY p.name;
Or using subqueries:
SELECT
p.name,
po.options,
poi.images
FROM products p
LEFT JOIN (SELECT productId, json_agg(name) AS options
FROM productOptions
GROUP BY productId) AS po ON p.id = po.productId
LEFT JOIN (SELECT
productId,
json_agg(fileName) AS images
FROM productOptions po
INNER JOIN images i ON i.productOptionId = po.id
GROUP BY productId) AS poi ON p.id = poi.productId;

Why does my LEFT JOIN work when I perform "SELECT * ", but fails when I select only the necessary columns?

I'm new to SQL but I'm trying to join two tables. However, it's not working as I expected. This is in Postgresql.
Here are the tables I'm trying to join.
My Tables
SELECT * FROM houses;
id | name | address | picture
----+----------------+-------------+------------
1 | House 1 | 440 S 3rd W | long-link2.jpg
2 | House 2 | 538 S 5th E | long-link.jpg
SELECT house_id, trunc(avg(score), 1) FROM house_reviews GROUP BY house_id;
house_id | trunc
----------+-------
1 | 3.0
2 | 3.0
My JOIN statements
Attempt 1 (works)
SELECT * FROM houses
LEFT JOIN (SELECT house_id, trunc(avg(score), 1) FROM house_reviews GROUP BY house_id) AS r
ON houses.id = r.house_id;
Attempt 2 (does not work)
SELECT id, name, address FROM houses
LEFT JOIN (SELECT house_id, trunc(avg(score), 1) FROM house_reviews GROUP BY house_id) AS r
ON houses.id = r.house_id;
The only difference between the two is that I don't select the picture in the attempt 2. But attempt 2 doesn't seem to join at all. Instead it displays
id | name | address
----+----------------+-------------
1 | Tuscany | 440 S 2nd W
2 | Mountain Lofts | 538 S 2nd W
meaning that it failed to join and is instead just displaying the houses table.
My Question
I'm confused why the join failed in the second table because I removed only one arbitrary column (pictures).
Is there a way that I can join the two tables together but also exclude the pictures column from the "houses" table?
Thank you!
You're only seeing data from houses because that's all you've selected. Try this:
SELECT
h.id, h.name, h.address,
r.avg_score
FROM houses h
LEFT JOIN (
SELECT house_id, trunc(avg(score), 1) avg_score
FROM house_reviews
GROUP BY house_id
) AS r
ON houses.id = r.house_id;

Unpack expression results from case statement

Four categories in category table.
id | name
--------------
1 | 'wine'
2 | 'chocolate'
3 | 'autos'
4 | 'real estate'
Two of the many (thousands of) forecasters in forecaster table.
id | name
--------------
1 | 'sothebys'
2 | 'cramer'
Relevant forecasts by the forecasters for the categories in the forecast table.
| id | forecaster_id | category_id | forecast |
|----+---------------+-------------+--------------------------------------------------------------|
| 1 | 1 | 1 | 'bad weather, prices rise short-term' |
| 2 | 1 | 2 | 'cocoa bean surplus, prices drop' |
| 3 | 1 | 3 | 'we dont deal with autos - no idea' |
| 4 | 2 | 2 | 'sell, sell, sell' |
| 5 | 2 | 3 | 'demand for cocoa will skyrocket - prices up - buy, buy buy' |
I want prioritized mapping of (forecaster, category, forecast) such that, if a forecast exists for some primary forecaster (e.g. 'cramer') use it because I trust him more. If a forecast exists for some secondary forecaster (e.g. 'sothebys') use that. If no forecast exists for a category, return a row with that category and null for forecast.
I have something that almost works and after I get the logic down I hope to turn into parameterized query.
select
case when F1.category is not null
then (F1.forecaster, F1.category, F1.forecast)
when F2.category is not null
then (F2.forecaster, F2.category, F2.forecast)
else (null, C.category, null)
end
from
(
select
FR.name as forecaster,
C.id as cid,
C.category as category,
F.forecast
from
forecast F
inner join forecaster FR on (F.forecaster_id = FR.id)
inner join category C on (C.id = F.category_id)
where FR.name = 'cramer'
) F1
right join (
select
FR.name as forecaster,
C.id as cid,
C.category as category,
F.forecast
from
forecast F
inner join forecaster FR on (F.forecaster_id = FR.id)
inner join category C on (C.id = F.category_id)
where FR.name = 'sothebys'
) F2 on (F1.cid = F2.cid)
full outer join category C on (C.id = F2.cid);
This gives:
'(sothebys,wine,"bad weather, prices rise short-term")'
'(cramer,chocolate,"sell, sell, sell")'
'(cramer,autos,"demand for cocoa will skyrocket - prices up - buy, buy buy")'
'(,"real estate",)'
While that is the desired data it is a record of one column instead of three. The case was the only way I could find to achieve the ordering of cramer first sothebys next and there is lots of duplication. Is there a better way and how can the tuple like results be pulled back apart into columns?
Any suggestions, especially related to removal of duplication or general simplification appreciated.
This sounds like a case for DISTINCT ON (untested):
SELECT DISTINCT ON (c.id)
fr.name AS forecaster,
c.name AS category,
f.forecast
FROM forecast f
JOIN forecaster fr ON f.forecaster_id = fr.id
RIGHT JOIN category c ON f.category_id = c.id
ORDER BY
c.id,
CASE WHEN fr.name = 'cramer' THEN 0
WHEN fr.name = 'sothebys' THEN 1
ELSE 2
END;
For each category, the first row in the ordering will be picked. Since Cramer has a higher id than Sotheby's, it will be given preference.
Adapt the ORDER BY clause if you need a more complicated ranking.

How to use COUNT() in more that one column?

Let's say I have this 3 tables
Countries ProvOrStates MajorCities
-----+------------- -----+----------- -----+-------------
Id | CountryName Id | CId | Name Id | POSId | Name
-----+------------- -----+----------- -----+-------------
1 | USA 1 | 1 | NY 1 | 1 | NYC
How do you get something like
---------------------------------------------
CountryName | ProvinceOrState | MajorCities
| (Count) | (Count)
---------------------------------------------
USA | 50 | 200
---------------------------------------------
Canada | 10 | 57
So far, the way I see it:
Run the first SELECT COUNT (GROUP BY Countries.Id) on Countries JOIN ProvOrStates,
store the result in a table variable,
Run the second SELECT COUNT (GROUP BY Countries.Id) on ProvOrStates JOIN MajorCities,
Update the table variable based on the Countries.Id
Join the table variable with Countries table ON Countries.Id = Id of the table variable.
Is there a possibility to run just one query instead of multiple intermediary queries? I don't know if it's even feasible as I've tried with no luck.
Thanks for helping
Use sub query or derived tables and views
Basically If You You Have 3 Tables
select * from [TableOne] as T1
join
(
select T2.Column, T3.Column
from [TableTwo] as T2
join [TableThree] as T3
on T2.CondtionColumn = T3.CondtionColumn
) AS DerivedTable
on T1.DepName = DerivedTable.DepName
And when you are 100% percent sure it's working you can create a view that contains your three tables join and call it when ever you want
PS: in case of any identical column names or when you get this message
"The column 'ColumnName' was specified multiple times for 'Table'. "
You can use alias to solve this problem
This answer comes from #lotzInSpace.
SELECT ct.[CountryName], COUNT(DISTINCT p.[Id]), COUNT(DISTINCT c.[Id])
FROM dbo.[Countries] ct
LEFT JOIN dbo.[Provinces] p
ON ct.[Id] = p.[CountryId]
LEFT JOIN dbo.[Cities] c
ON p.[Id] = c.[ProvinceId]
GROUP BY ct.[CountryName]
It's working. I'm using LEFT JOIN instead of INNER JOIN because, if a country doesn't have provinces, or a province doesn't have cities, then that country or province doesn't display.
Thanks again #lotzInSpace.

Select rows of multiple tables via joins

I have some tables which are related to each others.
A short demonstration:
Sites:
id | clip_id | article_id | unit_id
--------------+------------+--------
1 | 123 | 12 | 7
Clips:
id | title | desc |
------------+--------
1 | foo2 | abc1
Articles:
id | title | desc | slug
------------+---------------------
1 | foo2 | abc1 | article.html
Units:
id | vertical_id | title |
------------------+-------+
1 | 123 | abc |
Verticals:
id | name |
-----------+
1 | vfoo |
Now I want to do something like below:
SELECT ALL VERTICAL, UNIT, SITE, CLIP, ARTICLE attributes
from VERTICAL, UNIT, SITE, CLIP, ARTICLE TABLES
WHERE vertical_id = 2
Can some one help me how can I use joins for this?
Here is a running example of possibly what you want: http://sqlfiddle.com/#!15/af63b/2
select * from
sites
inner join units on sites.unit_id=units.id
inner join clips on clips.id=sites.clip_id
inner join articles on articles.id=sites.article_id
inner join verticals on verticals.id=units.vertical_id
where units.vertical_id=123
The problem is, that the description you gave us did not clearly specify which columns to join:
(answered) Why does units have a link to site via site_id and sites a link back to units via unit_id?
(answered) Why does units have a link to verticals via vertical_id and verticals a link back to units via unit_id?
I am guessing that your data does not giva a consistent example to get rows using the join. For vertical_id=123 there is no corresponding entry in verticals.
Edit:
I corrected the SQL due to corrections within the question. With this the two questions are answered.
select s.id, s.clip_id, s.article_id, u.title, u.vertical_id, c.title, v.unit_id, c.desc, a.slug
from sites s
join units u on s.id = u.id
join clips c on u.id = c.id
join verticals v on c.id = v.id
join articles a on v.id = a.id
where v.vertical_id = 'any id'