tags:

views:

153

answers:

6

I have 1 table with data thus:

Col1      Col2  
------- --------  
Admin001  A  
Admin001  B  
Admin002  C   
Admin002  C  
Admin003  A  
Admin003  C

I need to find all instances of Col2 values with 'A' immediately followed by 'B'. 'A' followed by any other symbol does not count. Is there a way to use SQL to accomplish this?
Environment is DB2 LUW v9.5

Update: How can I do this if I make the table like below?

Col1    Col2      Col3  
----    ------- --------  
1      Admin001     A  
2      Admin002     C   
3      Admin002     C  
4      Admin003     A  
5      Admin003     C
6      Admin001     B 
7      Admin001     A
8      Admin001     C
9      Admin001     B  
A: 

How are you sorting the columns? If you aren't sorting them, you could get different results each time, as sometimes A would follow B, and sometimes B would follow A. If you are sorting them, you may be able to use an 'exists' test with the sorting expression.

There is no general method of getting the next (or previous) row in SQL, but many implementations provide their own built-in functions to help with that kind of thing. Unfortunately I'm not familiar with DB2.

tloflin
+3  A: 

Given that there is no implicit ordering of a set, then no, there isn't any reliable way to do this. Your data will need to be ordered (perhaps by a third column, or by column 1) for this to make any sense.

spender
+1  A: 

It looks like you're trying to find out who's grade dropped from A to B, so we'll also assume that you want the results where B follows A for the same admin.

SELECT DISTINCT t1.Col2
FROM table t1
INNER JOIN table t2 ON t1.Col2 = t2.Col2
LEFT OUTER JOIN table t3 ON t1.Col2 = t3.Col2
            AND t3.Col1 < t2.Col1 AND t3.Col1 > t1.Col1
WHERE t1.Col3 = 'A'
  AND t2.Col3 = 'B' AND t2.Col1 > t1.Col1
  AND t3.Col1 IS NULL

This yields any admin who has 'A' followed by 'B'.

The INNER JOIN and the first two expressions in the WHERE clause finds all records where 'B' occurs after 'A'. The left OUTER join and the last expression in the WHERE clause finds all records where there are grades between the A and B, and only takes the records without.

You asked to get these results, one per row, like this:

Col1    Col2      Col3  
----    ------- --------  
1      Admin001     A  
6      Admin001     B

I'm going to adapt the above query the easy way.

I'll simply get the A records, get the B records, and union them:

(SELECT t1.Col1, t1.Col2, t1.Col3
FROM table t1
INNER JOIN table t2 ON t1.Col2 = t2.Col2
LEFT OUTER JOIN table t3 ON t1.Col2 = t3.Col2
            AND t3.Col1 < t2.Col1 AND t3.Col1 > t1.Col1
WHERE t1.Col3 = 'A'
  AND t2.Col3 = 'B' AND t2.Col1 > t1.Col1
  AND t3.Col1 IS NULL)
UNION
(SELECT t2.Col1, t2.Col2, t2.Col3
FROM table t1
INNER JOIN table t2 ON t1.Col2 = t2.Col2
LEFT OUTER JOIN table t3 ON t1.Col2 = t3.Col2
            AND t3.Col1 < t2.Col1 AND t3.Col1 > t1.Col1
WHERE t1.Col3 = 'A'
  AND t2.Col3 = 'B' AND t2.Col1 > t1.Col1
  AND t3.Col1 IS NULL)
ORDER BY Col2, Col1

Notice that we're ordering by Col2 first, then Col1. You may also get more than one set of records for each user.

Marcus Adams
Hmm, this will not work since the same admin could have gone down to 'C' and come back up to 'B'. That sequence does not count.
Peter
You're right. Didn't know you had more than two grades per person. Will update it.
Marcus Adams
That should do it.
Marcus Adams
@Marcus, This will yield just the admin value. What I would like is the output printed like thus Col1 Col2 Col3 ---- ------- -------- 1 Admin001 A 6 Admin001 B
Peter
No problem. I've added the remaining columns to the result.
Marcus Adams
@Marcus: Ugh, this is totally my fault with the formatting. What I wanted was both the rows being printed,in separate lines, not in the same line. Thanks for your help.
Peter
+1: Hope you don't mind I reformatted your query to avoid scrolling.
Peter Lang
@Peter Lang, Thanks, looks great.
Marcus Adams
+1  A: 
 SELECT DISTINCT T1.Col2 
 FROM Table T1 INNER JOIN Table T2
 ON T2.Col2 = T1.Col2 AND T2.Col1 = (T1.Col1 + 1)
 WHERE T1.Col3 = 'A' AND T2.Col3 = 'B'

Update: As mentioned by Peter Lang, below, this will not work if the sequence in Col1 is interrupted. This version handles that situation and is more guaranteed to produce the correct result although if you're 100% certain the sequence will not be interrupted (that is, if you generate the sequence yourself in the same transaction as the analysis) the first should be faster:

 SELECT DISTINCT T1.Col2 
 FROM Table T1 INNER JOIN Table T2
 ON T2.Col2 = T1.Col2 
 AND T2.Col1 = (SELECT MIN(Col1) FROM Table T3 WHERE T3.Col1 > T1.Col1)
 WHERE T1.Col3 = 'A' AND T2.Col3 = 'B'
Larry Lustig
I would be careful with relying on `Col1` being gap-free...
Peter Lang
@Peter: why? I assume he's numbering for the purpose of producing this analysis. But if that's a concern, you ought to be able to sub in (SELECT MIN(Col1) FROM Table T3 WHERE T3.Col1 > T1.Col1) in place of the parenthesized expression in my original. ("Ought to" because I'm not a DB2 guru).
Larry Lustig
@Larry: These might be IDs from some sort of sequence that could produce gaps, or someone will decide to remove some rows later and the the report doesn't work any more. Who knows. So I'd prefer your second approach using the sub-query (+1)
Peter Lang
@Peter Lang: The second version is more guaranteed to be correct, so I will add it to the answer.
Larry Lustig
A: 

I think the following should work, assuming your updated table layout with 3 columns. (Otherwise it's impossible, because no ordering is available):

select t1.col2
from yourtable t1, yourtable t2
where t1.col3 = 'A'
and t2.col3 = 'B'
and t1.col1 + 1 = t2.col1;
tux21b
This doesn't work with the sample data because the admins change and they're not grouped together.
Marcus Adams
A: 

This query assumes that col1 is some sort of sequence or timestamp for each row. Without it, there's no way to determine if A happened before or after B.

WITH sorted AS 
(SELECT col1, col2, col3, ROWNUMBER() 
    OVER (PARTITION BY col2 ORDER BY col1) AS col4
FROM sometable
)
SELECT a.col1, a.col2, a.col3, b.col1, b.col3
FROM sorted a INNER JOIN sorted b
ON a.col2 = b.col2
WHERE a.col3 = 'A' AND b.col3 = 'B'
AND b.col4 = a.col4 + 1
;
Fred Sobotka