SQL Server - count the number of living grandparents - mysql

I'm homelearning databases and currently trying to solve the following problem:
Select id and name of people who are grandchildren. Also select for
each person the number of their still living grandparents.
My table is looking the following way:
id, name, date_of_birth, date_of_death (is NULL when the person is
alive), gender, father_id, mother_id
I was able to solve the part with selecting the id and name of people who are grandchildren the following way:
SELECT b.name, b.id
FROM persons a
JOIN persons b ON c.father_id = a.id or a.mother_id = a.id
JOIN persons c on p.father_id = c.id
WHERE b.father_id = a.id or b.mother_id = a.id;
However, I am unable to solve the part with the number of still living grandparents for each person.
Could you please help me?
Thanks

This is a question of joining the tables. I think you are all confused by the table aliases on the self-joins. So give them meaningful aliases:
p for the person
pp for the person's parents
gp for the person's grandparents
Then, once you have the JOINs correct, the rest is mostly aggregation:
SELECT p.name, p.id,
SUM(CASE WHEN gp.date_of_death IS NULL THEN 1 ELSE 0 END)
FROM persons p JOIN -- persons
persons pp -- parents
ON pp.id IN (p.father_id, p.mother_id) JOIN
persons gp
ON gp.id IN (pp.father_id, pp.mother_id)
GROUP BY p.name, p.id;
Note the conditional sum, which includes the date of death.

You need to join 3 copies of the table, group by person and count conditionally the number of living grandparents:
SELECT p.id, p.name,
count(case when p2.date_of_death is null then 1 end) living_grandparents
FROM persons p
JOIN persons p1 ON p1.id IN (p.father_id, p.mother_id)
JOIN persons p2 on p2.id IN (p1.father_id, p1.mother_id)
GROUP BY p.id, p.name
For MySql the condition could be simplified to:
sum(p2.date_of_death is null) living_grandparents

Check out the SQL Fiddle for the solution :)
http://sqlfiddle.com/#!9/441ad0/16
select
(
select
count(*)
from
people
where
(
id = p.fatherId
or id = p.motherId
)
and (
fatherId in (
select
id
from
people
where
dod is null
)
or motherId in (
select
id
from
people
where
dod is null
)
)
) as mycount,
p.name,
p.id,
p.fatherId,
p.motherId
FROM
people p
WHERE
p.fatherId in (
SELECT
id
from
people
where
id = p.fatherId
and fatherId in (
SELECT
id
from
people
where
1 = 1
)
)
OR p.motherId in (
SELECT
id
from
people
where
id = p.motherId
and motherId in (
SELECT
id
from
people
where
1 = 1
)
)

Related

Select distinct record from mapping table with condition

In my MySQL database I have these tables:
I want to select count of users who only own birds and no other pet.
So far I've came up with this:
SELECT COUNT(DISTINCT(user_id)) FROM users_pets_map WHERE pet_id IN (SELECT id FROM pets WHERE animal = 'bird')
but it doesn't satisfy the requirement of not owning other animals.
You can do aggregation :
select m.user_id, count(*)
from user_pets_map m inner join
pets p
on p.id = m.pet_id
group by m.user_id
having sum( p.animal <> 'bird' ) = 0;
In other way, you can also do :
select m.user_id, count(*)
from user_pets_map m inner join
pets p
on p.id = m.pet_id
group by m.user_id
having min(p.animal) = max(p.animal) and min(p.animal) = 'bird';
EDIT : If you want only Users count then you can do :
select count(distinct m.user_id)
from user_pets_map m
where not exists (select 1 from user_pets_map m1 where m1.user_id = m.user_id and m1.pet_id <> 3);
You can modify your query as below:
SELECT COUNT(DISTINCT(user_id)) FROM users_pets_map WHERE pet_id IN (SELECT id
FROM pets WHERE animal = 'bird') AND user_id NOT IN (SELECT user_id FROM
users_pets_map WHERE pet_id IN (SELECT id FROM pets WHERE animal <> 'bird'))
The last sub-query will fetch the pet_id who are not birds, the query outside it will fetch users who have animal other than birds. Finally combined your current query it will fetch you the users who does not have any other animals as well as have bird. Although the above query is not the best possible solution in terms of time complexity, but it's one of many solutions as well as easier to understand.
You can use GROUP BY AND HAVING
SELECT COUNT(DISTINCT(user_id)) FROM users_pets_map
WHERE pet_id IN (SELECT id FROM pets WHERE animal = 'bird')
GROUP BY pet_id HAVING COUNT(distinct pet_id)=1

Rewriting a query that has two sub queries using no sub queries

