1

I am stack trying to group my entities by datetime. every entity has the following columns: id, device, start, stop, ipaddress.

I want to be able to get entities within a range of date and group them by date(day) following with how many hours each days has.

This is what i have so far

public function findAllGroupedByDate($from = null, $to = null)
{
    $from = isset($from) && !is_null($from) ? $from : date('Y-m-d', time());
    $to = isset($to) && !is_null($to) ? $to : date('Y-m-d', time());

    $from = new \DateTime("-3 days");
    $to = new \DateTime("1 days");

    $from = date("Y-m-d", $from->getTimestamp());
    $to = date("Y-m-d", $to->getTimestamp());

    $q = $this->getEntityManager()->createQueryBuilder()
        ->select("timelog")
        ->from("AppBundle:Timelog", "timelog")
        ->where("timelog.start BETWEEN :from AND :to")
        ->setParameter("from", $from)
        ->setParameter("to", $to)
        ->orderBy("timelog.stop")
        ->getQuery();
    return $q->getResult();
}

public function findFromToday()
{
    $q = $this->createQueryBuilder('t')
            ->select('t.id', 't.start', 't.stop', 't.stop')
            ->where("t.start >= :today")
            ->setParameter("today", date('Y-m-d', time()))
            ->groupBy("t.id", "t.stop")
            ->orderBy("t.start", "asc")
            ->getQuery();

    return $q->getResult();
}

This is the code for my repository class.

and the code for from my controller looks like this:

$timelogsRepo = $this->getDoctrine()->getRepository('AppBundle:Timelog');

// Grab logs from today
$timelogsCollection = $timelogsRepo->findFromToday();
$tmpLogs = $timelogsRepo->findAllGroupedByDate();

// THIS SECTION IS FOR CALCULATING HOURS & MINUTES
    $minutes = 0;
    $hours = 0;
    foreach($timelogsCollection as $log)
    {
        $time1 = $log['start'];
        $time2 = $log['stop'];
        $interval = date_diff($time1, $time2);

        $hours += $interval->h;
        $minutes += $interval->i;

    }


    $minutes = $minutes >59 ? $minutes/60 : $minutes;


    return $this->render(':Timelog:index.html.twig', [
        'timelogs' => $logsOut,
        'hours' => $hours,
        'minutes' => $minutes
    ]);

So far i was able to calculate total spent time for a given day(only one day). Now i would like to get alle entities, group them by same date(day) and return data with interval.

Example DB table looks like this[id, device_id, start, stop, ipaddress]

1 1 2016-08-09 09:00:06 2016-08-09 12:00:06 127.0.0.1
2 1 2016-08-08 07:00:00 2016-08-08 13:00:00 127.0.0.1
3 1 2016-08-08 13:10:00 2016-08-08 15:05:00 127.0.0.1

So in this case my output would be something like:

[
    0 => ["date" => "2016-08-09", "hours" => 9.00, "ipaddress" =>      "127.0.0.1"],
    1 => ["date" => "2016-08-09", "hours" => 1.45, "ipaddress" => "127.0.0.1"]
]

the interval depends on start and stop both are type of DateTime

I have tried using doctrine-extensions: oro/doctrine-extensions but now i am getting exception error:

[2/2] QueryException: [Syntax Error] line 0, col 50: Error: Expected Unit is not valid for TIMESTAMPDIFF function. Supported units are: "MICROSECOND, SECOND, MINUTE, HOUR, DAY, WEEK, MONTH, QUARTER, YEAR", got 'stop'

My repository method looks like this:

public function findByToday()
{
    $fields = array(
        'DATE(stop) as stop',
        'TIME(SUM(TIMESTAMPDIFF(stop, start))) AS tdiff',
        'device_id',
        'ipaddress'
    );

    $q = $this->createQueryBuilder('t')
        ->select($fields)
        ->where('DATE(stop) = :today')
        ->setParameter('today', date('Y-m-d', time()))
        ->groupBy('device_id')
        ->getQuery();

    return $q->getResult();

}

my DB table:

id  device_id   start   stop    ipaddress
5   1   2016-08-09 09:00:06 2016-08-09 12:00:06 127.0.0.1
6   1   2016-08-08 07:00:00 2016-08-08 13:00:00 127.0.0.1
7   1   2016-08-08 13:10:00 2016-08-08 15:05:00 127.0.0.1

BTW i am using Symfony 3, could that be the problem?

Jan Burger
  • 43
  • 3

1 Answers1

0

Based on your table above (if I got it right) I would try to get the data directly with MYSQL and then work my way to Doctrine.

If you have the below table:

CREATE TABLE `testdevices` (
    `id` INT(11) NOT NULL AUTO_INCREMENT,
    `device` INT(11) NULL DEFAULT NULL,
    `dstart` DATETIME NULL DEFAULT NULL,
    `dend` DATETIME NULL DEFAULT NULL,
    `ip` TINYTEXT NULL,
    PRIMARY KEY (`id`)
);

And populated with below test data (I've created two devices the second one was conveniently active for exact 3 hours on today's date):

