Struggling with logic for checking consecutive data entries - php

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.

Related

get rows by date where "index" stops on current ID

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.

Efficient way to track returning users?

I'm in the process of finding an efficient way to track returning users.
List of options I went over so far:
save (update) login count per user, per day/week/month
save (update) which users have logged in, in a Text field (bad choice?), per day/week/month
deduce returning users via other database resources (user-added records from several tables)
I think this last option is most efficient, since I won't need to create a separate logging table.
However, a logging table seems more accurate, period-wise, right?
Update:
Accuracy still has the priority for this, so I went with the first option, together with some logic in PHP.
I've opted to store the login counts per week (of the year), like: 201433.
Question: regarding the PHP code, is there a way to combine the two queries, and leave out the nested loop (efficiency)?
Database Table:
+----+--------+--------+-----------+
| id | userId | logins | year_week |
+----+--------+--------+-----------+
| 1 | 1 | 4 | 201432 |
| 2 | 1 | 3 | 201433 |
| 3 | 2 | 2 | 201433 |
+----+--------+--------+-----------+
Queries:
SELECT
userId,
SUM(logins) as total
FROM
User_Logins
GROUP BY
userId
ORDER BY
userId
-----------
// get first year_week
SELECT
userId,
year_week
FROM
User_Logins
GROUP BY
userId
ORDER BY
userId
Query results:
+--------+-------+
| userId | total |
+--------+-------+
| 1 | 7 |
| 2 | 2 |
+--------+-------+
+--------+-----------+
| userId | year_week |
+--------+-----------+
| 1 | 201432 |
| 2 | 201433 |
+--------+-----------+
PHP code:
$returningUsers = 0;
$userLogins = $this->model->firstQuery();
$userLoginWeeks = $this->model->secondQuery();
$today = new DateTime();
function weeksPassed($today, $year, $week)
{
$today->setISODate($today->format('Y'), $today->format('W'));
// year and week from user first login
$yearWeek = new DateTime();
$yearWeek->setISODate($year, $week);
$daysPassed = $today->diff($yearWeek)->days;
$weeksPassed = $daysPassed / 7;
return $weeksPassed;
}
if ($userLogins && $userLoginWeeks)
{
foreach ($userLoginWeeks as $userLoginWeek)
{
$userId = $userLoginWeek->userId;
$year_week = $userLoginWeek->year_week;
$year = (int) substr($year_week, 0, 4);
$week = (int) substr($year_week, 4);
$weeksPassed = weeksPassed($today, $year, $week);
$totalLogins = 0;
// Look up user logins from other query
foreach ($userLogins as $logins)
{
if ($logins->userId == $userId)
{
$totalLogins = $logins->total;
break;
}
}
$avgLogins = $totalLogins;
// Average calculated over weeks that have passed since first login
if ($weeksPassed > 0)
{
$avgLogins = $totalLogins / $weeksPassed;
}
// if average >= 1 per week => returning user
if ($avgLogins >= 1)
{
$returningUsers++;
}
}
}
return $returningUsers;

Replace zero with last valid value Mysql

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

how to add time/duration from database using php

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.

php - Finding the total in a column

