MySQL: Tricky Query with regard to CASES - mysql

I have to solve a problem with a very complicated Query. Let´s sum it up for you:
The function should receive a customer number as an input parameter. If this
number is not contained in the tablecustomer, - 1 is to be output.
Otherwise, the customer bonus points are to be calculated. These result from
the reservations made by the customer.
The bonus points per reservation are calculated by multiplying the number of
reserved seats by the flight duration of the flight.
I already tried my best and worked this out:
CREATE FUNCTION customer_bonus(inputnumber INT(11))
RETURNS int
DETERMINISTIC
RETURN
CASE
WHEN
(SELECT COUNT(customer.ID) FROM customer WHERE inputnumber =
customer.ID) >= 1
THEN
(SELECT customer.ID, SUM(SELECT
flightexecution.FlightDurationInMinutes *
reservation.NoReservedSeats from customer, flightexecution,
reservation where inputnumber = customer.ID))
from customer, flightexecution, reservation
WHERE reservation.CustomerID = customer.ID AND customer.ID = inputnumber
AND flightexecution.FlightNo = reservation.FlightNo
AND reservation.DepartureDate = flightexecution.DepartureDate)
ELSE -1
END;
I run this query to test it:
select c.id, customer_bonus(c.ID) from customer c;
The result is just an 'OK' which means that something is wrong.
The funny thing: When i try out the simple Select Statements it works..
Could somebody of you help me?

