Why am I getting wrong values from my joined tables? - php

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.

Related

How can I join a table to itself having it look like it's three tables?

Honestly, I don't know how to join a table to itself to solve this problem.
I have a table that stored it's record in the format below:
I want to query the table and display it's record in this format:
This is what I have tried so far
Select f.score as first_term, s.score as second_term, t.score as term_tetm from table f left join table s left join table t using (studentid) where studentid = 001 group by subject
You can get the result by grouping on the subject name, but then you will have to use an aggregate function. Here is an example:
CREATE TABLE #StudentGrades
(
[SUBJECT] VARCHAR(50),
[STUDENT_ID] VARCHAR(3),
[SCORE] INT,
[TERM] VARCHAR(50)
)
INSERT INTO #StudentGrades ([SUBJECT], [STUDENT_ID],[SCORE],[TERM])
VALUES ('English', '001', 50, '1st_Term'),
('Mathematics', '001', 40, '1st_Term'),
('French', '001', 60, '1st_Term'),
('English', '001', 60, '2nd_Term'),
('Mathematics', '001', 50, '2nd_Term'),
('French', '001', 50, '2nd_Term'),
('Computer', '001', 70, '2nd_Term'),
('English', '001', 65, '3rd_Term'),
('Mathematics', '001', 60, '3rd_Term'),
('French', '001', 70, '3rd_Term'),
('Computer', '001', 80, '3rd_Term')
SELECT [SUBJECT],
MAX(CASE WHEN [TERM] = '1st_Term' THEN [SCORE] END) AS '1ST_TERM',
MAX(CASE WHEN [TERM] = '2nd_Term' THEN [SCORE] END) AS '2ND_TERM',
MAX(CASE WHEN [TERM] = '3rd_Term' THEN [SCORE] END) AS '3RD_TERM'
FROM #StudentGrades
GROUP BY [SUBJECT]
ORDER BY [SUBJECT]
This is a simple solution but it works. Selecting student, subject and then sub-select for each of the terms. Finally, we DISTINCT it because there will be repetitions due to same subject on more than 1 term.
SELECT
DISTINCT
student_id,
`subject`,
(SELECT score FROM test AS t2 WHERE t2.student_id = t1.student_id AND t1.subject=t2.subject AND t2.term=1) AS term1,
(SELECT score FROM test AS t2 WHERE t2.student_id = t1.student_id AND t1.subject=t2.subject AND t2.term=2) AS term2,
(SELECT score FROM test AS t2 WHERE t2.student_id = t1.student_id AND t1.subject=t2.subject AND t2.term=3) AS term3
FROM
test AS t1

Filter products by price

I am trying to filter product by price but ran into a problem with special price.
If the special price is applicable in product than below query is showing random results.
"SELECT * FROM tablename WHERE ((price >= ".(int)$min_price." AND price <= ".(int)$max_price." AND ('".date('Y-m-d')."' NOT BETWEEN special_price_startdate AND special_price_enddate OR special_price_startdate = NULL OR special_price_enddate= NULL)) OR (('".date('Y-m-d')."' BETWEEN special_price_startdate AND special_price_enddate AND special_price_startdate IS NOT NULL AND special_price_enddate IS NOT NULL) AND special_price >= ".(int)$min_price." AND special_price <= ".(int)$max_price.")) AND isactive = 1 AND isdeleted = 0 ORDER BY created DESC, productid DESC LIMIT ".(($page-1)*$perpage).",".$perpage;
The query does exactly what it should do according to its syntax: if special price is applicable and special price is between set limit, it returns the row. Otherwise, if the special price is not applicable and regular price is between the set limits it returns the row:
CREATE TABLE `tablename` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`price` int(11) DEFAULT NULL,
`special_price_startdate` date DEFAULT NULL,
`special_price_enddate` date DEFAULT NULL,
`isactive` bit(1) DEFAULT NULL,
`isdeleted` bit(1) DEFAULT NULL,
`special_price` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
Query adjusted for PHP inputs:
SELECT *
FROM tablename
WHERE ( ( price >= 99
AND price <= 999
AND ( '2017-04-21' NOT BETWEEN special_price_startdate
AND special_price_enddate
OR special_price_startdate = NULL
OR special_price_enddate = NULL))
OR ( ( '2017-04-21' BETWEEN special_price_startdate
AND special_price_enddate
AND special_price_startdate IS NOT NULL
AND special_price_enddate IS NOT NULL)
AND special_price >= 99
AND special_price <= 999))
AND isactive = 1
AND isdeleted = 0
/*ORDER BY created DESC, productid DESC*/
LIMIT 10;
Insert seed data:
INSERT INTO `tablename` values
(1, 120, '2017-04-20', '2017-04-22', 1, 0, 125),
(2, 125, '2017-04-27', '2017-04-28', 1, 0, 125),
(3, 120, '2017-04-20', '2017-04-22', 1, 0, 50),
(4, 50, '2017-04-27', '2017-04-28', 1, 0, 125)
Output:
1 120 4/20/2017 12:00:00 AM 4/22/2017 12:00:00 AM 1 0 125
2 125 4/27/2017 12:00:00 AM 4/28/2017 12:00:00 AM 1 0 125

