I have a list of business stored in a locations table, and stored in that table are hours the business opens and closes:
location
`mon_1_open`
`mon_1_closed`
`tue_1_open`
`tue_1_closed`
`wed_1_open`
`wed_1_closed`
ect...
I store the times in full hours and minutes, so say a business is open from 9:00AM to 5:30PM on monday.. mon_1_open = '900' AND mon_1_closed = '1730'.
I can't seem to figure out a way to find the day of week and output if the business is else open or closed based on the time of day.
Any suggestions?
This does not necessarily answer your question, but it may in the long run.
Your database scheme seems flawed. It definitely is not normalized. I would address that before it becomes a big issue, as you have noticed that it makes it hard to locate certain businesses hours. Here is a draft scheme that might be better suiting.
TABLE: locations
id INT AUTO_INCREMENT PRIMARY KEY
name VARCHAR(50)
TABLE: location_hours
id INT AUTO_INCREMENT PRIMARY KEY
location_id INT - Foreign Key references locations table
day CHAR(3) - (examples: mon, tue, wed, thu, fri, sat, sun)
hours VARCHAR(4) - (could also be int)
Then to get todays date, this can be done in MySQL with DATE_FORMAT %a, an example query:
SELECT locations.name, location_hours.hours
FROM locations
JOIN location_hours ON locations.id = location_hours.location_id
WHERE location_hours.day = DATE_FORMAT(NOW(), '%a')
AND location.name = 'Someway Business'
ORDER BY location_hours.hour
You should not need an open / close given that the the ORDER BY knows that 0900 < 1430 since it is a VARCHAR (although INT should know how to sort it as well), but your code when adding businesses will either need to update this record or you will need another field active to signify if that row should be used in the query. Just remember to use 24 hour time. Again this is a mock up, I just created it on the spot so it probably could use some improvements, but that would be better then doing a hack like you would have to with your current code.
UPDATE
Addressing the comment about finding if it is open or close:
Just use the PHP date function and call date('Hi') this will pull out the current time in 24-hour time, then you just do a simple if statement to see if it is between that, if it is, it is opened.
IE:
$sql = "SELECT locations.name, location_hours.hours
FROM locations
JOIN location_hours ON locations.id = location_hours.location_id
WHERE location_hours.day = DATE_FORMAT(NOW(), '%a')
AND location.name = 'Someway Business'
ORDER BY location_hours.hour";
$result = mysql_query($sql) or trigger_error("SQL Failed with Error: " . mysql_error());
$times = array();
while ($row = mysql_fetch_assoc($result)) {
if (empty($times['open'])) {
$times['open'] = $row['hours'];
}else {
$times['closed'] = $row['hours'];
}
}
$currentTime = date('Hi');
if ($times['open'] <= $currentTime
&& $times['closed'] > $currentTime) {
echo "Open";
}else {
echo "Closed";
}
Given that my logic is correct. Again, this is just pseudo code an example of usage. Given I just wrote it up on the spot. The above assumes you are only querying one business at a time.
$dayOfWeek = strtolower(date('D'));
$query = '
SELECT
location,
'.$dayOfWeek.'_1_open <= '.date('Gi').' AND
'.$dayOfWeek.'_1_closed >= '.date('Gi').' as is_open';
That should work.
However, you really should use a proper time datatype for the open/closed columns.
Related
I have run into a delayed processing time for a PHP program,
I have a MySQL record with over 1000 tables;
Each table is created once a new device is added, e.g assets_data_imeixx - to assets_data_imeixx1000th table
Each table contains about 45,000 rows of records inserted every 10 seconds,
Below is my PHP code to query the database and fetch all these records based on datetime.
Issue: The program executes without error but it takes about 1.3minutes to 4mins for very large records.
PHP Code:
$ms = mysqli connection string in config.php //$ms is OKAY
$user_id = '5';
$q = "SELECT * FROM `user_assets` WHERE `user`='".$user_id ."' ORDER BY `imei` ASC";
$r = mysqli_query($ms,$q);
$result = array(); //$result array to contain all data
while($row =mysqli_fetch_array($r)){
//fetch 7 days record
for ($i=1; $i < 7; $i++) {
$date = "-" . $i . " days";
$days_ago = date('Y-m-d', strtotime($date, strtotime('today')));
$sql1 = "SELECT * FROM assets_data_" . $row["imei"] . " WHERE dt_time LIKE '" . $days_ago . "%' LIMIT 1"; // its correct
//$result1 = $conn->query($sql1);
$result1 = mysqli_query($ms,$sql1);
$row2 = mysqli_fetch_array($result1);
echo $row['imei']." ".$row2['dt_server']."<br/>";
}
}
Above code fetches over 1000 devices from user_assets table, These IMEI each has its own table that contains over 45,000 records in each table of location data.
The for loop iterates over each IMEI table and records.
Above code runs without error but take so much time to complete, I want to find a solution to optimize and have code execute in a very short time max 5 seconds.
I need help and suggestions on optimizing and running this large scale of data and iteration.
(from Comment)
CREATE TABLE gs_object_data_863844052008346 (
dt_server datetime NOT NULL,
dt_tracker datetime NOT NULL,
lat double DEFAULT NULL,
lng double DEFAULT NULL,
altitude double DEFAULT NULL,
angle double DEFAULT NULL,
speed double...
(From Comment)
gs_object_data_072101424612
gs_object_data_072101425049
gs_object_data_072101425486
gs_object_data_072101445153
gs_object_data_111111111111111
gs_object_data_1234567894
gs_object_data_222222222222222
gs_object_data_2716325849
gs_object_data_2716345818
gs_object_data_30090515907
gs_object_data_3009072323
gs_object_data_3009073758
gs_object_data_352093088838221
gs_object_data_352093088839310
gs_object_data_352093088840045
gs_object_data_352121088128697
gs_object_data_352121088132681
gs_object_data_352621109438959
gs_object_data_352621109440203
gs_object_data_352625694095355
gs_object_data_352672102822186
gs_object_data_352672103490900
gs_object_data_352672103490975
gs_object_data_352672103490991
gs_object_data_352887074794052
gs_object_data_352887074794102
gs_object_data_352887074794193
gs_object_data_352887074794417
gs_object_data_352887074794425
gs_object_data_352887074794433
gs_object_data_352887074794441
gs_object_data_352887074794458
gs_object_data_352887074794474
gs_object_data_352887074813696
gs_object_data_352887074813712
gs_object_data_352887074813720
gs_object_data_352887074813753
gs_object_data_352887074813761
gs_object_data_352887074813803
900+ tables each having different location data.
Requirement: Loop through each table, fetch data for selected date range say:
"SELECT dt_server FROM gs_object_data_" . $row["imei"] . " WHERE dt_server BETWEEN '2022-02-05 00:00:00' AND '2022-02-12 00:00:00'";
Expected Result: Return result set containing data from each table containing information for the selected date range. That means having 1000 tables will have to be looped through each table and also fetch data in each table.
I agree with KIKO -- 1 table not 1000. But, if I understand the rest, there are really 2 or 3 main tables.
Looking at your PHP -- It is often inefficient to look up one list, then go into a loop to find more. The better way (perhaps 10 times as fast) is to have a single SELECT with a JOIN to do both selects at once.
Consider some variation of this MySQL syntax; it may avoid most of the PHP code relating to $days_ago:
CURDATE() - INTERVAL 3 DAY
After also merging the Selects, this gives you the rows for the last 7 days:
WHERE date >= CURDATE() - INTERVAL 7 DAY
(I did not understand the need for LIMIT 1; please explain.)
Yes, you can use DATETIME values as strings, but try not to. Usually DateTime functions are more efficient.
Consider "composite" indexes:
INDEX(imei, dt)
which will be very efficient for
WHERE imei = $imei
AND dt >= CURDATE() - INTERVAL 7 DAY
I would ponder ways to have less redundancy in the output; but that should mostly be done after fetching the raw data from the table(s).
Turn on the SlowLog with a low value of long_query_time; it will help you locate the worst query; then we can focus on it.
An IMEI is up to 17 characters, always digits? If you are not already using this, I suggest BIGINT since it will occupy only 8 bytes.
For further discussion, please provide SHOW CREATE TABLE for each of the main tables.
Since all those 1000 tables are the same it would make sense to put all that data into 1 table. Then partition that table on date, use proper indexes, and optimize the query.
See: Normalization of Database
Since you limit results to one user, and one row per device, it should be possible to execute a query in well below one second.
I have around 500,000 records of personal profile in MySQL database containing a birthdate column (dob). Since I had to get the ages for each profile, I needed to calculate it dynamically which I can either do via PHP (date_diff(date_create($dob), date_create('today'))->y) or through SQL ('SELECT TIMESTAMPDIFF(YEAR, dob, CURDATE()) AS age').
Which of the two is faster or more preferred especially if I have hundreds of thousands of rows?
In general, the best approach is to do such calculations on the server.
The ideal approach would be to use a generated column. This has been available since MySQL 5.7.5, and would be expressed as:
alter table t add age unsigned as
(TIMESTAMPDIFF(YEAR, dob, CURDATE()));
Alas, you can only use deterministic functions for generated columns. curdate() and now() are not deterministic, because their values can change with each call.
The next best thing is to use a view:
create view v_t as
select t.*,
TIMESTAMPDIFF(YEAR, dob, CURDATE())
from t;
Then, when you query the view, you'll have the age. This is true no matter where you query it. And it is the same logic everywhere.
The only caveat to doing the calculation on the server is that it uses server time, rather than local application time. If that is an issue, then that is a strong argument for doing the calculation locally.
Here is a test:
Create a table with 100K random dates
drop table if exists birthdays;
create table birthdays (
id int auto_increment primary key,
dob date
);
insert into birthdays (dob)
select '1950-01-01' + interval floor(rand(1)*68*365) day as dob
from information_schema.COLUMNS c1
, information_schema.COLUMNS c2
, information_schema.COLUMNS c3
limit 100000
;
Run this PHP script
<?php
header('Content-type: text/plain');
$db = new PDO("mysql:host=localhost;dbname=test", "test","");
### SQL
$starttime = microtime(true);
$stmt = $db->query("SELECT id, dob, TIMESTAMPDIFF(YEAR, dob, CURDATE()) AS age FROM birthdays");
$data = $stmt->fetchAll(PDO::FETCH_OBJ);
$runtime = microtime(true) - $starttime;
echo "SQL: $runtime \n";
### PHP
$starttime = microtime(true);
$stmt = $db->query("SELECT id, dob FROM birthdays");
$data = $stmt->fetchAll(PDO::FETCH_OBJ);
foreach ($data as $row) {
$row->age = date_diff(date_create($row->dob), date_create('today'))->y;
}
$runtime = microtime(true) - $starttime;
echo "PHP: $runtime \n";
Result:
SQL: 0.19094109535217
PHP: 1.203684091568
It looks like the SQL solution is 6 times faster. But that is not quite true. If we remove the code which calculates the age from both solutions, we will get something like 0.1653790473938. That means the overhead for SQL is 0.025 sec, while for PHP it is 1.038 sec. So SQL is 40 times faster in this test.
Note: There are faster ways to calculate the age in PHP. For example
$d = date('Y-m-d');
$row->age = substr($d, 0, 4) - substr($row->dob, 0, 4) - (substr($row->dob, 5) > substr($d, 5) ? 1 : 0);
is like four times faster - while date('Y-m-d') consumes more than 80% of the time. If you find a way to avoid any date function, you might get close to the performance of MySQL.
if you want get all 500,000 records you should do this in MySql because performance is better than PHP
but, if you want get some of that data (for example 10 records) , do that with PHP it's better to handle. and performance not different
I have an hoursheet import form where users submit the hours they worked per day. This data is send to a table called hours.
This table contains the columns:
user, day, hours, begintime, endtime
I want to send every employee an email when they didn't send in their worked hours per day.
What I've tried so far: create an array with the active users. I created an array from an sql query in php with all the hoursheets per user from 'yesterday'.
With array_diff_assoc I have the users which didn't send in their hoursheet for 'yesterday'.
So no problem here. But I'm looking for a more clean solution that can do it for several days in the past. Let's say the last month. Does anybody have a good suggestion to push me in the right direction?
Database is sql
Programming language php
Don't get me wrong, I don't need tons of code. I'm not lazy I want to try to look for this myself. I just need a push in the right direction.
Real-life example:
Table structure:
Hourimport table <----one to many <---- User table
|
one to many
|
Hourimport detail
Hourimport contains the dayreports unique record number, user, date, status, mail
Hourimport detail contains the details records of the dayreport. Multiple detail records per day.. For example one travel hour from 8 to 9 and working hours from 9 to 5. Breaks and miles driven. *not needed for this example because all the data I need is in the hourimport table.
users contains id, name, mail etc
echo '<br>STEP 1: <br>Connected successfully <br><br>';
$yesterdayT = date('Y-m-d', mktime(0, 0, 0, date("m"), date("d")-1, date("Y")));
$yesterday = date("Y-m-d H:i:s", strtotime($gisterenT));
$yesterday = "'$gisteren'";
//////////////////////////////////////////////////////////
$result = mysql_query("select id as employee from users inner join user_group on users.id=user_group.user_id where group_id=10 or group_id=11");
if (!$result) {
echo 'Could not run query: ' . mysql_error();
exit;
}
$rows = array();
while($rows[]=mysql_fetch_array($result));
echo "<br>A <--<pre>";
print_r ($rows);
echo "</pre><br><br>";
//////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////
$result1 = mysql_query("select employee from hourimport INNER JOIN hourimport_detail ON hourimport.id=hourimport_detail.parent_id WHERE hourimport.date =$yesterday");
if (!$result1) {
echo 'Could not run query: ' . mysql_error();
exit;
}
$rows1 = array();
while($rows1[]=mysql_fetch_array($result1));
echo "<br>B <--<pre>";
print_r ($rows1);
echo "</pre><br><br>";
//////////////////////////////////////////////////////////
$result3=array_diff_assoc($rows,$rows1);
echo "<br>Users that didnt send in their dayreport<pre>";
print_r ($result3);
echo "</pre><br><br>"
This is what I have till now. And this works till so far.
Hourimport
id int(11) auto_increment
date datetime
employee int(11)
mail int(11)
status text
Assuming you have a table users with a primary key id column or something (and the user column is a number referencing users.id), and the day format is something that can be easily compared (for example date), you can do something like this:
SELECT * FROM users WHERE id NOT IN (SELECT DISTINCT(user) FROM hours WHERE day > :limit)
:limit is then a placeholder for the minimum day to filter against (I'm a Java guy and that's the syntax for JPA, for PHP I think it's $1 but you'll figure it out ^^)
First, you need a table of all the users who submitted their hours. Then you need a table of ALL the users.
What you would then do, is do RIGHT OUTER JOIN on the two tables. The resultset would give you the users who haven't submitted their hours mapped to nulls.
i have a table where i enter entries for applicants. it has a column date_added where the type is timestamp and the default is current_timestamp. i want to make a weekly and monthly report but i can't decide what would be the best thing to do. i need an sql statement that return the number of entry like this..
switch($searchby){
case "monthly": $qry = "select * from tblapplicant where date_added > $month"; break;
case "weekly": $qry = "select * from tblapplicant where date_added > $week"; break;
}
$res = mysql_query($select);
$rec_count = mysql_num_rows($res);
echo "There are <font color='red' size='3'>".$rec_count."</font> matching records found.";
i know this is incorrect but this is all i can think at the moment.
another thing i want is, i want it to be the exact year and month report..
edit:
output should be:
There are 13 applicants for the whole month of June 2013. - or if weekly -
There are 3 applicants for the third quarter of July 2013.
I suggest the following approach. First, use your php code to determine the day that your search period begins, and the day after it ends. For example, if it's a monthtly report for September 2013, your start date would be 2013-09-01, and the end date would be 2013-10-01. Then you simply have one query.
where date_added >= $startdate
and date_added < $enddate
The major benefits of this approach are:
The time component of your timestamp field gets handled.
Your query will run reasonably quickly, especially if date_added is indexed.
It's easier to sort out programmnig logic with application code than with sql
You can write your query as a stored procedure to make it even faster.
This can be your sql statement
select * from tblapplicant where month(date_added) > $month
select * from tblapplicant where DAYOFWEEK(date_added) = X
You should replace X whith your weeek day 1 = Sunday, 2 = Monday, …, 7 = Saturday
I have a table in a database with the columns
d_monday, d_tuesday, d_wednesday, d_thursday, d_friday, d_saturday, d_sunday
The values for these are either 0 or Monday, Tuesday, Wednesday, Thursday etc depending on the day.
in my select statement I'm using
SELECT * from DEALS WHERE d_monday=DAYNAME(NOW()) OR d_tuesday=DAYNAME(NOW())
to find out whether the day name matches up with todays day, if it does then it returns results otherwise it doesn't display anything.
For some reason it's returning results when I haven't any value set for that specific day.
Any idea what could be the issue?
I would be tempted to pick the field using a PHP string
$day = "d_". date('l'); //lowercase L is full day name
$SQL = "SELECT $day FROM DEALS WHERE [...]";
I'm guessing a bit but would this not take you in the right direction?
Or something like this
$day = date('l'); //lowercase L is full day name
$SQL = "SELECT * from DEALS WHERE d_monday='$day' OR d_tuesday='$day' ";
It seems to me that you might want
$day = date('l'); //lowercase L is full day name
$SQL = "SELECT * from DEALS WHERE d_$day='$day'";
The format seems a bit messed up or maybe I just got the wrong end of the stick.
Hope that helps.
Consider removing your seven columns for the days of week and adding a separate table DealDays with a foreign key referencing Deals:
CREATE TABLE DealDays (
DealDayID int NOT NULL,
DealID int NOT NULL,
DayName varchar(10) NOT NULL, /* or, perhaps, enum ('Sunday', 'Monday', …) */
PRIMARY KEY (DealDayID),
FOREIGN KEY (DealID) REFERENCING Dels (DealID), /* or whatever is Deal PK's name */
UNIQUE (DealID, DayName) /* to make sure you can't add the same day twice per deal */
);
Then you would be able to find deals for the present day like this:
SELECT
d.DealID,
d.whatever,
…
FROM Deals d
INNER JOIN DealDays dd ON d.DealID = dd.DealID
WHERE dd.DayName = DAYNAME(NOW())
your query works fine. May be problem is in your data type of column(s) please check that.
My suggestion is that for that purpose you can use only one column so that your database is optimised.