SQL query assistance - sql

I have this schema:
Hotel (**hotelNo**, hotelName, city)
Room (**roomNo, hotelNo**, type, price)
Booking (**hotelNo, guestNo, dateFrom**, dateTo, roomNo)
Guest (**guestNo**, guestName, guestAddress)
** denotes primary keys
I have to complete this query:
Display each hotel and its most common room.
I have this query, which isn't quite correct:
SELECT r.hotelno, type, count(*)
FROM Hotel h, room r
WHERE h.hotelNo = r.hotelno
GROUP BY r.hotelNo, type;
This is what it outputs:
What am I doing wrong?

It looks as though you're seeking the type of room which has maximum number of bookings for rooms of a given type at each hotel - an aggregate (maximum) of another aggregate (count of bookings of room type).
Build it up piece-wise. The number of bookings of rooms of each type at each hotel:
SELECT r.hotelno, r.type, count(*) AS num_bookings
FROM Booking AS b
JOIN Room AS r ON b.hotelNo = r.hotelno AND b.roomNo = r.roomNo
GROUP BY r.hotelNo, r.type;
Now, you need to know which room type has the maximum at each hotel. That has to be done in two stages:
Find the maximum number of bookings at the hotel for any type.
Find the room types with that maximum number.
The first stage is:
SELECT s.hotelno, MAX(num_bookings) AS max_bookings
FROM (SELECT r.hotelno, r.type, count(*) AS num_bookings
FROM Booking AS b
JOIN Room AS r ON b.hotelNo = r.hotelno AND b.roomNo = r.roomNo
GROUP BY r.hotelNo, r.type
) AS s
GROUP BY s.hotelno;
The second stage uses both the previous results for a final answer:
SELECT t.hotelno, t.type
FROM (SELECT r.hotelno, r.type, count(*) AS num_bookings
FROM Booking AS b
JOIN Room AS r ON b.hotelNo = r.hotelno AND b.roomNo = r.roomNo
GROUP BY r.hotelNo, r.type) AS t
JOIN (SELECT s.hotelno, MAX(num_bookings) AS max_bookings
FROM (SELECT r.hotelno, r.type, count(*) AS num_bookings
FROM Booking AS b
JOIN Room AS r ON b.hotelNo = r.hotelno AND b.roomNo = r.roomNo
GROUP BY r.hotelNo, r.type
) AS s
GROUP BY s.hotelno) AS m
ON t.hotelno = m.hotelno AND t.num_bookings = m.max_bookings;
If your DBMS supports WITH clauses, you can write that more succinctly.

If you are looking for popularity, you would need to take into account the Booking table. Add the Booking table to your FROM statement, link on hotelNo and roomNo and do a count on the Booking table. This should give you the counts you want.
Edit:
Here is some sample code for you (tested):
SELECT TOP (100) PERCENT dbo.Hotel.hotelName, dbo.Room.type, COUNT(*) AS Count
FROM dbo.Booking INNER JOIN
dbo.Room ON dbo.Booking.roomNo = dbo.Room.roomNo AND dbo.Booking.hotelNo = dbo.Room.hotelNo
INNER JOIN dbo.Hotel ON dbo.Room.hotelNo = dbo.Hotel.hotelNo
GROUP BY dbo.Hotel.hotelName, dbo.Room.type
ORDER BY Count DESC

I think you're going to have to use an inner query to get this one working:
SELECT dbo.Hotel.hotelName, pop.type, pop.Count
FROM dbo.Hotel
INNER JOIN (
SELECT TOP 1 dbo.Hotel.hotelNo, dbo.Room.type, COUNT(*) AS Count
FROM dbo.Hotel
INNER JOIN dbo.Room ON dbo.Room.hotelNo = dbo.Hotel.hotelNo
INNER JOIN dbo.Booking ON dbo.Booking.roomNo = dbo.Room.roomNo AND dbo.Booking.hotelNo = dbo.Hotel.hotelNo
GROUP BY dbo.Hotel.hotelNo, dbo.Room.type
ORDER BY Count DESC, dbo.Room.type
) AS pop ON pop.hotelNo = dbo.Hotel.HotelNo
ORDER BY dbo.Hotel.hotelName

