Relational division - SQL - sql

I have 3 tables.
Owner(owner_id, name)
House(code, owner_id, price)
Buyer(buyer_id, name)
Bought(buyer_id, code, price_bought, date_bought)
I have the following query:
List the names of the buyers that bought all the houses from some owner?
I know how to find if someone bought all the houses from a particular owner (say owner with id = 1):
SELECT name
FROM buyer
WHERE NOT EXISTS (SELECT code
FROM house
WHERE owner_id = 1
AND code NOT IN (SELECT code
FROM bought
WHERE bought.buyer_id= buyer.buyer_id))
How can I make this work for all owners?

The sentence: "List the names of the buyers that bought all the houses from some owner?". This can be interpreted two ways. (1) All the houses the buyer bought are from one owner. Or (2) All the houses sold by one owner when to the same buyer.
The following answers (1):
select b.buyer_id
from bought b join
house h
on b.code = h.code
group by b.buyer_id
having min(h.owner_id) = max(h.owner_id);
The answer to the second question is similar. However, the focus is on owners rather than buyers.
select min(b.buyer_id)
from bought b join
house h
on b.code = h.code
group by h.owner_id
having min(b.buyer_id) = max(b.buyer_id);
EDIT:
In both cases, the logic is quite similar, but let's look at the second query. The join is just combining the buyer and owner ids together (not really interesting).
The group by is creating a single row for each owner_id. The having clause then adds the condition that the query only returns the owner id when the minimum buyer and the maximum buyer are the same -- meaning there is only one value. You can also express this condition as count(distinct buyer_id) = 1, but min() and max() generally perform a bit better than count(distinct).
The select clause then returns those buyers. You could also include the owner to see whose house(s) they bought.

Related

SQL queries: how to get the value which appears the most in the total of two different tables?

Context: I want to know which vehicle brand appears the most in different accidents.
I have the table vehicle (v_number, brand).
Problem is, I have two different accident tables:
One refers to driven cars involved in an accident, let's call it acc_drive (v_number, acc_number, driver) [v_number FK vehicle]
The other refers to parked cars which are involved in an accident, let's call it acc_park (v_number, acc_number) [v_number FK vehicle, acc_number FK acc_drive]
Now, I'm trying to get the vehicle brand which appears the most in the total of the two tables. For example, if Audi cars appeared 2 times in acc_drive and 3 times in acc_park, the total number of appearences would be 5.
I'm having a really hard time trying to figure this out, so a helping hand would be much appreciated!
UNION ALL can be used to bring the tables together for the JOIN:
select v.brand, count(a.v_number)
from vehicle v left join
((select v_number
from acc_drive
) union all
(select v_number
from acc_park
)
) a
on v.v_number = a.v_number
group by v.brand
order by count(v_number) desc; -- put the biggest numbers first
Note that this uses a left join. So brands with no accidents will be included in the results.
Try this-
SELECT TOP 1 brand,COUNT(*)
FROM vehicle A
INNER JOIN acc_drive B ON A.v_number = B.v_number
INNER JOIN acc_park C ON A.v_number = C.v_number
GROUP BY brand
ORDER BY COUNT(*) DESC

How to include zero results when querying one single table?

I have a table called Apartments that has three columns: apartment_type, person, date. It includes the apartment type selected by a certain person and date. I need to count how many people picked each of the apartment types. Some apartment type have 0 population.
Here is my query:
SELECT apartment_type, COUNT(*) AS TOTAL
FROM Apartments
GROUP BY apartment_type
It works great, but it doesn't include apartment types with a value of 0. Please, help me to correct this query.
In case some appartment_type have 0 population - your table will not contain any record with that type - so you must add some join from another table, where all apartment types exists. Or use union to create all 0 populated entries.
Something like:
SELECT apartment_type, COUNT(*) AS TOTAL
FROM (SELECT * FROM Apartments UNION ALL SELECT apartment_type, 0 as person, 0 as date from SomeTableWithFullListOfTypes group by apartment_type) as tmp
GROUP BY apartment_type
I generally agree with Nosyara's answer, but I don't agree with his sample query with the union all. I'm not sure it works, and it's certainly too complicated.
As stated already, if you don't have a table with all the possible apartment types, create one. Then you can write your query using a simple left join:
select t.apartment_type, count(a.apartment_type) as total
from apartment_types t
left join apartments a
on a.apartment_type = t.apartment_type
group by t.apartment_type
Note how count(*) was replaced by count(a.apartment_type). That change is necessary to have an accurate count in the case where you don't have apartments for a certain apartment type.
SELECT apartment_type, COUNT(apartment.*) AS TOTAL
FROM apartment_type
left join apartment
on apartment_type.aparentment_type = apartements.apartment_type
GROUP BY apartment_type
Using a left join will give you everything from the left side of the join (so all your types) and anything from the right that matches.

Querying records that meet muliple criteria

Hi I’m trying to write a query and I’m struggling to figure out how to go about it.
I have a suppliers table and a supplier parts table I want to write a query that lists suppliers that have specified related Parts in the supplier parts table. If a supplier doesn’t have all specified related parts then they should not be listed.
At the moment I have written a very basic query that lists the supplier if they have a related supplier part that meets the criteria.
SELECT id ,name
FROM
efacdb.dbo.suppliers INNER JOIN [efacdb].[dbo].[spmatrix] ON
id = spmsupp
WHERE spmpart
IN ('ALUM_5083', 'ALUM_6082')
I only want to show the supplier if they have both parts related. Does anyone know how I could do this?
Use a subquery with counting distinct occurences:
select * from suppliers s
where 2 = (select count(distinct spmpart) from spmatrix
where id = spmsupp and spmpart in ('ALUM_5083', 'ALUM_6082'))
As a note, you can modify your query to get what you want, just by using an aggregation:
SELECT id, name
FROM efacdb.dbo.suppliers INNER JOIN
[efacdb].[dbo].[spmatrix]
ON id = spmsupp
WHERE spmpart IN ('ALUM_5083', 'ALUM_6082')
GROUP BY id, name
HAVING MIN(spmpart) <> MAX(spmpart);
If you know there are no duplicates, then having count(*) = 2 also solves the problem.