MySQL: How to get five posts from each user?

I want to select users from one table, and check for id in the other by setting LIMIT 5 for each.
Here's what I have, but I can't reuse, the a.id in the 2nd union
SELECT a.id AS USER,
p.id AS post_id
FROM (
(SELECT a.id
FROM `user` a
WHERE a.date=...)
UNION
(SELECT p.id
FROM `post` p
WHERE p.user_id=a.id LIMIT 5)`` `) AS post
Thank you, aloha
The query might look like below:
Main Query:
SELECT
t.userID,
t.postID
FROM
(
SELECT
user.id AS userID,
post.id AS postID,
IF (#prev = post.user_id ,#cn := #cn + 1 ,#cn := 0) SL,
#prev := post.user_id
FROM (SELECT #cn := 0, #prev := 0) var,post
INNER JOIN user ON user.id = post.user_id
ORDER BY post.user_id) t
WHERE t.SL < 5;
The query will pick 5 posts for each user.
TEST:
Unable to add an SQL FIDDLE.
So here's some test data with schema to check the query.
DROP TABLE IF EXISTS `post`;
CREATE TABLE `post` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
PRIMARY KEY (`id`)
);
INSERT INTO `post` VALUES ('1', '71');
INSERT INTO `post` VALUES ('2', '66');
INSERT INTO `post` VALUES ('3', '66');
INSERT INTO `post` VALUES ('4', '71');
INSERT INTO `post` VALUES ('5', '66');
INSERT INTO `post` VALUES ('6', '71');
INSERT INTO `post` VALUES ('7', '71');
INSERT INTO `post` VALUES ('8', '71');
INSERT INTO `post` VALUES ('9', '66');
INSERT INTO `post` VALUES ('10', '66');
INSERT INTO `post` VALUES ('11', '66');
INSERT INTO `post` VALUES ('12', '66');
INSERT INTO `post` VALUES ('13', '71');
INSERT INTO `post` VALUES ('14', '91');
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`)
);
INSERT INTO `user` VALUES ('66');
INSERT INTO `user` VALUES ('71');
INSERT INTO `user` VALUES ('91');
SELECT
*
FROM post;
Result:
id user_id
1 71
2 66
3 66
4 71
5 66
6 71
7 71
8 71
9 66
10 66
11 66
12 66
13 71
14 91
SELECT * FROM user;
Result:
id
66
71
91
Total posts per user query:
SELECT
user_id,
COUNT(*) totalPost
FROM post
GROUP BY user_id;
Result:
user_id totalPost
66 7
71 6
91 1
Final Result:
userID postID
66 2
66 3
66 5
66 12
66 11
71 1
71 13
71 8
71 7
71 6
91 14

How to verify if room is fully booked?

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);

How to join with multiple tables in MySQL?

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

Categories