Related

For each country, report the movie genre with the highest average rates

For each country, report the movie genre with the highest average ratings, and I am missing only one step that i cant figure it out.
Here's my current code:
SELECT c.code AS c_CODE, menres.genre AS GENRE, AVG(RATE) as AVERAGE_rate,MAX(RATE) AS MAXIMUM_rate, MIN(RATE) AS MINIMUM_rate from movirates
leftJOIN movgenres ON movgenres.movieid = movratings.movieid
left JOIN users ON users.userid = movrates.userid
left JOIN c ON c.code = users.city
LEFT JOIN menres ON movenres.genreid = menres.code
GROUP BY menres.genre , c.code
order by c.code asc, avg(rate) desc, menres.genre desc ;
You can use the ROW_NUMBER window function to assign a unique rank to each of your rows:
partitioned by country code
ordered by descendent average rating
Once you get this ranking, you may want to select all those rows which have the highest average rating (which are the same having the ranking equal to 1).
WITH cte AS (
SELECT c.code AS COUNTRY_CODE,
mg.genre AS GENRE,
AVG(rating) AS AVERAGE_RATING,
MAX(rating) AS MAXIMUM_RATING,
MIN(RATING) AS MINIMUM_RATING
FROM moviesratings r
INNER JOIN moviesgenres g ON g.movieid = r.movieid
INNER JOIN users u ON u.userid = r.userid
INNER JOIN countries c ON c.code = u.country
LEFT JOIN mGenres mg ON mg.code = g.genreid
GROUP BY mg.genre,
c.code
ORDER BY c.code,
AVG(rating) DESC,
mg.genre DESC;
)
SELECT *
FROM (SELECT *,
ROW_NUMBER() OVER(
PARTITION BY COUNTRY_CODE,
ORDER BY AVERAGE_RATING) AS rn
FROM cte) ranked_averages
WHERE rn = 1
Note: The code inside the common table expression is equivalent to yours. If you're willing to share your input tables, I may even suggest an improved query.
You should use window function in this case by using rank() then select the first rank only.
with mov_rates(c.code, genre, average, max, min)
as.
select c.code c_code,
e.genre genre,
avg (rate) avg
max (rate) max
min (rate) min
from movrates a
LEFT join movge.nres b on a.movieid = b.movieid
LEFT join users c on a.userid = c.user
LEFT join countr.ies d on c.code = d.code
left join mGenres e on b.genreid = e.code
group by d.country_code, e.x
),
rategenre (rank, c_code, genre, avgrate, max, min)
as
(
select rank() over (partition by c.c order by avgrates asc) rank,
country code,
genre,
average_r.ating,
maximum_rating,
minimum_.ating
from movrate \\just practicing on something
)
selec.t 2
from genre
where rank = 5
Reference:
OVER Clause

How to get count of customers?

I need some help with an SQL query.
Let's say we have this sample database which manages Bookings in a hotel:
Customer(ID, name, birthDate, city);
Room(number, floor, beds, roomtype, price);
Bookings(Customer, roomNumber, floor, checkInDate, checkOutDate);
I need to know which customers booked only and ONLY economic type of rooms.
This is my query:
select Customer from Bookings
join Room on(Bookings.num = camera.roomNumber and Bookings.floor=
Room.floor)
where (Bookings.Customer, Bookings.floor) not in (select number, floor from
Room where roomType != 'economic')
My issue is that this query shows me customers which booked economic rooms, but it also shows me customers which booked other type of rooms.
How can I restrict the output in order to get Customers which booked ONLY economic rooms?
Thank you
Use not exists :
select c.*
from Customer c
where not exists (select 1
from Bookings b
inner join Room r on b.num = r.roomNumber and b.floor = r.floor
where c.ID = b.Customer and r.roomType <> 'economic'
);
You can use aggregation:
select b.Customer
from Bookings b join
Room r
on b.num = r.roomNumber and b.floor = r.floor
group by b.Customer
having min(roomType) = max(roomType) and min(roomType) = 'economic';

SQL Query, Average climbed and pair that has climbed the most peaks

