Back

Explore Courses Blog Tutorials Interview Questions
0 votes
2 views
in BI by (17.6k points)
edited by

We are building a PowerBI reporting solution and I (well Stack) solved one problem and the business came up with a new reporting idea. Not sure of the best way to approach it as I know very little about PowerBI and the business seems to want quite complex reports.

We have two sequences of events from separate data sources. They both contain independent events occurring to vehicles. One describes what location a vehicle is within - the other describes incident events which have a reason code for the incident. The business wants to report on time spent in each location for each reason. Vehicles can change location totally independent of the incident events occurring - and events actually are DateTime and occur at random points throughout the day. Each type of event has a startime/endtime and a vehicleID.

Vehicle Location Events

+---------------+-----------+------------+-----------------+----------------+

| LocationDetailID | VehicleID | LocationID |  StartDateTime  |  EndDateTime |

+---------------+-----------+------------+-----------------+----------------+

|                1 |         1 |          1 |        2012-1-1 |      2016-1-1  |

|                2 |         1 |          2 |        2016-1-1 |      2016-4-1  |

|                3 |         1 |          1 |        2016-4-1 |      2016-11-1 |

|                4 |         2 |          1 |        2011-1-1 |      2016-11-1 |

+---------------+-----------+------------+-----------------+----------------+

Vehicle Status Events

+---------+---------------+-------------+-----------+--------------+

| EventID | StartDateTime | EndDateTime | VehicleID | ReasonCodeID |

+---------+---------------+-------------+-----------+--------------+

|       1 | 2012-1-1      | 2013-1-1    |         1 |            1 |

|       2 | 2013-1-1      | 2015-1-1    |         1 |            3 |

|       3 | 2015-1-1      | 2016-5-1    |         1 |            4 |

|       4 | 2016-5-1      | 2016-11-1   |         1 |            2 |

|       5 | 2015-9-1      | 2016-2-1    |         2 |            1 |

+---------+---------------+-------------+-----------+--------------+

Is there any way I can correlate the two streams together and calculate total time per Vehicle per ReasonCode per location? This would seem to require me to be able to relate the two events - so a change of location may occur partway through a given ReasonCode.

Calculation Example ReasonCodeID 4

  • VehicleID 1 is in location ID 1 from 2012-1-1 to 2016-1-1 and 2016-4-1 to 2016-11-1
  • VehicleID 1 is in location ID 2 from 2016-1-1 to 2016-4-1
  • VehcileID 1 has ReasonCodeID 4 from 2015-1-1 to 2016-5-1

