0

So, I've got a view that is admittedly not well-indexed and there's not much I can do about it.

The view has data that looks a bit like the one in this question, but my problem is essentially the opposite of theirs and I'm not sure their solution will work here, though a similar TVF or CTE is probably in the forecast.

My data looks like this at the moment:

CustomPollerAssignmentId     DateTime                Status
[Some Id B]                    2013-11-18 08:54:00   IDLE
[Some Id A]                    2013-11-18 08:54:00   DORMANT
[Some Id B]                    2013-11-18 08:53:00   IDLE
[Some Id A]                    2013-11-18 08:53:00   NOMINAL

Unlike the other question, I need to see that the status hasn't changed. The view comprises three separate tables. One with minute statistics, one with fifteen minute statistics (for between three and six months ago), and one for hourly statistics (for up to a year ago).

The goal here is to check which modems have been idle for at least the last 10 minutes. We've got about 1200 active modems, so this could be up to 12000 rows, which is why I'd prefer not to do it with C#, but I'm still kind of new to SQL and set-based thinking. I'm currently working with an instance of SQL Server 2012, but it's very new here and I'm not really experienced with the newer windowed functions since we were on 2008R2 until about a month ago.

To be honest, I'm not even sure where to begin here because my OOP background wants me to just grab the TOP 10 statuses for each and loop through. If all 10 == idle || dormant, add to the result set, but I know there's got to be a better way to do it in SQL. Can someone point me in the right direction?

EDIT

To try to clarify a bit:

  1. I'm using T-SQL.

  2. This isn't as simple as a WHERE NOT EXISTS clause.

  3. Regardless of whether or not the status has changed, there should be an entry for the remote's status unless it has been deactivated. This means that it could have (idle, idle, idle, idle, idle, nominal, idle, idle, idle, idle) statuses for the last 10 minutes and that example is a case I would not want to include. The result set should include ONLY those remotes which have had statuses which are only idle or dormant for the last 10 minutes. If the last status is more than three months ago, it will only have one status for a fifteen minute interval.

Community
  • 1
  • 1
Drew
  • 272
  • 1
  • 3
  • 15
  • If I understand you properly, you just want a WHERE clause that checks if it's IDLE or DORMANT: WHERE [Status] = 'IDLE' OR [Status] = 'DORMANT' – Pierre Arlaud Nov 18 '13 at 14:05
  • Yes, but it has to have remained IDLE or DORMANT for at least the last 10 minutes. If it's in either of the older tables, it would be as simple as a WHERE clause, but for the more active stuff, it's not that easy. – Drew Nov 18 '13 at 14:07
  • 1
    Is the DateTime the last updated status date ? If so you merely need to check the difference of time with CURDATE() to see if it's bigger than 10 minutes. Check how to deal with time intervals in your SQL dialect. – Pierre Arlaud Nov 18 '13 at 14:11
  • No, it's not. It's part of a poller that continuously polls the status of the modem. Whether or not the status has changed, it will be updated once per minute unless the modem has been deleted or deactivated. – Drew Nov 18 '13 at 14:18
  • "Regardless of whether or not the status has changed, there should be an entry for the remote's status unless it has been deactivated." contradicts itself... – T I Nov 18 '13 at 14:48
  • How so? There are multiple statuses because this also handles any alarms and warnings on the modem. Deactivated means it's no longer being supported by us because they've stopped paying. There's an offline status that's different. Any modem currently being supported *should* have a status every minute, but sometimes the poller times out. This means that it could go from Nominal to Alarm to Warning in a three minute period or it could stay idle that whole time and it would say idle, idle, idle. – Drew Nov 18 '13 at 14:53

2 Answers2

1

If i understand correctly what you're asking:

SELECT [theView].CustomPollerAssignmentId
FROM
[theView]
LEFT JOIN
(
  SELECT CustomPollerAssignmentId, MAX([DateTime]) AS LastTime
  FROM [theView]
  WHERE [Status] <> 'IDLE' AND [DateTime] <= @Now
  GROUP BY CustomPollerAssignmentId
) AS NotIdleStatus ON
  [theView].CustomPollerAssignmentId = NotIdleStatus.CustomPollerAssignmentId