My Databases look like so:
PEAK (NAME, ELEV, DIFF, MAP, REGION)
CLIMBER (NAME, SEX)
PARTICIPATED (TRIP_ID, NAME)
CLIMBED (TRIP_ID, PEAK, WHEN)
PEAK gives info about the mountain peaks that the user is interested in. The table lists the name of each peak, it elevation(in ft), its difficulty level(on a scale of 1-5), the map that it is located on, and the region of the Sierra Nevada that it is located in.
CLIMBER lists the members of club, and gives their name and gender.
PARTICIPATED gives the set of climbers who participated in each of the various climbing trips. The number of participants in each trip varies.
CLIMBED tells which peaks were climbed on each climbing trip, along w/ the data that each peak was climbed.
I need help with writing a query for the following:
Compute the average number of peaks scaled by the men in the club and by the women in the club.
Which pair of climbers have climbed the most peaks together, and how many peaks is that?
Who has climbed more than 20 peaks in some 60 day span?
For the first query, so far I have found a way to compute the total number of peaks climbed by either gender, for men:
SELECT SUM(C)
FROM
(SELECT CD.PEAK, COUNT(*) C
FROM CLIMBED CD
WHERE CD.TRIP_ID IN
(SELECT TRIP_ID
FROM PARTICIPATED PA
WHERE PA.NAME IN
(SELECT NAME
FROM CLIMBER
WHERE SEX = 'M'))
GROUP BY CD.PEAK) T;
For the second query, I have the following which I'm fairly sure isn't correct:
SELECT TEMP2.TRIP_ID, COUNT (*)
FROM
(SELECT P1.NAME, P2.NAME, P1.TRIP_ID
FROM PARTICIPATED P1, PARTICIPATED P2
WHERE P1.NAME <> P2.NAME AND
P1.TRIP_ID = P2.TRIP_ID) TEMP1,
(SELECT *
FROM CLIMBED) TEMP2
WHERE TEMP2.TRIP_ID = TEMP1.TRIP_ID
GROUP BY TEMP2.TRIP_ID;
Question 1:
For total number of trips (including every time a peak was climbed)
SELECT t1.sex, AVG(t1.peak_count) AS average
FROM
(SELECT sex, COUNT(trip_id) AS peak_count
FROM climber c LEFT JOIN participated p ON c.name = p.name GROUP BY c.name, c.sex) t1
For each time a UNIQUE peak was climbed:
SELECT t1.sex, AVG(t1.peak_count) AS average
FROM
(SELECT sex, COUNT(trip_id) AS peak_count
FROM climber c LEFT JOIN participated p ON c.name = p.name GROUP BY c.name, c.sex) t1
Question 2:
SELECT P1.Name, P2.Name, COUNT(DISTINCT p1.trip_id) AS trips
FROM participated p1 INNER JOIN participated p2 ON p1.trip_id = p2.trip_id
WHERE p1.name > p2.name -- > instead of <> gets only one of the pairs
GROUP BY P1.Name, P2.Name
HAVING COUNT(DISTINCT p1.trip_id) > 0
ORDER BY trips DESC
Question 3:
SELECT p.name, cl.when AS span_begin_date, DATEADD(day, 60, cl.when) AS span_end_date, count(c2.trip_id) AS peaks
FROM climbed cl LEFT JOIN
climbed c2 ON c2.when BETWEEN cl.when AND DATEADD(day, 60, cl.when)
GROUP BY p.name, cl.when, DATEADD(day, 60, cl.when)
HAVING COUNT(c2.trip_id) > 20
ORDER BY peaks
Here is my solution. If you provide sample data, this can be verified. For question 3, the some 60 day span is not clear. Can you please specify better?
Question 1
select x.sex, avg(x.peaks_escalated) as peaks
from (
select u.name, u.sex, count(distinct c.peak) as peaks_escalated
from t1_climbed c
inner join t1_participated p on c.trip_id = p.trip_id
inner join t1_climber u on p.name = u.name
group by u.name, u.sex ) x
group by x.sex
Question 2
with list1 as (
select u.name as member, c.trip_id, c.peak, c.when
from t1_climbed c
inner join t1_participated p on c.trip_id = p.trip_id
inner join t1_climber u on p.name = u.name
)
select a.member as m1, b.member as m2, count(distinct a.peak) as total
from list1 a inner join list1 b
on a.trip_id = b.trip_id
and a.peak = b.peak
and a.when = b.when
and a.member <> b.member
group by a.member, b.member
Oracle Setup:
CREATE TABLE PEAK (
NAME VARCHAR2(50) PRIMARY KEY,
ELEV INT,
DIFF INT,
MAP VARCHAR2(10),
REGION VARCHAR2(10)
);
CREATE TABLE CLIMBER (
NAME VARCHAR2(50) PRIMARY KEY,
SEX CHAR(1) CHECK ( SEX IN ( 'M', 'F' ) )
);
-- Created this to have a primary key
CREATE TABLE TRIPS (
TRIP_ID INT PRIMARY KEY
);
CREATE TABLE PARTICIPATED (
TRIP_ID INT REFERENCES TRIPS( TRIP_ID ),
NAME VARCHAR2(50) REFERENCES CLIMBER( NAME ),
PRIMARY KEY ( TRIP_ID, NAME )
);
CREATE TABLE CLIMBED (
TRIP_ID INT REFERENCES TRIPS( TRIP_ID ),
PEAK VARCHAR2(50) REFERENCES PEAK ( NAME ),
"WHEN" DATE
);
Question 1
SELECT sex,
AVG( num_peaks ) AS avg_peaks
FROM (
SELECT c.*,
COUNT( DISTINCT l.peak ) num_peaks
FROM CLIMBED l
INNER JOIN
PARTICIPATED p
ON ( p.trip_id = l.trip_id )
RIGHT OUTER JOIN
CLIMBER c
ON ( p.name = c.name )
GROUP BY c.name, c.sex
)
GROUP BY sex;
You need to OUTER JOIN climbers as they could have not participated in any trips (so having climbed 0 peaks) and this needs to be taken into account in the average. It is also possible that a person could have climbed a peak multiple times - when you want the number of peaks climbed by a person you want to exclude multiple climbs on the same peak and will need to use COUNT( DISTINCT ... ) (or another similar technique) - if you want to count multiple climbs then remove the DISTINCT keyword.
Question 2:
SELECT *
FROM (
SELECT name1,
name2,
COUNT( DISTINCT c.peak ) AS num_peaks_climbed
FROM (
SELECT p1.name AS name1,
p2.name AS name2,
p1.trip_id
FROM PARTICIPATED p1
INNER JOIN
PARTICIPATED p2
ON ( p1.trip_id = p2.trip_id AND p1.name < p2.name )
) p
INNER JOIN
climbed c
ON ( p.trip_id = c.trip_id )
GROUP BY name1, name2
ORDER BY num_peaks_climbed DESC
)
WHERE ROWNUM = 1;
Question 3:
SELECT *
FROM (
SELECT p.name,
COUNT( c.peak ) OVER ( PARTITION BY p.name
ORDER BY c."WHEN"
RANGE BETWEEN INTERVAL '-60' DAY PRECEDING
AND CURRENT ROW
) AS num_peaks_in_60_days,
c."WHEN" AS last_date_of_range
FROM PARTICIPATED p
INNER JOIN
climbed c
ON ( p.trip_id = c.trip_id )
)
WHERE num_peaks_in_60_days > 20;

