Query to get previous value - sql

I have a scenerio where I need previous column value but it should not be same as current column value.
Table A:
+------+------+-------------+
| Col1 | Col2 | Lead_Col2 |
+------+------+-------------+
| 1 | A | NULL |
| 2 | B | A |
| 3 | B | A |
| 4 | C | B |
| 5 | C | B |
| 6 | C | B |
| 7 | D | C |
+------+------+-------------+
As Given above, I need previuos column(Col2) value. which is not same as current value.

Try:
select *
from (select col1,
col2,
lag(col2, 1) over(order by col1) as prev_col2
from table_a)
where col2 <> prev_col2

The name lead_col2 is misleading, because you really want a lag.
Here is a brute force method that uses a correlated subquery to get the index of the value and then joins the value in:
select aa.col1, aa.col2, aa.col2
from (select col1, col2,
(select max(col1) as maxcol1
from a a2
where a2.id < a.id and a2.col2 <> a.col2
) as prev_col1
from a
) aa left join
a
on aa.maxcol1 = a.col1
EDIT:
You can also use logic with lead() and ignore NULLs. If a value is the last in its sequence, then use that value, otherwise set it to NULL. Then use lag() with ignoreNULL`s:
select col1, col2,
lag(col3) over (order by col1 ignore nulls)
from (select col1, col2,
(case when col2 <> lead(col2) over (order by col1) then col2
end) as col3
from a
) a;

Try this:
select t.col1
,t.col2
,first_value(lag_col2) over (partition by col2 order by ord) lag_col2
from (select t.*
,case when lag_col2 = col2 then 1 else 0 end ord
from (select t.*
,lag (col2) over (order by col1) lag_col2
from table1 t
)t
)t
order by col1
SQL Fiddle

Related

SQL - Pick first row satisfying a condition below every row satisfying another condition

Suppose I have this table, named Table
+----+------+------+
| ID | Col1 | Col2 |
+----+------+------+
| 1 | A | 0 |
| 2 | B | 0 |
| 3 | C | 1 |
| 4 | A | 1 |
| 5 | D | 0 |
| 6 | A | 0 |
| 7 | F | 1 |
| 8 | H | 1 |
+----+------+------+
I want this result:
+----+------+------+
| ID | Col1 | Col2 |
+----+------+------+
| 3 | C | 1 |
| 4 | A | 1 |
| 7 | F | 1 |
+----+------+------+
That is:
If Col1 = A and Col2 = 1, take the corresponding row
If Col1 = A and Col2 = 0, take the first row below it where Col2 = 1
I tried something like
SELECT CASE
WHEN t.Col2 > 0
THEN t.Col2
WHEN t1.Col2 > 0
THEN t1.Col2
WHEN t2.Col2 > 0
THEN t2.Col2
...
FROM Table t
JOIN table t1 ON t.id - 1 = t1.id
JOIN table t2 ON t.id - 2 = t2.id
...
WHERE t.Col2 = 'A'
but it's not quite what I was looking for.
I couldn't come up with any solution. What should I do?
Use window functions SUM() and MIN():
with
cte1 as (
select *, sum(case when col1 = 'A' and col2 = 0 then 1 else 0 end) over (order by id) grp
from tablename
),
cte2 as (
select *, min(case when col2 = 1 then id end) over (partition by grp order by id) next_id
from cte1
)
select id, col1, col2
from cte2
where (col1 = 'A' and col2 = 1) or (id = next_id)
See the demo.
Results:
> id | col1 | col2
> -: | :--- | ---:
> 3 | C | 1
> 4 | A | 1
> 7 | F | 1
Hmmm . . . I am thinking lag():
select t.*
from (select t.*,
lag(col1) over (order by id) as prev_col1,
lag(col2) over (order by id) as pev_col2
from t
) t
where col1 = 'A' and col2 = 1 or
(pev_col1 = 'A' and prev_col2 = 0);
Here is a query that finds all such rows you asked for where either it's Col1=A and Col2=1 or it's the first Col2=1 following a Col1=A and Col2=0.
A brief explanation is the query only considers rows where Col2=1. It takes the row if Col1=A of course. But it also takes the row if it looks back to find the closest previous row with Col2=1 as well as the closest previous row where Col1=A and Col2=0 and it finds former is further back than the latter (or the former doesn't exist).
create table MyTable (
ID int not null identity(1,1),
Col1 varchar(100) not null,
Col2 varchar(100) not null
);
insert MyTable (Col1, Col2) values ('A', '0');
insert MyTable (Col1, Col2) values ('B', '0');
insert MyTable (Col1, Col2) values ('C', '1');
insert MyTable (Col1, Col2) values ('A', '1');
insert MyTable (Col1, Col2) values ('D', '0');
insert MyTable (Col1, Col2) values ('A', '0');
insert MyTable (Col1, Col2) values ('F', '1');
insert MyTable (Col1, Col2) values ('H', '1');
select * from MyTable;
select *
from MyTable as t
where t.Col2 = 1
and (t.Col1 = 'A'
or isnull((select top (1) t2.ID
from MyTable as t2
where t2.ID < t.ID
and t2.Col2 = 1
order by t2.ID desc
), 0)
<
(select top (1) t2.ID
from MyTable as t2
where t2.ID < t.ID
and t2.Col1 = 'A'
and t2.Col2 = 0
order by t2.ID desc
)
)
order by t.ID;

How to select last three non-NULL columns across multiple columns

For example, if my dataset looks like this:
id | col1 | col2 | col3 | col4 | col5 | col6
---+------+------+------+------+------+-----
A | a1 | a2 | a3 | a4 | a5 | a6
B | b1 | b2 | b3 | b4 | NULL | NULL
C | c1 | c2 | c3 | NULL | NULL | NULL
The desired output would be:
id | col1 | col2 | col3 | col4 | col5 | col6
---+------+------+------+------+------+-----
A | a4 | a5 | a6 |
B | b2 | b3 | b4 |
C | c1 | c2 | c3 |
Does anyone know how to achieve that?
I just found this thread: https://dba.stackexchange.com/questions/210431/select-first-and-last-non-empty-blank-column-of-a-record-mysql
This allow me to pick the last non-null column, but I have no idea to to get the second and third last column in the same time as well.
This will do what you request (db <> fiddle)
Edit: The initial version probably didn't do what you want if there were less than three NOT NULL values in a row. This version will shift them left.
SELECT Id,
CA.Col1,
CA.Col2,
CA.Col3,
NULL AS Col4,
NULL AS Col5,
NULL AS Col6
FROM YourTable
CROSS APPLY (SELECT MAX(CASE WHEN RN = 1 THEN val END) AS Col1,
MAX(CASE WHEN RN = 2 THEN val END) AS Col2,
MAX(CASE WHEN RN = 3 THEN val END) AS Col3
FROM (SELECT val,
ROW_NUMBER() OVER (ORDER BY ord) AS RN
FROM
(SELECT TOP 3 *
FROM (VALUES(1, col1),
(2, col2),
(3, col3),
(4, col4),
(5, col5),
(6, col6) ) v(ord, val)
WHERE val IS NOT NULL
ORDER BY ord DESC
) d1
) d2
) CA
You can also use pivot and unpivot to achieve the desired result.
try the following:
;with cte as
(
select id, cols, col as val, ROW_NUMBER() over (partition by id order by cols desc) rn
from #t
unpivot
(
col for cols in ([col1], [col2], [col3], [col4], [col5], [col6])
)upvt
)
select id, ISNULL([3], '') as col1, ISNULL([2], '') as col2, ISNULL([1], '') as col3, '' col4, '' col5, '' col6
from
(
select id, val, rn from cte
)t
pivot
(
max(val) for rn in ([1], [2], [3])
)pvt
order by 1
Please find the db<>fiddle here.

DENSE_RANK() without duplication

Here's what my data looks like:
| col1 | col2 | denserank | whatiwant |
|------|------|-----------|-----------|
| 1 | 1 | 1 | 1 |
| 2 | 1 | 1 | 1 |
| 3 | 2 | 2 | 2 |
| 4 | 2 | 2 | 2 |
| 5 | 1 | 1 | 3 |
| 6 | 2 | 2 | 4 |
| 7 | 2 | 2 | 4 |
| 8 | 3 | 3 | 5 |
Here's the query I have so far:
SELECT col1, col2, DENSE_RANK() OVER (ORDER BY COL2) AS [denserank]
FROM [table1]
ORDER BY [col1] asc
What I'd like to achieve is for my denserank column to increment every time there is a change in the value of col2 (even if the value itself is reused). I can't actually order by the column I have denserank on, so that won't work). See the whatiwant column for an example.
Is there any way to achieve this with DENSE_RANK()? Or is there an alternative?
I would do it with a recursive cte like this:
declare #Dept table (col1 integer, col2 integer)
insert into #Dept values(1, 1),(2, 1),(3, 2),(4, 2),(5, 1),(6, 2),(7, 2),(8, 3)
;with a as (
select col1, col2,
ROW_NUMBER() over (order by col1) as rn
from #Dept),
s as
(select col1, col2, rn, 1 as dr from a where rn=1
union all
select a.col1, a.col2, a.rn, case when a.col2=s.col2 then s.dr else s.dr+1 end as dr
from a inner join s on a.rn=s.rn+1)
col1, col2, dr from s
result:
col1 col2 dr
----------- ----------- -----------
1 1 1
2 1 1
3 2 2
4 2 2
5 1 3
6 2 4
7 2 4
8 3 5
The ROW_NUMBER is only required in case your col1 values are not sequential. If they are you can use the recursive cte straight away
Try this using window functions:
with t(col1 ,col2) as (
select 1 , 1 union all
select 2 , 1 union all
select 3 , 2 union all
select 4 , 2 union all
select 5 , 1 union all
select 6 , 2 union all
select 7 , 2 union all
select 8 , 3
)
select t.col1,
t.col2,
sum(x) over (
order by col1
) whatyouwant
from (
select t.*,
case
when col2 = lag(col2) over (
order by col1
)
then 0
else 1
end x
from t
) t
order by col1;
Produces:
It does a single table read and forms group of consecutive equal col2 values in increasing order of col1 and then finds dense rank on that.
x: Assign value 0 if previous row's col2 is same as this row's col2 (in order of increasing col1) otherwise 1
whatyouwant: create groups of equal values of col2 in order of increasing col1 by doing an incremental sum of the value x generated in the last step and that's your output.
Here is one way using SUM OVER(Order by) window aggregate function
SELECT col1,Col2,
Sum(CASE WHEN a.prev_val = a.col2 THEN 0 ELSE 1 END) OVER(ORDER BY col1) AS whatiwant
FROM (SELECT col1,
col2,
Lag(col2, 1)OVER(ORDER BY col1) AS prev_val
FROM Yourtable) a
ORDER BY col1;
How it works:
LAG window function is used to find the previous col2 for each row ordered by col1
SUM OVER(Order by) will increment the number only when previous col2 is not equal to current col2
I think this is possible in pure SQL using some gaps and islands tricks, but the path of least resistance might be to use a session variable combined with LAG() to keep track of when your computed dense rank changes value. In the query below, I use #a to keep track of the change in the dense rank, and when it changes this variable is incremented by 1.
DECLARE #a int
SET #a = 1
SELECT t.col1,
t.col2,
t.denserank,
#a = CASE WHEN LAG(t.denserank, 1, 1) OVER (ORDER BY t.col1) = t.denserank
THEN #a
ELSE #a+1 END AS [whatiwant]
FROM
(
SELECT col1, col2, DENSE_RANK() OVER (ORDER BY COL2) AS [denserank]
FROM [table1]
) t
ORDER BY t.col1

Get column name where values differ between two rows

I have a table with lots of columns. Sometimes I need to find differences between two rows. I can do it just by scrolling through screen but it is dull. I'm looking for a query that will do this for me, something like
SELECT columns_for_id_1 != columns_for_id_2
FROM xyz
WHERE id in (1,2)
Table:
id col1 col2 col3 col4
1 qqq www eee rrr
2 qqq www XXX rrr
Result:
"Different columns: id, col3"
Is there a simple way to do this?
UPDATE
Another example as wanted:
What I have (table has more than 50 column, not only 7):
Id| Col1 | Col2 | Col3 | Col4 | Col5 | Col6 |
==============================================
1 | aaa | bbb | ccc | ddd | eee | fff |
----------------------------------------------
2 | aaa | XXX | ccc | YYY | eee | fff |
Query:
SELECT *
FROM table
WHERE Id = 1 OR Id = 2
AND "columns value differs"
Query result: "Id, Col2, Col4"
OR something like:
Id|Col2 |Col4 |
===============
1 |bbb |ddd |
---------------
2 |XXX |YYY |
Right now I have to scroll through more than 50 columns to see if rows are the same, it's not efficient and prone to mistakes. I don't want any long query like
SELECT (COMPARE Id1.Col1 with Id2.Col1 if different then print "Col1 differs", COMPARE Id1.Col2 with Id2.Col2...) because I will compare the rows myself faster ;)
Something like this:
SELECT col, MIN(VAL) AS val1, MAX(val) AS val2
FROM (
SELECT id, val, col
FROM (
SELECT id, [col1], [col2], [col3], [col4]
FROM mytable
WHERE id IN (1,2)) AS src
UNPIVOT (
val FOR col IN ([col1], [col2], [col3], [col4])) AS unpvt ) AS t
GROUP BY col
HAVING MIN(val) <> MAX(val)
Output:
col val1 val2
================
col3 eee XXX
Try this simple query, may be help you
SELECT (CASE WHEN a.col1 <> b.col1 THEN 'Different Col1'
WHEN a.col2 <> b.col2 THEN 'Different Col2'
...
ELSE 'No Different' END) --You can add only required columns here
FROM xyz AS a
INNER JOIN xyz AS b ON b.id = 1 --First Record
WHERE a.id = 2 --Second record to compare
If you are on SQL Server 2012 then you can also use LEAD/LAG windowed funuction to do this. MSDN Reference is here - https://msdn.microsoft.com/en-us/library/hh213125.aspx
select
id,
col1,
col2,
col3,
col4,
stuff(diff_cols,len(diff_cols-1),1,'') diff_cols
from
(
SELECT
id,
col1,
col2,
col3,
col4,
concat
(
'Different columns:',
CASE
WHEN LEAD(id, 1,0) OVER (ORDER BY id) <> id THEN 'id,'
WHEN LEAD(col1, 1,0) OVER (ORDER BY id) <> col1 THEN 'col1,'
WHEN LEAD(col2, 1,0) OVER (ORDER BY id) <> col2 THEN 'col2,'
WHEN LEAD(col3, 1,0) OVER (ORDER BY id) <> col3 THEN 'col3,'
WHEN LEAD(col4, 1,0) OVER (ORDER BY id) <> col4 THEN 'col4,'
) diff_cols
FROM xyz
) tmp

How to remove null values and get row from values from table

Please help me with the task below. I have table a with four columns
col1,col2,col3 and col4. I want to retrieve from these columns, removing nulls.
So, if my table has
col1 | col2 | col3 | col4
-----+------+------+-----
A | B | NULL| NULL
C | D | NULL| NULL
NULL | NULL | E | F
NULL | NULL | G | H
I want result to be
col1 | col2 | col3 | col4
-----+------+------+-----
A | B | E | F
C | D | G | H
Here is a solution. I have used the analytic ROW_NUMBER() to synthesize a key for joining the rows. The join is full outer in order to cater for unequal assignments of nulls and values.
with cte as (select * from t23)
, a as ( select col1, row_number() over (order by col1) as rn
from cte
where col1 is not null )
, b as ( select col2, row_number() over (order by col2) as rn
from cte
where col2 is not null )
, c as ( select col3, row_number() over (order by col3) as rn
from cte
where col3 is not null )
, d as ( select col4, row_number() over (order by col4) as rn
from cte
where col4 is not null )
select a.col1
, b.col2
, c.col3
, d.col4
from a
full outer join b
on a.rn = b.rn
full outer join c
on a.rn = c.rn
full outer join d
on a.rn = d.rn
/
The SQL Fiddle is for Oracle, but this solution will work for any flavour of database which supports a ranking analytic function. The common table expression is optional, it just makes the other sub-queries easier to write.