Given the database schema:
Part( PID, PName, Producer, Year, Price)
Customer( CID, CName, Province)
Supply(SID, PID, CID, Quantity, Amount, Date)
And the query:
Select cname, Province
From Customer c
Where exists (
Select *
from Supply s
join Part p on p.pId = s.pId
Where CId = c.CId
and p.Producer = 'Apple'
)
and Not exists (
Select *
from Supply n
join Part nap on nap.pId = n.pId
Where CId = c.CId
and nap.Producer != 'Apple'
)
How would I go about rewriting this query without the two sub queries?
You can use the LEFT JOIN/NULL pattern to find customers who haven't bought any non-Apple products. Then you can do this all with just joins. You'll have to join with Supply and Parts twice, once for finding Apple products, then again for excluding non-Apple products.
SELECT distinct c.name, c.province
FROM Customer AS c
JOIN Supply AS s1 ON s1.cid = c.cid
JOIN Parts AS p1 ON p1.pid = s1.pid
LEFT JOIN Supply AS s2 ON s2.cid = c.cid
LEFT JOIN Parts AS p2 ON p2.pid = s2.pid AND p2.producer != 'Apple'
WHERE p1.producer = 'Apple' AND p2.pid IS NULL
Notice that in the LEFT JOIN you put restrictions of the second table in the ON clause, not the WHERE clause. See Return row only if value doesn't exist for more about this part of the query.
You want customer who only bought Apple products?
One possible solution is based on conditional aggregation:
Select c.cname, c.Province
From Customer c
join
( -- this is not a Subquery, it's a Derived Table
Select s.CId -- assuming there's a CId in Supply
from Supply s
join Part p
on p.pId = s.pId
group by s.CId
-- when there's any other supplier this will return 1
having max(case when p.Producer = 'Apple' then 0 else 1 end) = 0
) as p
on p.CId = c.CId

Select from 3 tables with two order by before two group by

I try to get a list of products with each newest and lowest offer price
Table product:
id | name
Table offer:
id | product_id | price | created | dealer_id
Table invalids:
id | offer_id | status
I have tried:
SELECT * FROM product INNER JOIN
(
SELECT offer.product_id , offer.price
FROM offer
LEFT JOIN invalids
ON offer.id = invalids.offer_id
WHERE invalids.id IS NULL
GROUP BY offer.dealer_id
ORDER BY offer.created DESC
) o
ON o.product_id = product.id
ORDER BY product.name
I have tried an sqlfiddle http://sqlfiddle.com/#!9/32658/3 with this offer values:
(`id`, `price`, `dealer_id`, `product_id`, `created`)
(1,12.60,1,1,'2015-05-17 08:44:45'),
(2,13.00,1,1,'2015-08-17 08:44:45'),
(3,20.00,1,1,'2015-08-17 08:45:30'),
(4,10.00,1,1,'2015-08-17 08:45:46'),
(5,4.00,2,1,'2015-05-17 08:44:11'),
(6,11.00,2,1,'2015-08-17 08:44:46'),
(7,5.00,2,1,'2015-08-17 08:45:31'),
(9,110.00,2,2,'2015-08-17 08:46:58'),
(10,11.00,2,2,'2015-08-17 08:47:12');
Expected value for product ID 1 is offer ID 7 with price 5.
These steps I think I must realize:
Order offers by created and group by dealer_id to get newest entries
Take result from step 1 and order it by price to get smallest price.
Make this for all products
Maybe I must use a second SELECT FROM offer with GROUP BY and ORDER BY but how do I get I the product_id from the first (outer) select?
Well I would start by getting the latest date for each product offer like this:
SELECT product_id, MAX(created) AS latestOffer
FROM offer
GROUP BY product_id;
Once you have that, you can join it to the original table to get that offer:
SELECT o.*
FROM offer o
JOIN(
SELECT product_id, MAX(created) AS latestOffer
FROM offer
GROUP BY product_id) tmp ON tmp.product_id = o.product_id AND tmp.latestOffer = o.created;
Here is an SQL Fiddle example.
This query should help you:
SELECT *
FROM product
JOIN (
SELECT product_id, min(price) as minPrice, max(created) as newestOffer
FROM offer
WHERE id NOT IN (SELECT offer_id FROM invalids)
GROUP BY 1
) as b
ON product.id = b.product_id
A shot in the dark based on what I understand you to be after...
lots of nested subqueries.. keep thinking there's got to be a better way...
SELECT OO.ID, OO.Price, OO.Dealer_Id, OO.Product_ID, OO.created, P.name
FROM Offer OO
INNER JOIN (
SELECT Min(Price) as MinP
FROM offer O
INNER JOIN (
SELECT max(OI.created) as LatestOffer, OI.Dealer_ID, OI.Product_ID
FROM Offer OI
LEFT JOIN invalids I
on OI.Id = I.offer_Id
WHERE I.ID is null
GROUP BY OI.Dealer_Id, OI.Product_Id
) B
on O.Dealer_Id = B.Dealer_Id
and O.Product_Id = B.Product_Id
and O.Created = B.LatestOffer
) Z
on OO.Price = Z.MinP
INNER JOIN product P
on P.ID = OO.Product_ID
SQL FIDDLE