WHERE
  [theView].[DateTime] <= @Now AND
  [theView].[Status] = 'IDLE' AND
  (
    [theView].[DateTime] > NotIdleStatus.LastTime OR
    NotIdleStatus.LastTime IS NULL
  )
GROUP BY [theView].CustomPollerAssignmentId
  HAVING MIN([theView].[DateTime]) <= DATEADD(MINUTE, -10, @Now)    

The concept here is to

  1. For each ID, select the latest time for non-idle status up till the current time.
  2. For each ID, join idle status against the latest non-idle time.
  3. Group the status by ID.
  4. Filter for those that have been idle before 10 minutes ago.
  5. Also filter for those that are either idle later than its latest non-idle status or does not have non-idle status.

The following code selects the last non-idle time for each of IDs:

SELECT CustomPollerAssignmentId, MAX([DateTime]) AS LastTime
FROM [theView]
WHERE [Status] <> 'IDLE' AND [DateTime] <= @Now
GROUP BY CustomPollerAssignmentId

We are using LEFT JOIN so that any status that is IDLE but never in any other status is still captured. The ON clause joins them by ID.

The following code selects those that are idle and is before the current time:

WHERE
  [theView].[DateTime] <= @Now AND
  [theView].[Status] = 'IDLE' AND ...

The following code groups the records by ID and selects the ID that has the earliest time earlier than 10 minutes before the current time:

GROUP BY [theView].CustomPollerAssignmentId
  HAVING MIN([theView].[DateTime]) <= DATEADD(MINUTE, -10, @Now)    

Also, you will need to pass in the @Now value, which would be the current time that you want to check.

Fun Mun Pieng
  • 6,751
  • 3
  • 28
  • 30
  • 1
    You may need to change the `[Status] <> 'IDLE'` and `[theView].[Status] = 'IDLE'` to include whatever other statuses that you consider relevant. – Fun Mun Pieng Nov 18 '13 at 15:24
  • This is definitely producing the results I was looking for. It's still a bit slow, but your explanation was very clear and helped me figure out what I needed to do. Thank you! – Drew Nov 18 '13 at 16:07
  • 1
    actually, there is a bug in my code.. I have made correction to my answer.. basically, the bug causes records that have non-idle status after an idle status to be selected as well.. – Fun Mun Pieng Nov 20 '13 at 07:52
  • Yeah, I thought I saw it doing that. It was on the right track, though and the explanation was fantastic. I actually ended up realizing there was a table which actually stored how much usage there was and I could infer idle / dormant based on that. Thank you again, though! – Drew Nov 20 '13 at 13:43
0
SELECT v1.*
FROM (
    SELECT *
    FROM vdata
    WHERE OnlineStatus IN ('IDLE', 'DORMANT')
) v1
WHERE NOT EXISTS (
    SELECT 1
    FROM vdata v2
    WHERE v2.ModemId = v1.ModemId
    AND v2.MinuteMarker > v1.MinuteMarker 
    AND v2.MinuteMarker <= DATEADD(MINUTE, 10, v1.MinuteMarker )
    AND v2.OnlineStatus NOT IN ('IDLE', 'DORMANT')
)
T I
  • 9,785
  • 4
  • 29
  • 51
  • Intuitively, I didn't think this was what I was looking for, but I've been playing around with it since you answered, just to see. At the moment, it's taking about 20 seconds and even when modified to do sort of what I need it to, it's not what I'm looking for. It's got multiple sets of 200,000 row reads and giving me multiple rows per distinct modem since the statuses can look like "LAT/ETHDISC/TRI_STATE_IDLE/" or "UPSNR/DOWNSNR/ETHDISC/LOSTCONTACT/TRI_STATE_IDLE/". – Drew Nov 18 '13 at 15:06