Item exclusions by date in MySQL - mysql

I have a very simple reservations system I have built for an RV Park. It only has 8 RV parking spaces in it. The issue I am having is it seems the available RV spots for reservation is spotty at best. Each spot has a check out time of 11am and a check in time of 2pm similar to a hotel.
Effectively a user hits the site, choose an arrival and departure date and clicks search. A list then shows up from an ajax query that shows all 8 RV spots and whether or not they are available for reservation. Apparently I have something effed up in my query that is not taking into consideration whether the arrival date falls into a range of dates for another reservation.
I have read a couple of other similar but not the same questions on SE and none of the proposed solutions fixed what I needed since I am not combining any other tables or keys. This is a simple 1 table system that is basically just a record of the registrations.
$sql = "SELECT id AS rid, sid, paid, arriveDate, departDate FROM saved_reservations
WHERE
arriveDate BETWEEN ".$db->quote($formattedArrival.' 14:00:00')." AND ".$db->quote($formattedDeparture.' 11:00:00')."
OR
departDate BETWEEN ".$db->quote($formattedArrival.' 14:00:00')." AND ".$db->quote($formattedDeparture.' 11:00:00')."
";
$sql .= " AND paid = 1 ORDER BY arriveDate ASC";
sid is the ID of the specific RV site. Just a numeric value 1-8.
I think where I am going wrong is my system is not picking up reservations from before the submitted arrival date but I am not 100% sure. Any ideas or suggestions for this query?
Here is my table structure:
CREATE TABLE `saved_reservations` (
`id` int(4) NOT NULL,
`sid` int(2) NOT NULL,
`arriveDate` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
`departDate` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
`paid` tinyint(1) NOT NULL DEFAULT '0',
`amount` int(4) NOT NULL,
`payment_date` int(15) NOT NULL,
`txn_id` varchar(100) NOT NULL,
`first_name` varchar(65) NOT NULL,
`last_name` varchar(65) NOT NULL,
`email` varchar(100) NOT NULL,
`phone` varchar(15) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Saved Reservations';

You query does not take into consideration a case when existing reservation is within desired reservation interval.
Anyway, here is my take on your problem, given user inputs are arriveDate and departDate the logic should be as follows.
# find unavailable sports for provided period
select sid from reservations R where arriveDate < R.departDate and departDate > R.arriveDate
# find available spots for provided period
select sid from reservations R where not (arriveDate < R.departDate and departDate > R.arriveDate)
SQL Fiddle

Related

Improve query speed suggestions

For self education I am developing an invoicing system for an electricity company. I have multiple time series tables, with different intervals. One table represents consumption, two others represent prices. A third price table should be still incorporated. Now I am running calculation queries, but the queries are slow. I would like to improve the query speed, especially since this is only the beginning calculations and the queries will only become more complicated. Also please note that this is my first database i created and exercises I have done. A simplified explanation is preferred. Thanks for any help provided.
I have indexed: DATE, PERIOD_FROM, PERIOD_UNTIL in each table. This speed up the process from 60 seconds to 5 seconds.
The structure of the tables is the following:
CREATE TABLE `apxprice` (
`APX_id` int(11) NOT NULL AUTO_INCREMENT,
`DATE` date DEFAULT NULL,
`PERIOD_FROM` time DEFAULT NULL,
`PERIOD_UNTIL` time DEFAULT NULL,
`PRICE` decimal(10,2) DEFAULT NULL,
PRIMARY KEY (`APX_id`)
) ENGINE=MyISAM AUTO_INCREMENT=28728 DEFAULT CHARSET=latin1
CREATE TABLE `imbalanceprice` (
`imbalanceprice_id` int(11) NOT NULL AUTO_INCREMENT,
`DATE` date DEFAULT NULL,
`PTU` tinyint(3) DEFAULT NULL,
`PERIOD_FROM` time DEFAULT NULL,
`PERIOD_UNTIL` time DEFAULT NULL,
`UPWARD_INCIDENT_RESERVE` tinyint(1) DEFAULT NULL,
`DOWNWARD_INCIDENT_RESERVE` tinyint(1) DEFAULT NULL,
`UPWARD_DISPATCH` decimal(10,2) DEFAULT NULL,
`DOWNWARD_DISPATCH` decimal(10,2) DEFAULT NULL,
`INCENTIVE_COMPONENT` decimal(10,2) DEFAULT NULL,
`TAKE_FROM_SYSTEM` decimal(10,2) DEFAULT NULL,
`FEED_INTO_SYSTEM` decimal(10,2) DEFAULT NULL,
`REGULATION_STATE` tinyint(1) DEFAULT NULL,
`HOUR` int(2) DEFAULT NULL,
PRIMARY KEY (`imbalanceprice_id`),
KEY `DATE` (`DATE`,`PERIOD_FROM`,`PERIOD_UNTIL`)
) ENGINE=MyISAM AUTO_INCREMENT=117427 DEFAULT CHARSET=latin
CREATE TABLE `powerload` (
`powerload_id` int(11) NOT NULL AUTO_INCREMENT,
`EAN` varchar(18) DEFAULT NULL,
`DATE` date DEFAULT NULL,
`PERIOD_FROM` time DEFAULT NULL,
`PERIOD_UNTIL` time DEFAULT NULL,
`POWERLOAD` int(11) DEFAULT NULL,
PRIMARY KEY (`powerload_id`)
) ENGINE=MyISAM AUTO_INCREMENT=61039 DEFAULT CHARSET=latin
Now when running this query:
SELECT i.DATE, i.PERIOD_FROM, i.TAKE_FROM_SYSTEM, i.FEED_INTO_SYSTEM,
a.PRICE, p.POWERLOAD, sum(a.PRICE * p.POWERLOAD)
FROM imbalanceprice i, apxprice a, powerload p
WHERE i.DATE = a.DATE
and i.DATE = p.DATE
AND i.PERIOD_FROM >= a.PERIOD_FROM
and i.PERIOD_FROM = p.PERIOD_FROM
AND i.PERIOD_FROM < a.PERIOD_UNTIL
AND i.DATE >= '2018-01-01'
AND i.DATE <= '2018-01-31'
group by i.DATE
I have run the query with explain and get the following result: Select_type, all simple partitions all null possible keys a,p = null i = DATE Key a,p = null i = DATE key_len a,p = null i = 8 ref a,p = null i = timeseries.a.DATE,timeseries.p.PERIOD_FROM rows a = 28727 p = 61038 i = 1 filtered a = 100 p = 10 i = 100 a extra: using where using temporary using filesort b extra: using where using join buffer (block nested loop) c extra: null
Preferably I run a more complicated query for a whole year and group by month for example with all price tables incorporated. However, this would be too slow. I have indexed: DATE, PERIOD_FROM, PERIOD_UNTIL in each table. The calculation result may not be changed, in this case quarter hourly consumption of two meters multiplied by hourly prices.
"Categorically speaking," the first thing you should look at is indexes.
Your clauses such as WHERE i.DATE = a.DATE ... are categorically known as INNER JOINs, and the SQL engine needs to have the ability to locate the matching rows "instantly." (That is to say, without looking through the entire table!)
FYI: Just like any index in real-life – here I would be talking about "library card catalogs" if we still had such a thing – indexes will assist both "equal to" and "less/greater than" queries. The index takes the computer directly to a particular point in the data, whether that's a "hit" or a "near miss."
Finally, the EXPLAIN verb is very useful: put that word in front of your query, and the SQL engine should "explain to you" exactly how it intends to carry out your query. (The SQL engine looks at the structure of the database to make that decision.) Although the EXPLAIN output is ... (heh) ... "not exactly standardized," it will help you to see if the computer thinks that it needs to do something very time-wasting in order to deliver your answer.

SELECT-Statement for displaying a temporary state (two tables)

I am sorry for the title, but I could not find the correct wording.
Situation: I got a schema with two tables:
check_result:
node_name
requirement_number
status
reason
source
acceptance
node_name
requirement_number
status
reason
valid_from
valid_until
acceptor (mail / name...)
So the idea is to show the status of a check consisting of several entries in the check_table, where existing valid acceptances "overlay" the result / status of the check_table. For the beginning, not all information are neccessary. The output should contain:
requirement_number | status | reason | source/acceptor
How could I achieve this?
Edit: Requested outputs
sec_ora_acceptance | CREATE TABLE `sec_ora_acceptance` (
`node_name` varchar(20) NOT NULL,
`instance_oracle_sid` varchar(20) NOT NULL,
`req_no` int(11) NOT NULL,
`status` enum('OK','NOK','OPEN','NA') NOT NULL,
`reason` text NOT NULL,
`acceptor` varchar(45) NOT NULL,
`acceptor_mail` varchar(50) DEFAULT NULL,
`date` date NOT NULL,
`valid_until` date DEFAULT '9999-12-31',
PRIMARY KEY (`node_name`,`instance_oracle_sid`,`req_no`)
)
sec_ora_result | CREATE TABLE `sec_ora_result` (
`check_id` int(11) NOT NULL,
`req_no` int(11) NOT NULL,
`status` enum('OK','NOK','OPEN','NA') NOT NULL COMMENT 'OK, NOK, OPEN, N(ot)A(pplicable)',
`reason` text,
PRIMARY KEY (`check_id`,`req_no`)
)
Edit#2: Requested Information (example and results)
I adjusted the columns in the sec_ora_result, to make it easier (no other tables needed for comparison - just the two tables)
table sec_ora_result:
check_id|req_no|status|reason|node_name|instance_oracle_sid|source
1|1|OPEN|Could not be tested automatically|abc|ora1|automatic_security_test
2|4|OK|Software Version is OK|abc|ora1|automatic_security_test
3|5|NOK|There is a Problem|abc|ora1|autotic_security_test
table sec_ora_acceptance:
node_name|instance_oracle_sid|req_no|status|reason|acceptor|acceptor_mail|date|valid_until
abc|ora1|1|OK|Manual proof|Markus|markus#email.com|2014-02-20|9999-12-31
The result should now consist of the following
req_no|status|reason|source
1|OK|Manual proof|Markus
4|OK|Software Version is OK|automatic_security_test
5|NOK|There is a Problem|automatic_security_test
Regards
Markus
EDIT:
As far as I understand, then you would need something like this...
SELECT SR.req_no,
ISNULL( SA.status, SR.status) as Status,
ISNULL( SA.reason, SR.reason) as Reason,
ISNULL( SA.acceptor, SR.source) as Source
FROM sec_ora_result SR
left join sec_ora_acceptance SA on SA.req_no = SR.req_no
Note that we could use ISNULL because status, reason and acceptor are NOT NULL, so if they are NULL means the whole row is NULL and then we can take the SR field, however, if any of those fields could be null, then you would need a regular CASE WHEN SA.req_no is null then Field1 else Field2 end.
Also, take a look to the key used for the left join, not sure if you are filtering only by req_no or you also need something else...

Optimize frequently ran query

I have added advertisements to my website which have quite some conditions to meet before delivering to a browsing user. Here's a detailed explanation:
These are the fields that require explaining:
start is by default '0000-00-00' and it indicates whether the ad has been yet paid or not. When an ad payment is accepted start is set to the day after, or any date the customer choses.
impresssions is respectively the remaining impressions of the advertisement
impressions_total and impressions_perday are self explanatory
and the other fields used in the query are just fields that validate whether the user falls into the specifications of the advertisement's auditory
An advertisement has to be paid to start displaying in the first place, however it can be set to start on a future date so the start value will be set but the ad shouldn't show up before it is time to. Then since customers can limit impressions per day I need to pick up only advertisements that have enough impressions for the day in progress. For example if an advertisement is started in 30/08/2013 with 10,000 impressions and 2,000 impressions per day then it shouldn't be able to show up today (31/08/2013) if it has less than 6,000 impressions because it's the second day of the campaign. As well as if the term period is say 5 days, and 5 days have passed, the advertisement has to be shown regardless of remaining impressions. Then there are those other comparisons to validate that the user is fit for this ad to display and the whole thing gets so complicated.
I am not quite good with mysql, although I have managed to construct a working query I am very concerned about optimizing it. I am most certain that the methods I have used are highly inefficient but I couldn't find a better way online. That's why I'm asking this question here, if anyone can help me improve the performance of this query?
SELECT `fields`,
FROM `ads`
WHERE (`impressions`>0 && `start`!='0000-00-00')
AND `start`<CURDATE() AND
(
`impressions`>(`impressions_total`-(CONVERT(CURDATE()-date(`start`), UNSIGNED)*`impressions_perday`))
OR (`impressions_total`/`impressions_perday` < CURDATE()-date(`start`))
-- this is the part where I validate the impressions for the day
-- and am most concerned that I haven't built correctly
)
AND
(
(
(YEAR(NOW())-YEAR("user's birthday") BETWEEN `ageMIN` AND `ageMax`)
AND (`sex`=2 OR `sex`="user's gender")
AND (`country`='' OR `country`="user's country")
) OR `applyToUnregistered` = 1
)
ORDER BY $random_order -- Generate random order pattern
Schema:
CREATE TABLE `ads` (  
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
 `headline` varchar(25) NOT NULL,
 `text` varchar(90) NOT NULL,
 `url` varchar(50) NOT NULL,
 `country` varchar(2) DEFAULT '0',
 `ageMIN` tinyint(2) unsigned NOT NULL,
 `ageMax` tinyint(2) unsigned NOT NULL,
 `sex` tinyint(1) unsigned NOT NULL DEFAULT '2',
 `applyToUnregistered` tinyint(1) unsigned NOT NULL DEFAULT '0',
 `creator` int(10) unsigned NOT NULL,
 `created` int(10) unsigned NOT NULL,
 `start` date NOT NULL,
 `impressions_total` int(10) unsigned NOT NULL,
 `impressions_perday` mediumint(8) unsigned NOT NULL,
 `impressions` int(10) unsigned NOT NULL,
 PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=27 DEFAULT CHARSET=utf8
You have a very complicated query from an optimization perspective.
The only indexes that can be used on the where clause are on ads(impressions) or ads(start). Because you use inequalities, you cannot combine them.
Can you modify the table structure to have an ImpressionsFlag? This would be 1 if there are any impressions and 0 otherwise. If so, then you can try an index on ads(ImpressionsFlag, Start).
If that helps with performance, the next step would be to break up the query into separate subqueries and bring them together using union all. The purpose is to design indexes to optimize the underlying queries.

database model for a rotating rounds system

Struggling with how to correctly model a database for a fairly simple app i'm putting together. App is just a simple thing i'm using to learn a few framework. Each week someone in the office has to take a turn bringing in beers. This is done on a rotation, we take it in turns following a specific order. Occasionally someone isn't around and they skip their turn but will have to take it the following week (or indeed the first subsequent week they can). Obviously we also have staff come and staff leave so this needs to be accounted for. Initially i'd modelled this in terms of drinkers and rounds. Each time someone bought a round they would be entered into the rounds table, whoever is first in the order with the least rounds is up next. This works perfectly until someone new joins as without inserting dummy data to cover all their "missing" rounds it will be their turn every time until they have caught up on the number of rounds everyone else has done.
This is currently what I have:
CREATE TABLE `rounds` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`drinker_id` int(11) NOT NULL,
`description` text COLLATE utf8_unicode_ci NOT NULL,
`created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
`updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
PRIMARY KEY (`id`)
)
CREATE TABLE `drinkers` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`email` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`id`)
)
Any ideas how I can re-model this in a way that will allow for people coming, and going, and people skipping their turn?
Thanks.
You only need one table like
CREATE TABLE `drinkers` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`email` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`is_available` TINYINT(1) NOT NULL DEFAULT 0,
`last_buy` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
PRIMARY KEY (`id`),
INDEX(`last_buy`)
)
and you can get the person who needs to buy drinks like this
SELECT * FROM drinkers
WHERE is_available
ORDER BY last_buy ASC
LIMIT 1
I would keep the drinkers (but add a flag if someone is sick - 0/1) and put rounds as this
CREATE TABLE `rounds` (
`drinker_id` int(11) NOT NULL,
`passed` int(11) NOT NULL,
`passed_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
UNIQUE KEY (`id`)
)
Then put in round a record for every user with his ID and 0 for passed, 0000-00-00 00:00:00 for passed at. When someone buys drinks mark that in the 'rounds' table, with making passed to passed + 1 and updating the date. If some of the drinkers leaves work, remove him from rounds. If a new one comes add him to rounds with passed equals to the max passed values (give them a fresh start :) ). If some one is sick, he obviously can not buy drinks so someone else should. To define who should buy drinks, run this
SELECT r.passed, r.drinker_id
FROM drinkers d
JOIN rounds r ON d.id = r.drinker_id
WHERE d.sick = 0
ORDER BY r.passed DESC, d.id ASC
This will give you the driker with least times buying a drink.
In rounds table you will have somethin like that
drinker id, passed, date_passed
====================================
1 0 0000-00-00
2 1 2013-03-01
3 0 0000-00-00

MySQL: Precision of a Datefield

since I have launched a podcast recently I wanted to analyse our Downloaddata. But some clients seem to send multiple requests. So I wanted to only count one request per IP and User-Agent every 15 Minutes. Best thing I could come up with is the following query, that counts one request per IP and User-Agent every hour. Any ideas how to solve that Problem in MySQL?
SELECT episode, podcast, DATE_FORMAT(date, '%d.%m.%Y %k') as blurry_date, useragent, ip FROM downloaddata GROUP BY ip, useragent
This is the table I've got
CREATE TABLE `downloaddata` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`date` datetime NOT NULL,
`podcast` varchar(255) DEFAULT NULL,
`episode` int(4) DEFAULT NULL,
`source` varchar(255) DEFAULT NULL,
`useragent` varchar(255) DEFAULT NULL,
`referer` varchar(255) DEFAULT NULL,
`filetype` varchar(15) DEFAULT NULL,
`ip` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=216 DEFAULT CHARSET=utf8;
Personally I'd recomend collecting every request, and then only taking one every 15 mins with a distict query, or perhaps counting the number every 15 mins.
If you are determined to throw data away so it can never be analysed though.
Quick and simple is to just the date and have an int column which is the 15 minute period,
Hour part of current time * 4 + Minute part / 4
DatePart functions are what you want to look up. Things is each time you want to record, you'll have to check if they have in the 15 minute period. Extra work, extra complexity and less / lower quality data...
MINUTE(date)/15 will give you the quarter hour (0-3). Ensure that along with the date is unique (or ensure UNIX_TIMESTAMP(date)/(15*60) is unique).