I have a log` that saves log records (amount earned, etc) of employees and a code that separates the data into tables grouped under each employee id:
Empid: 0001
---------------------------
| Logid | Hours | Pay |
---------------------------
| 1001 | 10 | 50 |
---------------------------
| 1002 | 2 | 10 |
---------------------------
Empid: 0003
---------------------------
| Logid | Hours | Pay |
---------------------------
| 1003 | 3 | 9 |
---------------------------
| 1004 | 6 | 18 |
---------------------------
I managed this with the following semi-pseudocode:
$query = mysql_query("SELECT * FROM `log` ORDER BY empid");
$id = 0;
while ($list = mysql_fetch_assoc($query)) {
if ($id != $list['logid']) {
create header (Logid, Hours, Pay)
$id = $list['logid'];
}
add each data row for the empid
}
But now I would like to add the total of the Pay column and put it at the bottom of each table for each empid.
By putting the code $total_pay = $total_pay + $list['pay'] in the while loop I can get the total pay but I can't figure out how I might be able to show the total at the bottom.
Would really appreciate any advice on this!
This should do it. You basically sum up until the id is changing.
$sum = 0;
while ($list = mysql_fetch_assoc($query)) {
if ($id != $list['logid']) {
//create the totals using $sum !!!
// after that re-set sum to 0
$sum = 0;
//create header (Logid, Hours, Pay)
$id = $list['logid'];
}
$sum += $list['Pay'];
//add each data row for the empid
}
Also...
Please, don't use mysql_* functions in new code. They are no longer maintained and are officially deprecated. See the red box? Learn about prepared statements instead, and use PDO, or MySQLi - this article will help you decide which. If you choose PDO, here is a good tutorial.
There are two ways that you can do this.
PHP
Keep a running total of all of the "pay" values, and add it into your table at the bottom. For example:
$i=0;
while ($list = mysql_fetch_assoc($query)) { // for each row in your results
if ($id != $list['EmployeeId']) { // We only enter this loop if the EmployeeId doesn't equal $id. This can happen because either $id doesn't exist yet, or it doesn't match the previous EmployeeId
$i++; // increase $i by 1
if($i>1) { // Enter this loop only if $i is greater than or equal to 2 (if it is less than two, then this is our first time running this script, and adding a footer row wouldn't make any sense).
create footer (EmployeeId, Hours, Pay); // Log Id is irrelevant here
}
// reset your variables here
$id = $list['EmployeeId']; // set $id = the first or the new Employee ID
$total_pay = $list['pay']; // This is our first time for this Employee, so don't just add it to the running total
create header (EmployeeId, Hours, Pay) // Create the top half of your table
} else { // The EmployeeId has been established: we only need to change the running total
$total_pay = $total_pay + $list['pay'];
}
// add a data row for each LogId. This executes every time we go through the loop
create_normal_row(LogId, EmployeeId, Hours, Pay)
}
// At this point, both Employees have a header, and all data rows. However, we left the loop before we could add the last Employee's footer row
// Let's add one more footer row for the last user
create_footer (Logid, Hours, Pay);
SQL
MySQL has a function that does something very similar to what you are trying to do called ROLLUP. You can read more about it here:
http://dev.mysql.com/doc/refman/5.0/en/group-by-modifiers.html
Basically, you would change your query to work like this:
SELECT LogId, EmployeeId, SUM(Hours), SUM(Pay) FROM `log`
GROUP BY empid, logid WITH ROLLUP
This query will return a dataset that looks like this:
---------------------------------------
| Logid | EmployeeId| Hours | Pay |
---------------------------------------
| 1001 | 1 | 10 | 50 |
---------------------------------------
| 1002 | 1 | 2 | 10 |
---------------------------------------
| NULL | 1 | 12 | 60 |
---------------------------------------
| 1003 | 2 | 3 | 9 |
---------------------------------------
| 1004 | 2 | 6 | 18 |
---------------------------------------
| NULL | 2 | 9 | 27 |
---------------------------------------
| NULL | NULL | 21 | 87 |
---------------------------------------
Whenever $list['Logid'] is null, you know that you have a "total" row. Be careful though, this will add a "sum of all employees" row at the bottom of your dataset. If $list['EmployeeId'] is null, then you know you're in this "total" row.
On a related note (I'm not sure if this is what you're asking for), you can show this stuff in a table by using HTML <table> elements.
Each row would look like this:
<table> <!-- shown at the beginning of each table -->
<tr> <!-- shown at the beginning of each row -->
<td> <!-- shown at the beginning of each table cell -->
Your text goes here
</td> <!-- shown at the end of each table cell -->
<td>
More text can go here
</td>
</tr> <!-- shown at the end of each row -->
</table> <!-- shown at the end of each table -->
<tr>s can be repeated indefinitely within each <table>, and <td>s can be repeated within <tr>s.

Categories