I have the following tables:
Let's assume the first table represents a booking made by a customer for a hotel.
+------------+-----------+-----------+--------------+----------------+
| booking_id | client_id | room_type | arrival_date | departure_date |
+------------+-----------+-----------+--------------+----------------+
| 1 | 1 | 1 | 2016-05-30 | 2016-06-03 |
+------------+-----------+-----------+--------------+----------------+
The second table contains the prices for different types of room. The prices vary for different periods of time.
+---------------+-----------+------------+------------+-------+
| room_price_id | room_type | start_date | end_date | price |
+---------------+-----------+------------+------------+-------+
| 1 | 1 | 2016-03-01 | 2016-05-31 | 150 |
| 2 | 1 | 2016-06-01 | 2016-08-31 | 200 |
+---------------+-----------+------------+------------+-------+
My question is: how can I calculate the total cost for a booking if the price is not the same during the whole period of the booking? (in my case the total cost would be 700)
I read the following links: MySQL: Select all dates between date range and get table data matching dates and Select data from date range between two dates, but I don't get it on how to solve my problem since the entries in the table with the prices contain only date ranges and not explicit dates as in the first link.
The trick with your question is in formulating the join condition between the booking and prices tables. Once this is out of the way, a simple aggregation gets you the result you want. The join condition should be true whenever any point in a given price range falls within the range of the arrival or departure date.
Then, to count days we use the greater of the arrival date or the start date, and the lesser of the departure date or the end date.
SELECT
b.booking_id,
SUM((DATEDIFF(LEAST(b.departure_date, p.end_date),
GREATEST(b.arrival_date, p.start_date)) + 1) * p.price) AS total
FROM booking b
INNER JOIN prices p
ON b.room_type = p.room_type AND
(p.start_date BETWEEN b.arrival_date AND b.departure_date OR
p.end_date BETWEEN b.arrival_date AND b.departure_date)
GROUP BY b.booking_id;
The output for your sample data is 900. This is correct because:
2 days x 150 = 300
3 days x 200 = 600
total = 900
Demo here:
SQLFiddle
And here is another demo which highlights the case where a stay overlaps an entire price range.
Related
I have a table of invoices that can be in multiple currencies that looks like this:
| id | issue_date | total | currency |
|----|------------|-------|----------|
| 1 | 2020-04-20 | 1234 | EUR |
| 2 | 2020-04-26 | 2345 | USD |
| 1 | 2020-04-27 | 9876 | EUR |
| 3 | 2020-04-28 | 3456 | RON |
And i have a currency table that holds currency exchange rates that looks like this:
| id | date | currency_id | rate |
|----|------------|-------------|---------|
| 1 | 2020-04-20 | EUR | 1 |
| 2 | 2020-04-20 | USD | 1.08600 |
| 3 | 2020-04-20 | RON | 4.83560 |
What I would like to achieve is to calculate each invoice price based on its issue_date, currency and a target currency.
All currency exchange rates are based on EUR so its value will always be 1. Currencies are updated daily but there are dates missing (during weekend exchange rates don't update) so calculation needs to be based on most recent exchange rate until invoice.issue_date
So what I tried was this:
SELECT
`i`.`id`,
`i`.`total`,
`i`.`currency`,
`exr1`.`rate` as `invoice_rate`,
`exr2`.`rate` AS `target_rate`,
`i`.`total` * `exr1`.`rate` as `euro_price`,
`i`.`total` * `exr1`.`rate` / `exr2`.`rate` AS `target_price`
FROM `invoices` as `i`
LEFT JOIN `exchange_rates` AS `exr1`
ON
`exr1`.`currency_id` = `i`.`currency` AND
`exr1`.`date` = `i`.`issue_date`
LEFT JOIN `exchange_rates` as `exr2`
ON
`exr2`.`currency_id` = 'RON' AND
`exr2`.`date` = `i`.`issue_date`
GROUP BY
`i`.`id`,
`invoice_rate`,
`target_rate`
ORDER BY `i`.`issue_date` DESC
Problem nr. 1
Because there are no exhange rates for the exact invoice dates I get null values. I tried changing the LEFT JOIN ON to something like exr1.date <= i.issue_date but GROUP BY invoice doesn't work anymore (i get duplicates).
Problem nr. 2
For rows that have exchange rates on that exact day I get wrong values because based on the target currency I need to either multiply or divide:
i.total * exr1.rate * exr2.rate AS usd_price vs i.total * exr1.rate / exr2.rate AS usd_price
https://www.db-fiddle.com/f/e5GnVnry5sAiXwbuScV6JT/19
This is a (rare) case where a dependent subquery is the way to go. Here's the overall query (https://www.db-fiddle.com/f/e5GnVnry5sAiXwbuScV6JT/21)
SELECT id,
total,
currency,
rate,
total / rate euro_price
FROM ( SELECT i.id,
i.total,
i.currency,
(SELECT e.rate
FROM exchange_rates e
WHERE e.currency_id = i.currency
AND e.date <= i.issue_date
ORDER BY e.date DESC
LIMIT 1) rate
FROM invoices i
) d
The dependent subquery is this:
SELECT e.rate
FROM exchange_rates e
WHERE e.currency_id = i.currency
AND e.date <= i.issue_date
ORDER BY e.date DESC
LIMIT 1
It finds the exchange rate for the most recent date equal to or before the issue_date. It's called dependent because it refers to column values in its outer query.
This isn't going to be fast. A covering index on exchange_rates(currency_id, date DESC, rate) will help. Like this.
CREATE INDEX lookup ON exchange_rates(currency_id, date DESC, rate);
I used a nested query so the outer query can simply refer to rate as a column when it needs to, rather than repeating the dependent subquery.
Also note I think you want to divide, not multiply, when computing your euro_price.
I left the second rate lookup to you.
**Pro tip* Only use the backtick marks when your table or column name is a reserved word in the query language. Your queries are MUCH easier to read without them.
I have two tables (Invoices and taxes) in mysql:
Invoices:
- id
- account_id
- issued_at
- total
- gross_amount
- country
Taxes:
- id
- invoice_id
- tax_name
- tax_rate
- taxable_amount
- tax_amount
I'm trying to retrive a report like this
rep_month | country | total_amount | tax_name | tax_rate(%) | taxable_amount | tax_amount
--------------------------------------------------------------------------------------
2017-01-01 | ES | 1000 | TAX1 | 21 | 700 | 147
2017-01-01 | ES | 1000 | TAX2 | -15 | 700 | 105
2016-12-01 | FR | 100 | TAX4 | 20 | 30 | 6
2016-12-01 | FR | 100 | B2B | 0 | 70 | 0
2017-01-01 | GB | 2500 | TAX3 | 20 | 1000 | 200
The idea behind this is that an invoice has a has_many relation with taxes. So an invoice can have or not taxes. The report should show the total amount collected (total_amount) for a given country (regardess if it includes taxes)
and indicate which part of that total amount is taxable (taxable_amount) for an specific tax.
My current approach is this one:
SELECT
DATE_FORMAT(invoices.issued_at, '%Y-%m-01') AS rep_month,
invoices.country AS country
( SELECT sum(docs.gross_amount)
FROM invoices AS docs
WHERE docs.country = invoices.country
AND DATE_FORMAT(docs.issue_date, '%Y-%m-01') = rep_month
) AS total_amount,
taxes.tax_name AS tax_name,
taxes.tax_rate AS tax_rate,
SUM(taxes.taxable_amount) AS taxable_amount,
SUM(taxes.tax_amount) AS tax_amount
FROM invoices
JOIN taxes ON invoices.id = taxes.document_id
AND documents.issue_date BETWEEN '2016-01-01' AND '2017-12-31'
GROUP BY account_id, rep_month, country, tax_name, tax_rate
ORDER BY country desc
Well, this works but for a real dataset (thousands of records) it's really slow as the select subquery for retrieving the total_amount is being run for each row of the report.
I cannot make a LEFT JOIN taxes with a direct SUM(gross_amount) as the GROUP BY groups by tax name and rate and I need to show the total collected per country regardless if the amount was taxed or not. Is there a faster alternative to this?
I do not know the exact use case of using this query but the issue is the way with which you're trying to structure the DB, you're trying to get the entire data in one go.
Ideally, you should run the query you have and store in a different table (summary table) and then query directly from the summary table whenever you want. And if you have a new entry in the Invoices table then you can use the query to run either on every entry or periodically update the summary table via a cronjob.
How do I generate the following reports using MYSQL?
My table 'reservations' looks like this:
id | arrival | created.
1 | 2014-3-30 | 2014-3-1
2 | 2014-3-31 | 2014-3-2
3 | 2014-3-28 | 2014-3-2
4 | 2014-3-01 | 2014-3-1
5 | 2014-3-01 | 2014-3-1
I want to generate the following two aggregation reports for the "arrival" column for the whole month as shown below:
(1)
arrival | count | total_monthly_arrivals
2014-03-01 | 2 | 5
2014-03-02 | 0 | 5
2014-03-03 | 0 | 5
...
2014-03-30 | 1 | 5
2014-03-31 | 1 | 5
(2)
January | 5
...
March | 5
...
December | 0 | 5
I want these 2 result sets. It generates date according & month according report and generate result set in these form.
I tried to use group by with count in first resultset but it doesn't retrieve for a date that does not exist. Again I wanna put month condition such that I choose month. where month = '02' or something like this. Is that possible?
My SQLFiddle should answer both parts of your question: http://sqlfiddle.com/#!2/9f130/31
This will return the arrival date with a count of how many people are coming that day and how many monthly arrivals there are
select distinct(r.arrival) as arrival_date,
count(r.arrival) as total_per_day,
sa.month_total as total_arrival_per_month
from reservations as r
,(select substr(arrival,6,2) as month,
count(substr(arrival,6,2)) as month_total
from reservations
group by month) as sa
where substr(r.arrival,6,2) = sa.month
group by arrival_date,total_arrival_per_month;
This will return the month of the year and how many people are booked for that month and how many are arriving that month. (Updated with the suggestion from agrizzo.)
select MONTHNAME(STR_TO_DATE(substr(r.arrival,6,2), '%m')) as arrival_date,
sa.month_total as total_arrival_per_month
from reservations as r
,(select substr(arrival,6,2) as month,
count(substr(arrival,6,2)) as month_total
from reservations
group by month) as sa
where substr(r.arrival,6,2) = sa.month
group by arrival_date,total_arrival_per_month;
There is however, no way for me to give you every day/month of the year without a fully qualified data set, provided by and prefilled by you. That's on you to do and provide us with.
However, you can check this thread. Get a list of dates between two dates and leverage their information with my queries to get your desired results.
I need a query that will give me the result that is either (a) the highest price or (b) the one with the oldest timestamp. In all cases, price should trump timestamp (ie if a record has a really old timestamp by higher price than all others, it should always return the record with the highest price)
Here are a few scenarios:
id | price | date
1 | 5 | 2012-02-20 08:59:06
2 | 5 | 2012-02-20 09:59:06
3 | 7 | 2012-02-20 10:59:06
Should return id 3 because it is highest price
id | price | date
1 | 5 | 2012-02-20 08:59:06
2 | 5 | 2012-02-20 09:59:06
3 | 5 | 2012-02-20 10:59:06
should return id 1 since it is the oldest
In my current query i am doing this:
SELECT * FROM table ORDER BY price, date DESC LIMIT 1
Unfortunately this query is not working how i've outlined it above.
Thanks for your help
I'm having trouble determining precisely what you are after, however it sounds like you are looking for the oldest timestamp for the highest price, and so the following should suffice
SELECT *
FROM table
ORDER BY
price DESC, // Favour the highest price
date ASC // Now get the one with oldest date at this price
LIMIT 1
i hope i understand your question, try this
SELECT
p.*
FROM table p
WHERE price = (SELECT MAX(price) FROM table)
OR date = (SELECT MIN(date) FROM table)
Say, I got these two tables....
Table 1 : Hotels
hotel_id hotel_name
1 abc
2 xyz
3 efg
Table 2 : Payments
payment_id payment_date hotel_id total_amt comission
p1 23-03-2010 1 100 10
p2 23-03-2010 2 50 5
p3 23-03-2010 2 200 25
p4 23-03-2010 1 40 2
Now, I need to get the following details from the two tables
Given a particular date (say, 23-03-2010), the sum of the total_amt for each of the hotel for which a payment has been made on that particular date.
All the rows that has the date 23-03-2010 ordered according to the hotel name
A sample output is as follows...
+------------+------------+------------+---------------+
| hotel_name | date | total_amt | commission |
+------------+------------+------------+---------------+
| * abc | 23-03-2010 | 140 | 12 |
+------------+------------+------------+---------------+
|+-----------+------------+------------+--------------+|
|| paymt_id | date | total_amt | commission ||
|+-----------+------------+------------+--------------+|
|| p1 | 23-03-2010 | 100 | 10 ||
|+-----------+------------+------------+--------------+|
|| p4 | 23-03-2010 | 40 | 2 ||
|+-----------+------------+------------+--------------+|
+------------+------------+------------+---------------+
| * xyz | 23-03-2010 | 250 | 30 |
+------------+------------+------------+---------------+
|+-----------+------------+------------+--------------+|
|| paymt_id | date | total_amt | commission ||
|+-----------+------------+------------+--------------+|
|| p2 | 23-03-2010 | 50 | 5 ||
|+-----------+------------+------------+--------------+|
|| p3 | 23-03-2010 | 200 | 25 ||
|+-----------+------------+------------+--------------+|
+------------------------------------------------------+
Above the sample of the table that has to be printed...
The idea is first to show the consolidated detail of each hotel, and when the '*' next to the hotel name is clicked the breakdown of the payment details will become visible... But that can be done by some jquery..!!! The table itself can be generated with php...
Right now i am using two separate queries : One to get the sum of the amount and commission grouped by the hotel name. The next is to get the individual row for each entry having that date in the table. This is, of course, because grouping the records for calculating sum() returns only one row for each of the hotel with the sum of the amounts...
Is there a way to combine these two queries into a single one and do the operation in a more optimized way...??
Hope i am being clear.. Thanks for your time and replies...
EDIT : Added Queries Too
Query to get the sum()
SELECT DATE_FORMAT($payments.payment_date, '%d-%m-%Y %T') as payment_date,
$payments.payment_id AS payment_id,
$payments.payment_amount AS payment_amount,
$payments.agent_commision AS commision,
$payments.comm_percentage AS percentage,
$hotels.hotel_name AS hotel
SUM($payments.payment_amount) AS tot_amt
SUM($payments.agent_commision) AS tot_ag_comsn
FROM $payments
JOIN $hotels
ON $payments.hotel_id = $hotels.hotel_id
WHERE DATE_FORMAT(payment_date,'%d-%m-%Y') = '$date_report'
GROUP BY $payments.hotel_id
ORDER BY $payments.payment_date ASC
Query to get the individual rows
SELECT DATE_FORMAT($payments.payment_date, '%d-%m-%Y %T') as payment_date,
$payments.payment_id AS payment_id,
$payments.payment_amount AS payment_amount,
$payments.agent_commision AS commision,
$payments.comm_percentage AS percentage,
$hotels.hotel_name AS hotel
FROM $payments
JOIN $hotels
ON $payments.hotel_id = $hotels.hotel_id
WHERE DATE_FORMAT(payment_date,'%d-%m-%Y') = '$date_report'
GROUP BY $payments.payment_date
ORDER BY $payments.payment_date ASC
I add the rows returned by the two queries in two separate temporary tables and then use php to print the table as shown above...
EDIT 2 : And also, appreciate if someone suggests a better title for this post... ;)
This is the domain of report writers, or equivalent logic in a programming language.