mysql> select * from testdevices;
+----+--------+---------------------+---------------------+-----------+
| id | device | dstart              | dend                | ip        |
+----+--------+---------------------+---------------------+-----------+
|  1 |      1 | 2016-08-09 09:00:06 | 2016-08-09 12:00:06 | 127.0.0.1 |
|  2 |      1 | 2016-08-08 07:00:00 | 2016-08-08 13:00:00 | 127.0.0.1 |
|  3 |      1 | 2016-08-08 13:10:00 | 2016-08-11 22:14:46 | 127.0.0.1 |
|  4 |      2 | 2016-08-11 13:00:00 | 2016-08-11 14:00:00 | 127.0.0.1 |
|  5 |      2 | 2016-08-11 15:00:00 | 2016-08-11 16:00:00 | 127.0.0.1 |
|  6 |      2 | 2016-08-11 17:00:00 | 2016-08-11 18:00:00 | 127.0.0.1 |
+----+--------+---------------------+---------------------+-----------+
6 rows in set (0.00 sec)

Following MYSQL query will then I believe output the data you want:

SELECT DATE(dend) as dend, TIME(SUM(TIMEDIFF(dend, dstart))) AS tdiff, device, ip 
FROM testdevices WHERE DATE(dend) = '2016-08-11' 
GROUP BY device ;

And the result would look like this:

+------------+----------+--------+-----------+
| dend       | tdiff    | device | ip        |
+------------+----------+--------+-----------+
| 2016-08-11 | 81:04:46 |      1 | 127.0.0.1 |
| 2016-08-11 | 03:00:00 |      2 | 127.0.0.1 |
+------------+----------+--------+-----------+
2 rows in set (0.01 sec)

Note that second device time is correct. Now, the remaining question is how to do that in Doctrine. As far as I know the TIMEDIFF function is not a part of Doctrine yet so I would propose 2 different approaches:

  1. Write your own function as in http://www.doctrine-project.org/2010/03/29/doctrine2-custom-dql-udfs.html#date-diff (There is example of DATEDIFF in this link so it is easily adjustable), or
  2. Use https://packagist.org/packages/oro/doctrine-extensions

Your final repository function in Doctrine/Symfony2 with oro/doctrine-extensions installed would look something like this:

 $fields = array(
            'DATE(dend) as dend', 
            'SUM(TIMESTAMPDIFF(SECOND, dend, dstart)) AS tdiff',
            'device', 
            'ip'
        );

public function findFromToday()
{
    $q = $this->createQueryBuilder('t')
            ->select($fields)
            ->where('DATE(dend) = :today')
            ->setParameter('today', date('Y-m-d', time()))
            ->groupBy('device')
            ->getQuery();

    return $q->getResult();

I wrote this function without testing it at all and from the top of my head. You will probably find that it fails. This would give the time difference in seconds as the TIMESTAMPDIFF requires units to be specified. Install oro/doctrine-extensions with

composer require oro/doctrine-extensions
  • Tomislav thank you for your response, i am going to try it out, will let you know how it went – Jan Burger Aug 12 '16 at 13:40
  • Hi i have tried it but still not working, now i am getting an expection error: [2/2] QueryException: [Syntax Error] line 0, col 50: Error: Expected Unit is not valid for TIMESTAMPDIFF function. Supported units are: "MICROSECOND, SECOND, MINUTE, HOUR, DAY, WEEK, MONTH, QUARTER, YEAR", got 'stop' – Jan Burger Aug 12 '16 at 14:46
  • I have added it above, it's below – Jan Burger Aug 12 '16 at 15:09
  • It's obvious, TIMESTAMPDIFF AND TIMEDIFF are not the same functions. You need to provide units to TIMESTAMPDIFF TIMESTAMPDIFF(unit,datetime_expr1,datetime_expr2) Example: SELECT TIMESTAMPDIFF(MONTH,'2003-02-01','2003-05-01'); See https://dev.mysql.com/doc/refman/5.5/en/date-and-time-functions.html#function_timestampdiff – Tomislav Darlic Aug 12 '16 at 15:15
  • @Jan Burger I've changed my original answer. This would give you answer in seconds. – Tomislav Darlic Aug 12 '16 at 15:21
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/120820/discussion-between-tomislav-darlic-and-jan-burger). – Tomislav Darlic Aug 12 '16 at 15:42
  • Now i am getting this query exception: QueryException: [Semantical Error] line 0, col 91 near 'device FROM AppBundle\Entity\Timelog': Error: Invalid PathExpression. Must be a StateFieldPathExpression – Jan Burger Aug 12 '16 at 19:08
  • Did u update your config.yml and registered ORM functions? Something's mixed up in your query. From the error line I suppose that you have a column named device in your query but there is none in your code above. Post the code somewhere it's hard to guess which part of query you didn't copy correctly. – Tomislav Darlic Aug 12 '16 at 20:00
  • Yes i did, copied from the docs. I have changed most of them with something like t.device, t.start, t.stop and so on. – Jan Burger Aug 12 '16 at 20:01
  • Cannot help u without seeing what you did with the code. Try this http://stackoverflow.com/questions/14216470/symfony2-and-doctrine-error-invalid-pathexpression-must-be-a-statefieldpathe – Tomislav Darlic Aug 12 '16 at 21:39
  • i have applied IDENTITY(t.device) but i get this error: An exception occurred while executing 'SELECT DATE(t0_.stop) AS sclr_0, TIME(SUM(TIMESTAMPDIFF(SECOND, t0_.stop, t0_.start))) AS sclr_1, t0_.device_id AS sclr_2 FROM timelog t0_ WHERE DATE(t0_.stop) = ? GROUP BY t0_.device_id' with params ["2016-08-13"]: SQLSTATE[42000]: Syntax error or access violation: 1055 Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'fuzztec.t0_.stop' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by – Jan Burger Aug 13 '16 at 14:02
  • Copy the code here and a sample data so we can look into it... This way it's just a guesswork – Tomislav Darlic Aug 22 '16 at 12:07