SQL take max of similar id's from join

I'm fairly new to more advanced SQL queries
Given the following tables and associated fields:
Person
PersonId, FirstName, LastName
Order
OrderId, PersonId, OrderDateTime
I want to write a query that will join both tables by PersonId and will retrieve every person and their most recent order.
So if James Doe (PersonId = 1) below has many orders in the orders table,
OrderId, PersonId, OrderDateTime
1 1 12/1/2013 9:01 AM
2 1 2/1/2011 5:01 AM
3 2 10/1/2010 1:10 AM
it will only take the most recent for his.
PersonId NameFirst NameLast OrderId OrderDateTime
1 James Doe 1 12/1/2013 9:01 AM
2 John Doe 3 10/1/2010 1:10 AM
I have been trying something like this
SELECT p.PersonID, o.OrderID, MAX(o.OrderDateTime) From Person p
JOIN Orders o ON p.PersonID = o.PersonID
GROUP BY p.PersonID,
Thanks
The inner query in this solution is a temporary table containing the most recent orders for each person. I join this back to the Orders table to get the fields you want, and then join again to the Person table.
SELECT p.PersonID, p.NameFirst, p.NameLast, o.OrderID, o.OrderDateTime
FROM Person p INNER JOIN Orders o
ON o.PersonId = p.PersonId
INNER JOIN
(
SELECT o1.PersonId, MAX(o1.OrderDateTime) AS maxTime
FROM Orders o1
GROUP BY o1.PersonId
) t
ON o.PersonId = t.PersonId AND o.OrderDateTime = t.maxTime
You can use variables to simulate ROW_NUMBER not available in MySQL:
SELECT p.PersonId, FirstName, LastName,
o.OrderId, o.OrderDateTime
FROM Person AS p
LEFT JOIN (
SELECT OrderId, OrderDateTime, PersonId,
#row_number := IF(#pid <> PersonId,
IF(#pid:=PersonId, 1, 1),
IF(#pid:=PersonId, #row_number+1, #row_number+1)) AS rn
FROM `Order`
CROSS JOIN (SELECT #row_number := 0, #pid := 0) vars
ORDER BY OrderDateTime DESC
) AS o ON p.PersonId = o.PersonId AND o.rn = 1
rn = 1 for the top level record within each PersonId slice of the derived table. Using this predicate in the ON clause of the LEFT JOIN we can match each row of Person to the most recent row of Order and obtain all Order fields.
Demo here
EDIT:
In SQL-Server the query looks like this:
SELECT p.*, o.OrderId, o.OrderDateTime
FROM Person AS p
LEFT JOIN (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY PersonId
ORDER BY OrderDateTime DESC) AS rn
FROM [Order]
) AS o ON p.PersonId = o.PersonId AND o.rn = 1
So the most recent order per every person. You could do something like this:
select p.*, o.*
from person p
inner join orders o on p.personId = o.personId
inner join (
-- get the max order per person
SELECT max(orderId) as orderId, personId
from orders
group by personId
) maxOrder on o.orderId = maxOrder.orderId
joining o onto maxOrder on the orderId filters the result set only to the orders that are also the maximum order per customer.
Note that I used the ID on the table rather than the datetime, as the ID is guaranteed to be unique (for help with joins) - this is not always available depending on the nature of the use in the table, but it looks like it should work for your case.

SQL - Select distinct rows and join them with another table to get data

I have 2 tables:
1) person (person_id, person_name)
2) cars (car_id, person_id)
I want to get all the people's names that have cars with no duplicates.
This is what I have come up with:
SELECT person.person_name, cars.person_id
FROM cars
INNER JOIN person
ON person.person_id=cars.person_id
But I don't want duplicates, so I need to incorporate it using something like this:
SELECT DISTINCT person_id FROM cars
select person_name from person
where person_id in ( select person_id from cars )
SELECT DISTINCT person.person_name, person.person_id
FROM cars
INNER JOIN person
ON person.person_id=cars.person_id
Although there may be more performant alternatives.
SELECT p.person_id AS person_id, p.name AS name FROM person p, cars c
WHERE p.person_id = c.person_id
GROUP BY b.brand_id
SELECT DISTINCT(person.person_name)
FROM person, cars
WHERE person.person_id = cars.person_id
Try this:
SELECT p.person_id, p.person_name
FROM person p
WHERE EXISTS
(
SELECT 1
FROM cars c
WHERE c.person_id = p.person_id
)