I'm developing a hotel room booking system.
This system will contain some quantity of hotels, rooms & room_categories.
I have tables for these things already.
At the current moment I need to build a query to get the quantity of available rooms for each of room category on given dates.
My rooms table is like this:
--------------------------------------------
| id | name | hotel_id |room_category_id|
--------------------------------------------
| 1 | Room #1 | 1 | 1 |
| 2 | Room #2 | 1 | 1 |
| 3 | Room #3 | 1 | 2 |
| 4 | Room #4 | 1 | 2 |
| 5 | Room #5 | 1 | 3 |
| 6 | Room #6 | 1 | 3 |
| 7 | Room #7 | 1 | 4 |
| 8 | Room #8 | 1 | 4 |
--------------------------------------------
Room categories table is like this:
----------------------------------
| id | name | price | volume |
----------------------------------
| 1 | Standart | $100 | 2 |
| 2 | Comfort | $150 | 2 |
| 3 | Half Lux | $200 | 3 |
| 4 | Lux | $250 | 3 |
----------------------------------
Bookings table is like this:
------------------------------------------------------------------------
| id | booking_start | booking_end | room_id |room_category_id|hotel_id|
------------------------------------------------------------------------
| 1 | 2019-06-17 | 2019-07-17 | 1 | 1 | 1 |
| 2 | 2019-06-17 | 2019-07-17 | null | 2 | 1 |
| 3 | 2019-06-17 | 2019-07-17 | null | 3 | 1 |
------------------------------------------------------------------------
I'm trying this query
SELECT room_categories.name, COUNT(room_categories.name) as quantity FROM rooms
INNER JOIN room_categories
ON rooms.room_category_id = room_categories.id
WHERE hotel_id=1
AND room_categories.id NOT IN (
Select bookings.room_category_id FROM bookings
WHERE '2019-07-28' between booking_start and booking_end
OR booking_end between '2019-06-17' and '2019-07-28'
OR '2019-06-17' between booking_start and booking_end
OR booking_start between '2019-06-17' and '2019-07-28'
)
GROUP BY room_categories.name
ORDER BY quantity
Let's imagine I have 2 rooms for each category and 1 booking for each room category. This query return ONLY category I don't have ANY bookings on (in my case room_category=4).
-------------------
| name |quantity|
-------------------
|Standart| 2 |
-------------------
How should I build a query to get correct counts here like this:
|room_category|count|
---------------------
| Standart | 1 |
| Comfort | 1 |
| Half Lux | 1 |
| Lux | 2 |
---------------------
Your question is a little vague on what you mean by "available" and what dates you want. Let me assume that you want the numbers of rooms, by category, that are available for the entire period from 2019-06-17 to 2019-07-28 (that seems like a long time to me and a hotel that has rooms for that entire period does not seem to have a very good business).
SELECT rc.name,
COUNT(b.room_id IS NULL) as quantity
FROM rooms r JOIN
room_categories rc
ON rc.room_category_id = r.id LEFT JOIN
bookings b
ON b.room_id = r.room_id AND
b.booking_start <= '2019-07-28' AND
b.booking_end >= '2019-06-17'
WHERE r.hotel_id = 1
GROUP BY rc.name
ORDER BY quantity DESC;
The LEFT JOIN is matching any booking that has a booking during the date range. The outer query is then counting rows that do not match. Note that the filter is not in the WHERE clause, so you can get counts of 0.
Related
I want to know what is the top fruit on 2021-08-15 (completed with highest total price), table below:
product
------------------
id | name
------------------
1 | banana
2 | orange
3 | apple
4 | watermelon
5 | pineapple
sales
--------------------------------------------------------------------------------
s_id | sn.id | sn.product_id | sn.status | sn.total_price | created_at
--------------------------------------------------------------------------------
1 | 1 | 2 | BOOKED | 300 | 2021-08-15 12:20:32
| 2 | 5 | COMPLETED | 800 |
| 3 | 5 | COMPLETED | 200 |
2 | 4 | 2 | COMPLETED | 500 | 2021-08-16 09:00:59
| 5 | 1 | CANCELLED | 1000 |
How to write a query on a table with nested records?
Does MySQL even have nested record data type?
select *
from
(select p.id,p.name,sum(s.total_price) as sumOfSales
from product p join sales s on p.id = s.product_id
where s.status = "COMPLETED"
group by p.id,p.name) T
order by sumOfSales desc
Hi I have three tables here is all
First. count_internets
id| company_id | item_id | created_at
1 | 1 | 1 | 2020-10-14 |
2 | 1 | 2 | 2020-10-15 |
3 | 2 | 4 | 2020-10-16 |
4 | 2 | 5 | 2020-11-20 |
5 | 1 | 1 | 2020-11-22 |
6 | 1 | 1 | 2020-11-23 |
7 | 2 | 5 | 2020-11-23 |
Second compaies
id | name
1 | Company 1
2 | Company 2
Third items
id | name | company_id
1 | Product 1 | 1
2 | Product 2 | 1
3 | Product 4 | 2
4 | Product 5 | 5
I want to get infomartion how many items sold during one month grouping by company like below
| Company | Products | Month | Count
| Company 1 | Product 1 | OCT | 1
| Company 1 | Product 2 | OCT | 1
| Company 1 | Product 1 | NOV | 1
| Company 1 | Product 4 | OCT | 1
| Company 2 | Product 5 | NOV | 2
I tried many SQL queries, but I can not solve. Please help to solve this query.
This one should work
SELECT c.name as company, i.name as products, EXTRACT(MONTH FROM FROM_UNIXTIME(created_at)) as month, count(*)
FROM count_internets
JOIN companies as c on company_id = c.id
JOIN items as i on item_id = i.id
GROUP BY company, products, month
Be aware that if that the query extract only the month (so no difference between years). If you need the year too you have to extract it EXTRACT(YEAR FROM FROM_UNIXTIME(created_at)) as year.
But your example is not very clear, company 1 didn't sell product 3. Furthermore, what's the meaning of the company_id in items table?
I designed a till system about 6 years ago, and while the code is dismal and horrible to look at, it is still going and the cafe I designed it for has been using it all this time.
However, they have recently acquired a card machine, and so now when they want to view all the sales for the day they wnat to see cash vs card in two separate tables.
This is proving tricky and the SQL for it is stumping me - I can't get my head around it.
Here are the tables involved:
categories
+----+-------------------+---------+------------+
| id | name | display | ts |
+----+-------------------+---------+------------+
| 1 | Drinks | 1 | 2016-10-14 |
| 2 | General Snacks | 1 | 2016-10-14 |
| 3 | Lunch Options | 1 | 2016-10-14 |
| 4 | Conference Drinks | 1 | 2016-10-14 |
+----+-------------------+---------+------------+
products
+----+-----------------+-------+------+-----+---------+------------+
| id | name | price | cost | cID | display | ts |
+----+-----------------+-------+------+-----+---------+------------+
| 1 | English Tea | 0.6 | 0.09 | 1 | 1 | 2018-02-15 |
| 2 | Speciality Teas | 0.8 | 0.17 | 1 | 1 | 2018-02-15 |
| 3 | Crisps | 0.6 | 0.41 | 3 | 1 | 2018-02-15 |
| 4 | Chocolate Bar | 0.6 | 0.5 | 3 | 1 | 2018-02-15 |
+----+-----------------+-------+------+-----+---------+------------+
receipts
+----+-----+-----+----------+------------+
| id | oID | pID | quantity | ts |
+----+-----+-----+----------+------------+
| 1 | 1 | 26 | 1 | 2013-11-21 |
| 2 | 2 | 6 | 2 | 2013-11-21 |
| 3 | 3 | 2 | 1 | 2013-11-21 |
| 4 | 4 | 3 | 1 | 2013-11-21 |
+----+-----+-----+----------+------------+
sales
+----+-------+----------+------+------+--------+------------+
| id | total | tendered | flag | card | userID | ts |
+----+-------+----------+------+------+--------+------------+
| 1 | 1 | 1 | 0 | 0 | 4 | 2013-11-21 |
| 2 | 2 | 2 | 0 | 0 | 4 | 2013-11-21 |
| 3 | 0.6 | 0.6 | 0 | 0 | 4 | 2013-11-21 |
| 4 | 0.6 | 0.6 | 0 | 0 | 4 | 2013-11-21 |
+----+-------+----------+------+------+--------+------------+
Please bear in mind that I wrote this a long time ago and I'm aware that its not perfect. Just to explain the above, oID stands for orderID but really should be salesID and links to the sales table ID, and pID stands for productID and is a foreign key linking to the products table. Similarily, cID is really categoryID.
Okay So the cafe manager had requested a table that looks like the following:
+---------------+-----+------+-------+------------+-----------+
| Drinks | Qty | Cost | Price | Cost-Total | Qty-total |
+---------------+-----+------+-------+------------+-----------+
| Juice Carton | 2 | 33p | 60p | 66p | £1.20 |
| Filter Coffee | 11 | 20p | 80p | £2.20 | £8.80 |
| Sub Total | 13 | | | £2.86 | £10.00 |
| Grand Total | 13 | | | £2.86 | £10.00 |
+---------------+-----+------+-------+------------+-----------+
Which has been fine, I've used the following SQL statement to produce this:
SELECT
categories.name AS category, products.name, pID,
(SELECT SUM(quantity) FROM receipts s WHERE s.pID = r.pID AND DATE(s.ts) = CURDATE()) AS quantity,
products.price,
products.cost
FROM receipts r
LEFT JOIN products ON r.pID = products.id
LEFT JOIN categories ON products.cID = categories.id
WHERE DATE(r.ts) = CURDATE()
GROUP BY r.pID ORDER BY categories.name;
However now I need to display two tables, one for card and one for cash. Now the card payments are marked in the sales table with a 1 in the card column. So I tried writing this:
SELECT
categories.name AS category, products.name, pID,
(SELECT SUM(quantity) FROM receipts s WHERE s.pID = r.pID AND DATE(s.ts) = CURDATE()) AS quantity,
products.price,
products.cost
FROM receipts r
LEFT JOIN products ON r.pID = products.id
LEFT JOIN sales x on r.oID = x.id
LEFT JOIN categories ON products.cID = categories.id
WHERE DATE(r.ts) = CURDATE() AND x.card = 1
GROUP BY r.pID ORDER BY categories.name;
However it is just displaying the same data as the first query. I know where the problem is - its in the embedded SELECT statement (AS quantity), as I'm not specifying in there whether its a card payment or a cash payement. I thought that just by adding x.card = 1 a the end would do it, but evidentally not.
Can anyone help me fix this SQL problem? How can I get the card condition into the embedded SQL, as it is retrieving from receipts and receipts does not hold information as to wether its a card payment or not?
I'm lost as to how to proceed really. All help will be appreciated!
In it's basic form:
SELECT * FROM Sales WHERE card = 1
Will display just the card payments
This will give you any sales from today:
SELECT *
FROM
categories,
products,
sales,
reciepts,
LEFT JOIN
products ON reciepts.pID
sales ON reciepts.oID
categories ON products.cID
WHERE DATE(s.ts) = CURDATE()
AND sales.card = 1
`
So what you have there is correct. It's something else you are missing...
From what I can see so far you haven't selected the sales table which could be your issue.
Have you tried running the code in SQL management studio and seeing what the result is?
Given a statuses table that holds information about products availability, how do I select the date that corresponds to the 1st day in the latest 20 days that the product has been active?
Yes I know the question is hard to follow. I think another way to put it would be: I want to know how many times each product has been sold in the last 20 days that it was active, meaning the product could have been active for years, but I'd only want the sales count from the latest 20 days that it had a status of "active".
It's something easily doable in the server-side (i.e. getting any collection of products from the DB, iterating them, performing n+1 queries on the statuses table, etc), but I have hundreds of thousands of items so it's imperative to do it in SQL for performance reasons.
table : products
+-------+-----------+
| id | name |
+-------+-----------+
| 1 | Apple |
| 2 | Banana |
| 3 | Grape |
+-------+-----------+
table : statuses
+-------+-------------+---------------+---------------+
| id | name | product_id | created_at |
+-------+-------------+---------------+---------------+
| 1 | active | 1 | 2018-01-01 |
| 2 | inactive | 1 | 2018-02-01 |
| 3 | active | 1 | 2018-03-01 |
| 4 | inactive | 1 | 2018-03-15 |
| 6 | active | 1 | 2018-04-25 |
| 7 | active | 2 | 2018-03-01 |
| 8 | active | 3 | 2018-03-10 |
| 9 | inactive | 3 | 2018-03-15 |
+-------+-------------+---------------+---------------+
table : items (ordered products)
+-------+---------------+-------------+
| id | product_id | order_id |
+-------+---------------+-------------+
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 1 | 3 |
| 4 | 1 | 4 |
| 5 | 1 | 5 |
| 6 | 2 | 3 |
| 7 | 2 | 4 |
| 8 | 2 | 5 |
| 9 | 3 | 5 |
+-------+---------------+-------------+
table : orders
+-------+---------------+
| id | created_at |
+-------+---------------+
| 1 | 2018-01-02 |
| 2 | 2018-01-15 |
| 3 | 2018-03-02 |
| 4 | 2018-03-10 |
| 5 | 2018-03-13 |
+-------+---------------+
I want my final results to look like this:
+-------+-----------+----------------------+--------------------------------+
| id | name | recent_sales_count | date_to_start_counting_sales |
+-------+-----------+----------------------+--------------------------------+
| 1 | Apple | 3 | 2018-01-30 |
| 2 | Banana | 0 | 2018-04-09 |
| 3 | Grape | 1 | 2018-03-10 |
+-------+-----------+----------------------+--------------------------------+
So this is what I mean by latest 20 active days for e.g. Apple:
It was last activated at '2018-04-25'. That's 4 days ago.
Before that, it was inactive since '2018-03-15', so all these days until '2018-04-25' don't count.
Before that, it was active since '2018-03-01'. That's more 14 days until '2018-03-15'.
Before that, inactive since '2018-02-01'.
Finally, it was active since '2018-01-01', so it should only count the missing 2 days (4 + 14 + 2 = 20) backwards from '2018-02-01', resulting in date_to_start_counting_sales = '2018-01-30'.
With the '2018-01-30' date in hand, I'm then able to count Apple orders in the last 20 active days: 3.
Hope that makes sense.
Here is a fiddle with the data provided above.
I've got a standard SQL solution, that does not use any window function as you are on MySQL 5
My solution requires 3 stacked views.
It would have been better with a CTE but your version doesn't support it. Same goes for the stacked Views... I don't like to stack views and always try to avoid it, but sometimes you have no other choice, because MySQL doesn't accept subqueries in FROM clause for Views.
CREATE VIEW VIEW_product_dates AS
(
SELECT product_id, created_at AS active_date,
(
SELECT created_at
FROM statuses ti
WHERE name = 'inactive' AND ta.created_at < ti.created_at AND ti.product_id=ta.product_id
GROUP BY product_id
) AS inactive_date
FROM statuses ta
WHERE name = 'active'
);
CREATE VIEW VIEW_product_dates_days AS
(
SELECT product_id, active_date, inactive_date, datediff(IFNULL(inactive_date, SYSDATE()),active_date) AS nb_days
FROM VIEW_product_dates
);
CREATE VIEW VIEW_product_dates_days_cumul AS
(
SELECT product_id, active_date, ifnull(inactive_date,sysdate()) AS inactive_date, nb_days,
IFNULL((SELECT SUM(V2.nb_days) + V1.nb_days
FROM VIEW_product_dates_days V2
WHERE V2.active_date >= IFNULL(V1.inactive_date, SYSDATE()) AND V1.product_id=V2.product_id
),V1.nb_days) AS cumul_days
FROM VIEW_product_dates_days V1
);
The final view produce this :
| product_id | active_date | inactive_date | nb_days | cumul_days |
|------------|----------------------|----------------------|---------|------------|
| 1 | 2018-01-01T00:00:00Z | 2018-02-01T00:00:00Z | 31 | 49 |
| 1 | 2018-03-01T00:00:00Z | 2018-03-15T00:00:00Z | 14 | 18 |
| 1 | 2018-04-25T00:00:00Z | 2018-04-29T11:28:39Z | 4 | 4 |
| 2 | 2018-03-01T00:00:00Z | 2018-04-29T11:28:39Z | 59 | 59 |
| 3 | 2018-03-10T00:00:00Z | 2018-03-15T00:00:00Z | 5 | 5 |
So it aggregates all active periods of all products, it counts the number of days for each period, and the cumulative days of all past active periods since current date.
Then we can query this final view to get the desired date for each product. I set a variable for your 20 days, so you can change that number easily if you want.
SET #cap_days = 20 ;
SELECT PD.id, Pd.name,
SUM(CASE WHEN o.created_at > PD.date_to_start_counting_sales THEN 1 ELSE 0 END) AS recent_sales_count ,
PD.date_to_start_counting_sales
FROM
(
SELECT p.*,
(CASE WHEN LowerCap.max_cumul_days IS NULL
THEN ADDDATE(ifnull(HigherCap.min_inactive_date,sysdate()),(-#cap_days))
ELSE
CASE WHEN LowerCap.max_cumul_days < #cap_days AND HigherCap.min_inactive_date IS NULL
THEN ADDDATE(ifnull(LowerCap.max_inactive_date,sysdate()),(-LowerCap.max_cumul_days))
ELSE ADDDATE(ifnull(HigherCap.min_inactive_date,sysdate()),(LowerCap.max_cumul_days-#cap_days))
END
END) as date_to_start_counting_sales
FROM products P
LEFT JOIN
(
SELECT product_id, MAX(cumul_days) AS max_cumul_days, MAX(inactive_date) AS max_inactive_date
FROM VIEW_product_dates_days_cumul
WHERE cumul_days <= #cap_days
GROUP BY product_id
) LowerCap ON P.id=LowerCap.product_id
LEFT JOIN
(
SELECT product_id, MIN(cumul_days) AS min_cumul_days, MIN(inactive_date) AS min_inactive_date
FROM VIEW_product_dates_days_cumul
WHERE cumul_days > #cap_days
GROUP BY product_id
) HigherCap ON P.id=HigherCap.product_id
) PD
LEFT JOIN items i ON PD.id = i.product_id
LEFT JOIN orders o ON o.id = i.order_id
GROUP BY PD.id, Pd.name, PD.date_to_start_counting_sales
Returns
| id | name | recent_sales_count | date_to_start_counting_sales |
|----|--------|--------------------|------------------------------|
| 1 | Apple | 3 | 2018-01-30T00:00:00Z |
| 2 | Banana | 0 | 2018-04-09T20:43:23Z |
| 3 | Grape | 1 | 2018-03-10T00:00:00Z |
FIDDLE : http://sqlfiddle.com/#!9/804f52/24
Not sure which version of MySql you're working with, but if you can use 8.0, that version came out with a lot of functionality that makes things slightly more doable (CTE's, row_number(), partition, etc.).
My recommendation would be to create a view like in this DB-Fiddle Example, call the view on server side and iterate programatically. There are ways of doing it in SQL, but it'd be a bear to write, test and likely would be less efficient.
Assumptions:
Products cannot be sold during inactive date ranges
Statuses table will always alternate status active/inactive/active for each product. I.e. no date ranges where a certain product is both active and inactive.
View Results:
+------------+-------------+------------+-------------+
| product_id | active_date | end_date | days_active |
+------------+-------------+------------+-------------+
| 1 | 2018-01-01 | 2018-02-01 | 31 |
+------------+-------------+------------+-------------+
| 1 | 2018-03-01 | 2018-03-15 | 14 |
+------------+-------------+------------+-------------+
| 1 | 2018-04-25 | 2018-04-29 | 4 |
+------------+-------------+------------+-------------+
| 2 | 2018-03-01 | 2018-04-29 | 59 |
+------------+-------------+------------+-------------+
| 3 | 2018-03-10 | 2018-03-15 | 5 |
+------------+-------------+------------+-------------+
View:
CREATE OR REPLACE VIEW days_active AS (
WITH active_rn
AS (SELECT *, Row_number()
OVER ( partition BY NAME, product_id
ORDER BY created_at) AS rownum
FROM statuses
WHERE name = 'active'),
inactive_rn
AS (SELECT *, Row_number()
OVER ( partition BY NAME, product_id
ORDER BY created_at) AS rownum
FROM statuses
WHERE name = 'inactive')
SELECT x1.product_id,
x1.created_at AS active_date,
CASE WHEN x2.created_at IS NULL
THEN Curdate()
ELSE x2.created_at
END AS end_date,
CASE WHEN x2.created_at IS NULL
THEN Datediff(Curdate(), x1.created_at)
ELSE Datediff(x2.created_at,x1.created_at)
END AS days_active
FROM active_rn x1
LEFT OUTER JOIN inactive_rn x2
ON x1.rownum = x2.rownum
AND x1.product_id = x2.product_id ORDER BY
x1.product_id);
I need help generating SQL for MySQL database.
I have three tables:
Organisations
Members
Payments
Organisations table:
+------------+---------+--------+
| id | name |website |
+------------+---------+--------+
| 1 | AAA | a.com |
|-------------------------------+
| 2 | BBB | b.com |
+------------+---------+--------+
Members table:
+------------+-------------------+--------+-----------------+-----------+
| id | organisation_id |name | Payment_confirm | join_date |
+------------+-------------------+--------+-----------------+-----------+
| 1 | 1 | james | 1 | 2013-8-02 |
|-----------------------------------------+-----------------+-----------+
| 2 | 1 | Jimmy | 0 | 2013-6-25 |
+------------+-------------------+--------+-----------------+-----------+
| 3 | 2 | Manny | 1 | 2013-07-02|
|-----------------------------------------+-----------------+-----------+
| 4 | 1 | Kim | 1 | 2013-09-02|
+------------+-------------------+--------+-----------------+-----------+
Payments table:
+------------+-------------------+--------+-----------------+----------------+
| id | member_id |amount | transaction_id | transferred_at |
+------------+-------------------+--------+-----------------+----------------+
| 1 | 1 | 100 | T1001 | 2013-8-03 |
|-----------------------------------------+-----------------+--------------- +
| 2 | 2 | 0 | null | Null |
+------------+-------------------+--------+-----------------+----------------+
| 3 | 3 | 200 | T1002 | Null |
|-----------------------------------------+-----------------+----------------+
| 4 | 4 | 50 | T1005 | 2013-09-05 |
+------------+-------------------+--------+-----------------+----------------+
How can I select the following?
Expecting the following output:
+------------+-------------------+--------+-----------------+---------------+--------------+
| Org name | Revenue |untransferred amount | Total members | last 30 days |
+------------+-------------------+--------------------------+---------------+--------------+
| AAA | 150 | 0 | 3 | 2 |
|-----------------------------------------------------------+---------------+--------------+
| BBB | 200 | 200 | 1 | 0 |
+------------+-------------------+--------------------------+---------------+--------------+
Org name = organisation name
Revenue = Total amount received
untransferred amount = transferred_at is null (payments table)
Total members = total members joined till today
last 30 days = total members joined last 30 days
You need to join your tables, group the results and select the desired logic:
SELECT org.name,
SUM(pmt.amount) AS revenue,
SUM(IF(pmt.transferred_at IS NULL, pmt.amount, 0)) AS untransferred
FROM Organisations org
JOIN Members mem ON mem.organisation_id = org.id
JOIN Payments pmt ON pmt.member_id = mem.id
GROUP BY org.id
See it on sqlfiddle.
select o.name,
sum(amount) as Revenue,
sum(if(transferred_at is null, amount, 0)) as untransfered_ammt,
sum(if(join_date>=curdate() - interval 30 day, 1, 0)) as last_30_d
from organisations o
inner join members m on o.id=m.organisation_id
inner join payments p on p.member_id=m.member_id
group by 1