Optimize MySQL SELECT containing multiple joined datetime columns - mysql

I have this query which returns what I want.
SELECT DISTINCT c.cust_id
FROM tbCustomers AS c
JOIN tbOrders AS o
ON o.order_custid = c.cust_id
JOIN tbPayments AS p
ON p.pay_custid = c.cust_id
WHERE (
DATE(o.order_date) > DATE_ADD(NOW(), INTERVAL -14 DAY)
AND o.order_status = '3'
AND o.order_exported = '0'
)
OR (
DATE(p.pay_date) > DATE_ADD(NOW(), INTERVAL -14 DAY)
AND p.pay_exported = '0'
)
However it is (relatively) slow to execute and I'd like to find a way to speed it up.
Can anyone give me a few pointers?
EDIT:
EXPLAIN AND CREATE as requested
EXPLAIN:
(with db name removed)
CREATE(S):
CREATE TABLE IF NOT EXISTS `tbCustomers` (
`cust_id` int(11) NOT NULL,
`cust_roundid` int(11) DEFAULT NULL,
`cust_email` varchar(250) DEFAULT NULL,
`cust_pass` varchar(32) DEFAULT NULL,
`cust_firstname` varchar(50) NOT NULL,
`cust_surname` varchar(50) NOT NULL,
`cust_address1` varchar(50) NOT NULL,
`cust_address2` varchar(50) DEFAULT NULL,
`cust_town` varchar(50) DEFAULT NULL,
`cust_county` varchar(50) DEFAULT NULL,
`cust_postcode` varchar(10) NOT NULL,
`cust_phone` varchar(20) DEFAULT NULL,
`cust_evephone` varchar(20) DEFAULT NULL,
`cust_delpointid` int(11) DEFAULT NULL,
`cust_notes` text NOT NULL,
`cust_delnotes` text,
`cust_delorder` int(10) NOT NULL DEFAULT '0',
`cust_likes` text,
`cust_dislikes` text,
`cust_active` int(1) NOT NULL DEFAULT '1',
`cust_auth` int(1) NOT NULL,
`cust_newsletter` tinyint(4) NOT NULL DEFAULT '1',
`cust_rep` varchar(250) DEFAULT NULL,
`cust_join` datetime DEFAULT NULL,
`cust_howheard` varchar(25) DEFAULT NULL,
`cust_friendname` varchar(50) DEFAULT NULL,
`cust_friend1staddress` varchar(50) DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=18694 ;
-- --------------------------------------------------------
--
-- Table structure for table `tbOrders`
--
CREATE TABLE IF NOT EXISTS `tbOrders` (
`order_id` int(11) NOT NULL,
`order_custid` int(11) NOT NULL,
`order_date` datetime NOT NULL,
`order_status` int(1) NOT NULL,
`order_exported` tinyint(1) NOT NULL DEFAULT '0',
`order_subid` int(11) DEFAULT NULL,
`discount_vouchers` longtext
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=310575 ;
-- --------------------------------------------------------
--
-- Table structure for table `tbPayments`
--
CREATE TABLE IF NOT EXISTS `tbPayments` (
`pay_id` int(11) NOT NULL,
`pay_custid` int(11) NOT NULL,
`pay_date` datetime NOT NULL,
`pay_value` float NOT NULL,
`pay_detail` varchar(200) NOT NULL,
`pay_stref` varchar(50) DEFAULT NULL,
`pay_nomcode` int(5) NOT NULL,
`pay_exported` tinyint(1) NOT NULL DEFAULT '0'
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=128264 ;
--
-- Indexes for dumped tables
--
--
-- Indexes for table `tbCustomers`
--
ALTER TABLE `tbCustomers`
ADD PRIMARY KEY (`cust_id`), ADD KEY `cust_name` (`cust_firstname`,`cust_surname`), ADD KEY `cust_email` (`cust_email`), ADD KEY `cust_newsletter` (`cust_newsletter`);
--
-- Indexes for table `tbOrders`
--
ALTER TABLE `tbOrders`
ADD PRIMARY KEY (`order_id`), ADD KEY `order_custid` (`order_custid`), ADD KEY `order_date` (`order_date`), ADD KEY `order_status` (`order_status`), ADD KEY `order_exported` (`order_exported`), ADD KEY `order_subid` (`order_subid`);
--
-- Indexes for table `tbPayments`
--
ALTER TABLE `tbPayments`
ADD PRIMARY KEY (`pay_id`);
--
-- AUTO_INCREMENT for dumped tables
--
--
-- AUTO_INCREMENT for table `tbCustomers`
--
ALTER TABLE `tbCustomers`
MODIFY `cust_id` int(11) NOT NULL AUTO_INCREMENT,AUTO_INCREMENT=18694;
--
-- AUTO_INCREMENT for table `tbOrders`
--
ALTER TABLE `tbOrders`
MODIFY `order_id` int(11) NOT NULL AUTO_INCREMENT,AUTO_INCREMENT=310575;
--
-- AUTO_INCREMENT for table `tbPayments`
--
ALTER TABLE `tbPayments`
MODIFY `pay_id` int(11) NOT NULL AUTO_INCREMENT,AUTO_INCREMENT=128264;
Also, apologies for the incorrect column name in the where clause - fixed now
Cheers!

I have assumed that pay_custid should be replaced with pay_date in the WHERE clause.
Rewrite the query to:
SELECT DISTINCT c.cust_id
FROM tbCustomers AS c
JOIN tbOrders AS o
ON o.order_custid = c.cust_id
JOIN tbPayments AS p
ON p.pay_custid = c.cust_id
WHERE (
o.order_date > CURDATE() - INTERVAL 14 DAY
AND o.order_status = 3
AND o.order_exported = 0
)
OR (
p.pay_date > CURDATE() - INTERVAL 14 DAY)
AND p.pay_exported = 0
)
This allows the optimizer to use an index on order_date and pay_date, if it is wrapped in the DATE function the indexes cannot be used.
I'd add indexes to both date fields, but a clever composite index on each table may be more flexible.
Even better may be:
SELECT DISTINCT c.cust_id
FROM tbCustomers AS c
LEFT JOIN tbOrders AS o
ON o.order_custid = c.cust_id
AND o.order_date > CURDATE() - INTERVAL 14 DAY
AND o.order_status = 3
AND o.order_exported = 0
LEFT JOIN tbPayments AS p
ON p.pay_custid = c.cust_id
AND p.pay_date > CURDATE() - INTERVAL 14 DAY)
AND p.pay_exported = 0
WHERE (o.order_cust_id IS NOT NULL OR p.pay_cust_id IS NOT NULL)

Related

Calculate Sum that cross 3 tables in MYSQL

I have 3 tables (trx_batch, trx_doc_trx_item).
I want user to enter id(trx_batch). Then the system will auto calculate the sum of trx_item which linked to trx_doc, and trx_doc is linked to trx_batch.
My table:
CREATE TABLE IF NOT EXISTS `trx_batch` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`batchDate` date NOT NULL,
`status` varchar(1) NOT NULL
PRIMARY KEY (`id`)
);
CREATE TABLE IF NOT EXISTS `trx_doc` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`idBatch` int(11) NOT NULL,
`subject` varchar(200) DEFAULT NULL,
`status` varchar(1) NOT NULL DEFAULT 'A',
PRIMARY KEY (`id`)
);
CREATE TABLE IF NOT EXISTS `trx_item` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`idDoc` int(11) NOT NULL,
`amount` decimal(14,4) NOT NULL,
`amtTax` decimal(12,4) DEFAULT NULL,
PRIMARY KEY (`id`)
);
Sample data that I have:
trx_batch
id: 1
trx_doc
id:1
idBatch: 1
trx_item:
id:1
idDoc:1
amount: 500
amtTax : 30
id:2
idDoc:1
amount:100
amtTax:20
If user enter 1 as id for trx_batch. The total should be 650.00.
How to write in mysql format? Please help me thanks!
SELECT
SUM( COALESCE( i.amount, 0 ) + COALESCE( i.amtTax, 0 ) ) AS SumAmountAndTax
FROM
trx_item AS i
INNER JOIN trx_doc AS d ON i.idDoc = doc.id
INNER JOIN trx_batch AS b ON d.idBatch = b.id
WHERE
b.id = :batchIdParameterValue

