Mysql query to get hotel room availability - mysql

I'm implementing a booking platform I have 3 tables:
"hotel" - to hold the hotel information
"hotel_room" - to hold room info per hotel
"hotel_room_price" - have the availability by date, number of rooms available and price
I want to search by start date and end date, local and number of rooms (each room have the number of adults and number of child)
Here is some example of my tables:
CREATE TABLE `hotel` (
`id` int(11) NOT NULL,
`name` varchar(255) NOT NULL,
`rating` smallint(6) NOT NULL DEFAULT '3' COMMENT '0 - Not Rated | 1 - One Star | 2 - Two Stars | 3 - Three Stars | 4 - Four Stars | 5 - Five Stars',
`local` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `hotel_room` (
`id` int(11) NOT NULL,
`hotel_id` int(11) NOT NULL,
`name` varchar(255) NOT NULL,
`max_capacity_adult` smallint(6) NOT NULL,
`max_capacity_child` smallint(6) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
ALTER TABLE `hotel_room` ADD CONSTRAINT `fk_hotel_room_hotel_id` FOREIGN KEY (`hotel_id`) REFERENCES `hotel` (`id`) ON DELETE CASCADE;
CREATE TABLE `hotel_room_price` (
`id` int(11) NOT NULL,
`hotel_room_id` int(11) NOT NULL,
`price_adult` decimal(20,2) DEFAULT NULL,
`price_child` decimal(20,2) DEFAULT NULL,
`quantity` int(11) NOT NULL DEFAULT '1' COMMENT 'available rooms, 0 if there is no more available',
`date` date NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
ALTER TABLE `hotel_room_price` ADD CONSTRAINT `fk_hotel_room_price_hotel_room_id` FOREIGN KEY (`hotel_room_id`) REFERENCES `hotel_room` (`id`) ON DELETE CASCADE;
What is the better approach get the available rooms when a user search, one note it is possible to search for multiple rooms for example:
start_date = 2019-06-25
end_date = 2019-06-29
local = "Tomar"
Room=[
[
nr_adults = 2,
nr_children=1
],
[
nr_adults = 4,
nr_children=0
]
]
I think first thing to do it check only hotels from the right local then check if the room can hold the number of adults and children if yes check for availability.
I'm with lot of problems to create a query or multiple queries to handle this in the right way.
You can check and example of my database here http://sqlfiddle.com/#!9/458be2c

Here is query that selects all room ID's available in a given time frame. In this instance I picked June 26- June 28. This should be a good starting point for the rest of the query.
SELECT hotel_room_id
FROM hotel_room_price
WHERE date between '2019-06-26' AND '2019-06-28'
AND quantity > 0
GROUP BY hotel_room_id
HAVING COUNT(*) > DATEDIFF('2019-06-28', '2019-06-26')
Here is a somewhat hacky query to get some of the information about the rooms. Note there is not functionality for searching for multiple rooms in this sample:
SELECT h.name AS Name, h.rating AS Rating, sq.name AS Type
FROM hotel h
INNER JOIN
(SELECT *
FROM hotel_room
WHERE hotel_room.id IN
(SELECT hotel_room_id
FROM hotel_room_price
WHERE date between '[START DATE]' AND '[END DATE]'
AND quantity > 0
GROUP BY hotel_room_id
HAVING COUNT(*) > DATEDIFF('[END DATE]', '[START DATE]'))
AND max_capacity_child >= [CHILD COUNT]
AND max_capacity_adult >= [ADULT COUNT]) sq
ON h.id = sq.hotel_id
WHERE h.local = "[LOCATION]"

Related

MySQL - Match with min. number in a range - improve join performance with order by and group

I have 2 tables. One that contains stock information with a timestamp, price, ticker_id.
CREATE TABLE `data_realtime` (
`id` mediumint(9) unsigned NOT NULL AUTO_INCREMENT,
`timestamp` int(10) NOT NULL,
`ticker_id` smallint(5) unsigned NOT NULL,
`price` decimal(7,2) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ticker_timestamp` (`ticker_id`,`timestamp`) USING BTREE,
CONSTRAINT `data_realtime_ibfk_2` FOREIGN KEY (`ticker_id`) REFERENCES
`tickers` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=1585421 DEFAULT CHARSET=latin1
And the other contains ticker info with ticker_id and name. (However I don't think this is relevant here)
CREATE TABLE `tickers` (
`id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
`ticker` text NOT NULL,
`ticker_name` varchar(100) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ticker` (`ticker`(5))
) ENGINE=InnoDB AUTO_INCREMENT=9416 DEFAULT CHARSET=latin1
I run a query that gets the latest rows for each ticker (using ticker_id).
SELECT ticker_id, max(timestamp) max_timestamp FROM `data_realtime` GROUP BY ticker_id
I want to then get the corresponding stock price, 3 minutes ago.
I do this by joining the initial result (latest prices) with all rows newer than 3 mins; then ordering by them by the timestamp; then grouping them by ticker_id.
This is my query:
SELECT * FROM (
SELECT
data_latest.*, data_3m.timestamp timestamp_3m, data_3m.price price_3m
FROM (
SELECT B.* FROM (
SELECT
ticker_id, max(timestamp) max_timestamp
FROM
`data_realtime`
GROUP BY ticker_id)
A
LEFT JOIN
data_realtime B
ON A.ticker_id=B.ticker_id and A.max_timestamp=B.timestamp)
data_latest
LEFT JOIN
data_realtime data_3m
ON
data_latest.timestamp <= (data_3m.timestamp + (60*3) )
AND
data_latest.timestamp>data_3m.timestamp
AND
data_latest.ticker_id=data_3m.ticker_id
ORDER BY
data_3m.timestamp ASC)
data_3m_sorted
GROUP BY ticker_id
As you can see from the Create Table, there exists a primary key on 'id' and a combined unique key on 'timestamp' and 'ticker'.
The query takes a little over 2 secs to run with about 1.5M rows in the table. Is there anything I can do to improve the query performance.
Will adding single indexes on ticker_id and timestamp column help? If so, why?
Thanks for the help.

Display unmatched data along with aggregate functions and multiple joins

So, what i have is a system using MySQL for storage that should be storing donations made by people (donators). Donation is entered into system by authorized user.
Here are create tables for all 4 tables:
CREATE TABLE `donator` (
`DONATOR_ID` int(11) NOT NULL AUTO_INCREMENT,
`DONATOR_NAME` varchar(50) NOT NULL,
`STATUS` char(1) COLLATE NOT NULL DEFAULT 'A',
PRIMARY KEY (`DONATOR_ID`)
)
CREATE TABLE `user` (
`USER_ID` int(11) NOT NULL AUTO_INCREMENT,
`USERNAME` varchar(100) NOT NULL,
`PASSWORD` varchar(200) NOT NULL,
`TYPE` char(1) COLLATE NOT NULL,
PRIMARY KEY (`USER_ID`)
)
CREATE TABLE `sif_res` (
`RES_ID` int(11) NOT NULL AUTO_INCREMENT,
`RES_NAME` varchar(50) NOT NULL,
`MON_VAL` double NOT NULL,
PRIMARY KEY (`RES_ID`)
)
CREATE TABLE `donations` (
`DONATION_ID` int(11) NOT NULL AUTO_INCREMENT,
`RESOURCE` int(11) NOT NULL,
`AMOUNT` int(11) NOT NULL,
`DONATOR` int(11) NOT NULL,
`ENTRY_DATE` datetime NOT NULL,
`ENTERED_BY_USER` int(11) NOT NULL,
PRIMARY KEY (`DONATION_ID``),
KEY `fk_resurs` (`RESOURCE``),
KEY `fk_donator` (`DONATOR``),
KEY `fk_user` (`ENTERED_BY_USER``),
CONSTRAINT `fk_1` FOREIGN KEY (`DONATOR`) REFERENCES `donator` (`DONATOR_ID`) ON UPDATE CASCADE,
CONSTRAINT `fk_2` FOREIGN KEY (`RESOURCE`) REFERENCES `sif_res` (`RES_ID`) ON UPDATE CASCADE,
CONSTRAINT `fk_3` FOREIGN KEY (`ENTERED_BY_USER`) REFERENCES `user` (`USER_ID`) ON UPDATE CASCADE
)
As you can see, I have a list of donators, users and resources that can be donated.
Now, I want to display all donators' name and their id's, however in third column I would like to display their balance (sum of all of items they donated) - this is calculated with
donation.AMOUNT * sif_res.MON_VAL
for each donation
The SQL SELECT I have written works, however donators that haven't donated anything are left out (they are not matched by JOIN). I would need that it displays everyone (with STATUS!=D) even if they don't have any entries (in that case their balance may be 0 or NULL)
This is my SQL i have written:
SELECT DONATOR_ID
, DONATOR_NAME
, round(SUM(d.AMOUNT * sr.MON_VAL)) as BALANCE
from donator c
join donations d on c.DONATOR_ID=d.DONATOR
join sif_res sr on sr.RES_ID=d.RESOURCE
where c.STATUS!='D'
group by DONATOR_ID, DONATOR_NAME
So, if i execute next sentences:
INSERT INTO donator(DONATOR_NAME, STATUS) VALUES("John", 'A'); //asigns id=1
INSERT INTO donator(DONATOR_NAME, STATUS) VALUES("Willie", 'A'); //asigns id=2
INSERT INTO user (USERNAME, PASSWORD, TYPE) VALUES("user", "pass", 'A'); //asigns id=1
INSERT INTO sif_res(RES_NAME, MON_VAL) VALUES("Flour", "0.5"); //asigns id=1
INSERT INTO donations(RESOURCE, AMOUNT, DONATOR, ENTRY_DATE, ENTERED_BY_USER) VALUES(1, 100, 1, '2.2.2017', 1);
I will get output (with my SELECT sentence above):
DONATOR_ID | DONATOR_NAME | BALANCE
--------------------------------------------
1 | John | 50
What i want to get is:
DONATOR_ID | DONATOR_NAME | BALANCE
--------------------------------------------
1 | John | 50
2 | Willie | 0
I have tried all version of joins (left, right, outer, full,..) however none of them worked for me (probably because i was using them wrong)
If it was just the problem of unmatched data i would be able to solve it, however the aggregate function SUM and another JOIN make it all more complicated
Using a left outer join on the second two tables should do the trick:
SELECT c.DONATOR_ID
, c.DONATOR_NAME
, ifnull(round(SUM(d.AMOUNT * sr.MON_VAL)),0) as BALANCE
from donator c
left outer join donations d on c.DONATOR_ID=d.DONATOR
left outer join sif_res sr on sr.RES_ID=d.RESOURCE
where c.STATUS!='D'
group by DONATOR_ID, DONATOR_NAME
I also wrapped the BALANCE expression in ifnull to display 0 instead of null.

SQL: Linking Two Tables

Let me start my saying I have the following two tables:
Fixtures Table:
| straightred_fixture | CREATE TABLE `straightred_fixture` (
`fixtureid` int(11) NOT NULL,
`hometeamscore` int(11) DEFAULT NULL,
`awayteamscore` int(11) DEFAULT NULL,
`homegoaldetails` longtext,
`awaygoaldetails` longtext,
`awayteamid` int(11) NOT NULL,
`hometeamid` int(11) NOT NULL,
PRIMARY KEY (`fixtureid`),
KEY `straightred_fixture_2e879a39` (`awayteamid`),
KEY `straightred_fixture_bcb6decb` (`hometeamid`),
KEY `straightred_fixture_d6d641f1` (`soccerseasonid`),
KEY `straightred_fixture_fixturematchday2_f98c3a75_uniq` (`fixturematchday`),
CONSTRAINT `D9b896edf0aff4d9b5c00682a8e21ea3` FOREIGN KEY (`fixturematchday`) REFERENCES `straightred_fixturematchday` (`fixturematchdayid`),
CONSTRAINT `straightr_soccerseasonid_92496b92_fk_straightred_season_seasonid` FOREIGN KEY (`soccerseasonid`) REFERENCES `straightred_season` (`seasonid`),
CONSTRAINT `straightred_fixtu_awayteamid_3d1961ba_fk_straightred_team_teamid` FOREIGN KEY (`awayteamid`) REFERENCES `straightred_team` (`teamid`),
CONSTRAINT `straightred_fixtu_hometeamid_6e37e94b_fk_straightred_team_teamid` FOREIGN KEY (`hometeamid`) REFERENCES `straightred_team` (`teamid`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 |
Team Table:
| straightred_team | CREATE TABLE `straightred_team` (
`teamid` int(11) NOT NULL,
`teamname` varchar(36) NOT NULL,
`country` varchar(36) DEFAULT NULL,
`stadium` varchar(36) DEFAULT NULL,
`homepageurl` longtext,
`wikilink` longtext,
`teamcode` varchar(5) DEFAULT NULL,
`teamshortname` varchar(24) DEFAULT NULL,
`currentteam` smallint(5) unsigned DEFAULT NULL,
PRIMARY KEY (`teamid`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 |
The Query::
select hometeamid as team, awayteamid as opponent, hometeamscore as team_score, awayteamscore as opponent_score,
(case when (hometeamscore-awayteamscore)>0 then 'W' when (hometeamscore-awayteamscore)<0 then 'L' ELSE 'D' END) as result, 'home' as mstatus
from straightred_fixture sf, straightred_team st;
What I want to do is return the team name rather than the id of the teams in the query above. I realize some sort of join is required but I am getting myself in a right muddle.
You need to join on the teams table twice, once for the home team and once for the away team:
SELECT home.teamname AS team,
away.teamname AS opponent,
hometeamscore AS team_score,
awayteamscore AS opponent_score,
CASE WHEN (hometeamscore - awayteamscore) > 0 then 'W'
WHEN (hometeamscore - awayteamscore) < 0 then 'L'
ELSE 'D'
END AS result,
'home' AS mstatus
FROM straightred_fixture sf
JOIN straightred_team home ON hometeamid = home.teamid
JOIN straightred_team away ON awayteamid = away.teamid
My suggestion based on my experience is probably one you won't like (I'm including it as an answer because a comment was too long.
In my opinion/experience here is what you need to do. You need to break up the fixtures table.
The fixtures table should have the information that is specific to the fixture itself (date, time, perhaps other specific information)
A related table can be called fixture_participants and includes the team information - each fixture has two rows in fixture participants.
In my solution, I created a column for 'home/away' represented by H/A and wins/losses represented by W/L (integers might be a better solution now that I think about it). I'm thinking this is european football so you should include a column for goals scored.
So you can now easily identify home and away teams you just have to write slightly more involved queries but you can also say 'what is the average goals scored by team A on the road' much more easily.
PS - if you need a sample of what i'm talking about this repository builds a similar table relationship for USA Basketball

Optimize tables MySQL

I have a query that is executed in 35s, which is waaaaay too long.
Here are the 3 tables concerned by the query (each table is approx. 13000 lines long, and should be much longer in the future) :
Table 1 : Domains
CREATE TABLE IF NOT EXISTS `domain` (
`id_domain` int(11) NOT NULL AUTO_INCREMENT,
`domain_domain` varchar(255) NOT NULL,
`projet_domain` int(11) NOT NULL,
`date_crea_domain` int(11) NOT NULL,
`date_expi_domain` int(11) NOT NULL,
`active_domain` tinyint(1) NOT NULL,
`remarques_domain` text NOT NULL,
PRIMARY KEY (`id_domain`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Table 2 : Keywords
CREATE TABLE IF NOT EXISTS `kw` (
`id_kw` int(11) NOT NULL AUTO_INCREMENT,
`kw_kw` varchar(255) NOT NULL,
`clics_kw` int(11) NOT NULL,
`cpc_kw` float(11,3) NOT NULL,
`date_kw` int(11) NOT NULL,
PRIMARY KEY (`id_kw`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Table 3 : Linking between domain and keyword
CREATE TABLE IF NOT EXISTS `kw_domain` (
`id_kd` int(11) NOT NULL AUTO_INCREMENT,
`kw_kd` int(11) NOT NULL,
`domain_kd` int(11) NOT NULL,
`selected_kd` tinyint(1) NOT NULL,
PRIMARY KEY (`id_kd`),
KEY `kw_to_domain` (`kw_kd`,`domain_kd`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
The query is as follows :
SELECT ng.*, kd.*, kg.*
FROM domain ng
LEFT JOIN kw_domain kd ON kd.domain_kd = ng.id_domain
LEFT JOIN kw kg ON kg.id_kw = kd.kw_kd
GROUP BY ng.id_domain
ORDER BY kd.selected_kd DESC, kd.id_kd DESC
Basically, it selects all domains, with, for each one of these domains, the last associated keyword.
Does anyone have an idea on how to optimize the tables or the query ?
The following will get the last keyword, according to your logic:
select ng.*,
(select kw_kd
from kw_domain kd
where kd.domain_kd = ng.id_domain and kd.selected_kd = 1
order by kd.id_kd desc
limit 1
) as kw_kd
from domain ng;
For performance, you want an index on kw_domain(domain_kd, selected_kd, kw_kd). In this case, the order of the fields matters.
You can use this as a subquery to get more information about the keyword:
select ng.*, kg.*
from (select ng.*,
(select kw_kd
from kw_domain kd
where kd.domain_kd = ng.id_domain and kd.selected_kd = 1
order by kd.id_kd desc
limit 1
) as kw_kd
from domain ng
) ng left join
kw kg
on kg.id_kw = ng.kw_kd;
In MySQL, group by can have poor performance, so this might work better, particularly with the right indexes.

mySql subtract row of different table

I want to subtract between two rows of different table:
I have created a view called leave_taken and table called leave_balance.
I want this result from both table:
leave_taken.COUNT(*) - leave_balance.balance
and group by leave_type_id_leave_type
Code of both table
-----------------View Leave_Taken-----------
CREATE ALGORITHM = UNDEFINED DEFINER=`1`#`localhost` SQL SECURITY DEFINER
VIEW `leave_taken`
AS
select
`leave`.`staff_leave_application_staff_id_staff` AS `staff_leave_application_staff_id_staff`,
`leave`.`leave_type_id_leave_type` AS `leave_type_id_leave_type`,
count(0) AS `COUNT(*)`
from
(
`leave`
join `staff` on((`staff`.`id_staff` = `leave`.`staff_leave_application_staff_id_staff`))
)
where (`leave`.`active` = 1)
group by `leave`.`leave_type_id_leave_type`;
----------------Table leave_balance----------
CREATE TABLE IF NOT EXISTS `leave_balance` (
`id_leave_balance` int(11) NOT NULL AUTO_INCREMENT,
`staff_id_staff` int(11) NOT NULL,
`leave_type_id_leave_type` int(11) NOT NULL,
`balance` int(3) NOT NULL,
`date_added` date NOT NULL,
PRIMARY KEY (`id_leave_balance`),
UNIQUE KEY `id_leave_balance_UNIQUE` (`id_leave_balance`),
KEY `fk_leave_balance_staff1` (`staff_id_staff`),
KEY `fk_leave_balance_leave_type1` (`leave_type_id_leave_type`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=3 ;
------- Table leave ----------
CREATE TABLE IF NOT EXISTS `leave` (
`id_leave` int(11) NOT NULL AUTO_INCREMENT,
`staff_leave_application_id_staff_leave_application` int(11) NOT NULL,
`staff_leave_application_staff_id_staff` int(11) NOT NULL,
`leave_type_id_leave_type` int(11) NOT NULL,
`date` date NOT NULL,
`active` int(11) NOT NULL DEFAULT '1',
`date_updated` date NOT NULL,
PRIMARY KEY (`id_leave`,`staff_leave_application_id_staff_leave_application`,`staff_leave_application_staff_id_staff`),
KEY `fk_table1_leave_type1` (`leave_type_id_leave_type`),
KEY `fk_table1_staff_leave_application1` (`staff_leave_application_id_staff_leave_application`,`staff_leave_application_staff_id_staff`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=32 ;
Well, I still don't think you've provided enough information. It would be very helpful to have some sample data and your expected output (in tabular format). That said, I may have something you can start working with. This query finds all staff members, calculates their current leave (grouped by type), and determines the difference between that and their balance by leave type. Take a look at it, and more importantly (perhaps) the sqlfiddle here that I used which has the sample data in it (very important to determining if this is the correct path for your data).
SELECT
staff.id_staff,
staff.name,
COUNT(`leave`.id_leave) AS leave_count,
leave_balance.balance,
(COUNT(`leave`.id_leave) - leave_balance.balance) AS leave_difference,
`leave`.leave_type_id_leave_type AS leave_type
FROM
staff
JOIN `leave` ON staff.id_staff = `leave`.staff_leave_application_staff_id_staff
JOIN leave_balance ON
(
staff.id_staff = leave_balance.staff_id_staff
AND `leave`.leave_type_id_leave_type = leave_balance.leave_type_id_leave_type
)
WHERE
`leave`.active = 1
GROUP BY
staff.id_staff, leave_type;
Good luck!