I have two tables: Car and CarBorrowed.
The Car table contains all cars in the car pool with an ID and a group the car belongs to. For example:
ID 1, Car 1, Group Renault
ID 2, Car 3, Group Renault
ID 3, Car 4, Group VW
ID 4, Car 6, Group BMW
ID 5, Car 7, Group BMW
The CarBorrow table contains all cars which are borrowed on a particular day
Car 1, Borrowed on 23.08.2012
Car 3, Borrowed on 23.08.2012
Car 5, Borrowed on 23.08.2012
Now I want all groups, where no cars are left (today= 23.08.2012). So I should get "Group Renault"
First, join the tables, so we have for every car its borrows(a day).
select c.id, c.GroupName, cb.day
from car c
left join (select * from CarBorrow where day = '23 Aug 2012') cb
on (c.id = cb.id)
All cars not borrowed will have null at day.
After this, we shoud select all Groups that does not have nulls.
Bellow an trick to get it:
select GroupName
FROM(
select c.id, c.GroupName, cb.day
from car c
left join (select * from CarBorrow where day = '23 Aug 2012') cb
on (c.id = cb.id)
)
group by GroupName
having count(day) = count(*)
(Days that are null are not counted by COUNT)
SELECT distinct(D1.CARGROUP)
FROM den_car d1
MINUS
(SELECT D.CARGROUP
FROM den_car d
WHERE d.id IN (SELECT c.ID
FROM den_car c
MINUS
SELECT b.id
FROM den_car_borrow b
WHERE B.DATE_BORROW = TO_DATE (SYSDATE)))
This may be optimized but the idea is simple: Find the borrowed ones, subtract it from all cars. Then find the remaning groups.
Hope it helps. (By the way of course there are lots of other ways to do it.)
Hmmm . . . One way to approach this query is to count the cars in a group and also count the cars on a particular day, then take the groups where the borrowed equals the available:
select borrowed.BorrowedOn, available.CarGroup
from (select c.CarGroup, count(*) as cnt
from car c
group by c.CarGroup
) available left outer join
(select c.CarGroup, cb.BorrowedOn, count(*) as cnt
from CarBorrowed cb join
Car c
on cb.CarId = c.CarId
group by c.CarGroup, cb.BorrowedOn
) borrowed
on available.CarGroup = borrowed.CarGroup
where available.cnt = borrowed.cnt
By the way, "Group" is a bad name for a column, since it is a SQL reserved word. I've renamed it to CarGroup.
If the same car can be borrowed more than once on a given day, then change the count(*) in the second subquery to count(distinct cb.carId).
If you want just one day, you can add a clause to the WHERE clause.
I think I have a solution now:
select x.groupname from
(select a.groupname, count(*) as cnt from car a group by a.groupname) x
inner join
(
select b.groupname, count(*) as cnt from car b where b.carid in (select caraid from modavail where day ='23.08.2012')
group by b.groupname
) y
on x.groupname = y.groupname
where x.cnt = y.cnt and y.cnt ! = 0 ORDER BY GROUPNAME;
Thanks for your help!!!!
Related
I have 3 tables:
companies (id, name)
union_products (id, name)
products (id, company_id, union_product_id, price_per_one_product)
I need to get all companies which have products with union_product_id in (1,2) and total price of products (per company) is less than 100.
What I am trying to do now:
select * from "companies" where exists
(
select id from "products"
where "companies"."id" = "products"."company_id"
and "union_product_id" in (1, 2)
group by id
having COUNT(distinct union_product_id) = 2 AND SUM(price_per_one_product) < 100
)
The problem I stuck with is that I'm getting 0 rows from the query above, but it works if I'll change COUNT(distinct union_product_id) = 2 to 1.
DB fiddle: https://www.db-fiddle.com/f/iRjfzJe2MTmnwEcDXuJoxn/0
Try to join the three tables as the following:
SELECT C.id, C.name FROM
products P JOIN union_products U
ON P.union_product_id=U.id
JOIN companies C
ON P.company_id=C.id
WHERE P.union_product_id IN (1, 2)
GROUP BY C.id, C.name
HAVING COUNT(DISTINCT P.union_product_id) = 2 AND
SUM(P.price_for_one_product) < 100
ORDER BY C.id
See a demo.
SELECT c.name FROM "companies" c
JOIN "products" p ON c.id = p.company_id
WHERE union_product_id IN (1, 2) AND price_for_one_product < 100
GROUP BY c.name
HAVING COUNT(DISTINCT p.name) =2
This would provide you all the company(s) name(s) which has provides both union_product_id 1 and 2 and the price_for_one_product/ price_per_one_product is less than 100.
Note: You might need to change price_for_one_product with price_per_one_product, as in question you have used price_per_one_product but db-fiddle link table defination has used price_for_one_product.
I have a movie table which has year and movie details like title , movie id( mid) and a table m_cast where i have all the actors in that movie.
I would like to get all the actors who have never been unemployed for more than 3 years. ( Assuming actors are unemployed between two consecutive movies)
i code i came up with is
select a.yr1 y1 , b.yr2 y2 , a.yr1 - b.yr2 diff from
(select substr(substr(trim(year),-5),0,5) yr1 , * from movie m inner join m_cast p on m.mid = p.mid order by pid , yr1) a ,
(select substr(substr(trim(year),-5),0,5) yr2 , * from movie m inner join m_cast p on m.mid = p.mid order by pid, yr2) b on a.yr1 > b.yr2
where not exists
(select count(*) from movie m inner join m_cast p on m.mid = p.mid
and cast(substr(substr(trim(year),-5),0,5) as integer) < a.yr1 and cast(substr(substr(trim(year),-5),0,5) as integer) > b.yr2)
Self join itself takes a lot of time. And lag and lead functions do not work in SQLite version i am using.
I'm assuming the movie table has a column called year, and a column to identify the actor's name. Something like : year int, actorId int
The fastest way to run your query is to filter the last 3 years from your movie table and then to group by your actors the distinct count of years.
Example after filtering
ActorId Year
1. 2018
1. 2018
1. 2017
2. 2016
2. 2017
2. 2018
Then group by and select distinct :
Select actorId from movieTable group by actorId having count (distinct (Year)) =3
And that will only return the actors who have worked in the last 3 years. Once you have your actors id's filtered out in that column do a join to the table that holds their names.
Sorry about the format of my writing - did it from my cellphone.
Regards,
Jorge D. Lopez
I think I have end up in a bit of a dead end.
Let's say I have a dataset, which is fairly easy -
person_id and book_id. Which is pretty much factual table that says person X bought books A, B and C.
I know how to find out how many persons have bought Book X and Book Y together.
This is
select a.book_id as B1, b.book_id as B2, count(b.person_id) as
Bought_Together
from dbo.data a
cross join dbo.data b
where a.book_id != b.book_id and a.person_id = b.person_id
group by a.book_id, b.book_id
Yet again this is where my brain decided to shut down. I know that I would probably need to do it so that
count(b.person_id) / all the people that bought book A * 100
but im not entirely sure.
I hope I was clear enough.
EDIT1: I'm using SQL Server 2017 currently, so i think the correct answer is T-SQL?.
In the end the format should be something similliar to this. Also there is no cases where person A could have bought three copies of book X.
Book1 Book2 HowManyPeopleBoughtBook2
1 2 50%
1 3 7%
2 3 15%
2 1 40%
3 1 60%
3 2 20%
EDIT2: Let it be said there is hundreds of thousands of rows in the database. Yes this is bit related to a data science course i am taking - hence huge amounts of data.
You can extend your logic to do this:
select a.book_id as B1, b.book_id as B2,
count(b.book_id) as bought_second_book,
count(b.book_id) * 1.0 / book_cnt as ratio_Bought_Together
from (select a.*, count(*) over (partition by a.book_id) as book_cnt
from dbo.data a
) a left join
dbo.data b
on a.person_id = b.person_id and a.book_id <> b.book_id
group by a.book_id, b.book_id, a.book_cnt;
This assumes that people buy a book only once. If there are duplicates, then count(distinct) would adjust for that.
If you would like to generate all possible combinations of the pairs of books bought together along with the percentage of the persons who bought that combination the following can help
create table data1(book_id int, person_id int)
insert into data1
select *
from (values(1,300)
,(2,300)
,(2,301)
,(1,301)
,(3,301)
)t(book_id,person_id)
with books
as (select distinct book_id
from data1 a
)
,tot_persons
as (select count(distinct person_id) as tot_cnt
from data1
)
,pairs
as (
select a.book_id as col1 /* This block generates all possible pair combinations of books*/
,b.book_id as col2
from books a
join books b
on a.book_id<b.book_id
)
select a.col1,a.col2
,count(b.person_id)*100/(select tot_cnt from tot_persons) as percent_of_persons_buying_both
from pairs a
join data1 b
on a.col1=b.book_id
where exists(select 1
from data1 b1
where b.person_id=b1.person_id
and a.col2=b1.book_id)
group by a.col1,a.col2
On my phone, apologies for typo's
SELECT
SUM(bought_b) * 100.0 / COUNT(*)
FROM
(
SELECT
person_id,
MAX(CASE WHEN book_id = 'A' THEN 1 END) AS bought_a,
MAX(CASE WHEN book_id = 'B' THEN 1 END) AS bought_b
FROM
data
WHERE
book_id IN ('A', 'B')
GROUP BY
person_id
)
person_stats
WHERE
bought_a = 1
On my phone, apologies for typo's
EDIT : just saw that you want all combinations, just just one set combination.
WITH
book AS
(
SELECT DISTINCT book_id FROM data
)
SELECT
book_a_id,
book_b_id,
bought_b * 100.0 / bought_b
FROM
(
SELECT
book_a.book_id AS book_a_id,
book_b.book_id AS book_b_id,
COUNT(DISTINCT data_a.person_id) AS bought_a,
COUNT(DISTINCT data_b.person_id) AS bought_b
FROM
book AS book_a
CROSS JOIN
book AS book_b
INNER JOIN
data AS data_a
ON data_a.book_id = book_a.book_id
LEFT JOIN
data AS data_b
ON data_b.book_id = book_b.book_id
GROUP BY
book_a.book_id,
book_b.book_id
)
stats
I have a table called BB_BOATBKG which holds passengers travel details with columns Z_ID, BK_KEY and PAXSUM where:
Z_ID = BookingNumber* LegNumber
BK_KEY = BookingNumber
PAXSUM = Total number passengers travelled in each leg for a particular booking
For Example:
Z_ID BK_KEY PAXSUM
001234*01 001234 2
001234*02 001234 3
001287*01 001287 5
001287*02 001287 5
002323*01 002323 7
002323*02 002323 6
I would like to get a list of all Booking Numbers BK_KEY from BB_BOATBKG where the total number of passengers PAXSUM is different in each leg for the same booking
Example, For Booking number A, A*Leg01 might have 2 Passengers, A* Leg02 might have 3 passengers
Dependent of your RDBMs there might be several options availible. A solution that should work for most is:
SELECT A.Z_ID, A.BK_KEY, A.PAXSUM
FROM BB_BOATBKG A
JOIN (
SELECT BK_KEY
FBB_BOATBKGROM BB_BBK_KEY
GROUP BY BK_KEY
HAVING COUNT( DISTINCT PAXSUM ) > 1
) B
ON A.BK_KEY = B.BK_KEY
If your DBMS support OLAP functions, have a look at RANK() OVER (...)
It's a little counterintuitive, but you could join the table to itself on {BK_KEY, PAXSUM} and pull out only the records whose joined result is null.
I think this does it:
SELECT
a.BK_KEY
FROM
BB_BOATBKG a
LEFT OUTER JOIN BB_BOATBKG b ON a.BK_KEY = b.BK_KEY AND a.PAXSUM = b.PAXSUM
WHERE
b.Z_ID IS NULL
GROUP BY
a.BK_KEY
Edit: I think I missed anything beyond the trivial case. I think you can do it with some really nasty subselecting though, a la:
SELECT
b.BK_KEY
FROM
(
SELECT
a.BK_KEY,
Count = COUNT(*)
FROM
(
SELECT
a.BK_KEY,
a.PAXSUM
FROM
BB_BOATBKG a
GROUP BY
a.BK_KEY,
a.PAXSUM
HAVING
COUNT(*) = 1
) a
GROUP BY
a.BK_KEY
) b
INNER JOIN
(
SELECT
c.BK_KEY,
Count = COUNT(*)
FROM
BB_BOATBKG c
GROUP BY
c.BK_KEY
) c ON b.BK_KEY = c.BK_KEY AND b.Count = c.Count
Imagine you have two tables, with a one to many relationship.
For this example, I will suggest that there are two tables: Person, and Homes.
The person table holds a persons name, and gives them an ID. The homes table, holds the association of homes to a person. PID joins to "Person.ID"
And, in this tiny DB, a person can have no homes, or many homes.
I hope I drew that right.
How do I write a select, that returns everyone with every specified house type?
Let's say these are valid "Types" in the homes table:
Cottage, Main, Mansion, Spaceport.
I want to return everyone, in the Person table, who has a spaceport and a Cottage.
The best I could come up with was this:
SELECT DISTINCT( p.name ) AS name
FROM person p
INNER JOIN homes h ON h.pid = p.id
WHERE 'spaceport' in (
SELECT DISTINCT( type ) AS type
FROM homes
WHERE pid = p.id
)
AND 'cottage' in (
SELECT DISTINCT( type ) AS type
FROM homes
WHERE pid = p.id
)
When I wrote that, it works, but I'm pretty sure there has to be a better way.
The HAVING clause here will guarantee that the persons returned have both types, not just one or the other.
SELECT p.name
FROM person p
INNER JOIN homes h
ON p.id = h.pid
AND h.type IN ('spaceport', 'cottage')
GROUP BY p.name
HAVING COUNT(DISTINCT h.type) = 2
select * from homes;
home_id person_id type
--
1 1 cottage
2 1 mansion
3 2 cottage
4 3 mansion
5 4 cottage
6 4 cottage
To find the id numbers of every person who has both a cottage and a mansion, group by the id number, restrict the output to cottages and mansions, and count the distinct types.
select person_id
from homes
where type in ('cottage','mansion')
group by person_id
having count(distinct type) = 2;
person_id
--
1
You can use this query in a join to get all the columns from the person table.
select person.*
from person
inner join (select person_id
from homes
where type in ('cottage','mansion')
group by person_id
having count(distinct type) = 2) T
on person.person_id = T.person_id;
Thanks to Joe for pointing out an error in my count().
Not sure about the performance on this one, but here goes:
SELECT PID FROM (
SELECT PID, COUNT(PID) cnt FROM (
SELECT DISTINCT PID, Type FROM Homes
WHERE Type IN ('Type1', 'Type2', 'Type3')
) a
GROUP BY PID
) b
WHERE b.cnt = 3
You'd have to dynamically generate your IN clause as well as the WHERE b.CNT clause.