Related
I am trying out a problem which states me to find " For each year, count the number of movies in that year that had only female actors".
Table schema is as follows:
-------------------- ----------------------- ----------------------
| Movie | | Person | | Cast |
-------------------- ------------------------ ----------------------
| MovieID | year | | PersonID | Gender | | MovieID | PersonID |
-------------------- ------------------------ ----------------------
Running the following query:
SELECT M.YEAR, COUNT(M.MID) NUMBER_OF_FEMALE_ONLY_MOVIES FROM MOVIE M
WHERE M.MID IN (SELECT X.MID FROM (SELECT AX.MID, COUNT(AX.PID) TOTAL_CAST
FROM M_CAST AX GROUP BY AX.MID) X
WHERE
X.TOTAL_CAST = (SELECT COUNT(A.PID) FROM M_CAST A, PERSON B WHERE A.MID =
X.MID AND
TRIM(B.PID) = TRIM(A.PID) AND B.GENDER = 'Female')) GROUP BY M.YEAR
My results are :
---------------------------------------
| year | NUMBER_OF_FEMALE_ONLY_MOVIES |
---------------------------------------
| 1999 | 1 |
| 2005 | 1 |
| 2009 | 1 |
| 2012 | 1 |
| 2018 | 1 |
----------------------------------------
But I need to return 0 as count for the years which do not have any such movies.
Eg.
2013 0
WITH
PERSON_CAST_MERGE AS
(
SELECT P.PID,C.MID,GENDER
FROM PERSON P
INNER JOIN M_CAST C ON C.PID = P.PID
),
MALE_COUNT AS
(
SELECT F.MID FROM PERSON_CAST_MERGE F
WHERE TRIM(F.GENDER) NOT LIKE "%FEMALE%"
),
FEMALE_COUNT AS
(
SELECT F.MID FROM PERSON_CAST_MERGE F
WHERE TRIM(F.GENDER) LIKE "%FEMALE%"
),
ONLY_FEMALE AS
(
SELECT F.MID FROM FEMALE_COUNT F
WHERE F.MID NOT IN (SELECT M.MID FROM MALE_COUNT M)
),
TEST AS
(
SELECT M.YEAR,COUNT(M.MID) AS NO_OF_MOVIES
FROM ONLY_FEMALE F
INNER JOIN MOVIE M ON M.MID = F.MID
GROUP BY M.YEAR
)
SELECT M.YEAR,
CASE
WHEN M.YEAR IN (SELECT F.YEAR FROM TEST F) THEN
(SELECT F.NO_OF_MOVIES FROM TEST F WHERE F.YEAR = M.YEAR)
WHEN M.YEAR <> (SELECT F.YEAR FROM TEST F) THEN
0
END
AS NO_OF_MOVIES
FROM MOVIE M
GROUP BY M.YEAR
I'd suggest exploring the data within the CTE to get a better understanding.
First CTE (all_cast): Return the entire movie cast
Second CTE (male_present): Return movie id's from all_cast where there exists male actors.
Result: Return movies from all_cast where movie id is not present in male_present
WITH all_cast AS (
SELECT SUBSTR(m."year",-4) as 'Year', m.title, trim(m.MID) as MID, p.Name, trim(p.Gender) as Gender
FROM Movie m
JOIN M_Cast mc
ON m.MID = mc.MID
JOIN Person p
ON trim(mc.PID) = p.PID
),
male_present AS (
SELECT year, mid, name
FROM all_cast
WHERE Gender = 'Male'
)
SELECT year, COUNT(DISTINCT mid) as 'All Female Cast'
FROM all_cast a
WHERE NOT EXISTS (SELECT * FROM male_present WHERE a.mid = mid)
GROUP BY year
You need only the group by with subquery as you require reference to the movieids of personids with gender as female in person
SELECT YEAR, COUNT(*) FROM
MOVIE
Where MovieId IN (SELECT MOVIEId
from CAST WHERE PERSONID IN
(Select PersonId from Person Where
Gender ='FEMALE'))
Group by Year
Try this- A DISTINCT MovieID is required as there may have multiple Female casting for a single movie. Distinct will provide the actual count of movies.
SELECT
M.Year,
COUNT(DISTINCT MovieID)
FROM Movie M
INNER JOIN Cast C ON M.MovieID = C.MovieID
INNER JOIN Person P ON C.PersonID = P.PersonID
WHERE P.Gender = 'Female'
GROUP BY M.Year;
I think the problem can be solved by joining all tables and filtering on WHERE clause for female actors. In this case joining tables will also give better performance rather than sub-querying.
Please try the following code:
Select year, count(*)
from movie
join Cast on movie.movieid = cast.movieid
join person on person.personid = cast.personid
where person.gender = 'Female'
group by year
Please let me know if that works fine for you.
By merging your query with the Movie table using the outer left join, you can get the desired results. The time taken will be very low compared to the answer posted by #Lucky
WITH FEMALE_ONLY AS
(SELECT M.YEAR,
COUNT(M.MID) COUNT_ALL_FEMALE
FROM MOVIE M
WHERE M.MID IN
(SELECT Q.MID
FROM
(SELECT MC.MID,
COUNT(MC.PID) total
FROM M_CAST MC
GROUP BY MC.MID) Q
WHERE Q.total =
(SELECT COUNT(A.PID)
FROM M_CAST A,
PERSON B
WHERE A.MID = Q.MID
AND TRIM(B.PID) = TRIM(A.PID)
AND B.Gender = 'Female'))
GROUP BY M.YEAR)
SELECT DISTINCT M.year,
coalesce(FO.COUNT_ALL_FEMALE, 0) FEMALE_ONLY_MOVIES
FROM Movie M
LEFT OUTER JOIN FEMALE_ONLY FO ON M.year = FO.year
ORDER BY M.year;
You can do like this
select z.year, count(*)
from Movie z
where not exists (select *
from Person x, M_Cast xy
where x.PID = xy.PID and xy.MID = z.MID and x.gender!='Female')
group by z.year;
I have this table structure:
TABLE: PERSON TABLE: CAR
PersonID PersonID | CarID
------ ---------|---------
1 1 | 51
1 | 52
TABLE: PET TABLE: AGE
PersonID | PetID Person | AgeID
---------|---- -------|----
1 | 81 1 | 20
1 | 82
1 | 81
One person can have many cars and pets, but only one age.
I want to count the number of cars someone has, count the number of pets someone has, and list their age.
This is what I have so far:
select
car.personid as person,
count(car.carid) as cars,
null as pets
from car
where car.personid = 1
group by car.personid
union all
select
pet.personid as person,
null as cars,
count(pet.petid) as pets
from pet
where pet.personid = 1
group by pet.personid
This produces:
Person | Cars | Pets
-------|------|-----
1 | 2 | null
1 | null | 3
But I'd like the results to look like this:
Person | Cars | Pets | Age
-------|------|------|----
1 | 2 | 3 | 20
There's a fiddle here: http://sqlfiddle.com/#!3/f584a/1/0
I'm completely stuck on how to bring the records into one row and add the age column.
SQL Fiddle
Query 1:
SELECT p.PersonID,
( SELECT COUNT(1) FROM CAR c WHERE c.PersonID = p.PersonID ) AS Cars,
( SELECT COUNT(1) FROM PET t WHERE t.PersonID = p.PersonID ) AS Pets,
a.AgeID AS Age
FROM PERSON p
LEFT OUTER JOIN
AGE a
ON ( p.PersonID = a.PersonID )
Results:
| PersonID | Cars | Pets | Age |
|----------|------|------|-----|
| 1 | 2 | 3 | 20 |
Query 2:
WITH numberOfPets AS (
SELECT PersonID,
COUNT(1) AS numberOfPets
FROM PET
GROUP BY PersonID
),
numberOfCars AS (
SELECT PersonID,
COUNT(1) AS numberOfCars
FROM CAR
GROUP BY PersonID
)
SELECT p.PersonID,
COALESCE( numberOfCars, 0 ) AS Cars,
COALESCE( numberOfPets, 0 ) AS Pets,
AgeID AS Age
FROM PERSON p
LEFT OUTER JOIN AGE a ON ( p.PersonID = a.PersonID )
LEFT OUTER JOIN numberOfPets t ON ( p.PersonID = t.PersonID )
LEFT OUTER JOIN numberOfCars c ON ( p.PersonID = c.PersonID )
Results:
| PersonID | Cars | Pets | Age |
|----------|------|------|-----|
| 1 | 2 | 3 | 20 |
Should work with duplicate Petid or duplicate carid
SqlFiddle Demo
WITH person_cte
AS (SELECT *
FROM person),
car_count
AS (SELECT Count(1) AS car,
p.personid
FROM person_cte p
LEFT OUTER JOIN car c
ON p.personid = c.personid
GROUP BY p.personid),
pet_count
AS (SELECT Count(1) AS Pet,
p.personid
FROM person_cte p
LEFT OUTER JOIN pet c
ON p.personid = c.personid
GROUP BY p.personid)
SELECT c.personid,
c.car,
p.pet,
a.ageid
FROM car_count c
INNER JOIN age a
ON c.personid = a.personid
INNER JOIN pet_count p
ON p.personid = c.personid;
If there wont be any duplicates in Carid or Petid then use this
SqlFiddle Demo
SELECT p.personid,
a.ageid,
Count(DISTINCT carid) as carid,
Count(DISTINCT petid) as petid
FROM person p
INNER JOIN age a
ON p.personid = a.personid
LEFT OUTER JOIN car c
ON p.personid = c.personid
LEFT OUTER JOIN pet pe
ON p.personid = pe.personid
GROUP BY p.personid,
a.ageid
One issue I see with most of these responses is that they will only include people who own a car. What if the person doesn't have a vehicle, but has pets? What if they haven't entered their age, yet? You'd lose that metric.
Tie the person table into this as the main requirement. To get the rest of the numbers you could take various approcahes, such as a simple series of left outer joins on the other tables and count their result.
Also note that tagging "ID" at the end of values is a misnomer and considered bad design practice. If it's an age, just call it "age" or "age_value", but not "AgeID". I would also suggest denormalizing your AGE and PERSON tables and make Age (not AgeID) a nullable field.
E.G.
SELECT
PERSON.PersonID,
AgeID AS Age,
CarCount,
PetCount
FROM
#PERSON AS PERSON
LEFT OUTER JOIN AGE AS AGE
ON AGE.PersonID = PERSON.PersonID
LEFT OUTER JOIN
( SELECT PersonID, COUNT( 1 ) AS CarCount FROM CAR GROUP BY PersonID ) AS CAR
ON CAR.PersonID = PERSON.PersonID
LEFT OUTER JOIN
( SELECT PersonID, COUNT( 1 ) AS PetCount FROM PET GROUP BY PersonID ) AS PET
ON PET.PersonID = PERSON.PersonID
You need to be joining on single values, so do your counts within subqueries
select c.PersonID,a.CarID,b.PetID,c.AgeID from (
select person.PersonID, COUNT(car.CarID) as CarID
from Person INNER JOIN Car on Person.PersonID = Car.PersonID
group by Person.PersonID) a
inner join (
select person.PersonID, COUNT(Pet.PetID) as PetID
from Person INNER JOIN Pet on Person.PersonID = Pet.PersonID
group by Person.PersonID) b
on a.PersonID = b.PersonID
inner join (select PersonID,AgeID from Age) c
on a.PersonID = c.PersonID
Another method is
select person,
sum(cars) as cars,
sum(pets) as pets
from
(
select
car.personid as person,
count(car.carid) as cars,
null as pets
from car
where car.personid = 1
group by car.personid
union all
select
pet.personid as person,
null as cars,
count(pet.petid) as pets
from pet
where pet.personid = 1
group by pet.personid
) as t
group by person
Do you want to count distinct number of cars/pets? If so, add a distinct within the count.
select
person.personid as person,
count(car.carid) as cars,
count(pet.petid) as pets
age.ageID
from person
left outer join pet on pet.personid = person.personid
left outer join car on car.personid = person.personid
left outer join age on age.personid = person.personid
where car.personid = 1
group by car.personid, age.ageID;
I have two tables with schema and data as below. Table A has an id and an associated name. Table B associates the id from Table A with a price and otherAttr. For each entry in Table A, there may be multiple entries with different prices in Table B, but otherAttr is the same for each entry.
Given an id for Table A, I would like to select the name, otherAttr, and the minimum price.
The below query returns multiple results, I need to write a query that will return a single result with the minimum price.
SELECT a.name, b.price, b.otherAttr
FROM A a
LEFT Join B b on b.idFromA = 1
WHERE a.id = 1
Table A Table B
id | name idFromA | price | otherAttr
-------- ---------------------------
1 | A 1 | 200 | abc
2 | B 1 | 300 | abc
1 | 400 | abc
2 | 20 | def
2 | 30 | def
2 | 40 | ef
I massively oversimplified my example. In addition to selecting the min price and otherAttr from Table B, I also have to select a bunch of other attributes from joins on other tables. Which is why I was thinking the Group By and Min should be a subquery of the join on Table B, as a way to avoid Grouping By all the attributes I am selecting (because the attributes being selected for vary programmatically).
The Actual query looks more like:
SELECT a.name, b.price, b.otherAttr, c.x, c.y, d.e, d.f, g.h....
FROM A a
LEFT Join B b on b.idFromA = 1
LEFT Join C c on something...
LEFT Join D d on something...
LEFT Join G g on something...
WHERE a.id = 1
To get this, you could use GROUP BY in an INNER query:
SELECT gd.name, gd.price,gd.otherAttr, c.x, c.y, d.e, d.f, g.h....
FROM
(SELECT a.id,a.name, MIN(b.price) as price, b.otherAttr
FROM A a
LEFT Join B b on b.idFromA = 1
WHERE a.id = 1
GROUP BY a.id,a.name,b.otherAttr) gd
LEFT Join B b on b.idFromA = 1
LEFT Join C c on something...
LEFT Join D d on something...
LEFT Join G g on something...
Try:-
SELECT a.name, MIN(b.price) minprice, b.otherAttr
FROM A a
LEFT Join B b ON a.Id = b.idFromA
GROUP BY a.name, b.otherAttr
HAVING a.id = 1
You could just do this instead:
SELECT a.name, MIN(b.price), MIN(b.otherAttr)
FROM TableA a
LEFT JOIN TableB b on b.idFromA = a.id
GROUP BY a.name
HAVING a.id = 1;
You need to inner join on price as well in addition to id on the subquery to intersect the right record(s) with the lowest price(s). Then TOP(1) will return only one of those records. You can avoid using TOP(1) if you can expand the conditions and group by fields in the subquery so you schema can assure only a single record is returned for that combination of attributes. Lastly, avoid left joins when intersecting sets.
SELECT TOP(1) p.id, p.price, b.OtherAttr
FROM B as b
INNER JOIN
(SELECT A.id, min(B.price) as price
FROM B
INNER JOIN A on A.id=B.idFromA and A.ID=1
GROUP BY A.id) as p on b.idFromA=p.id and b.price=p.price
I have a Users table and a Groups table. Users can be in multiple groups via a 'UserInGroup' table and Groups can have a 'GroupTypeId'.
[User]
--------------
Id | Name
1 | Bob
2 | James
[UserInGroup]
-----------------
UserId | GroupId
1 1
1 2
[Group]
Id | Name | TypeId
------------------------
1 | Directors | 1
2 | IT | 1
3 | London | 2
I want to create a query to return for example users that are in both 'Directors' AND 'London' (rather than 'Directors' OR 'London'). However, I only want to AND groups of a different 'Type', I want to OR groups of the same type. I could do with having a separate table per group type but I can't as they are created dynamically.
Ideally I want to be able to query users who are in 'Directors' OR 'IT' AND 'London'.
What is the most efficient way of doing this?
This problem is commonly known as Relational Division.
SELECT a.Name
FROM [user] a
INNER JOIN UserInGroup b
ON a.ID = b.UserID
INNER JOIN [Group] c
ON b.groupID = c.TypeId
WHERE c.Name IN ('Directors','London')
GROUP BY a.Name
HAVING COUNT(*) = 2
SQLFiddle Demo
SQL of Relational Division
But if a UNIQUE constraint was not enforce on GROUP for every USER, DISTINCT keywords is needed to filter out unique groups:
SELECT a.Name
FROM [user] a
INNER JOIN UserInGroup b
ON a.ID = b.UserID
INNER JOIN [Group] c
ON b.groupID = c.TypeId
WHERE c.Name IN ('Directors','London')
GROUP BY a.Name
HAVING COUNT(DISTINCT c.Name) = 2
OUTPUT from both queries
╔══════╗
║ NAME ║
╠══════╣
║ Bob ║
╚══════╝
I arrived at the following solution (with help from J W and this article):
SELECT
u.Name UserName
FROM [User] u
INNER JOIN [UserInGroup] uig
ON uig.UserId = u.Id
INNER JOIN [Group] g
ON g.Id = uig.GroupId
WHERE
g.Id IN (1,2,3) -- these are the passed in groupids
GROUP BY
u.Name
having count(distinct g.TypeId)
= (select count(distinct g1.TypeId)
from [group] g1 where g1.Id IN (1,2,3))
This allows me to group the relational division by a discriminator field. An alternative would be this:
SELECT a.Name
FROM [User] a
INNER JOIN
(
SELECT b.UserID
FROM UserInGroup b
INNER JOIN [Group] c
ON b.groupID = c.Id
WHERE c.Name IN ('Directors','IT')
GROUP BY b.UserID
HAVING COUNT(DISTINCT c.Name) >= 1
) b ON a.ID = b.UserID
INNER JOIN
(
SELECT DISTINCT b.UserID
FROM UserInGroup b
INNER JOIN [Group] c
ON b.groupID = c.Id
WHERE c.Name = 'London'
) c ON a.ID = c.UserID
With an extra join for each GroupTypeId. Execution plans look similar, so I went with the first option.
My question, is there a faster way to the following query?
I'm using ORACLE 10g
Say i have a table Manufacturer and Car, and i want to count all occurrences of the column 'Car.Name'. here is How i'd do it:
SELECT manuf.Name, COUNT(car1.Name), COUNT(car2.Name), COUNT(car3.Name)
FROM Manufacturer manuf
LEFT JOIN (SELECT * FROM Car c where c.Name = 'Ferrari1') car1 ON manuf.PK = car1.ManufPK
LEFT JOIN (SELECT * FROM Car c where c.Name = 'Ferrari2') car2 ON manuf.PK = car2.ManufPK
LEFT JOIN (SELECT * FROM Car c where c.Name = 'Ferrari3') car3 ON manuf.PK = car3.ManufPK
GROUP BY manuf.Name
Wanted Results:
Manufacturer | Ferrari1 | Ferrari2 | Ferrari3
----------------------------------------------
Fiat | 1 | 0 | 5
Ford | 2 | 3 | 0
I tried this with few LEFT JOINs, and it worked fine. But when i added a lot (like 90+), it was ultra slow (more than 1 minute).
My question, is there a faster way to do this query?
If you are happy to see the cars counted down the page, try:
select m.Name manufacturer_name,
c.Name car_name,
count(*)
from Manufacturer m
left join Car c
on m.PK = c.ManufPK and c.Name in ('Ferrari1','Ferrari2','Ferrari3')
group by m.Name, c.Name
If you need to see individual cars across the page, try:
select m.Name manufacturer_name,
sum(case c.Name when 'Ferrari1' then 1 else 0 end) Ferrari1_Count,
sum(case c.Name when 'Ferrari2' then 1 else 0 end) Ferrari2_Count,
sum(case c.Name when 'Ferrari3' then 1 else 0 end) Ferrari3_Count
from Manufacturer m
left join Car c
on m.PK = c.ManufPK and c.Name in ('Ferrari1','Ferrari2','Ferrari3')
group by m.Name
SELECT manuf.Name, COUNT(DISTINCT c.Name)
FROM Manufacturer manuf
LEFT JOIN Car c ON manuf.PK = c.ManufPK
GROUP BY manuf.Name
OR depending on your needs
SELECT manuf.Name, c.Name, COUNT(*) Cnt
FROM Manufacturer manuf
LEFT JOIN Car c ON manuf.PK = c.ManufPK
GROUP BY manuf.Name, c.Name
PS: Your question is not very clear. Provide some wanted resultset to refine the answer
You can also try this:
SELECT manuf.Name
, car1.cnt AS Ferrari1
, car2.cnt AS Ferrari2
, car3.cnt AS Ferrari3
FROM
Manufacturer AS manuf
LEFT JOIN
( SELECT ManufPK, COUNT(*) AS cnt
FROM Car
WHERE Name = 'Ferrari1'
GROUP BY ManufPK
) AS car1
ON car1.ManufPK = manuf.PK
LEFT JOIN
( SELECT ManufPK, COUNT(*) AS cnt
FROM Car
WHERE Name = 'Ferrari2'
GROUP BY ManufPK
) AS car2
ON car2.ManufPK = manuf.PK
LEFT JOIN
( SELECT ManufPK, COUNT(*) AS cnt
FROM Car
WHERE Name = 'Ferrari3'
GROUP BY ManufPK
) AS car3
ON car3.ManufPK = manuf.PK
ORDER BY manuf.Name