I have this data from data base.
+----+------------+----------+
| id | date time | duration |
+----+------------+----------+-----------
| 3 | 2012-12-20 09:28:53 | ? |
| 1 | 2012-12-20 19:44:10 | ? |
| 2 | 2012-12-23 16:25:15 | |
| 4 | 2012-12-23 18:26:16 | |
| 4 | 2012-12-24 08:01:27 | |
| 5 | 2012-12-29 20:57:33 | |
| 5 | 2012-12-29 20:57:33 | |
+----+------------+----------+------------
duration for id #1 should be equal to the date id #2 - id #1
duration for id #2 should be equal to the date id #3 - id #2
While if the id is the same, it will be added.
Sorry this is only in my mind, still very new in php so I don't know how to start.
Any help is appreciated.
Thanks
edited sorted by date. duration or total time for 1st record = 2nd record - 1st record
If I understand you correctly there is something which is called id=3 and it starts at "2012-12-20 09:28:53" then it ends at "2012-12-20 19:44:10" and in the same second something id=3 starts and you want to know how long everything lasts?
I would do a loop for all records, but I'd start from ending (in SQL: ...ORDER BY date_time DESC), assuming that end of the last (ie. id=5 which started on 2012-12-29 20:57:33) is now, then I calculate duration as substraction of dates (beginning and end), then I would take event beginning as end of the previous one and so on.
An example (not tested):
$end=now();
$dbh=new PDO(...); // here you need to connect to your db, see manual
while($record=$dbh->query('SELECT id, datetime FROM table_name ORDER BY datetime DESC')->fetchObject()){
$start=$record->datetime;
echo $duration=$end-$start; // convert $end and $start to timestamps, if necessary
$end=$start; // here I say that next (in sense of loop, in fact it is previous) record will end at the moment where this record started
}
This doesn't sum because I don't know how are you going to store your data, but I think you will manage with this.
EDITED
At the beginning I define an array:
$durations=array(); // this will hold durations
$ids=array(); // this will hold `id`-s
$last_id=-1; // the value that is not existent
Then the code follows and instead of echo I put this:
$duration=$end-$start;
if($last->id==$record->id){ // is this the same record as before?
$durations[count($durations)-1]->duration+=$duration; // if yes, add to previous value
}
else { // a new id
$durations[]=$duration; // add new duration to array of durations
$ids[]=$record->id; // add new id to array of ids
$last_id=$record->id; // update $last_id
}
and then $end=$start as above.
To view all durations and ids simply
for($i=0;$i<count($durations);$i++){
echo 'id='.$ids[$i].', duration='.$durations[$i].'<br />';
}
Note these tables are in reverse order.
Related
1- I am sorry for the title, I couldn't describe my complex situation better.
2- I have a table for a Double Accounting System where I am trying to calculate the balance at a specific date and until a specific transaction, and due to specific situations in the frond-end i need to get the result in a single query.
Table example is like that:
| id | date | amount |
| --- | ---------- | ------ |
| 93 | 2018-03-02 | -200 |
| 94 | 2018-01-23 | 250 |
| 108 | 2018-03-05 | 400 |
| 120 | 2018-01-23 | 720 |
| 155 | 2018-03-02 | -500 |
| 170 | 2018-03-02 | 100 |
And here is my simple query that I am using inside a loop of every transaction, because I want to show the new BALANCE after every transaction is made:
... for ...
Transactions::where('date', '<=', $item->date)->get()
... end ...
That query is returning the balance at the END of the day, means until the last transaction made that day, and I don't want this result.
Desired result is achieved by something like:
... for ...
Transactions::where('date', '<=', $item->date)
-> and transaction is < index of current $item
->get()
... end ...
Of course I can't use the ID because the ID is not related in this situation, as the whole ordering and calculation operations are date related.
So basically what i want is a query to get all the transactions from the age of stone until a specific date BUT exclude all the transactions made after the CURRENT one (in the loop).
For example, in the above table situation the query for:
Transaction ID # 93 should return: 93
Transaction ID # 94 should return: 94
Transaction ID # 108 should return: 94,120,93,155,170,108
Transaction ID # 120 should return: 94,120
Transaction ID # 155 should return: 94,120,155
..
...
....
The last transaction to get should be the current transaction.
I hope I could clear it well, I spend 3 days searching for a solution and I came up with this slow method:
$currentBalance = Transaction::where('date', '<=', $item->date)->get(['id']);
$array = array();
foreach ($currentBalance as $a) {
$array[] = $a->id;
}
$newBalanceA = array_slice($array, 0, array_search($item->id, $array) + 1);
$currentBalance = Transaction::whereIn('id', $newBalanceA)->sum('amount');
return $currentBalance;
It is slow and dirty, I appreciate saving me with a simple solution in 1 query if this is possible.
I'm trying to display first and second result from table, so that user can see both results on profil.php and the date from last result. I created a view vwresult that has these fields
id | ide | name | mark | date
---+-----+---------+------+-----------
1 | 1 | trickpd | 3 | 06.01.2018
1 | 2 | trickpd | 2 | 03.01.2018
5 | 3 | trickpd | 4 | 08.01.2018
5 | 4 | trickpd | 6 | 02.01.2018
That is my table with current result, insert new data in table will show more results in view table.
This is my code
$tst = mysqli_query($mysqli, "SELECT * FROM vwresult WHERE id=$ig AND name='trickpd' ORDER BY id, date desc");
$marks = [];
while($tstx = mysqli_fetch_assoc($tst)) {
$marks[] = $tstx['mark'];
}
After that I get stuck, and can't show the results.
The goal is:
to show mark 3 and 2 and date 06.01.2018 for id 1,
and when user opens details for id 5 to see results the will see 4, and 6 and also a 08.01.2018
Please help. Thank you all for your time.
Problem is with displaying the data.. I even create a two query one to show all the data and second with limit 1,1 but after that on script it's showing random data, not first and second result like I wont.. I'm trying to display last two results and the last date of inserting the data..
Consider the following table
+-------------+---------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+---------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| date | date | NO | | NULL | |
| sku | varchar(10) | | | NULL |
| impressions | int(11) | NO | | NULL | |
| sales | int(11) | NO | | NULL | |
+-------------+---------+------+-----+---------+----------------+
The table gets populated daily from a bulk download of the previous days sales records.
Each days download not only contains the previous days sales data but also all data from the last 90 days (possible 50k+ records).
However the data for previous days may change since the original insert due to matters outside our control, e.g.
Day 1.
Date: 2015-01-01
SKU: ABCD
Impressions: 100
Sales: 0
Day 2.
Date: 2015-01-01
SKU: ABCD
Impressions: 100
Sales: 3
Date: 2015-01-02
SKU: ABCD
Impressions: 105
Sales: 0
So for any given record from the data download it could be
a) Already seen and the same as before - ignore
b) New - add to database
c) Already seen but new data - Update
Arguably this could be trivially solved by checking each row as so
while (!$file->eof()) {
$row = $file->fgets();
$data = explode("\t", $row);
$sku = $data[0];
$date = $data[1];
$impressions = $data[2];
$sales = $data[3];
$order = $em->getRepository('Orders')->findOneBy(['sku' => $sku, 'date' => $date]);
if($order && $order->getImpressions() != $impressions && $order->getSales() != $sales) {
$order->setImpressions($impressions);
$order->setSales($sales);
} else {
... create new model
}
$em->persist($order);
}
However the rows which will have updated data will be minimal and doing a select for each and every row would mean this job would be incredibly slow due to sheer number of rows.
So my question is what patterns could be used to solve this problem as efficiently as possible?
Any ideas welcome
I would suggest you completely replace the previous 90 days' data with the newly downloaded data.
The reasoning is simple:
The processing time to do this will be trivial. 50,000 rows is tiny in database terms. I would probably do this even if it were a million rows.
Trying to replace only the changed rows is complicated and could introduce errors.
When you say "same as before" it seems like the keys are date and sku (combined) and sales and impressions are the fields that could be updated. If that's correct, then the most efficient way to do this in MySQL is to use INSERT ... ON DUPLICATE KEY UPDATE ... query:
Create a unique key on date and sku columns.
In your php script pre-parse all data from file (or do it in batches if you'd like).
Run a query similar to this (substitute actual data from parsed values in step 1):
INSERT INTO
mytable (`date`, sku, impressions, sales)
VALUES
('2015-01-01', 'ABCD', 100, 3),
('2015-01-02','ABCD', 100, 3),
...
ON DUPLICATE KEY UPDATE
impressions = VALUES(impressions),
sales = VALUES(sales)
A couple of notes:
check out the documentation for this syntax
if the next day's data update containing previous date record was supplementary, you could do sales = sales + VALUES(sales) but I don't think that's the case for you
i'm using mysql to store Kwh usage of my home. I get a fault rarely and get a 0 value to get stored. When i extract the values from my table i don't want to get that 0s but the last valid value before.
SELECT unix_timestamp(dataora) as time, kwhg
FROM misure
WHERE dataora BETWEEN '2013-10-08 00:00:00.000' AND '$data_scelta 23:59:59.997'
GROUP BY date(dataora),hour(dataora)
I used the above code to get the below table:
+-------+-----------+
| time | kwhg |
+-------+-----------+
| 9 | 2 |
| 10 | 3 |
| 11 | 0 |
| 12 | 4 |
| 13 | 0 |
+-------+-----------+`
I want to obtain
+--------+----------+
| time | kwhg |
+-------+-----------+
| 9 | 2 |
| 10 | 3 |
| 11 | 3 |
| 12 | 4 |
| 13 | 4 |
+-------+-----------+`
and remove the zero with the previus value.
Any tricks to do that?
You can use MySQL user-defined variables to return either the current row's value of kwhg if it's greater than zero, or else the variable defined on the previous row.
SELECT unix_timestamp(dataora) as time, #kwhg := IF(kwhg>0, kwhg, #kwhg) AS kwhg
FROM misure
WHERE dataora BETWEEN '2013-10-08 00:00:00.000' AND '$data_scelta 23:59:59.997'
GROUP BY date(dataora),hour(dataora)
Like #OddEssay's answer, this can't come up with a nonzero value if the first entry is zero. In that case, it will return whatever the current value of #kwhg is, which is probably NULL unless you've run the query before in the current session.
If it's a small result set, you could simply loop over it and created a fixed dataset with something like:
$fixedResults = array();
$lastGoodReading = 0;
foreach($results as $row){
if($row['kwhg']){
$fixedResults[$row['time']] = $row['kwhg'];
$lastGoodReading = $row['kwhg'];
} else {
$fixedResults[$row['time']] = $lastGoodReading;
}
}
Which will work if there is multiple failed readings in a row, but will still give zero if the first result fails.
You might also want to do something a bit more advanced, like checking both the previous result, and the next result and take an average of the two.
in mysql
UPDATE mytable
SET kwhg = (#n := COALESCE(number, #n))
ORDER BY time;
#n is a MySQL user variable
I'm trying to figure out how to handle a tricky little situation I've found myself in this morning. I have an entries table in my database where I store details about users' monthly entries (information capture stuff) - I want to increment the number (not the ID) of each entry once a month has passed. The idea is to use the "number" field to be able to identify consecutive monthly entries and to disregard entries within close proximity to one another.
When a user visits the site to start a new entry, I check the date of the last entry completed to see if it is more than 21 days ago (which qualifies as being a valid month) then I increment the "number" for this new entry. The problem is that I can end up with a sequence of entries which are all less than 21 days apart (and thus all have the same number), but collectively span more than 21 days! I need to be able to find some logic to handle this - anyone have any ideas?
An example of how this data is stored, and the problem I'm having, can be seen below.
+------+--------+------------+------------+----------------------------+
| id | number | initiated | updated | last_category_reached |
+------+--------+------------+------------+----------------------------+
| 4 | 1 | 1277914181 | 1277914320 | complete |
| 105 | 2 | 1282639343 | 1283444717 | complete |
| 397 | 3 | 1284999429 | 1285001298 | complete |
| 404 | 3 | 1287478550 | 1287478631 | complete |
| 636 | 3 | 1287479243 | 1287479377 | complete |
| 649 | 3 | 1287581361 | 1287581466 | complete |
| 652 | 3 | 1287585123 | 1287585365 | complete |
| 656 | 3 | 1290185205 | 1290424128 | complete |
| 1105 | 3 | 1292421193 | 1292426686 | complete |
| 1106 | 3 | 1292426769 | 1292426870 | complete |
+------+--------+------------+------------+----------------------------+
My php logic is below...
public function update_entry($stage = NULL)
{
// Get last number entered for this user
$last_entry = $this->last_entry();
// If part one, user profile is calling the update (passing the next stage as a param)
if ($stage === 'user/profile/2?s=p_prof&p=2')
{
// Only at this stage do we ever create a new entry
$entry = ORM::factory('data_entry');
// If no previous sessions, start from 1
if ($last_entry === FALSE)
$num = 1;
//Here we need to check the time period elapsed since the last submission
else
{
// Check if time difference between last visit and current time is less than 49 days and more than 21 days
if (($last_entry->initiated > time() - 4233600) && ($last_entry->initiated < time() - 1814400))
{
// Within allowed timeframe, ok to increment by one as a new entry
$num = $last_entry->number + 1;
}
// More than 49 days since last visit
elseif (($last_entry->initiated < time() - 4233600))
{
// Increment by two to break consecutive entries
$num = $last_entry->number + 2;
}
// Entry is within the last 21 days - if user never finished stages, use last entry created instead of creating a new one
else
{
// If they are back at the start having completed a full entry the last time, ok to create a new entry - otherwise use the one created the last time
if ($last_entry->last_category_reached !== 'complete')
$entry = $last_entry;
$num = $last_entry->number;
}
}
// Save the rest of the data for a new entry
$entry->number = $num;
$entry->initiated = time();
$entry->updated = time();
$entry->last_category_reached = $stage;
$entry->user_id = $this->id;
$entry->save();
}
// If it's been more than 49 days since last part completion of an entry, user won't be given option to finish the entry, so no need for time check here
elseif ($stage !== NULL)
{
// This must be a continuation of a form, not the beginning of a new one
// Just update the stage reached and save
$last_entry->last_category_reached = $stage;
$last_entry->updated = time();
$last_entry->save();
// Assign to $entry for return
$entry = $last_entry;
}
return $entry;
}
/**
* Returns the the last data entry session
* #return
*/
public function last_entry()
{
return $this
->limit(1)
->data_entries
->current();
}
What I would do in pseudo-code :
If there is a previous number, take the entry with max(number) and min(id).
Calculate the delay between the time of this entry and the current time.
If it is less than 21 days, I don't change numbers, if it's more, I change number.
If you apply this, you won't get periods that last more than 21 days.