سؤال

I am coming at SQL with plenty of imperative experience and trying to figure out how to do what feels like it needs a loop without a loop in SQL. I.e., what is the idiomatic way to not loop in SQL where you would absolutely loop in an imperative language?

I have a table in SQL (Server) something like this (assuming we select where the tag id is 999, and inc_indx is auto-incrementing)

inc_indx | State | Tag_Id
     400       5      999
     399       3      "
     397       0      "
     395      50      "
     392      39      "
...etc

Essentially, the state increases for a time, then unpredictably zeroes. I want to get the last value before each zero value in a given set.

To do this once, I would do this:

SELECT TOP 1 [State]
FROM [mytable]
WHERE [inc_indx] <
    (SELECT TOP 1 [inc_ndx]
    FROM [mytable]
    WHERE Tag_Id = 999
    AND State = 0)
AND Tag_Id = 999

To do this repeatedly, in imperative pythonish pseudocode and assuming some type of DB connection handling class, I would do this:

setOfZeroes = handler.query("SELECT [inc_ndx]
    FROM [mytable]
    WHERE Tag_Id = 999
    AND State = 0")

setOfMaxes = []
for index in setOfZeroes:
    max = handler.query("SELECT TOP 1 [State]
                  FROM [mytable]
                  WHERE [inc_indx] < "
                  + index + 
                  " AND Tag_Id = 999")
    setOfMaxes.add(max)

But, assuming I want to do this all in SQL and I don't want to loop, what is SQLese way to do this?

ANSWER:

Short version: CTEs and Joins

Long version (some of which is probably unnecessary):

-- narrow the scope, add a numbered row
WITH numbered AS (SELECT [State]
            ,inc_indx
            ,ROW_NUMBER() OVER (Order by inc_indx) As [row]
        FROM mytable
        WHERE Tag_Id = 999)
-- put the row numbers where state is zero in a second CTE
, zeroes AS (SELECT [row]
        FROM numbered
        WHERE State = 0)
SELECT [State], [inc_indx]
FROM numbered, zeroes
WHERE numbered.[row] = zeroes.[row] - 1
ORDER BY [inc_indx] desc
هل كانت مفيدة؟

المحلول

I usually break down the problem with CTE:s

WITH FirstZeroIndex AS (
    SELECT Tag_Id
          ,MIN(inc_indx) AS ZeroIdx
    FROM mytable
    WHERE State = 0
    GROUP BY Tag_Id
)
,LastNonZeroIndex AS (
    SELECT mytable.Tag_Id
          ,MAX(mytable.inc_indx) AS NonZeroIdx
    FROM mytable
         LEFT JOIN FirstZeroIndex
             ON myTable.Tag_Id = FirstZeroIndex.Tag_Id
    WHERE FirstZeroIndex.ZeroIdx IS NULL -- If we want tags without zeros
          OR mytable.inc_indx < FirstZeroIndex.ZeroIdx
    GROUP BY mytable.Tag_Id
)
SELECT * 
FROM LastNonZeroIndex
WHERE Tag_id = 999

نصائح أخرى

How about using apply? No doubt there are a few other ways, but this one works

select q1.*
from mytable as q1
cross apply (
    select top 1 *
    from mytable as q2
    where q2.Tag_Id = q1.Tag_Id
        and q2.State = 0
        and q2.inc_indx > q1.inc_indx
    order by q2.inc_indx desc
) as q2

This is the schema/data I used

CREATE TABLE mytable (
    inc_indx int,
    State int,
    Tag_Id int
)

insert into mytable
values
    (1, 5, 999), 
    (2, 3, 998), 
    (3, 0, 999), 
    (4, 50, 991), 
    (5, 39, 989), 
    (6, 0, 991)

Edit:

Ok from what I understand from your example and pseudo-code, is that a tag can have multiple states (including 0's) and you want to the states immediately preceding a 0 state for a specific tag? Like so?

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top