Very hard greatest n per group query

I have a very complexe query here, I try to give you an overview about the necessary tables here:
RPG
RPGCharacter
RPGPost
User
We have X Chars per RPG, x Posts per Char. 1 User can have X Chars, but 1 Char only depens on 1 User.
What I want is a query in which I got the last post per RPG within information about the Username who wrote this, the character and the RPG itself addition to a number how much RPGPosts per RPG we have (total).
This is how far I solved it until now:
SELECT c.RPGID, c.Name, DateTime, r.Name, u.Username, t.count
FROM dbo.RPGCharacter c inner join
(
SELECT CharacterID,
MAX(DateTime) MaxDate
FROM RPGPost
GROUP BY CharacterID
) MaxDates ON c.RPGCharacterID = MaxDates.CharacterID
INNER JOIN RPGPost p ON MaxDates.CharacterID = p.CharacterID
AND MaxDates.MaxDate = p.DateTime
Inner join RPG r on c.RPGID = r.RPGID
Inner join [User] u on u.UserID = c.OwnerID
inner join (Select RPG.RPGID, Count(*) as Count from RPGPost
inner join RPGCharacter on RPGPost.CharacterID = RPGCharacter.RPGCharacterID
inner join RPG on RPG.RPGID = RPGCharacter.RPGID
where RPGPost.IsDeleted = 0
Group by RPG.RPGID) t on r.RPGID = t.RPGID
Order by DateTime desc
Result : http://abload.de/image.php?img=16iudw.jpg
This query gives me all I want but has an Errors:
1) It gives me the last post per Character, but I need the last Post per RPG
Does this help? This should give you the last post per CharacterID in the RPGPost table and include the total number of posts for that CharacterID.
WITH RankedPost AS (
SELECT
P.PostID,
P.CharacterID,
P.DateTime
RANK() OVER (
PARTITION BY CharacterID,
ORDER BY DateTime DESC) Rank,
RANK() OVER (
PARTITION BY CharacterID,
ORDER BY DateTime ASC) Count
FROM RPGPost P)
SELECT
P.DateTime
P.CharacterID,
P.Count
FROM RankedPost P
WHERE
RankedPost.Rank = 0;