Between a MIN and MAX date in joined table

I have a table called Booking and a table called FacilityBooking. A booking is a composition of facility bookings, a one to many relation. The date and time of the booking is determined by the lowest start date, and the highest end date of the facility bookings that belongs to it.
I want to pull some statistics of how many private and how many business bookings there has been between two dates.
CREATE TABLE `Booking` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`comments` varchar(255) DEFAULT NULL,
`createdBy` varchar(255) DEFAULT NULL,
`customerName` varchar(255) DEFAULT NULL,
`email` varchar(255) DEFAULT NULL,
`isPaid` bit(1) DEFAULT NULL,
`isPrivateClient` bit(1) DEFAULT NULL,
`needsPermission` bit(1) DEFAULT NULL,
`phoneNumber` varchar(255) DEFAULT NULL,
`referenceNumber` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
# Dump of table FacilityBooking
# ------------------------------------------------------------
CREATE TABLE `FacilityBooking` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`comments` varchar(2000) DEFAULT NULL,
`from` datetime DEFAULT NULL,
`to` datetime DEFAULT NULL,
`bookablePlace_id` int(11) DEFAULT NULL,
`booking_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `FK_2tv9w7g5vyx9po8vs6ceogldb` (`bookablePlace_id`),
KEY `FK_n17h188ecbdos5lsva51b8j29` (`booking_id`),
CONSTRAINT `FK_n17h188ecbdos5lsva51b8j29` FOREIGN KEY (`booking_id`) REFERENCES `Booking` (`id`),
CONSTRAINT `FK_2tv9w7g5vyx9po8vs6ceogldb` FOREIGN KEY (`bookablePlace_id`) REFERENCES `BookablePlace` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
I have created an sqlfiddle: http://sqlfiddle.com/#!9/7ae95/2
And this is what i have so far:
SELECT
CASE isPrivateClient
WHEN 0 THEN "business"
WHEN 1 THEN "private"
END AS clientType,
count(isPrivateClient) as count
FROM
Booking
GROUP BY
isPrivateClient
So what i need from here is to join the facilitybookings and search between the lowest from date and the highest to date.
Hope someone can help me :)
Join the FacilityBooking table and filter using WHERE:
SELECT
CASE isPrivateClient
WHEN 0 THEN "business"
WHEN 1 THEN "private"
END AS clientType,
count(FacilityBooking.id) as count
FROM
Booking INNER JOIN FacilityBooking ON
Booking.id = FacilityBooking.booking_id
-- Between 2015-05-01 AND 2015-06-01 INCLUSSIVE'
WHERE FacilityBooking.from <= '2015-06-01' AND FacilityBooking.to >= '2015-05-01'
GROUP BY
isPrivateClient
fixed fiddle
If you want to only include "full" Booking in which all FacilityBooking are between 2 date, something like this should do the trick :
SELECT clientType, count(bookId)
FROM (
SELECT
b.id as bookId,
CASE b.isPrivateClient
WHEN 0 THEN "business"
WHEN 1 THEN "private"
END AS clientType,
Min(fb.from) as minFrom,
Max(fb.to) as maxTo
FROM
Booking b
INNER JOIN FacilityBooking fb ON b.id = fb.booking_id
GROUP BY bookId
) tbl
WHERE minFrom >= '2015-05-22' -- Min Date
AND maxTo <= '2015-05-24' -- Max Date
GROUP BY
clientType

MySQL Order by subquery column

I have problem with sql query. The idea is to select all loans that are after payment (status 1/2/3) between 8 and 21 days with calculated value from payment_day til now.
I have already done some query but can't use columns days_after_payment and days_after_part_payment in WHERE section. I would like to have one column like days_after_payment based on loan type.
SELECT l.*,
(SELECT SUM(`value`) FROM `loan_part` WHERE `loan_id` = l.id AND `paid`=0) AS left_to_pay,
-(DATEDIFF((SELECT date FROM `loan_part` WHERE `loan_id` = l.id AND `paid`=0 AND `date`<CURDATE() ORDER BY `date` LIMIT 1), NOW())) AS days_after_part_payment,
-(DATEDIFF(l.payment_date, NOW())) AS days_after_payment
FROM loan l
WHERE (l.type=1 or l.type=2) AND (l.status=1 OR l.status=2 OR l.status=3)
GROUP BY l.client_id
ORDER BY
CASE l.type
WHEN 1 THEN days_after_payment
WHEN 2 THEN days_after_part_payment
ELSE 1 END
ASC
CREATE TABLE IF NOT EXISTS `loan` (
`id` int(11) NOT NULL,
`value` int(11) NOT NULL,
`client_id` int(11) NOT NULL,
`status` int(11) NOT NULL,
`type` int(11) NOT NULL,
`payment_date` date DEFAULT NULL
) ENGINE=MyISAM AUTO_INCREMENT=2068 DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `loan_part` (
`id` int(10) unsigned NOT NULL,
`loan_id` int(11) NOT NULL,
`value` float NOT NULL,
`date` date DEFAULT NULL,
`paid` tinyint(1) NOT NULL DEFAULT '0'
) ENGINE=MyISAM AUTO_INCREMENT=1751 DEFAULT CHARSET=utf8;
Update1 : I had to cut unnecessary columns and rewrite it into English from my native language.
ORDER BY 7
"7" means the 7th field in the SELECT. That works for GROUP BY also. I had to see the table definition to count how many in l.*.
How come id is not declared AUTO_INCREMENT?

i get wrong results from mysql join query

I have 2 tables, that i want to join, one is rooms and another is reservations.
Basically I want to search for rooms which are not reserved (not in reservation table) and to get the details of those rooms (which are not in reservation table) from room table.
Here are my tables structure:
CREATE TABLE `room` (
`roomID` int(11) NOT NULL AUTO_INCREMENT,
`hotelID` int(11) NOT NULL,
`roomtypeID` int(11) NOT NULL,
`roomNumber` int(11) NOT NULL,
`roomName` varchar(255) NOT NULL,
`roomName_en` varchar(255) NOT NULL,
`roomDescription` text,
`roomDescription_en` text,
`roomSorder` int(11) NOT NULL,
`roomVisible` tinyint(4) NOT NULL,
PRIMARY KEY (`roomID`)
) ENGINE=InnoDB AUTO_INCREMENT=29 DEFAULT CHARSET=utf8;
CREATE TABLE `reservation` (
`reservationID` int(11) NOT NULL AUTO_INCREMENT,
`customerID` int(11) NOT NULL,
`hotelID` int(11) NOT NULL,
`reservationCreatedOn` datetime NOT NULL,
`reservationCreatedFromIp` varchar(255) CHARACTER SET greek NOT NULL,
`reservationNumberOfAdults` tinyint(4) NOT NULL,
`reservationNumberOfChildrens` tinyint(4) NOT NULL,
`reservationArrivalDate` date NOT NULL,
`reservationDepartureDate` date NOT NULL,
`reservationCustomerComment` text CHARACTER SET greek,
PRIMARY KEY (`reservationID`)
) ENGINE=InnoDB AUTO_INCREMENT=47 DEFAULT CHARSET=utf8;
CREATE TABLE `reservationroom` (
`reservationroomID` int(11) NOT NULL AUTO_INCREMENT,
`reservationID` int(11) NOT NULL,
`hotelID` int(11) NOT NULL,
`roomID` int(11) NOT NULL,
PRIMARY KEY (`reservationroomID`)
) ENGINE=InnoDB AUTO_INCREMENT=47 DEFAULT CHARSET=utf8;
Here is the query that I have right now, which gives me wrong results:
SELECT * FROM room r
LEFT JOIN reservation re
ON r.hotelID = re.hotelID
WHERE re.hotelID = 13
AND NOT
(re.reservationArrivalDate >= '2014-07-07' AND re.reservationDepartureDate <= '2014-07-13')
I also have created a fiddle, with the data from both tables included:
http://sqlfiddle.com/#!2/4bb9ea/1
Any help will be deeply appreciated
Regards, John
i agree that room number was missed,
but query template should looks like
SELECT
*
FROM
room r
LEFT JOIN reservation re
ON r.hotelID = re.hotelID
WHERE r.hotelID = 2
AND NOT (
re.hotelID IS NOT NULL
AND re.reservationArrivalDate >= '2014-07-07'
AND re.reservationDepartureDate <= '2014-09-23'
) ;
You need change table in where statement from reservation to room. Also you need add re.hotelID to where statement as well, because on where statement you need check that record is not null ans only after try to check dates
Given the newly-added reservationroom table, consider using a NOT EXISTS sub-query to find rooms without reservations:
SELECT
*
FROM
room r
WHERE NOT EXISTS
(SELECT
*
FROM
reservationroom rr
WHERE
rr.reservationroomID = r.roomID
)

my join returns 0 results

I have to following 3 tables: room, reservation and reservationroom
Their structure is as follows:
CREATE TABLE `room` (
`roomID` int(11) NOT NULL AUTO_INCREMENT,
`hotelID` int(11) NOT NULL,
`roomtypeID` int(11) NOT NULL,
`roomNumber` int(11) NOT NULL,
`roomName` varchar(255) NOT NULL,
`roomName_en` varchar(255) NOT NULL,
`roomDescription` text,
`roomDescription_en` text,
`roomSorder` int(11) NOT NULL,
`roomVisible` tinyint(4) NOT NULL,
PRIMARY KEY (`roomID`)
) ENGINE=InnoDB AUTO_INCREMENT=29 DEFAULT CHARSET=utf8;
CREATE TABLE `reservation` (
`reservationID` int(11) NOT NULL AUTO_INCREMENT,
`customerID` int(11) NOT NULL,
`hotelID` int(11) NOT NULL,
`reservationCreatedOn` datetime NOT NULL,
`reservationCreatedFromIp` varchar(255) CHARACTER SET greek NOT NULL,
`reservationNumberOfAdults` tinyint(4) NOT NULL,
`reservationNumberOfChildrens` tinyint(4) NOT NULL,
`reservationArrivalDate` date NOT NULL,
`reservationDepartureDate` date NOT NULL,
`reservationCustomerComment` text CHARACTER SET greek,
PRIMARY KEY (`reservationID`)
) ENGINE=InnoDB AUTO_INCREMENT=46 DEFAULT CHARSET=utf8;
CREATE TABLE `reservationroom` (
`reservationroomID` int(11) NOT NULL AUTO_INCREMENT,
`reservationID` int(11) NOT NULL,
`hotelID` int(11) NOT NULL,
`roomID` int(11) NOT NULL,
PRIMARY KEY (`reservationroomID`)
) ENGINE=InnoDB AUTO_INCREMENT=46 DEFAULT CHARSET=utf8;
(please note that foreign keys have been removed from create statements for sake of simplicity)
What I am trying to do: I want to get all rooms that are not reserved for specific dates, that is only the free rooms from the specific hotel (I have its ID)
Here is the query that I have right now:
SELECT r.* FROM room r
LEFT JOIN `reservationroom` rr
ON r.`hotelID` = rr.`hotelID`
AND r.`roomID` = rr.`roomID`
LEFT JOIN `reservation` re
ON rr.`reservationID` = re.`reservationID`
WHERE (rr.`reservationroomID` = ''
OR rr.`reservationroomID` IS NULL
AND re.`reservationArrivalDate` >= 2014-08-27
AND re.`reservationDepartureDate` <= 2014-08-29
AND r.`hotelID` = 10
AND r.`roomVisible` = 1);
This query now returns 0 results. It should return 9 records, since the hotel with ID = 10 has 9 rooms that are free (no resevations for specific dates exist in the reservation table)
Can anyone give me a hand with this please? I am trying to sort this out couple of hours, without any success.
You are using left join, so conditions on all but the first table should be in the on clauses. I think you want a query more like this:
SELECT r.*
FROM room r LEFT JOIN
`reservationroom` rr
ON r.`hotelID` = rr.`hotelID` AND
r.`roomID` = rr.`roomID` LEFT JOIN
`reservation` re
ON rr.`reservationID` = re.`reservationID` AND
re.`reservationArrivalDate` >= 2014-08-27 AND
re.`reservationDepartureDate` <= 2014-08-29
WHERE r.`hotelID` = 10 AND r.`roomVisible` = 1 AND re.reservationID is null;
I'm not sure what the comparison is to the empty string. It doesn't seem necessary for this purpose.