Creating a View SQL With Not Exists

I need to create a view in SQL that I will use on my queries.
I am using the following tables:
customer (name:string,credit:integer)
loan (no:string,type:string,minCredit:integer)
borrower (cname:string,lno:string,due:date)
where borrower.cname and borrower.lno are foreign keys referencing customer, respectively loan, whose keys are name , respectively no (number). Attribute loan.minCredit indicates the minimum credit required of a
customer to qualify for that loan.
I am trying to create a view that shows customers and loans they haven't taken.
Create view LoansNotTaken AS
SELECT c.name, l.no
FROM customer c, loan l
WHERE NOT EXISTS(
SELECT c1.name,l1.no
FROM customer c1, loan l1, borrower b1
WHERE c1.name=b1.cname AND l1.no=b1.lno AND lno=l1.no AND c.name=c1.name)
My basic idea in the second select is get all the customer and loans they have taken pairs. Then I'm trying to use the not exists to give me all pairs excluding those. However my result only gives me pairs as long as the customer didn't take out any loans whatsoever, and I need the one they didn't take out even if they did take out some. Can anyone please help me to understand what I'm doing wrong in my query.
If you want the loans they haven't taken, then do this in two steps. First, create a list of all customers and all loans. Then remove the ones that are "taken".
So:
select c.name, l.no
from customers c cross join
loans l left join
borrowers b
on b.name = c.name and b.lno = l.no
where b.name is null;
I figured out what I did wrong in my query. But yeah I wrote my own tables to test these and kept editing it to test for different things. If I have any more questions I'll make sure to post tables and results to make it easier to answer.
This worked which is essentially what I had, I added type to have more data.
Create view LoansNotTaken AS
SELECT c.name, l.no, l.type
FROM customer c, loan l
WHERE NOT EXISTS(
SELECT c1.name,l1.no
FROM customer c1, loan l1, borrower b1
WHERE b1.cname=c1.name AND lno=l1.no AND c.name = c1.name AND l.no=l1.no)
;
Find pairs of names of customers who share the same loan. Avoid listing a customer with himself (e.g. do not list (Joe,Joe)). Also avoid repeating pairs which are equal modulo swapping the components (e.g. only one of (John,Jane), (Jane,John) should be listed).
I need a relational algebra and relational tuple calculus solution to this question, thanks!
Additional restraint: Do not use grouping or aggregation
I need answer for this question with same above the query

Select based on the number of appearances of an id in another table

I have a table B with cids and cities. I also have a table C that has these cids with extra information. I want to list all the cids in table C that are associated with ALL appearances of a given city in Table B.
My current solution relies on counting the number of times the given city appears in Table B and selecting only the cids that appear that many times. I don't know all the SQL syntax yet, but is there a way to select for this kind of pattern?
My current solution:
SELECT Agents.aid
FROM Agents, Customers, Orders
WHERE (Customers.city='Duluth')
AND (Agents.aid = Orders.aid)
AND (Customers.cid = Orders.cid)
GROUP BY Agents.aid
HAVING count(Agents.aid) > 1
It only works because I know right now with the HAVING statement.
Thanks for the help. I wasn't sure how to google this problem, since it's pretty specific.
EDIT: I'm pinpointing my problem a bit. I need to know how to determine if EVERY row in a table has a certain value for a field. Declaring a variable and counting the rows in a sub-selection and filtering out my results by IDs that appear that many times works, but It's really ugly.
There HAS to be a way to do this without explicitly count()ing rows. I hope.
Not an answer to your question, but a general improvement.
I'd recommend using JOIN syntax to join your tables together.
This would change your query to be:
SELECT Agents.aid
FROM Agents
INNER JOIN Orders
ON Agents.aid = Orders.aid
INNER JOIN Customers
ON Customers.cid = Orders.cid
WHERE Customers.city='Duluth'
GROUP BY Agents.aid
HAVING count(Agents.aid) > 1
What variant of SQL are you using?
To start with, you can (and should) use JOIN instead of doing it in the WHERE clause, e.g.,
select Agents.aid
from Agents
join Orders on Agents.aid = Orders.aid
join Customers on Customers.cid = Orders.cid
where Customers.city = 'Duluth'
group by Agents.aid
having count(Agents.aid) > 1
After that, I'm afraid I might be a little lost. Using the table names in your example query, what (in English, not pseudocode) are you trying to retrieve? For example, I think your sample query is retrieving the PK for all Agents that have been involved in at least 2 Orders involving Customers in Duluth.
Also, some table definitions for Agents, Orders, and Customers might help (then again, they might be irrelevant).
I'm not sure if I understood you problem, but I think the following query is what you want:
SELECT *
FROM customers b
INNER JOIN orders c USING (cid)
WHERE b.city = 'Duluth'
AND NOT EXISTS (SELECT 1
FROM customers b2
WHERE b2.city = b.city
AND b2.cid <> cid);
Probably you will need some indexes on these columns.