Therefore first Period in location 1 intersects with 365 days of ReasonCodeID 4 (2015-1-1 to 2016-1-1). 2nd period in location 1 intersects with 30 days (2016-4-1 to 2016-5-1). In location 2 intersects with 91 days of ReasonCodeID 4(2016-1-1 to 2016-4-1

Desired output would be the below.

+-----------+--------------+------------+------------+

| VehicleID | ReasonCodeID | LocationID | Total Days |

+-----------+--------------+------------+------------+

|         1 |            1 |          1 |        366 |

|         1 |            3 |          1 |        730 |

|         1 |            4 |          1 |        395 |

|         1 |            4 |          2 |         91 |

|         1 |            2 |          1 |        184 |

|         2 |            1 |          1 |        154 |

+-----------+--------------+------------+------------+

I have created a SQL fiddle that shows the structure here

Vehicles have related tables and I'm sure the business will want them grouped by vehicle class etc but if I can understand how to calculate the intersection points in this case that would give me the basis for the rest of reporting. 

1 Answer

0 votes
by (47.2k points)
  • It requires a CROSS JOIN implementation. Many to Many is the relationship between both the tables which implies the creation of a third table that bridges LocationEvents and VehicleStatusEvents tables so I think specifying the relationship in the expression could be easier.

  • I have used CROSS JOIN between both tables, then filtered the results only to get those rows which VehicleID columns are the same in both tables. I am also filtering the rows that VehicleStatusEvents range dates intersects LocationEvents range dates.

  • I have added a column to calculate the count of days between each intersection after filtering. Finally, the measure sums up the days for each VehicleID, ReasonCodeID and LocationID.

  • In order to implement the CROSS JOIN you will have to rename the VehicleID, StartDateTime and EndDateTime on any of both tables. It is necessary for avoiding ambigous column names errors.

  • I have renamed the columns as follows:

VehicleID : LocationVehicleID and StatusVehicleID 

StartDateTime : LocationStartDateTime and StatusStartDateTime 

EndDateTime : LocationEndDateTime and StatusEndDateTime 

  • After this you can use CROSSJOIN in the Total Days measure:

Total Days =

SUMX (

    FILTER (

        ADDCOLUMNS (

            FILTER (

                CROSSJOIN ( LocationEvents, VehicleStatusEvents ),

                LocationEvents[LocationVehicleID] = VehicleStatusEvents[StatusVehicleID]

                    && LocationEvents[LocationStartDateTime] <= VehicleStatusEvents[StatusEndDateTime]

                    && LocationEvents[LocationEndDateTime] >= VehicleStatusEvents[StatusStartDateTime]

            ),

            "CountOfDays", IF (

                [LocationStartDateTime] <= [StatusStartDateTime]

                    && [LocationEndDateTime] >= [StatusEndDateTime],

                DATEDIFF ( [StatusStartDateTime], [StatusEndDateTime], DAY ),

                IF (

                    [LocationStartDateTime] > [StatusStartDateTime]

                        && [LocationEndDateTime] >= [StatusEndDateTime],

                    DATEDIFF ( [LocationStartDateTime], [StatusEndDateTime], DAY ),

                    IF (

                        [LocationStartDateTime] <= [StatusStartDateTime]

                            && [LocationEndDateTime] <= [StatusEndDateTime],

                        DATEDIFF ( [StatusStartDateTime], [LocationEndDateTime], DAY ),

                        IF (

                            [LocationStartDateTime] >= [StatusStartDateTime]

                                && [LocationEndDateTime] <= [StatusEndDateTime],

                            DATEDIFF ( [LocationStartDateTime], [LocationEndDateTime], DAY ),

                            BLANK ()

                        )

                    )

                )

            )

        ),

        LocationEvents[LocationID] = [LocationID]

            && VehicleStatusEvents[ReasonCodeID] = [ReasonCodeID]

    ),

    [CountOfDays]

)

  • You can build a matrix (or any other visualization) using this measure in Power BI:

image

  • If you don't understand completely the measure expression, here is the T-SQL translation:

SELECT

    dt.VehicleID,

    dt.ReasonCodeID,

    dt.LocationID,

    SUM(dt.Diff) [Total Days]

FROM 

(

    SELECT

        CASE

            WHEN a.StartDateTime <= b.StartDateTime AND a.EndDateTime >= b.EndDateTime  -- Inside range

               THEN DATEDIFF(DAY, b.StartDateTime, b.EndDateTime)

            WHEN a.StartDateTime > b.StartDateTime AND a.EndDateTime >= b.EndDateTime  -- |-----|*****|....|

               THEN DATEDIFF(DAY, a.StartDateTime, b.EndDateTime)

            WHEN a.StartDateTime <= b.StartDateTime AND a.EndDateTime <= b.EndDateTime  -- |...|****|-----|

               THEN DATEDIFF(DAY, b.StartDateTime, a.EndDateTime)

            WHEN a.StartDateTime >= b.StartDateTime AND a.EndDateTime <= b.EndDateTime  -- |---|****|-----

               THEN DATEDIFF(DAY, a.StartDateTime, a.EndDateTime)

        END Diff,

        a.VehicleID,

        b.ReasonCodeID,

        a.LocationID --a.StartDateTime, a.EndDateTime, b.StartDateTime, b.EndDateTime

    FROM LocationEvents a

        CROSS JOIN VehicleStatusEvents b

    WHERE a.VehicleID = b.VehicleID

        AND 

        (

            (a.StartDateTime <= b.EndDateTime)

                AND (a.EndDateTime >= b.StartDateTime)

        )

) dt

GROUP BY dt.VehicleID,

         dt.ReasonCodeID,

         dt.LocationID


Note: In T-SQL you could use an INNER JOIN operator

Related questions

0 votes
1 answer
0 votes
1 answer
0 votes
1 answer

Browse Categories

...