The query you are using is likely returning a number of 2-field results. When attempting to return an int sourced from a query, the query can have only one result with a single field convertible to int. I think this is the query you are probably looking for:
SELECT SUM(fe.FlightDurationInMinutes * r.NoReservedSeats) AS bonusPoints
FROM reservation AS r
INNER JOIN flightexecution AS fe
ON r.FlightNo = fe.FlightNo
AND r.DepartureDate = fe.DepartureDate
WHERE r.CustomerID = inputnumber
You'll notice the query becomes much clearer when explicit JOINs are used. Honestly, I have no idea why anyone would teach implicit joins as anything more than a curiosity anymore. They've been seen as poor form (at best) for nearly two decades now.
With explicit joins, it would have been obvious that your SUM(SELECT subquery had no join conditions at all; meaning the sum you ended up calculating would have been of the [flight duration] * [reserved seats] of every combination of flightexecution and reservation, regardless of their relation to the customer or each other.

Related

MySQL INNER JOIN with GROUP BY and COUNT(*)

I've never been able to get my head around INNER JOINs (or any other JOIN types for that matter) so I'm struggling to work out how to use it in my specific situation. In fact, I'm not even sure if it's what I need. I've looked at other examples and read tutorials but my brain just doesn't seem to work the way needed to truly get it (or it doesn't function at all).
Here's the scenario:
I have two tables -
phone_numbers - this table has a list of phone numbers that
belong to lots of different customers. A single customer can have
multiple numbers. For simplicity's sake, we'll say the fields are
'number_id', 'customer_id', 'phone_number'.
call_history - this table has a record of every single call that one of these
numbers in the first table could have had. There's a record for
every individual call going back years. Again, for simplicity,
we'll say the relevant fields are customer_id, phone_number,
call_start_time.
What I'm trying to accomplish is to find all of the numbers that belong to a particular customer_id in the phone numbers table and use that information to search through the call_history table and find the number of calls each phone number has received, and group that by the number of calls for each number, preferably also showing zeros where a number hasn't received any calls at all.
The reason the zero calls is important is because that's the data I'm interested in. Otherwise, I could just get all the information out of the call_history table. But what I'm trying to achieve is find the numbers with no activity.
All I've been able to accomplish is run one query to get all of the numbers belonging to one customer:
SELECT customer_id, phone_number FROM phone_numbers WHERE customer_id = Y;
Then run a second query to get all phone calls for that customer_id for a set duration:
SELECT customer_id, phone_number, COUNT(*) FROM call_history WHERE customer_id = Y and call_start_time >= DATE_SUB(SYSDATE(), INTERVAL 30 DAY) GROUP BY phone_number;
I've then had to use the data returned from both queries and use a VLOOKUP function in Excel to match number of calls for each individual number from the second query to the list of all numbers from the first query, thus leaving blanks in my "all numbers" table and identifying those numbers that had no calls for that time period.
I'm hoping there's some way to do all of this with a single query and return a table of results, listing the zero number of calls with it and eliminate the whole manual Excel bit as it's not overly efficient and prone to human error.
Without at least a workable example from you, it's not easy to re-create your situation. Anyway, INNER JOIN might not return the result as how you expected. In my short time with MySQL, I mainly use 2 types of JOIN; one is already mentioned and the other is LEFT JOIN. From what I can understand in your question, what you want to achieve can be done by using LEFT JOIN instead of INNER JOIN. I may not be the best person to explain this to you but this is how I understand it:
INNER JOIN - only return anything that match in ON clause between two (or more) tables.
LEFT JOIN - will return everything from the table on the left side of the join and return NULL if ON get no match in the table on the right side of the join .. unless you specify some WHERE condition from something on the right table.
Now, here is my query suggestion and hopefully it'll be useful for you:
SELECT A.customer_id, A.phone_number,
SUM(CASE WHEN call_start_time >= DATE_SUB(SYSDATE(), INTERVAL 30 DAY)
THEN 1 ELSE 0 END) AS Total
FROM phone_numbers A
LEFT JOIN call_history B
ON A.customer_id=B.customer_id
GROUP BY A.customer_id,A.phone_number;
What I did here is I LEFT JOIN phone_numbers table with call_history on customer_id and I re-position the WHERE call_start_time >= .. condition into a CASE expression in the SELECT since putting it at WHERE will turn this into a normal join or inner join instead.
Here is an example fiddle : https://www.db-fiddle.com/f/hriFWqVy5RGbnsdj8i3aVG/1
For Inner join You should have to do like this way..
SELECT customer_id,phone_number FROM phone_numbers as pn,call_history as ch where pn.customer_id = ch.customer_id and call_start_time >= DATE_SUB(SYSDATE(), INTERVAL 30 DAY) GROUP BY phone_number;
Just add table name whatever you want to join and add condition

Join two SQL Queries based on first query answer

I have been looking everywhere and this would be the best website to ask someone for the help. I have to make SQL query that checks the total amount of bookings for the specific flight and then based on the number of bookings the system should provide the choice of an aircraft. First query works and it finds total number of bookings and i think i have the case statement right to choose an aircraft but i cant find the way of physically joining both queries , i tried to use unison , inner join and nested queries but it appears that Total number of seats booked (the answer from first query ) cannot be found please help me guys.
First SQL Query(find total number of bookings )
SELECT count(bookingdetails.FlightID)AS TotalNumberOfSeatsBooked,flightdetails.FlightID
FROM bookingdetails, bookingdetails AS TEMP,flightdetails
WHERE bookingdetails.BookingID = TEMP.BookingID
AND bookingdetails.FlightID= flightdetails.FlightID
Group BY FlightID;
SECOND SQL Query(Choose an aircraft type depending on how many bookings are made)
SELECT CASE chooseaircraft
WHEN TotalNumberOfSeatsBooked <= 110 THEN 'BA 146-200'
ELSE'Embraer 170'
END AS ChoiceOfAircraft
FROM aircrafttype;
Big Thanks to everyone
After one answer i think im heading in the right direction with merging the both queries together , the code now displays the total number of seats and flight number in the sub query but the choice of aircraft column still doesnt show but it does if you run the query by it self i know i am close to getting this and i would appreciate any help to become better in SQL the code i have now is :
SELECT count(bookingdetails.FlightID)AS TotalNumberOfSeatsBooked,flightdetails.FlightID
FROM bookingdetails, bookingdetails AS TEMP,flightdetails
WHERE bookingdetails.BookingID = TEMP.BookingID
AND bookingdetails.FlightID= flightdetails.FlightID
AND bookingdetails.FlightID= flightdetails.FlightID IN(
SELECT CASE WHEN count(bookingdetails.FlightID) <= 110 THEN 'BA 146-200'
ELSE'Embraer 170'
END AS ChoiceOfAircraft
FROM bookingdetails,flightdetails)
Group BY FlightID;
You can use the same expression count(bookingdetails.FlightID) in your CASE statement (or) wrap your first query in a subquery and access the column in your outer query. That is
CASE WHEN count(bookingdetails.FlightID) <= 110 THEN 'BA 146-200'
ELSE'Embraer 170'
END AS ChoiceOfAircraft

What's wrong with this mySQL logic?

In table "Booking" I hold details of a customer's booking (date/time/RowNumber).
In table "Seat" I hold details of all seats (RowNumber and Zone) in the theater.
The purpose of this query is to join Seat to Booking to see which seats are taken by displaying those rows in Seat which have a null entry in the corresponding Booking RowNumber. Here is the code:
SELECT s.Zone, s.RowNumber
FROM Booking b JOIN Seat s
ON s.RowNumber = b.RowNumber
WHERE b.PerfDate = '2016-12-12'
AND b.PerfTime = '20:30:00'
AND b.RowNumber is null;
The code is accepted but the result comes back as an empty set with the last AND statement or just shows what is in bookings without it. There is clearly something wrong with the logic I am using but I cannot pinpoint it as what I am trying to do makes sense when I read it. Probably an obvious mistake but some help would be appreciated.
Thank you in advance.
[Edit - I have spotted the logic error... it is the fact that I am asking for specific date and time when the records I want don't have those by definition but no idea how to get round it, seems like catch 22.]
You need to use a LEFT JOIN, and put all the criteria for the non-matching row into the ON clause.
SELECT s.zone, s.RowNumber
FROM Seat AS s
LEFT JOIN Booking AS b
ON s.RowNumber = b.RowNumber AND b.PerfDate = '2016-12-12' AND b.PerfTime = '20:30:00'
WHERE b.RowNumber IS NULL

Join error and order by

I'm trying to write a query which does the below:
For every guest who has the word “Edinburgh” in their address show the total number of nights booked. Be sure to include 0 for those guests who have never had a booking. Show last name, first name, address and number of nights. Order by last name then first name.
I am having problems with making the join work properly,
ER Diagram Snippet:
Here is my current (broken) solution:
SELECT last_name, first_name, address, nights
FROM booking
RIGHT JOIN guest ON (booking.booking_id = guest.id)
WHERE address LIKE '%Edinburgh%';
Here is the results from that query:
The query is partially complete, hoping someone can help me out and create a working version. I'm currently in the process of learning SQL so apologies if its a rather basic or dumb question!
Your query seems almost correct. You were joining the booking id with guets id which gave you some results because of overlapping (matching) ids, but this most likely doesn't correspond to the foreign keys. You should join on guest_id from booking to id from guest.
I'd add grouping to sum all booked nights for a particular guest (assuming that nights is an integer):
SELECT g.last_name, g.first_name, g.address, SUM(b.nights) AS nights
FROM guest AS g
LEFT JOIN booking AS b ON b.guest_id = g.id
WHERE g.address LIKE '%Edinburgh%'
GROUP BY g.last_name, g.first_name, g.address;
Are you sure that nights spent should be calculated using nights field? Why can it be null? If you'd like to show zero for null values just wrap it up with a coalesce function like that:
COALESCE(SUM(b.nights), 0)
Notes:
Rewriten RIGHT JOIN into LEFT JOIN, but that doesn't affect results - it's just cleaner for me
Using aliases eg. AS g makes the code shorter when specifying joining columns
Reference every column with their table alias to avoid ambiguity
SELECT g.first_name,
g.last_name,
g.address,
COALESCE(Sum(b.nights), 0)
FROM booking b
RIGHT JOIN guest g
ON ( b.guest_id = g.id )
WHERE address LIKE 'edinburgh%'
GROUP BY g.last_name,
g.first_name,
g.address;
This post answers your questions about how to make the query.
MySQL SUM with same ID
You can simply use COALESCE as referenced here to avoid the NULL Values
How do I get SUM function in MySQL to return '0' if no values are found?

Why my if else does not give the expected output? (mySQL)

I'm hoping you guys can help me in SQL because i'm a newbie iN sql. My problem is, the code does not give the expected output and i do not know how to fix that eventough i've searched through books and online resources. I have 2 tables (customer & order_status). The task is:
(1) select c_id,lname,address,city,description where c_id > 3
(2) select c_id,lname,address,o_status,item_total,remarks and update description to 'black' where c_id =3
(3)if item_total > 2, select o_status,item_total. ELse select o_status,item_total,remarks,order_no and update remarks to 'set'
So, here's the code:
#drop procedure if exists usp_GetAnything;
delimiter //
create procedure usp_GetAnything()
begin
declare total int ;
select total = item_total
from order_status;
select c_id,lname,address,city,description
from customer
where c_id > 3;
select c.c_id,c.lname,c.address,o.o_status,o.item_total,o.remarks,c.description
from customer c,order_status o
where c.c_id=o.c_id;
update customer
set description = 'black'
where c_id = 3;
if (total > 2) then
select o_status,item_total,remarks
from order_status
where item_total = total;
else
select o_status,item_total,remarks,order_no
from order_status
where item_total = total;
update order_status
set remarks = 'set';
end if;
end
I'm expecting the output to get the item_total for each row. If the item_total >2, it will just select, Else, it will update the remarks.. Each c_id have different no. of item_total.
Your code is a bit of a scramble, so I will try to help doing one piece at a time.
Your (1), where c_id > 3, what is the purpose of this, this will give ALL customers with ID greater than 3, but ok.
Formatting your queries for readability is also a good thing to get in the habit of. Also,
USE table aliases, especially when you get into longer table name references, makes it easier
to join/link/get fields/where/group by respectively. Even if one table.
select
c.c_id,
c.lname,
c.address,
c.city,
c.description
from
customer c
where
c.c_id > 3;
Your (2a). Your query does a simple join from the customer table to the order status based on
the customer's ID. This is ok, but it is also returning ALL customers that have an order,
and if someone has multiple orders, it will show that customer for EACH order they have.
Also, try to get in the habit of using JOIN conditions on tables instead of implied joins
within the WHERE clause. It makes it easier when you need to get into left/right join conditions.
select
c.c_id,
c.lname,
c.address,
o.o_status,
o.item_total,
o.remarks,
c.description
from
customer c
JOIN order_status o
ON c.c_id = o.c_id;
Now, if you only cared about a specific customer, add your WHERE clause for said customer ID.
Your (2b) component to update the description to 'black' where c_id =3, that was ok
update customer
set description = 'black'
where c_id = 3;
For #3, the item_total > 2. What is the basis of this item total. You had
declare total int ;
select total = item_total
from order_status;
This should not really do anything as it is going through every order and will just return
1(true) or 0(false) for every record. You have no criteria such as total per customer
or even just the number of orders for a given customer... Such as Person "A" has 5 orders on file,
and Person "B" has 1, Person "C" has 2. You will need to clarify your intent on that.
This poses the follow-up to your if/else based on the item_total > 2. select version(a) or (b)
of yet another query (not going to copy/paste that as no idea your intent)
In your if( total > 2 ) else condition, your update statement has no WHERE clause,
so it will update EVERY record in the order_status table (probably NOT what you intended).
So, with a bit more clarification, maybe some sample data myself and others could help.
Even as a newbie, if there are things that may be confidential otherwise, you can always mask
things by showing only the critical pieces, but not actually show production data, but show
sample data to help us follow your needs... now and in the future.