Question

In my Java Web application I use Postgresql and some data tables are filled automatically in server. In the database I have a STATUS table like below:

enter image description here

I want to select the data related to a vehicle between selected dates and where the vehicle stayed connected. Simply I want to select the data which are green in the above table which means I exactly want the data when firstly connected=true and the data when connected=false after the last connected=true. I tried to write a sql statement but I couldn't get the desired data.

What I did was:

select *from status where vehicleId='redcar' and 
date >= '2014-02-28 00:00:00' and date <= '2014-02-28 23:59:59' and ...

How could I get the requested data?

Was it helpful?

Solution 2

WITH cte AS
  ( SELECT statusId, vehicleId, connected, date,
           (connected <> LAG(connected) OVER (PARTITION BY vehicleId 
                                              ORDER BY date)
           ) AS status_changed
    FROM status 
    WHERE vehicleId = 'redcar' 
      AND date >= DATE '2014-02-28' 
      AND date <  DATE '2014-02-28' + interval '1 day'
  ) 
SELECT statusId, vehicleId, connected, date
FROM cte
WHERE status_changed 
   OR connected AND status_changed IS NULL 
ORDER BY date ;

Tested at SQL-Fiddle

OTHER TIPS

You can do this with a window function.

Something like this:

with status_changes as(
        select
             id,
             vehicleid,
             connected,
             connected != lag(connected)
                 over (partition by vehicleid
                       order by date asc) as has_changed
         from STATUS
)
select
    id,
    vehicleid, 
    connected,
    date
from status_changes
where has_changed = true
      and vehicle = 'redcar'
      and date between '2014-02-28 00:00:00' and '2014-02-28 23:59:59';
  • lag(status) will return the status that preceded it (within our partition).

  • partition by vehicleid (with an order by date) ensures that our lag() returns rows of the same vehicleid.

See the list of window functions for more on lag() and related functions.

You can do this using Gaps and Islands logic:

SELECT  VehicleID, Connected, MIN(Date) AS Date
FROM    (   SELECT  *,
                    DENSE_RANK() OVER(PARTITION BY VehicleID ORDER BY Date) - 
                        DENSE_RANK() OVER(PARTITION BY VehicleID, Connected ORDER BY Date) AS GroupingSet,
                    MIN(CASE WHEN Connected THEN Date END) OVER(PARTITION BY VehicleID) AS FirstConnected
            FROM    status
            WHERE   VehicleID = 'redcar'
            AND     date >= '2014-02-28 00:00:00' 
            AND     date < '2014-03-01 00:00:00'
        ) s
WHERE   Date > FirstConnected
GROUP BY VehicleID, Connected, GroupingSet

Example on SQL Fiddle

If you also need to retrieve the StatusID you will need to add a further ranking function and only select only the first row:

SELECT  StatusID,
        VehicleID, 
        Connected, 
        Date
FROM    (   SELECT  StatusID,
                    VehicleID, 
                    Connected, 
                    Date,
                    ROW_NUMBER() OVER(PARTITION BY VehicleID, Connected, GroupingSet ORDER BY Date) AS RowNumber
            FROM    (   SELECT  *,
                                DENSE_RANK() OVER(PARTITION BY VehicleID ORDER BY Date) - 
                                    DENSE_RANK() OVER(PARTITION BY VehicleID, Connected ORDER BY Date) AS GroupingSet,
                                MIN(CASE WHEN Connected THEN Date END) OVER(PARTITION BY VehicleID) AS FirstConnected
                        FROM    status
                        WHERE   VehicleID = 'redcar'
                        AND     date >= '2014-02-28 00:00:00' 
                        AND     date < '2014-03-01 00:00:00'
                    ) s
            WHERE   Date > FirstConnected
        ) s
WHERE   RowNumber = 1;

Example on SQL Fiddle

Or use DISTINCT ON:

SELECT  DISTINCT ON (VehicleID, Connected, GroupingSet) 
        StatusID, 
        VehicleID, 
        Connected, 
        Date
FROM    (   SELECT  *,
                    DENSE_RANK() OVER(PARTITION BY VehicleID ORDER BY Date) - 
                        DENSE_RANK() OVER(PARTITION BY VehicleID, Connected ORDER BY Date) AS GroupingSet,
                    MIN(CASE WHEN Connected THEN Date END) OVER(PARTITION BY VehicleID) AS FirstConnected
            FROM    status
            WHERE   VehicleID = 'redcar'
            AND     date >= '2014-02-28 00:00:00' 
            AND     date < '2014-03-01 00:00:00'
        ) s
WHERE   Date > FirstConnected
ORDER BY VehicleID, Connected, GroupingSet

Example on SQL Fiddle

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top