I am currently working on a booking system . I'm currently encountering a problem in finding out if an apartment is fully booked. In my database i have a table holding all the apartments and their details. I am trying to get the dates that all apartments for example with 4 bedrooms that are booked. I am running the following sql to return the booked dates of all 4 bedroom apartments.
SELECT *
FROM `apartment_booking` AS ab
JOIN apartment AS a ON ( a.id = apartmentId )
JOIN booking AS b ON ( b.id = bookingId )
WHERE bedrooms = '4'
ORDER BY checkIn
The return of the sql is
id CheckIn checkOut userId
74 2014-04-15 2014-04-22 1
75 2014-04-15 2014-04-22 1
102 2014-06-03 2014-07-07 1
71 2014-06-16 2014-06-23 1
114 2014-07-19 2014-08-02 1
121 2014-07-20 2014-08-02 1
57 2014-07-22 2014-08-05 1
122 2014-07-28 2014-08-02 1
117 2014-08-03 2014-08-10 1
As i have 4 apartments in the system with four bedrooms i would like to get the dates that all four bedrooms are booked.
Example with the output got the dates 2014-07-28 till 2014-08-02 are fully booked as in that date range there are in total four bookings.
Database:
CREATE TABLE `apartment` (
`id` int(11) NOT NULL auto_increment,
`code` varchar(4) NOT NULL,
`bedrooms` int(11) NOT NULL,
`description` varchar(500) default NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=26 ;
--
-- Dumping data for table `apartment`
--
INSERT INTO `apartment` (`id`, `code`, `bedrooms`, `description`) VALUES
(1, '1c', 3, ''),
(4, '4d', 4, NULL),
(5, '5b', 2, NULL),
(10, '10c', 3, NULL),
(11, '11b', 2, NULL),
(12, '12d', 4, NULL),
(13, '13c', 3, NULL),
(14, '14a', 1, 'Yo'),
(15, '15b', 2, NULL),
(16, '16b', 2, NULL),
(17, '17d', 4, NULL),
(22, '22d', 4, NULL),
CREATE TABLE `apartment_booking` (
`id` int(11) NOT NULL auto_increment,
`apartmentId` int(11) NOT NULL,
`bookingId` int(11) NOT NULL,
`ref` varchar(50) NOT NULL,
`pax` int(11) NOT NULL default '1',
`remarks` varchar(500) default NULL,
`guestFullName` varchar(30) default NULL,
`guestCountry` varchar(2) default NULL,
`guestFlightDetails` varchar(200) default NULL,
PRIMARY KEY (`id`),
KEY `apartmentId` (`apartmentId`),
KEY `bookingId` (`bookingId`),
KEY `ref` (`ref`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=179 ;
--
-- Dumping data for table `apartment_booking`
--
INSERT INTO `apartment_booking` (`id`, `apartmentId`, `bookingId`, `ref`, `pax`, `remarks`, `guestFullName`, `guestCountry`, `guestFlightDetails`) VALUES
(164, 1, 140, 'Hotelbeds', 5, '', 'Andrew Robertson', 'MT', '')
(165, 21, 141, 'Hotelbeds', 6, '', 'Pipitone', 'MT', ''),
(166, 5, 142, 'maltaholidaylets', 2, '', 'holly turpin', 'MT', ''),
(167, 12, 143, 'direct003', 4, '', 'Bernard Walch', 'MT', ''),
(168, 17, 144, 'meetingpoint', 4, '', 'Edvin Modigh', 'MT', ''),
(169, 23, 145, 'direct', 3, '', 'Andrea bacchetti', 'MT', ''),
(172, 25, 148, 'direct', 5, '', 'Wimold Peters', 'MT', ''),
(173, 20, 149, '7228110687', 4, '', 'Ms. Benedetta Tombari', 'MT', ''),
(174, 23, 149, '7228110687 meetingpoint', 2, '', 'Ms. Milena Moretti', 'MT', ''),
(175, 25, 150, 'meetingpoint', 6, '', 'N Burdett', 'MT', ''),
(176, 8, 151, 'Hotelbeds', 2, '', 'tito titti', 'MT', ''),
(177, 1, 152, 'meetingpoint', 3, '', 'Stephen Mckenna', 'MT', ''),
(178, 16, 153, 'mhcs', 4, '', 'Wojclech Blaszak', 'MT', '');
-- --------------------------------------------------------
--
-- Table structure for table `booking`
--
CREATE TABLE `booking` (
`id` int(11) NOT NULL auto_increment,
`reference` varchar(20) NOT NULL,
`dateTime` datetime NOT NULL,
`checkIn` date NOT NULL,
`checkOut` date NOT NULL,
`userId` int(11) default NULL,
PRIMARY KEY (`id`),
KEY `agent` (`userId`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=154 ;
--
-- Dumping data for table `booking`
--
INSERT INTO `booking` (`id`, `reference`, `dateTime`, `checkIn`, `checkOut`, `userId`) VALUES
(136, 'euroresort booking.b', '2014-07-02 09:30:08', '2014-08-04', '2014-08-11', 1),
(137, '7014505534', '2014-07-02 09:32:05', '2014-07-19', '2014-07-24', 1),
(138, 'BR4277518', '2014-07-02 09:45:02', '2014-08-09', '2014-08-16', 1),
(139, '100206154', '2014-07-02 10:11:45', '2014-07-27', '2014-08-03', 1),
(140, '120-135249-95', '2014-07-02 10:13:14', '2014-07-02', '2014-07-03', 1),
(141, '120-135181-94', '2014-07-02 10:14:31', '2014-08-10', '2014-08-17', 1),
(142, '000548MHL', '2014-07-02 12:38:54', '2014-08-25', '2014-09-01', 1),
(143, 'direct003', '2014-07-02 15:48:04', '2014-08-11', '2014-08-22', 1),
(144, 'SH3049361', '2014-07-02 15:52:18', '2014-08-05', '2014-08-14', 1),
(145, 'direct009', '2014-07-03 08:27:56', '2014-07-19', '2014-07-26', 1),
(148, 'direct010', '2014-07-04 08:12:13', '2014-07-08', '2014-07-22', 1),
(149, '7228110687', '2014-07-04 13:28:16', '2014-08-10', '2014-08-16', 1),
(150, '7308310623', '2014-07-07 08:39:04', '2014-08-11', '2014-08-20', 1),
(151, '120-135677-92', '2014-07-07 08:43:06', '2014-08-22', '2014-08-29', 1),
(152, '100209964', '2014-07-07 10:59:16', '2014-08-05', '2014-08-12', 1),
(153, 'mhcs', '2014-07-07 13:07:22', '2014-08-08', '2014-08-16', 1);
It gets a bit complicated.
The following query generates a range of numbers from 0 to 999, and adds each number as a number of days to the checkIn date for each booking, where the resulting date is less than or equal to the checkOut date for bookings for apartments with 4 rooms. This should give one row per apartment per day booked.
The number of booking ids for each date is then counted, and compared with the number of apartments with 4 bedrooms (from a sub query). The HAVING clause then discards all rows for dates where the number of aparments booked is not the same as the number of apartments with 4 rooms.
SELECT aBookedDate, sub2.apartment_cnt, COUNT(id) AS all_booking_cnt
FROM
(
SELECT booking.id, DATE_ADD(booking.checkIn, INTERVAL iCnt DAY) AS aBookedDate
FROM
(
SELECT units.i + tens.i * 10 + hundreds.i * 100 AS iCnt
FROM (SELECT 0 i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9)units
CROSS JOIN (SELECT 0 i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9)tens
CROSS JOIN (SELECT 0 i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9)hundreds
) sub0
CROSS JOIN booking
INNER JOIN apartment_booking ON booking.id = apartment_booking.bookingId
INNER JOIN apartment ON apartment.id = apartment_booking.apartmentId
WHERE DATE_ADD(booking.checkIn, INTERVAL iCnt DAY) <= booking.checkOut
AND apartment.bedrooms = 4
) sub1
CROSS JOIN
(
SELECT COUNT(*) AS apartment_cnt
FROM apartment
WHERE bedrooms = 4
) sub2
GROUP BY aBookedDate
HAVING all_booking_cnt = sub2.apartment_cnt
SQL fiddle for it:-
http://www.sqlfiddle.com/#!2/6edbe/5
You need left outer join, so you also show the apartments that are not booked.
SELECT *
FROM `apartment_booking` AS ab
JOIN apartment AS a ON ( a.id = apartmentId )
JOIN booking AS b ON ( b.id = bookingId )
WHERE bedrooms = '4' and userId is null
ORDER BY checkIn
Ones that have null user id will be empty (aka non-booked), since there is no booking connected to that apartment. You didn't say enough about the structure so I take it you delete the bookings rather than keeping history. If you keep all historical entries you need to check the date with today's date instead.
SELECT *
FROM `apartment_booking` AS ab
JOIN apartment AS a ON ( a.id = apartmentId )
LEFT OUTER JOIN booking AS b ON ( b.id = bookingId )
WHERE bedrooms = '4' and checkOut > NOW()
ORDER BY checkIn
EDIT:
It should look something like, I'll try to prepare a fiddle with that later:
SELECT
(COUNT(
SELECT *
FROM `apartment_booking` AS ab
JOIN apartment AS a ON ( a.id = apartmentId )
LEFT OUTER JOIN booking AS b ON ( b.id = bookingId )
WHERE bedrooms = '4' and checkIn <= <<<SOMEDATEHERE>>> and checkOut >= <<<<SOMEOTHERDATEHERE>>>>>
) >= 4);
The image of the table relation can be found at image.
-- Table structure for table `area`
CREATE TABLE `area` (
`area_id` int(10) NOT NULL auto_increment,
`area_name` varchar(255) NOT NULL,
PRIMARY KEY (`area_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=5 ;
INSERT INTO `area` (`area_id`, `area_name`) VALUES
(1, 'Area 1'),
(2, 'Area 2'),
(3, 'Area 3'),
(4, 'Area 4');
-- Table structure for table `fruits`
CREATE TABLE `fruits` (
`fruit_id` int(10) NOT NULL auto_increment,
`fruit_name` varchar(255) NOT NULL,
`area_id` int(10) NOT NULL,
PRIMARY KEY (`fruit_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=5 ;
INSERT INTO `fruits` (`fruit_id`, `fruit_name`, `area_id`) VALUES
(1, 'Apple', 1),
(2, 'Orange', 1),
(3, 'Mango', 2),
(4, 'Apricot', 3);
-- Table structure for table `vegetables`
CREATE TABLE `vegetables` (
`veg_id` int(10) NOT NULL auto_increment,
`veg_name` varchar(255) NOT NULL,
`area_id` int(10) NOT NULL,
PRIMARY KEY (`veg_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=3 ;
INSERT INTO `vegetables` (`veg_id`, `veg_name`, `area_id`) VALUES
(1, 'Chickpea', 1),
(2, 'Drumstick', 4);
If I use the following query I get the output as below
SELECT
`area`.`area_name` AS AreaName
,COUNT(*) AS num
FROM
`area`
INNER JOIN `fruits`
ON (`fruits`.`area_id` = `area`.`area_id`)
GROUP BY `fruits`.area_id
UNION ALL
SELECT
`area`.`area_name` AS AreaName
,COUNT(*) AS num
FROM
`area`
INNER JOIN `vegetables`
ON (`vegetables` .`area_id` = `area`.`area_id`)
GROUP BY `vegetables`.area_id
AreaName num
Area 1 2
Area 2 1
Area 3 1
Area 1 1
Area 4 1
But I want the output to be like:
it should fetch all the areas which are present in vegetables and fruits and if the area is repeating in either fruits or vegetables it should return the total count of area_id by totalling the count of fruits and vegetables.. so the output will be like below
AreaName num
Area 1 3
Area 2 1
Area 3 1
Area 4 1
You can use a subselect over your query and use SUM() to add the counts for same area
SELECT t.AreaName ,SUM(t.num) num
FROM ( ....) t
GROUP BY t.AreaName
Fiddle Demo
Here is my MySQL schema
CREATE TABLE IF NOT EXISTS `sales` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`date` date NOT NULL,
`total_tax` decimal(25,2) NOT NULL,
`total` decimal(25,2) NOT NULL,
`total_tax2` decimal(25,2) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=38 ;
INSERT INTO `sales` (`id`, `date`, `total_tax`, `total`, `total_tax2`) VALUES
(1, '2013-02-14', 6, 100, 21),
(2, '2013-02-18', 6, 100, 21),
(3, '2013-03-01', 6, 100, 21),
(4, '2013-03-07', 6, 100, 21),
(5, '2013-03-28', 6, 100, 21),
(6, '2013-03-28', 6, 100, 21),
(7, '2013-04-04', 6, 100, 21);
CREATE TABLE IF NOT EXISTS `purchases` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`date` date NOT NULL,
`total` decimal(25,2) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=15 ;
INSERT INTO `purchases` (`id`, `date`, `total`) VALUES
(1, '2013-02-15', 150),
(2, '2013-02-16', 18),
(3, '2013-03-05', 80),
(4, '2013-03-09', 50),
(5, '2013-03-16', 500),
(6, '2013-03-22', 200);
And I am trying to get the total values of sales.total, total_tax, total_tax2 and purchases.total by month by joining table on date. I am trying this SQL query:
SELECT date_format( sales.date, '%b %Y' ) AS MONTH,
SUM( COALESCE( sales.total, 0 ) ) AS sales,
SUM( COALESCE( purchases.total, 0 ) ) AS purchases,
SUM( COALESCE( total_tax, 0 ) ) AS tax1,
SUM( COALESCE( sales.total_tax2, 0 ) ) AS tax2
FROM sales
LEFT JOIN purchases
ON date_format(purchases.date, '%b %Y' ) = date_format(sales.date, '%b %Y' )
WHERE sales.date >= date_sub( now( ) , INTERVAL 12 MONTH )
GROUP BY date_format( purchases.date, '%b %Y' )
ORDER BY date_format( sales.date, '%m' ) ASC
and getting results as
Feb 2013 400 336 24 84
Mar 2013 1600 3320 96 336
Apr 2013 100 0 6 21
Why am I getting these incorrect values?
SQL Fiddle
You are joining by the month of the dates of your tables, meaning that you are multiplying the rows that you are suming. You either perform the aggregation previously or join by the key of your tables:
SELECT S.Month,
S.sales,
ISNULL(P.purchases,0) purchases,
S.tax1,
S.tax2
FROM ( SELECT date_format(date, '%b %Y') Month,
SUM(total) Sales,
SUM(total_tax) tax1,
SUM(total_tax2) tax2
FROM sales
WHERE sales.date >= date_sub( now( ) , INTERVAL 12 MONTH )
GROUP BY date_format(date, '%b %Y')) S
LEFT JOIN ( SELECT date_format(date, '%b %Y') Month,
SUM(total) purchases
FROM purchases
GROUP BY date_format(date, '%b %Y')) P
ON S.Month = P.Month
GROUP BY S.Month
ORDER BY S.Month
Here is the sqlfiddle with this option.
The Table:
(`post_id`, `forum_id`, `topic_id`, `post_time`)
(79, 8, 4, '2012-11-19 06:58:08');
(80, 3, 3, '2012-11-19 06:58:42'),
(81, 9, 9, '2012-11-19 06:59:04'),
(82, 11, 6, '2012-11-19 16:05:39'),
(83, 9, 9, '2012-11-19 16:07:46'),
(84, 9, 11, '2012-11-19 16:09:33'),
The Query:
SELECT post_id, forum_id, topic_id FROM posts
GROUP BY topic_id
ORDER BY post_time DESC
LIMIT 5
The Results:
[0] => [post_id] => 84 [forum_id] => 9 [topic_id] => 11
[1] => [post_id] => 82 [forum_id] => 11 [topic_id] => 6
[2] => [post_id] => 81 [forum_id] => 9 [topic_id] => 9
[3] => [post_id] => 80 [forum_id] => 3 [topic_id] => 3
[4] => [post_id] => 79 [forum_id] => 8 [topic_id] => 4
The Problem:
How to rewrite the query so that it returns post_id -> 83 instead of post_id -> 81 ?
They both have the same forum and topic ids, but post_id -> 81 has an older date than post_id -> 83.
But it seems that Group By gets the 'first' record and not the 'newest' one.
I tried changing the query to
SELECT post_id, forum_id, topic_id, MAX(post_time)
but that returns both post_id 81 and 83
If you select attributes that are not used in the group clause, and are not aggregates, the result is unspecified. I.e you don't know which rows the other attributes are selected from. (The sql standard does not allow such queries, but MySQL is more relaxed).
The query should then be written e.g. as
SELECT post_id, forum_id, topic_id
FROM posts p
WHERE post_time =
(SELECT max(post_time) FROM posts p2
WHERE p2.topic_id = p.topic_id
AND p2.forum_id = p.forum_id)
GROUP BY forum_id, topic_id, post_id
ORDER BY post_time DESC
LIMIT 5;
or
SELECT post_id, forum_id, topic_id FROM posts
NATURAL JOIN
(SELECT forum_id, topic_id, max(post_time) AS post_time
FROM posts
GROUP BY forum_id, topic_id) p
ORDER BY post_time
LIMIT 5;
It's not very beautiful , but it works:
SELECT * FROM (SELECT post_id, forum_id, topic_id FROM posts
ORDER BY post_time DESC) as temp
GROUP BY topic_id
try something like
SELECT post_id, forum_id, topic_id
FROM (SELECT post_id, forum_id, topic_id
FROM posts
ORDER BY post_time DESC)
GROUP BY topic_id
ORDER BY topic_id desc
LIMIT 0,5
change the order by and limit as needed.
Maybe not the best way of doing it but sometimes the function group_concat() can be userfull, it will return a string of all aggregated values sorted like you want and seperated by comma (coupled value are separated by space). I then use the function SPLIT_STRING() to cut the first id in the string.
SELECT
post_id,
SPLIT_STRING( group_concat( forum_id, post_time ORDER BY post_time DESC ) ,' ',1 )as forum_id,
SPLIT_STRING( group_concat( topic_id, post_time ORDER BY post_time DESC ) ,' ',1 )as topic_id ,
FROM posts
GROUP BY topic_id
ORDER BY post_time DESC
LIMIT 5
So the aggregated forum_id, post_time will be like this :
81 2012-11-19 06:59:04,83 2012-11-19 16:07:46
So you need to work with a string representation of integers and datetime couples, each couples separated by comma so I used this function to get the first INT :
CREATE FUNCTION SPLIT_STRING(str VARCHAR(255), delim VARCHAR(12), pos INT)
RETURNS VARCHAR(255)
RETURN REPLACE(SUBSTRING(SUBSTRING_INDEX(str, delim, pos),
LENGTH(SUBSTRING_INDEX(str, delim, pos-1)) + 1),
delim, '');
Note : function SPLIT_STRING(str, delim, pos) was found here : Equivalent of explode() to work with strings in MySQL
This will also work fine for you.
SELECT *
FROM (
SELECT post_id, forum_id, topic_id FROM posts
ORDER BY post_time DESC
LIMIT 5
) customeTable
GROUP BY topic_id