I have four tables: SCHED_FLIGHT, AIRCRAFT, PLANETYPE and RESERVATIONS. I want to do something like this:
select S.SCHED_NO as "Scheduled Flight Number", P.CAPACITY - COUNT (SELECT * FROM RESERVATIONS R WHERE R.SCHED_NO = S.SCHED_NO) "Remaining Seats"
from PLANETYPE P, AIRCRAFT A, SCHED_FLIGHT S
where S.SERIAL_NO = A.SERIAL_NO and A.TYPE_NO = P.TYPE_NO;
As you can see, each SCHEDULED_FLIGHT has a AIRCRAFT SERIAL_NO, each AIRCRAFT has a PLANETYPE TYPE_NO and each PLANETYPE has a different capacity, so, each SCHED_FLIGHT has a capacity based on the plane and I want to get the number of remaining seats counting the number of RESERVATIONS made to that flight.
Of course the code doesn't work, but I have no idea how to solve this problem. Any tips?
edit: I've received some answers, but first I was just showing two tables as an example - but actually there's 4 tables involved and I would have to include a where clause or multiple joins... so I'm still confused. What should I do now? Look at my code.
Update:
I'm not sure that all of columns correspond to your real columns, but this query might look like this:
SELECT
SF.SCHED_NO AS 'Scheduled Flight Number',
SF.CAPACITY - COUNT (R.SCHED_NO) AS 'Remaining Seats'
FROM
(
SELECT
SF.SCHED_NO,
P.CAPACITY
FROM SCHEDULED_FLIGHT SF
INNER JOIN AIRCRAFT A
ON SF.AIRCRAFT_SERIAL_NO = A.SERIAL_NO
INNER JOIN PLANETYPE P
ON A.PLANETYPE_NO = P.PLANETYPE_NO
) SF
LEFT JOIN RESERVATIONS R
ON SF.SCHED_NO = R.SCHED_NO
GROUP BY SF.SCHED_NO, SF.CAPACITY
I would be inclined to approach this as an aggregation with a join. First, aggregate the reservations by flight number. Then join that back to FLIGHT and calculate the remaining capacity:
SELECT F.FLIGHT_NO, (F.CAPACITY - COALESCE(cnt, 0)) as Remaining
FROM FLIGHT F LEFT JOIN
(SELECT R.FLIGHT_NO, COUNT(*) as cnt
FROM RESERVATIONS R
GROUP BY R.FLIGHT_NO
) R
ON R.FLIGHT_NO = F.FLIGHT_NO;
Related
All i want to do is to join two tables, list ALL the rows from the first table, find the average from the second table from all the rows, then list only the ones that are greater than the average.
This is wahat i have done so far, and i am only getting one greater than the average but there are others.
SELECT winner_age, AVG(actor_age) FROM oscar_winners
INNER JOIN actors ON actors.id = oscar_winners.id
WHERE winner_age > (
SELECT AVG(actor_age)
)
You don't really need a join here:
SELECT o.WINNER_AGE
FROM OSCAR_WINNERS o
WHERE o.WINNER_AGE > (SELECT AVG(a.ACTOR_AGE)
FROM ACTORS a)
Something like this?
SELECT actors.*, (SELECT AVG(actor_age) from actors) as average
FROM oscar_winners
INNER JOIN actors ON actors.id = oscar_winners.id and actors.winner_age > (SELECT AVG(actor_age) from actors)
The problem with your query is because you are using a where clause, while you should probably be using having:
SELECT w.winner_age, AVG(a.actor_age)
FROM oscar_winners w
INNER JOIN actors a
ON actors.id = oscar_winners.id
group by w.winner_age
having w.winner_age > AVG(a.actor_age)
I have a database where I store names every computer make and model when they get booked into our system as ticket. I want to SUM up the unique values and display a table showing how many the computer manufacturer name appears.
i.e.
HP 30%
Dell 10%
Toshiba 40%
Sony 20%
My query looks like this which shows every occurrence from every ticket. I am not sure how to go about translating to the table above:
SELECT
ComputerMake.ComputerMakeName
FROM
Ticket
INNER JOIN Asset ON Ticket.AssetID = Asset.AssetID
INNER JOIN ComputerMake ON Asset.ComputerMakeID = ComputerMake.ComputerMakeID
I am sure its a simple job of summing up each ComputerMakeName but this is beyond my basic SQL experience!
Thank you in advanced.
So first you need to count how many times each computer make is booked, and then calc the percentage:
SELECT
ComputerMake.ComputerMakeName
, count(*) * 100 / (select count(*) from FROM
Ticket
INNER JOIN Asset ON Ticket.AssetID = Asset.AssetID
INNER JOIN ComputerMake ON Asset.ComputerMakeID = ComputerMake.ComputerMakeID) as count_computers
FROM
Ticket
INNER JOIN Asset ON Ticket.AssetID = Asset.AssetID
INNER JOIN ComputerMake ON Asset.ComputerMakeID = ComputerMake.ComputerMakeID
group by ComputerMake.ComputerMakeName
Consider using CTE to simplify the query and avoid redundancy
WITH computer_CTE(computerName) AS
(
SELECT ComputerMake.ComputerMakeName
FROM Ticket
INNER JOIN Asset ON Ticket.AssetID = Asset.AssetID
INNER JOIN ComputerMake ON Asset.ComputerMakeID = ComputerMake.ComputerMakeID
)
Select computerName, (Count(*) * 100 / (Select Count(*) From computer_CTE)) as Percent
From computer_CTE
Group By computerName
I'm taking my first steps in terms of practical SQL use in real life.
I have a few tables with contractual and financial information and the query works exactly as I need - to a certain point. It looks more or less like that:
SELECT /some columns/ from CONTRACTS
Linked 3 extra tables with INNER JOIN to add things like department names, product information etc. This all works but they all have simplish one-to-one relationship (one contract related to single department in Department table, one product information entry in the corresponding table etc).
Now this is my challenge:
I also need to add contract invoicing information doing something like:
inner join INVOICES on CONTRACTS.contnoC = INVOICES.contnoI
(and selecting also the Invoice number linked to the Contract number, although that's partly optional)
The problem I'm facing is that unlike with other tables where there's always one-to-one relationship when joining tables, INVOICES table can have multiple (or none at all) entries that correspond to a single contract no. The result is that I will get multiple query results for a single contract no (with different invoice numbers presented), needlessly crowding the query results.
Essentially I'm looking to add INVOICES table to a query to just identify if the contract no is present in the INVOICES table (contract has been invoiced or not). Invoice number itself could be presented (it is with INNER JOIN), however it's not critical as long it's somehow marked. Invoice number fields remains blank in the result with the INNER JOIN function, which is also necessary (i.e. to have the row presented even if the match is not found in INVOICES table).
SELECT DISTINCT would look to do what I need, but I seemed to face the problem that I need to levy DISTINCT criteria only for column representing contract numbers, NOT any other column (there can be same values presented, but all those should be presented).
Unfortunately I'm not totally aware of what database system I am using.
Seems like the question is still getting some attention and in an effort to provide some explanation here are a few techniques.
If you just want any contract with details from the 1 to 1 tables you can do it similarily to what you have described. the key being NOT to include any column from Invoices table in the column list.
SELECT
DISTINCT Contract, Department, ProductId .....(nothing from Invoices Table!!!)
FROM
Contracts c
INNER JOIN Departments D
ON c.departmentId = d.Department
INNER JOIN Product p
ON c.ProductId = p.ProductId
INNER JOIN Invoices i
ON c.contnoC = i.contnoI
Perhaps a Little cleaner would be to use IN or EXISTS like so:
SELECT
Contract, Department, ProductId .....(nothing from Invoices Table!!!)
FROM
Contracts c
INNER JOIN Departments D
ON c.departmentId = d.Department
INNER JOIN Product p
ON c.ProductId = p.ProductId
WHERE
EXISTS (SELECT 1 FROM Invoices i WHERE i.contnoI = c.contnoC )
SELECT
Contract, Department, ProductId .....(nothing from Invoices Table!!!)
FROM
Contracts c
INNER JOIN Departments D
ON c.departmentId = d.Department
INNER JOIN Product p
ON c.ProductId = p.ProductId
WHERE
contnoC IN (SELECT contnoI FROM Invoices)
Don't use IN if the SELECT ... list can return a NULL!!!
If you Actually want all of the contracts and just know if a contract has been invoiced you can use aggregation and a case expression:
SELECT
Contract, Department, ProductId, CASE WHEN COUNT(i.contnoI) = 0 THEN 0 ELSE 1 END as Invoiced
FROM
Contracts c
INNER JOIN Departments D
ON c.departmentId = d.Department
INNER JOIN Product p
ON c.ProductId = p.ProductId
LEFT JOIN Invoices i
ON c.contnoC = i.contnoI
GROUP BY
Contract, Department, ProductId
Then if you actually want to return details about a particular invoice you can use a technique similar to that of cybercentic87 if your RDBMS supports or you could use a calculated column with TOP or LIMIT depending on your system.
SELECT
Contract, Department, ProductId, (SELECT TOP 1 InvoiceNo FROM invoices i WHERE c.contnoC = i.contnoI ORDER BY CreateDate DESC) as LastestInvoiceNo
FROM
Contracts c
INNER JOIN Departments D
ON c.departmentId = d.Department
INNER JOIN Product p
ON c.ProductId = p.ProductId
GROUP BY
Contract, Department, ProductId
I would do it this way:
with mainquery as(
<<here goes you main query>>
),
invoices_rn as(
select *,
ROW_NUMBER() OVER (PARTITION BY contnoI order by
<<some column to decide which invoice you want to take eg. date>>) as rn
)
invoices as (
select * from invoices_rn where rn = 1
)
select * from mainquery
left join invoices i on contnoC = i.contnoI
This gives you an ability to get all of the invoice details to your query, also it gives you full control of which invoice you want see in your main query. Please read more about CTEs; they are pretty handy and much easier to understand / read than nested selects.
I still don't know what database you are using. If ROW_NUMBER is not available, I will figure out something else :)
Also with a left join you should use COALESCE function for example:
COALESCE(i.invoice_number,'0')
Of course this gives you some more possibilities, you could for example in your main select do:
CASE WHEN i.invoicenumber is null then 'NOT INVOICED'
else 'INVOICED'
END as isInvoiced
You can use
SELECT ..., invoiced = 'YES' ... where exists ...
union
SELECT ..., invoiced = 'NO' ... where not exists ...
or you can use a column like "invoiced" with a subquery into invoices to set it's value depending on whether you get a hit or not
Firstly, my SQL knowledge is little rusty. I am trying to generate a report of reviews each patient has gone through for a time period. A review is done as part of a Doctors' round. The following are the corresponding tables with relevant columns:
Patients: (id, name)
Rounds: (id, patient_id, date)
Reviews: (id, round_id, review)
The report should look like the following:
Patient | Reviews
_________________________
Patient 1 | 2
_________________________
Patient 2 | 1
_________________________
Patient 3 | 0
_________________________
I tried the following SQL statement:
SELECT
p.name as patient,
COUNT(r.round_id) as reviews
FROM
patients as p
JOIN rounds as ro ON p.id = ro.patient_id
JOIN reviews as r ON ro.id = r.round_id
WHERE
r.review_date between '2012-02-01' AND '2012-02-29'
GROUP BY
p.name
But, the above query only returns rows where reviews count is > 1. I want it to return even if the count is 0.
The simplest way to Join the tables, and to include instances where there is no match in one of those tables, is to use a LEFT OUTER JOIN. This will match all records to the left, regardless of whether a match was found on the right side of the JOIN.
Since your r.review_date is in your WHERE clause, no matches can occur unless there is a review between those dates. So to include instances where there is no review, you must allow for that in your WHERE clause by adding "OR r.review_date IS NULL" as below. You may also want to consider filtering on the round.date field instead, so that you are only looking at instances where there were valid rounds performed within that time frame. ie. "WHERE ro.date between '2012-02-01' AND '2012-02-29'"
eg.
SELECT
p.name as patient,
COUNT(r.round_id) as reviews
FROM
patients as p
JOIN rounds as ro ON p.id = ro.patient_id
LEFT OUTER JOIN reviews as r ON ro.id = r.round_id
WHERE
r.review_date between '2012-02-01' AND '2012-02-29' OR r.review_date IS NULL
GROUP BY
p.name
Note: If you want to report records without any rounds, you will also have to make the first JOIN a LEFT OUTER JOIN as well.
FROM
patients as p
LEFT OUTER JOIN rounds as ro ON p.id = ro.patient_id
LEFT OUTER JOIN reviews as r ON ro.id = r.round_id
SELECT p.name AS patient
, COUNT(r.ID) AS reviews
FROM patients AS p
LEFT JOIN rounds AS ro ON p.id = ro.patient_id
LEFT JOIN reviews AS r ON ro.id = r.round_id
AND r.review_date BETWEEN '2012-02-01'
AND '2012-02-29'
GROUP BY p.name
Will get you a list of all patients, including those who have not had a round or a review in between your specific dates. Patients with no rounds or reviews will have a 0.
So, I'm going thru a lot of exercises for a final SQL exam I have on thursday and I came across another query I'm having doubts about.
The tables in the exercise are supposed to be from a hotel DB. You have three tables involved:
STAY ROOM ROOM_TYPE
=========== ============ ============
PK ID_STAY PK ID_ROOM PK ID_ROOM_TYPE
DAYS_QUANT ID_ROOM_TYPE FK DESCRIPTION
DATE PRICE
ID_ROOM FK
The query they are asking me to do is "Show all data for the Room that has been rented for the highest amount of days (in total) in 2011, by room type (you have to show ID Room Type and Description)"
This is the way I solved it, I don't know if it's ok:
SELECT RT.ID_ROOM_TYPE, RT.DESCRIPTON, R.*, SUM(S.DAYS_QUANT)
FROM STAY S, ROOM R, ROOM_TYPE RT
WHERE YEAR(S.DATE) = '2011'
GROUP BY RT.ID_ROOM_TYPE, RT.DESCRIPTON, R.*
ORDER BY SUM(S.DAYS_QUANT) DESC
LIMIT 1
So, the first thing I'm not sure about, is that R.* I included. Can I put it like that in a SELECT? Can it also be included like that in a GROUP BY?
The other thing I'm not sure about if I will be allowed to use LIMIT or SELECT TOP 1 statements in the exam. Can anyone think of a way to solve this without using those? like with a MAX() statement or something?
I believe that you are not allowed to use CTEs so I expanded last part of Steve Kass's answer. You may get desired results without TOP or Limit clauses by comparing total days a room was occupied by max total number of days any room of the same type was occupied. To do so, you would first sum days by room and then, using identical derived table, get maximum of days per room type. Joining the two by room type and days you would isolate most used rooms. Then you join starting tables to show all the data. Unlike TOP or Limit this will produce more records in case of a tie.
P.S. this is NOT tested. I believe it will work, but there might be a typo.
select r.*, rt.*, roomDays.TotalDays
from Room r inner join Room_type rt
on r.id_room_type = rt.id_room_type
inner join
(select id_room, id_room_type, sum(days_quant) TotalDays
from Stay
inner join Room
on Stay.id_room = Room.id_room
where year(Date) = 2011
group by id_room, id_room_type) roomDays
on r.id_room = roomDays.id_room
inner join
(select id_room_type, max(TotalDays) TotalDays
from
(select id_room, id_room_type, sum(days_quant) TotalDays
from Stay
inner join Room
on Stay.id_room = Room.id_room
where year(Date) = 2011
group by id_room, id_room_type) roomDaysHelper
group by id_room_type) roomTypeDays
on r.id_room_type = roomTypeDays.id_room_type
and roomDays.TotalDays = roomTypeDays.TotalDays
select r.*, t.*
from room r
join room_type t on t.id_room_type = r.id_room_type
where r.id in
(select
(select r.id_room
from room r
join stay on stay.id_room = r.id_room
where year(s.date) = '2011'
and r.id_room_type = t.id_room_type
group by r.id_room
order by sum(s.days_quant) desc
limit 1) room_id
from room_type t)
It's always possible to avoid LIMIT 1 or SELECT TOP. One way is to express the top row as the row for which there is no higher row. WHERE NOT EXISTS expresses the idea of "for which there is no."
One way to think of this is as follows: Select those rooms (along with their total days and type information) for which there is no room of the same type with a greater number of total days. That gives you this query (not carefully proofread):
with StayTotals as (
select
STAY.ID_ROOM,
ROOM_TYPE.ID_ROOM_TYPE,
ROOM_TYPE.DESCRIPTION,
SUM(STAY.DAYS_QUANT) AS TotalDays2011
from STAY join ROOM on STAY.ID_ROOM = ROOM.ID_ROOM
join ROOM_TYPE on ROOM.ID_ROOM_TYPE = ROOM_TYPE.ID_ROOM_TYPE
where YEAR(STAY.DATE) = 2011
group by STAY.ID_ROOM, ROOM_TYPE.ID_ROOM_TYPE, ROOM_TYPE.DESCRIPTION
)
select *
from StayTotals as T1
where not exists (
select *
from StayTotals as T2
where T2.ID_ROOM_TYPE = T1.ID_ROOM_TYPE
and T2.TotalDays2011 > T1.TotalDays2011
);
If you can't use CTEs (the WITH clause), you can rewrite it using subqueries, but it's awkward.
Ranking functions have been part of the SQL standard for quite a while. If you can use them, this may also work:
with StayTotals as (
select
STAY.ID_ROOM,
ROOM_TYPE.ID_ROOM_TYPE,
ROOM_TYPE.DESCRIPTION,
SUM(STAY.DAYS_QUANT) AS TotalDays2011
from STAY join ROOM on STAY.ID_ROOM = ROOM.ID_ROOM
join ROOM_TYPE on ROOM.ID_ROOM_TYPE = ROOM_TYPE.ID_ROOM_TYPE
where YEAR(STAY.DATE) = 2011
group by STAY.ID_ROOM, ROOM_TYPE.ID_ROOM_TYPE, ROOM_TYPE.DESCRIPTION
), StayTotalsRankedByType as (
select
ID_ROOM,
ID_ROOM_TYPE,
DESCRIPTION,
TotalDays2011,
RANK() OVER (
PARTITION BY ID_ROOM_TYPE
ORDER BY TotalDays2011 DESC
) as RankInRoomType
from StayTotals
)
select
ID_ROOM,
ID_ROOM_TYPE,
DESCRIPTION,
TotalDays2011
from StayTotalsRankedByType
where RankInRoomType = 1;
Finally, one other way to pull in additional columns to describe the grouped MAX results is to use a "carryalong" sort, which was a handy technique before ranking functions were available. Adam Machanic gives an example here, and there are useful threads on the topic from Usenet, such as this one.
How about this?
select room.id_room, room_type.description, room.price
from room inner join room_type
on room.id_room.type = room_type.id_room_type
where room.room_id = (select room_id from stay
where year (date) = 2011
group by id_room
order by sum (days_quant) desc);
Unfortunately, this query (as it is now) doesn't show how for many days the most popular room had been rented. But there's no 'limit 1'!
Thank you all! with all the ideas you gave me I came up with this, let me know if you think it's ok please!
SELECT R.ID_ROOM, R.ID_ROOM_TYPE, T.DESCRIPTION, SUM(S.DAYS_CUANT)
FROM ROOM R, ROOM_TYPE T, STAY S
(SELECT ID_STAY, SUM(S.DAYS_QUANT) TOTALDAYS
FROM STAY S
WHERE YEAR(S.DATE) = 2011
GROUP BY S.ID_STAY) STAYHELPER
WHERE YEAR(S.DATE) = 2011
GROUP BY R.ID_ROOM, R.ID_ROOM_TYPE, T.DESCRIPTION
HAVING SUM(S.DAYS_QUANT) >= ALL STAYHELPER.TOTALDAYS