PHP mySQL Time Between Rows - php

Hello I am stuck on this. Looking to be able to pull the time between 2 start/stop rows from a mySQL database. My table looks like this
fillercdown_ndx | time | B3_4_5
1 | 2016-06-16 14:59:45 | 0
2 | 2016-06-16 15:03:11 | 1
Basically when its recorded as 0 the machine stopped and when the record is 1 the machine restarted. I need to be able to calculate the amount of time the machine was down between certain times like 8AM-5PM. Was going to use PHP to display it upon users time entered, but have no idea on the SQL command.
Anyone know the best way to be able to find this?
Create table
CREATE TABLE `fillercdown` (
`fillercdown_ndx` int(11) NOT NULL AUTO_INCREMENT,
`time` datetime DEFAULT NULL,
`B3_4_5` int(11) DEFAULT NULL,
PRIMARY KEY (`fillercdown_ndx`),
KEY `fillercdowntimendx` (`time`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=latin1
UPDATE:
There will be hundreds of these "start" and "stop" entries. My end all goal would be in php to give the user a small form asking them to provide a time range like 8AM-5PM and then be able to calculate all the time where the machine was "stopped" (which is when B3_4_5 is at 0). I just can't seem to figure out the right SQL call to get the time differences between each 0 and 1 and add them together between a set time range

This is what I am currently using to do the same thing it sounds like you're trying to do. I'm using PDO but this could be adapted fairly easily for mysqli if you need to use that. This depends on alternating off/on values by time. If there are any consecutive off or on rows for whatever reason, the expected result becomes ambiguous.
// First select everything in the requested range ordered by time.
$sql = "SELECT `time`, B3_4_5 FROM your_table WHERE `time` BETWEEN ? AND ? ORDER BY `time`";
$stmt = $pdo->prepare($sql);
$stmt->execute([$query_start, $query_end]);
// Initialize two DateTime objects (start and end) used to calculate the total time.
$total_start = new DateTime('00:00');
$total_end = clone $total_start;
$off = null;
while ($row = $stmt->fetchObject()) {
if ($row->B3_4_5) {
$on = new DateTime($row->time);
// increment total end time with difference between current off/on times
if ($off) {
$total_end->add($on->diff($off));
}
} else {
$off = new DateTime($row->time);
}
}
// total downtime is difference between start and end DateTimes
$total_downtime = $total_start->diff($total_end);
$total_downtime is a DateInterval object. You can get return a message using its format method:
echo $total_downtime->format('Total downtime: %h hours, %i minutes, %s seconds.');

This is the basic idea... It selects the result into a single row andcolumn, which you can then fetch withPHP`...
This solution assumes that stops & starts come in pairs, i.e: the ID of a start will be +1 that of a stop. Otherwise you should SELECT/JOIN the ID that is > that of the stopped one, and LIMIT it to 1.
A self join might not yield optimal performance wise, so be careful and measure execution times with some data to be on the safe side.
http://sqlfiddle.com/#!9/de72bf/1
CREATE TABLE fillercdown (
`fillercdown_ndx` int(11) NOT NULL AUTO_INCREMENT,
`time` datetime DEFAULT NULL,
`B3_4_5` int(11) DEFAULT NULL,
PRIMARY KEY (`fillercdown_ndx`),
KEY `fillercdowntimendx` (`time`)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=latin1;
INSERT INTO fillercdown
(`time`, `B3_4_5`)
VALUES
('2016-06-16 14:00:45', 0),
('2016-06-16 14:01:00', 1),
('2016-06-16 16:00:00', 0),
('2016-06-16 16:01:00', 1)
;
SELECT SUM(TIMEDIFF(g.`time`, f.`time`))
FROM fillercdown f
INNER JOIN fillercdown g
ON g.`fillercdown_ndx` = (f.`fillercdown_ndx` + 1)
AND g.`B3_4_5` = 1
AND TIME(g.`time`) BETWEEN '08:00:00' AND '17:00:00'
WHERE f.`B3_4_5` = 0
AND TIME(f.`time`) BETWEEN '08:00:00' AND '17:00:00'
If you want to include times where the machine stopped but has not yet restarted, you can do something like this:
http://sqlfiddle.com/#!9/d113b9/9
INSERT INTO fillercdown
(`time`, `B3_4_5`)
VALUES
('2016-06-16 14:00:45', 0),
('2016-06-16 14:01:00', 1),
('2016-06-16 16:00:00', 0),
('2016-06-16 16:01:00', 1),
('2016-06-16 16:02:00', 0)
;
SELECT SUM(TIMEDIFF(COALESCE(g.`time`, '2016-06-16 16:02:01'), f.`time`))
FROM fillercdown f
LEFT JOIN fillercdown g
ON g.`fillercdown_ndx` = (f.`fillercdown_ndx` + 1)
AND g.`B3_4_5` = 1
AND TIME(g.`time`) BETWEEN '08:00:00' AND '17:00:00'
WHERE TIME(f.`time`) BETWEEN '08:00:00' AND '17:00:00' AND f.`B3_4_5` = 0
You could replace 2016-06-16 16:02:01 with NOW() or something based on f.time, it depends on your application needs of course.
If you never want to get NULL but rather 0, if there are no matching rows, then do something like: COALESCE(SUM(...), 0).
If you prefer a scripted solution you can always do something like this:
SELECT f.`time`, f.`B3_4_5`
FROM fillercdown f
WHERE TIME(f.`time`) BETWEEN '08:00:00' AND '17:00:00'
And then compute the sum, like so (psuedocode):
stopped = null;
pairs = []
for ( row in rows )
if ( row.isStopped ) stopped = row
else
pairs += [stopped, row]
stopped = null
sum = 0
for ( pair in pairs )
sum += duration( pair[0], pair[1] )

Related

How to fix reading from till to date and time mysql?

On my testpage i have 2 bootstrap datetime pickers.
When the user selects a start date/time and a end date/time and push the button the screen will be filled by data from sql database.
But i have trouble reading from the right time, date is no problem.
The code what i have is this.:
$date=$_POST['q'];
$date=explode(' ',$date);
echo "Date".': '.$date[0]."<br/>";
echo "Time".': '.$date[1]."<br/>";
$date1=$_POST['q1'];
$date1=explode(' ',$date1);
echo "Date".': '.$date1[0]."<br/>";
echo "Time".': '.$date1[1]."<br/>";
$a1 = $_POST['a1'];
$q = $date[0];
$q1 = $date1[0];
$q2 = $date[1];
$q3 = $date1[1];
$query = mysqli_query($conn,"SELECT *
FROM `metingen`
WHERE (Datum >= '$q' AND Datum <= '$q1')
and (Tijd >= '$q2' AND Tijd <= '$q3')
ORDER BY Id DESC");
Its not pritty but witout the tijd(time) its works great, with tijd(time) i miss data.
The date and time i split, as you can see , because the database what i want to use is not made by me, so the date is a seperated column and also time a seperated and both are also text.
if i echo the explode date/time a see.:
Date: 15-02-2019
Time: 01:00:32
Date: 16-02-2019
Time: 22:55:33
But the data what i get is from 15-2-2019 00:30:56 and till 15-2-2019 22:11:08
I don't see all the dates i miss dates from the 16 and i get dates before my start time.
I think i have something wrong in my sql rule but , i don't know what.
can somebody help me please.
This is my create and insert code of my table (i shorted it in a bit).:
CREATE TABLE `metingen` (
`Id` int(11) NOT NULL,
`Datum` varchar(255) DEFAULT NULL,
`Tijd` varchar(255) DEFAULT NULL,
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
--
-- Gegevens worden geƫxporteerd voor tabel `metingen`
--
INSERT INTO `metingen` (`Id`, `Datum`, `Tijd`) VALUES
(1, '17-1-2019', '10:31:39'),
(4, '18-1-2019', '10:30:01'),
(40, '28-1-2019', '23:59:42'),
(41, '28-1-2019', '00:50:12'),
(42, '29-1-2019', '02:00:42'),
(49, '29-1-2019', '06:22:53'),
(56, '5-2-2019', '19:35:02'),
(236, '13-2-2019', '13:58:43')
ALTER TABLE `metingen`
ADD KEY `Id` (`Id`);
ALTER TABLE `metingen`
MODIFY `Id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=319;
You should not store date and time as separate fields in your database. You should just combine them into one datetime field in your database. You should then insert and compare such datetime values in one of the supported literal formats, such as YYYY-MM-DD H:m:s
Now back to your original situation. The problem is that you require the time condition also when the table's date is strictly between the two extreme dates (not equal to either of them). In that case there should be no constraint on the time part: all times would be OK on such days.
So here is how the SQL would look:
SELECT *
FROM metingen
WHERE ( Datum > '$q' OR ( Datum = '$q' AND Tijd >= '$q2' ) )
AND ( Datum < '$q1' OR ( Datum = '$q1' AND Tijd <= '$q3' ) )
ORDER BY Id DESC
But again, this is not the best practice.
If you have stored your dates as varchar (not as date), using the D-M-YYYY format, while your input is DD-MM-YYYY, then it becomes even more complex (and slow):
SELECT *
FROM metingen
WHERE ( STR_TO_DATE(Datum, '%d-%m-%Y') > STR_TO_DATE('$q', '%d-%m-%Y')
OR ( STR_TO_DATE(Datum, '%d-%m-%Y') = STR_TO_DATE('$q', '%d-%m-%Y')
AND Tijd >= '$q2' ) )
AND ( STR_TO_DATE(Datum, '%d-%m-%Y') < STR_TO_DATE('$q1', '%d-%m-%Y')
OR ( STR_TO_DATE(Datum, '%d-%m-%Y') = STR_TO_DATE('$q1', '%d-%m-%Y')
AND Tijd <= '$q3' ) )
ORDER BY Id DESC
Another issue is that you inject strings that are posted to the page, and so they are user-driven. This represents a SQL Injection vulnerabilty. Instead you should use prepared statements and pass the datetime strings as parameters.

INSERT ON DUPLICATE KEY UPDATE IF statements

I'm trying to create a script for stats about visitors to my site. To do this, I record the visitor's IP, along with the date of the day and the number of times it has passed.
If this is the first visit, on all records in the database. But I want to count 1 pass per person per day.
What I am trying to do : If the IP already exists, and the date is different from the day : we assign the date of the day, and increment the number of passing (+1).
The Problem : When the date is different from the day, it is changed, BUT: the number of passing continues to increment even if the IP has already been counted that day.
It should only be done the next day, when the date changes...
Here is my table structure :
--
-- Table structure for table `ChartsGuests`
--
CREATE TABLE `ChartsGuests` (
`IP_Guest` varchar(39) NOT NULL,
`Date` varchar(10) NOT NULL,
`Total` int(11) NOT NULL,
PRIMARY KEY (`IP_Guest`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB DEFAULT CHARSET=utf8;
Here is the code :
$IP_NewGuest = $_SERVER['REMOTE_ADDR'];
$Today = date('d/m/Y');
$SQL = "INSERT INTO `ChartsGuests` (`IP_Guest` , `Date`, `Total`) VALUES ('".$IP_NewGuest."' , '".$Today."', 1)
ON DUPLICATE KEY UPDATE
Date = IF(Date != '".$Today."', VALUES(Date), '".$Today."'),
Total = IF(Date != '$Today', VALUES(Total), Total + 1 )";
$REQ = $DB->prepare($SQL);
$REQ->execute() or die(var_dump($REQ->errorInfo()));
// echo $SQL;
It should only be done the next day, when the date changes... I do not know where the problem comes from, and this is the first time I use the "ON DUPLICATE KEY" with an "IF" ...
Thank you in advance !
Your problem is that your duplicate key is just on the IP address, but your table is really unique per IP Address/Date combo. As a result, visits on subsequent days overwrite the rows for the previous day.
If you change the logic of your table to have composite unique key on those two fields, the query will generate inserts for new (IP,Date) combos, and updates for (IP,date) combos that have been seen already.
If you fix that, you don't need the conditional (nor PHP for the current date), and you can just make this your SQL:
INSERT INTO `ChartsGuests` (`IP_Guest` , `Date`, `Total`)
VALUES ('".$IP_NewGuest."' , CURDATE(), 1)
ON DUPLICATE KEY UPDATE Total = Total + 1 )";

MySQL procedures - incrementally recalculate rows

I have a quite trivial task of calculating budget entries (income/outcome/balance). There can be thousands of entries and I can change any of them in the middle. As the result, all later entries balance must be recalculated.
Right now I am doing it in PHP by iterating through array of all entries, and updating rows that changed. It takes too much time that way - my server stops responding for several minutes.
I suppose that it happens because PHP calls MySQL for every entry update, though for PHP itself this task of array iteration and recalculation is pretty cheap. I think that there must be a way to throw this task at MySQL, so it does the iteration/recalculation/update itself, which might be cheap as well.
I am not an expert in MySQL at all, but I heard that there are stored procedures that might be the cure.
Here is my MySQL (5.5.33) table:
CREATE TABLE `entry` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`date` date DEFAULT NULL,
`is_income` tinyint(1) NOT NULL DEFAULT '0',
`income` decimal(20,2) DEFAULT NULL,
`outcome` decimal(20,2) DEFAULT NULL,
`balance` decimal(20,2) DEFAULT NULL,
PRIMARY KEY (`id`)
)
Here is my PHP (5.3.27):
//...
//$DB is a class for operating DB
$entries = $DB->get_all('entry'); //retrieves all entries from 'entry' table, sorted by date
$balance = 0;
foreach ($entries as $e) {
if ($e['is_income']) {
$balance += $e['income'];
} else {
$balance -= $e['outcome'];
}
if ($balance <> $e['balance']) {
$e1 = $e;
$e1['balance'] = $balance;
$DB->update('entry', $e1); //update the row by doing query('UPDATE `entry` ... WHERE id=' . $entry_id);
}
}
Can you point me the right direction? Thanks!
I think you can do this in a single SQL UPDATE query, no procedure needed.
UPDATE entry AS e1
JOIN (SELECT * FROM entry ORDER BY date) AS e2 ON e1.id = e2.id
CROSS JOIN (SELECT #balance := 0) AS var
SET e1.balance = (#balance := #balance + IF(e2.is_income, e2.income, -e2.outcome))
The user variable #balance serves the same purpose as the PHP variable $balance. The subquery is needed because MySQL doesn't allow use of ORDER BY in a multi-table UPDATE query, so you need to join with a subquery that produces the IDs in date order.
The "proper" way is to do the summation when displaying the report, and not store it in the table.
For only "thousands", it should not be a performance problem.

how to retrieve count from mysql between 2 hours

Consider the following table:
CREATE TABLE `trans` (
`transid` varchar(255) NOT NULL,
`affid` varchar(255) NOT NULL,
`timestamp` varchar(255) NOT NULL,
UNIQUE KEY `transid` (`transid`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
and PHP code:
case "today":
$sale=[];
for ($i = 0 ; $i < 23 ; $i++) {
$hours[]= $i;
$clicks[$i]=$api["clicks"][$i];
}
break;
I want to add into the $sale array today's count of sales for each hour. Something like: $hours[5] = select count(*) from trans where .. date is between today's 04:59:59 - 06:00:00.
First of all, if you are trying to use unix timestamps for your timestamp field, stop it now. You are already making your life painful. Use a MySQL timestamp field. There are honestly very few use cases where it is a good idea to use a unix timestamp field.
If you had a timestamp field you could do something like
SELECT HOUR(`timestamp`) AS `hour`, SUM(`transid`) AS `transaction_count`
FROM trans
WHERE `timestamp` LIKE CONCAT(DATE(NOW()),'%')
GROUP BY `hour`
ORDER BY `hour` ASC
That would give you the sales count for every hour of the current day. Make sure you add an index on timestamp for optimal query performance.
If you don't have a timestamp, then you need to utilize extra FROM_UNIXTIME() conversions, which would prevent you from being able to use an index on timestamp
Something like this:
<?php
$from=strtotime("".date("Y-m-d")." 04:59:59");
$to=strtotime("".date("Y-m-d")." 06:00:00");
?>
"SELECT * FROM `your_table`
WHERE `date` > '".$from."'
AND `date` < '".$to."'"
maybe?
You can use gmdate() if you need the current UTC time instead of the local time which you get with date().

Using Joins to Eliminate Multiple SELECTs in Loop

I'm trying to teach myself PHP/mysql by building a joe's goals clone, if you will.
Basically each user has multiple goals, and each day they record how many times a certain event occurred. For example, say my goal is to drink only 1 cup of coffee per day. If I had 3 today (oops!), I'd record 3 "checks" for today. I use a table called 'checks' to hold the check count for each day.
I have the following tables, and sample inserts:
CREATE TABLE `users` (
`user_id` int(5) NOT NULL AUTO_INCREMENT,
`user_email` varchar(50) NOT NULL,
`user_name` varchar(25) NOT NULL,
PRIMARY KEY (`user_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 ;
-- Dumping data for table `users`
INSERT INTO `users` VALUES (1, 'xxx#xxx.com', 'xxx');
INSERT INTO `users` VALUES (2, 'some#guy.com', 'SomeGuy');
CREATE TABLE `goal_settings` (
`goal_id` int(5) NOT NULL AUTO_INCREMENT,
`user_id` int(5) NOT NULL,
`goal_description` varchar(100) NOT NULL,
PRIMARY KEY (`goal_id`),
KEY `user_id` (`user_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
-- Dumping data for table `goal_settings`
INSERT INTO `goal_settings` VALUES (1, 1, 'Run 1k');
INSERT INTO `goal_settings` VALUES (2, 1, 'Read 20 pages');
INSERT INTO `goal_settings` VALUES (3, 2, 'Cups of Coffee');
CREATE TABLE `checks` (
`check_id` int(40) NOT NULL AUTO_INCREMENT,
`goal_id` int(5) NOT NULL,
`check_date` date NOT NULL,
`check_count` int(3) NOT NULL,
PRIMARY KEY (`check_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
-- Dumping data for table `checks`
INSERT INTO `checks` VALUES (6, 1, '2012-03-02', 3);
INSERT INTO `checks` VALUES (2, 1, '2012-03-01', 2);
INSERT INTO `checks` VALUES (3, 2, '2012-03-01', 1);
INSERT INTO `checks` VALUES (5, 1, '2012-02-29', 1);
The output I'd like has goal_ids as rows and a range of dates as columns (like a week view calendar).
goal_id | 2012-03-01 | 2012-03-02 | 2012-03-03 | ... 2012-03-08 |
--------------------------------------------------------------------
1 2 3 0 ... 0
2 1 0 0 ... 0
Please note that when no checks exist for a given goal on a given day, 0 is returned instead of NULL.
I was able to get it working, poorly, using PHP. Truncated code, but I hope it shows basically what I tried: [$goal_ids is an array holding all goals associated with a user. $num_days is the number of days (i.e. columns) to be displayed, and $goal_days is an array used to hold the days we're looking to get info for].
$mysqli = new mysqli('xxx','xxx','xxx','goals');
$stmt = $mysqli->stmt_init();
$stmt = $mysqli->prepare("SELECT checks.check_count AS check_count
FROM `checks` WHERE goal_id = ? AND check_date = ?");
for($i=0; $i<=$goal_count - 1; $i++){
echo '<tr id="'.$goalid.'">';
for($j=0; $j <=$num_days; $j++){
$checkdate = $goal_days[$j];
$goalid = (integer) $goal_ids[$i];
if (!$stmt->bind_param("ii", $goalid, $checkdate)) {
echo "Binding parameters failed: (" . $stmt->errno . ") " . $stmt->error;
}
if (!$stmt->execute()) {
echo "Execute failed: (" . $stmt->errno . ") " . $stmt->error;
}
$stmt->bind_result($check_count);
if($stmt->fetch()){
echo "<td>".$check_count."</td>";
}
else{
echo '<td>0</td>';
}
}
echo "</tr>";
}
echo "</table>";
$stmt->close();
This is obviously inefficient because for m goals and n days, it makes m x n select statements.
From reading, it seems like I'm basically trying to make a pivot table, but I've read that they are inefficient also, and I'm guessing what I'm doing is better handled by PHP than by doing a pivot table?
That leaves me with joins, which is what I think I'm asking for help with. I have considered creating a new column for every day, but I think it's not ideal. I'm open to totally changing the schema if necessary.
I hope I've been clear enough. Any help or pointers in the right direction would be greatly appreciated.
Thanks!
If I understand correctly your problem then I would suggest you should do a regular select in which each combination of goal_id and check_date will get a record in the result set, and then at the client side you will make a column for each check_date say by having an array of arrays and insert the checkcount in it.
This should at least be faster than having m x n select statements.
For more efficiency your sql can sort it by the goal_id and check_date, this will cause the records to be grouped together.
Here is an example of the sql statement:
SELECT check_date, goal_id, check_count FROM checks ORDER BY goal_id, check_date
Here is PHP sample code, assuming you have an array of arrays "$array_of_arrays" (initialized to zero to avoid the null problem) with the outer key being the goal_id and the inner key being the check_date:
while ($row = mysqli_fetch_result($result)){
$row_goal_id = $row["goal_id"];
$row_check_date = $row["check_date"];
$array_of_arrays[$row_goal_id][$row_check_date] = $row["check_count"];
}
And then you can use the array of arrays to do what you like, say for if you wish to output as an HTML table example then join the inner array with </td><td> and the outer array with </td></tr><tr><td>.
An example of how to create and initialize the "$array_of_arrays" array would be as follows (assuming you have an array $goals containing all the goals and an array $dates containing all the dates, if you don't know then in advance you can fetch them from the checks table by doing a SELECT DISTINCT)
$array_of_arrays = array();
foreach ($goals as $key=>$value){
$array_of_arrays[$value] = array();
foreach ($checks as $key1=>$value1){
$array_of_arrays[$value][$value1] = 0;
}
}
A similar approach can be used to generate the final HTML table as follows:
$final_array = array();
foreach ($array_of_arrays as $key=>$value){
$final_array[$key] = implode("</td><td>", $value);
}
$final_str = implode("</td></tr><tr><td>", $final_array);
$table_str = "<table><tr><td>" . $final_str . "</td></tr></table>";
Consider adding a table of days (or creating temporary one at runtime) holding just consecutive dates or dates you need. You could then get a nice list of check counts using a single query:
SELECT g.goal_id, d.day, COALESCE(c.check_count,0) as check_count
FROM
goal_settings g
JOIN
days d
LEFT JOIN
checks c
ON c.goal_id = g.goal_id AND c.check_date = d.day
WHERE
g.user_id = 1
AND d.day BETWEEN '2012-03-01' AND '2012-03-03'
ORDER BY g.goal_id, d.day
resulting in a rowset like:
goal_id | day | check_count
1 | 2012-03-01 | 2
1 | 2012-03-02 | 3
1 | 2012-03-03 | 0
2 | 2012-03-01 | 1
2 | 2012-03-02 | 0
2 | 2012-03-03 | 0
And then fetch those rows in a loop with php to build a nice html table - if goal_id changed then print new row and so on.

Categories