SQL Joins Clarification

I wish to display the hospitalid,hosp name and hosp type for the hospital which have/has the highest no of doctors associated with them.
I have two tables:
Doctor: doctorid, hospitalid
Hospital: hospitalid, hname, htype
SELECT d.hospitalid,h.hname,h.htype
FROM doctor d
INNER JOIN hospital h ON d.hospitalid = h.hospitalid
GROUP BY d.hospitalid,h.hname,h.htype
HAVING MAX(count(d.doctorid));
I tried the above code, but i get an error "group func is nested too deeply". How should i modify d code?
This is a common error when learning SQL, thinking that having Max(col) says "keep only the row with the max". It simply means having <some function on the column> without any condition. For instance, you could say having count(d.doctorid) = 1 to get hospitals with only one doctor.
The way to do this is to order the columns and then take the first row. However, the syntax for "take the first row" varies by database. The following works in many SQL dialects:
SELECT d.hospitalid,h.hname,h.htype
FROM doctor d INNER JOIN
hospital h
ON d.hospitalid = h.hospitalid
GROUP BY d.hospitalid,h.hname,h.htype
order by count(d.doctorid) desc
limit 1;
In SQL Server and Sybase, the syntax is:
SELECT top 1 d.hospitalid,h.hname,h.htype
FROM doctor d INNER JOIN
hospital h
ON d.hospitalid = h.hospitalid
GROUP BY d.hospitalid,h.hname,h.htype
order by count(d.doctorid) desc;
In Oracle:
select t.*
from (SELECT d.hospitalid,h.hname,h.htype
FROM doctor d INNER JOIN
hospital h
ON d.hospitalid = h.hospitalid
GROUP BY d.hospitalid,h.hname,h.htype
order by count(d.doctorid) desc
) t
where rownum = 1;
EDIT (based on comment):
To get all rows with the maximum, then you can do something similar to your original query. It is just more complicated. You can calculate the maximum number using a subquery and do the comparison in the having clause:
SELECT d.hospitalid, h.hname, h.htype
FROM doctor d INNER JOIN
hospital h
ON d.hospitalid = h.hospitalid join
GROUP BY d.hospitalid,h.hname,h.htype
having count(d.doctorid) = (select max(NumDoctors)
from (select hospitalid, count(*) as NumDoctors
from hospitalId
group by hospitalid
) hd
)
As a note, there are easier mechanisms in other databases.
This is how I would write it for SQL Server. THe specific details might vary depending teh database backend you are using.
SELECT TOP 1 a.hospitalid,a.hname,a.htype
FROM
(SELECT d.hospitalid,h.hname,h.htype, count(d.doctorid) as doctorcount FROM doctor d INNER JOIN hospital h ON d.hospitalid = h.hospitalid
GROUP BY d.hospitalid,h.hname,h.htype) a
ORDER BY doctorcount DESC;