I have these tables:
person (id primary key, name)
money (acct primary key, loaner)
loan (id primary key, acct)
How would I create a SQL query that shows for each loaner the names of persons who took more than four loans from that specific loaner? And I want the 4 persons that he loaned to be different with each other.
SELECT
p.id, p.name, m.loaner, COUNT(*)
FROM
person p
INNER JOIN
loan l ON p.id = l.id
INNER JOIN
money m ON l.acct = m.acct
GROUP BY
id, name, lower
HAVING
COUNT(*) = 4
With this query you can find the first part of the question - what should I add?
I would try this out and see what happens :D
SELECT distinct *
FROM person as p_loaner_detailed
WHERE p_loaner_detailed.id in (
SELECT loanerId
FROM (
SELECT p.id, p.name, m.loaner as loanerId
COUNT(*)
FROM person p
INNER JOIN loan l
ON p.id = l.id
INNER JOIN money m
ON l.acct = m.acct
GROUP BY id, name, loanerId
HAVING COUNT(*) > 4
)
)
Related
I have a many to many relation with three columns, (owner_id,property_id,ownership_perc) and for this table applies (many owners have many properties).
So I would like to find all the owner_id who has many properties (property_id) and connect them with other three tables (Table 1,3,4) in order to get further information for the requested result.
All the tables that I'm using are
Table 1: owner (id_owner,name)
Table 2: owner_property (owner_id,property_id,ownership_perc)
Table 3: property(id_property,building_id)
Table 4: building(id_building,address,region)
So, when I'm trying it like this, the query runs but it returns empty.
SELECT address,region,name
FROM owner_property
JOIN property ON owner_property.property_id = property.id_property
JOIN owner ON owner.id_owner = owner_property.owner_id
JOIN building ON property.building_id=building.id_building
GROUP BY owner_id,address,region,name
HAVING count(owner_id) > 1
ORDER BY owner_id;
Only when I'm trying the code below, it returns the owner_id who has many properties (see image below) but without joining it with the other three tables:
SELECT a.*
FROM owner_property a
JOIN (SELECT owner_id, COUNT(owner_id)
FROM owner_property
GROUP BY owner_id
HAVING COUNT(owner_id)>1) b
ON a.owner_id = b.owner_id
ORDER BY a.owner_id,property_id ASC;
So, is there any suggestion on what I'm doing wrong when I'm joining the tables? Thank you!
This query:
SELECT owner_id
FROM owner_property
GROUP BY owner_id
HAVING COUNT(property_id) > 1
returns all the owner_ids with more than 1 property_ids.
If there is a case of duplicates in the combination of owner_id and property_id then instead of COUNT(property_id) use COUNT(DISTINCT property_id) in the HAVING clause.
So join it to the other tables:
SELECT b.address, b.region, o.name
FROM (
SELECT owner_id
FROM owner_property
GROUP BY owner_id
HAVING COUNT(property_id) > 1
) t
INNER JOIN owner_property op ON op.owner_id = t.owner_id
INNER JOIN property p ON op.property_id = p.id_property
INNER JOIN owner o ON o.id_owner = op.owner_id
INNER JOIN building b ON p.building_id = b.id_building
ORDER BY op.owner_id, op.property_id ASC;
Always qualify the column names with the table name/alias.
You can try to use a correlated subquery that counts the ownerships with EXISTS in the WHERE clause.
SELECT b1.address,
b1.region,
o1.name
FROM owner_property op1
INNER JOIN owner o1
ON o1.id_owner = op1.owner_id
INNER JOIN property p1
ON p1.id_property = op1.property_id
INNER JOIN building b1
ON b1.id_building = p1.building_id
WHERE EXISTS (SELECT ''
FROM owner_property op2
WHERE op2.owner_id = op1.owner_id
HAVING count(*) > 1);
I have a problem I've been working on. I've broken it down to a couple of steps below. I have trouble combining all the queries together to solve the following:
Find members who have spent over $1000 in departments that have
brought in more than $10000 total ordered by the members' id.
Schema:
departments(id, name)
products (id, name, price)
members(id, name, number, email, city, street_name, street_address)
sales(id, department_id, product_id, member_id, transaction_date
Step 1)
I found the departments that have brought in more than 10,000$
select s.department_id
from sales s join products p on
s.product_id = p.id
group by s.department_id
having sum(price) > '10000'
Step 2) I found the members and the departments that they shop in
select *
from members m
join sales s
on m.id = s.member_id
join departments d
on d.id = s.department_id
Step 3) I combined 1 and 2 to find members taht shop in departments that have brought in more than 10,000
select *
from members m
join sales s
on m.id = s.member_id
join departments d
on d.id = s.department_id
where s.department_id in
(select s.department_id
from sales s join products p on
s.product_id = p.id
group by s.department_id
having sum(price) > '10000')
Step 4) I found members and their id, email, total_spending > 1,000$
select m.id, m.name, m.email, sum(price) as total_spending
from members m join sales s on
m.id = s.member_id
join products p on
p.id = s.product_id
group by m.id
having sum(price) > '1000'
Step 5)
All of the steps work individually but when I put them together in my attempt:
select m.id, m.name, m.email, sum(price) as total_spending
from members m join sales s on
m.id = s.member_id
join products p on
p.id = s.product_id
where m.id in (select distinct m.id
from members m
join sales s
on m.id = s.member_id
join departments d
on d.id = s.department_id
where s.department_id in
(select s.department_id
from sales s join products p on
s.product_id = p.id
group by s.department_id
having sum(price) > '10000'))
group by m.id
having sum(price) > '1000'
The output is wrong. (This is on CodeWars) If someone could point me in the right direction that would be really great! Thank you.
Try to group by member_id and department_id:
select s.member_id,s.department_id,sum(p.price) as total_spending
from members m
join sales s on m.id = s.member_id
join products p on p.id = s.product_id
where s.department_id in (
select s.department_id
from sales s
join products p on s.product_id = p.id
group by s.department_id
having sum(p.price) > 10000 -- the departments which brought in more than $10000 total
)
group by s.member_id,s.department_id
having sum(p.price) > 1000 -- who have spent over $1000 in one department
And if you need you will able to calc how much spent each of members:
select member_id,sum(total_spending) total
from
(
-- the first query is here
) q
group by member_id
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'
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;
There are three tables: businesses, categories, categorizations,
CREATE TABLE businesses (
id SERIAL PRIMARY KEY,
name varchar(40)
);
CREATE TABLE categories (
id SERIAL PRIMARY KEY,
name varchar(40)
);
CREATE TABLE categorizations (
business_id integer,
category_id integer
);
So business has many categories through categorizations.
If I want to select businesses without categories, I would do something
like this:
SELECT businesses.* FROM businesses
LEFT OUTER JOIN categorizations
ON categorizations.business_id = businesses.id
LEFT OUTER JOIN categories
ON categories.id = categorizations.category_id
GROUP BY businesses.id
HAVING count(categories.id) = 0;
The question is: How do I select businesses without categories AND
businesses with category named "Media" in one query?
You can use a union:
SELECT businesses.*
FROM businesses
LEFT OUTER JOIN categorizations
ON categorizations.business_id = businesses.id
GROUP BY businesses.id
HAVING count(categorizations.business_id) = 0
UNION
SELECT businesses.*
FROM businesses
INNER JOIN categorizations
ON categorizations.business_id = businesses.id
INNER JOIN categories
ON categories.id = categorizations.category_id
WHERE categories.name = 'Media';
Note that in the first instance (businesses with no categories at all) that you won't need to join as far as categories - you can detect the lack of category in the junction table. If it is possible for the same business to have the same category more than once, you'll need to introduce the second query with DISTINCT.
I would try:
SELECT b.* FROM businesses b
LEFT JOIN categorizations cz ON b.business_id = cz.business_id
LEFT JOIN categories cs ON cz.category_id = cs.category_id
WHERE COALESCE(cs.name, 'Media') = 'Media';
... in the hope that businesses with no categorizations would get NULL entries on their joins.
The double-negation trick works for this kind of selections:
SELECT * FROM businesses b
WHERE NOT EXISTS (
SELECT *
FROM categorizations bc
JOIN categories c ON bc.category_id = c.category_id
WHERE bc.business_id = b.business_id
AND c.name <> 'Media'
);