Subtract previous row value to current row - sql

I have the following table:
id value acc_no
-----------------
1 12 1
2 14 1
3 15 1
4 10 2
5 16 2
6 19 1
7 7 3
8 24 2
Expected output
id value acc_no result
------------------------------
1 12 1 12(current row values of acc_no=1)
2 14 1 2(14 (current row values)-12(previous row value of acc_no=1))
3 15 1 1(15-14)
4 10 2 10(current row values of acc_no=2)
5 16 2 6(16 (current row values)-12(previous row value of acc_no=2))
6 19 1 4(19(current row values)-15(previous row value of acc_no=1))
7 7 3 7(current row values of acc_no=3)
8 24 2 8(24(current row values)-16(previous row value of acc_no=2))
I tried this query:
select
id, value,
acc_no,
(value - (select value from tb_acc t1 where t1.id = t.id - 1)) as result
from
tb_acc t
But I didn't get the proper output as expected

DECLARE #Test TABLE (
id int,
value int,
acc_no int
)
INSERT #Test(id, value, acc_no)
VALUES
(1, 12, 1),
(2, 14, 1),
(3, 15, 1),
(4, 10, 2),
(5, 16, 2),
(6, 19, 1),
(7, 7, 3),
(8, 24, 2)
SELECT id, t.value, acc_no, t.value - ISNULL(v.value, 0) AS result
FROM #Test t
OUTER APPLY (
SELECT TOP (1) value
FROM #Test
WHERE id < t.id
AND acc_no = t.acc_no
ORDER by id DESC
) v

You can do like
Option one: Using LAG() function (I just notice you are using 2008 but I post it for other readers as well)
SELECT *,
Value - LAG(Value, 1, 0) OVER(PARTITION BY acc_no ORDER BY ID) Result
FROM T
ORDER BY ID;
Option two: Using a CTE and a window function + ISNULL()
WITH CTE AS
(
SELECT *,
ROW_NUMBER() OVER(PARTITION BY acc_no ORDER BY id) RN
FROM T
)
SELECT T1.id,
T1.value,
T1.acc_no,
T1.value - ISNULL(T2.value, 0) Result
FROM CTE T1 LEFT JOIN CTE T2
ON T1.acc_no = T2.acc_no
AND
T1.RN = T2.RN + 1
ORDER BY T1.id;
Live Demo

Using window functions:
;WITH CTE AS
(
SELECT id, value, acc_no,
ROW_NUMBER() OVER (PARTITION BY acc_no ORDER BY id) AS seq
FROM tb_acc
)
SELECT t1.*, t1.value - COALESCE(t2.value, 0)
FROM CTE AS t1
LEFT JOIN CTE AS t2 ON t1.acc_no = t2.acc_no AND t1.seq = t2.seq + 1

You just need a windowed SUM:
SELECT
id
,value
,acc_no
,value - isnull(sum([value]) over (partition by acc_no order by id rows between 1 preceding and 1 preceding ), 0) as result
FROM tb_acc t
order by id

Related

Rows Columns Traverse

I have data in the below format
id idnew
1 2
3 4
2
4 7
6 8
7
Result Should be something like this
ID should be followed by idnew
1
2
3
4
2
4
7
6
8
7
Thanks in advance
This should maintain the order:
SELECT id
FROM (
SELECT id, ROW_NUMBER() OVER (ORDER BY id) AS RowNumber
FROM myTable
UNION ALL
SELECT idnew, ROW_NUMBER() OVER (ORDER BY idnew) +
(SELECT COUNT(*) FROM dbo.myTable) AS RowNumber
FROM myTable
WHERE idnew IS NOT NULL
) a
ORDER BY RowNumber
I am assuming the id column is NOT NULL-able.
NOTE: If you want to keep the NULL values from the idnew column AND maintain the order, then remove the WHERE clause and ORDER BY id in the second select:
SELECT id
FROM (
SELECT id, ROW_NUMBER() OVER (ORDER BY id) AS RowNumber
FROM myTable
UNION ALL
SELECT idnew, ROW_NUMBER() OVER (ORDER BY id) +
(SELECT COUNT(*) FROM dbo.myTable) AS RowNumber
FROM myTable
) a
ORDER BY RowNumber
This is fully tested, try it here: https://rextester.com/DVZXO21058
Setting up the table as you described:
CREATE TABLE myTable (id INT, idnew INT);
INSERT INTO myTable (id, idnew)
VALUES (1, 2),
(3, 4),
(2, NULL),
(4, 7),
(6, 8),
(7, NULL);
SELECT * FROM myTable;
Here is the query to do the trick:
SELECT mixed_id FROM
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS row_num,
id,
idnew
FROM myTable
) AS x
UNPIVOT
(
mixed_id for item in (id, idnew)
) AS y
WHERE mixed_id IS NOT NULL
ORDER BY row_num, mixed_id;
In order not to further complicate the query, this is taking advantage of 'id' would rank ahead of 'idnew' as a string. I believe string ranking is not the key issue here.
Using Cross Apply
;WITH CTE (id,idnew)
AS
(
SELECT 1,2 UNION ALL
SELECT 3,4 UNION ALL
SELECT 2,NULL UNION ALL
SELECT 4,7 UNION ALL
SELECT 6,8 UNION ALL
SELECT 7,NULL
)
SELECT New
FROM CTE
CROSS APPLY ( VALUES (id),(idnew))AS Dt (New)
WHERE dt.New IS NOT NULL
Result
New
---
1
2
3
4
2
4
7
6
8
7

How to group nodes with relationships in SQL

I have the following table which lists related nodes:
;WITH CTE AS
( SELECT *
FROM (VALUES (1,2)
,(2,1)
,(3,4)
,(3,5)
,(4,3)
,(4,5)
,(5,3)
,(5,4)
,(6,NULL)
,(7,NULL)
,(8,9)
,(9,8)
) AS ValuesTable(ID,RelatedID)
)
SELECT *
FROM CTE
How can I assign unique IDS (GUID or integer GroupID) to each group, So, 1 and 2 will be on the same group, 3, 4, 5 on a different group, 6 is alone in it's group and so is 7, and 8 and 9 are one more group?
My answer so far seems very cumbersome:
;WITH CTE AS
( SELECT *
FROM (VALUES (1,2)
,(2,1)
,(3,4)
,(3,5)
,(4,3)
,(4,5)
,(5,3)
,(5,4)
,(6,NULL)
,(7,NULL)
,(8,9)
,(9,8)
) AS ValuesTable(ID,RelatedID)
)
SELECT DENSE_RANK() OVER(ORDER BY CA.IDList) AS GroupID,
ID,
RelatedID
FROM CTE
CROSS APPLY (SELECT STUFF((SELECT ',' + CONVERT(NVARCHAR(255), ID)
FROM CTE AS CTEInner
WHERE CTEInner.ID = CTE.ID
OR CTEInner.ID = CTE.RelatedID
OR CTEInner.RelatedID = CTE.RelatedID
OR CTEInner.RelatedID = CTE.ID
FOR XML PATH(''),TYPE).value('(./text())[1]','NVARCHAR(MAX)'),1,1,'') AS IDList) AS CA
But it provides the correct answer:
GroupID ID RelatedID
1 1 2
1 2 1
2 3 4
2 3 5
2 4 3
2 4 5
2 5 3
2 5 4
3 6 NULL
4 7 NULL
5 8 9
5 9 8
Adding a unique number for each group is not hard but it does require a few steps.
The first step would be to select unique values for the groups - so for instance the group with (1, 2) and (2, 1) will contain only a single record - (1, 2).
The next step is to get rid of the records that creates multiple paths for the same relationship - in this case - (3, 4), (4, 5), (3, 5) - means that 5 is the related to both 3 and 4, but for the recursive cte to work, we only need a single relationship path - so either (3, 4), (4, 5) or (3, 4), (3, 5) but not both.
The next step is to create a recursive cte based on these unique values, so that each group can get it's unique number.
After that, you can select from the original cte joined to the recursive cte and get the unique group numbers:
;WITH CTE AS
( SELECT *
FROM (VALUES (1,2)
,(2,1)
,(3,4)
,(3,5)
,(4,3)
,(4,5)
,(5,3)
,(5,4)
,(6,NULL)
,(7,NULL)
,(8,9)
,(9,8)
) AS ValuesTable(ID,RelatedID)
)
, PreUniqueValues AS
(
SELECT MIN(ID) AS ID,
MAX(RelatedID) As RelatedID
FROM CTE AS B
GROUP BY (ID + ISNULL(RelatedID, 0)) + (ID * ISNULL(RelatedID, 0))
)
, UniqueValues AS
(
SELECT ID, MIN(RelatedID) As RelatedID
FROM PreUniqueValues
GROUP BY ID
)
, Recursive AS
(
SELECT ID, RelatedId, DENSE_RANK() OVER(ORDER BY ID) As GroupID
FROM UniqueValues AS T0
WHERE NOT EXISTS
(
SELECT 1
FROM UniqueValues AS T1
WHERE T1.ID = T0.RelatedID
)
UNION ALL
SELECT UV.ID, UV.RelatedID, GroupID
FROM UniqueValues As UV
JOIN Recursive As Re
ON UV.ID = Re.RelatedId
)
SELECT CTE.ID, CTE.RelatedID, GroupID
FROM CTE
JOIN Recursive
ON CTE.ID = Recursive.ID OR CTE.ID = ISNULL(Recursive.RelatedID, 0)
ORDER BY ID
Results:
ID RelatedID GroupID
1 2 1
2 1 1
4 3 2
4 5 2
5 3 2
5 4 2
6 NULL 3
7 NULL 4
8 9 5
9 8 5
This is a graph-walking problem and you would seem to need recursive CTEs. The logic looks like this:
WITH t AS (
SELECT *
FROM (VALUES (1,2)
,(2,1)
,(3,4)
,(3,5)
,(4,3)
,(4,5)
,(5,3)
,(5,4)
,(6,NULL)
,(7,NULL)
,(8,9)
,(9,8)
) AS ValuesTable(ID,RelatedID)
),
cte as (
select distinct id, id as relatedId, ',' + convert(varchar(max), id) + ',' as relatedIds
from t
union all
select cte.id, t.relatedId, cte.relatedIds + convert(varchar(max), t.relatedId) + ','
from cte join
t
on cte.relatedId = t.id
where cte.relatedId is not null and
cte.relatedIds not like '%,' + convert(varchar(max), t.relatedId) + ',%'
)
SELECT id, min(relatedId) as grp,
dense_rank() over (order by min(relatedId)) as grp_number
FROM cte
GROUP BY id;
Here is a db<>fiddle.

How can I select distinct by one column?

I have a table with the columns below, and I need to get the values if COD is duplicated, get the non NULL on VALUE column. If is not duplicated, it can get a NULL VALUE. Like the example:
I'm using SQL SERVER.
This is what I get:
COD ID VALUE
28 1 NULL
28 2 Supermarket
29 1 NULL
29 2 School
29 3 NULL
30 1 NULL
This is what I want:
COD ID VALUE
28 2 Supermarket
29 2 School
30 1 NULL
What I'm tryin' to do:
;with A as (
(select DISTINCT COD,ID,VALUE from CodId where ID = 2)
UNION
(select DISTINCT COD,ID,NULL from CodId where ID != 2)
)select * from A order by COD
You can try this.
DECLARE #T TABLE (COD INT, ID INT, VALUE VARCHAR(20))
INSERT INTO #T
VALUES(28, 1, NULL),
(28, 2 ,'Supermarket'),
(29, 1 ,NULL),
(29, 2 ,'School'),
(29, 3 ,NULL),
(30, 1 ,NULL)
;WITH CTE AS (
SELECT *, RN= ROW_NUMBER() OVER (PARTITION BY COD ORDER BY VALUE DESC) FROM #T
)
SELECT COD, ID ,VALUE FROM CTE
WHERE RN = 1
Result:
COD ID VALUE
----------- ----------- --------------------
28 2 Supermarket
29 2 School
30 1 NULL
Another option is to use the WITH TIES clause in concert with Row_Number()
Example
Select top 1 with ties *
from YourTable
Order By Row_Number() over (Partition By [COD] order by Value Desc)
Returns
COD ID VALUE
28 2 Supermarket
29 2 School
30 1 NULL
I would use GROUP BY and JOIN. If there is no NOT NULL value for a COD than it should be resolved using the OR in JOIN clause.
SELECT your_table.*
FROM your_table
JOIN (
SELECT COD, MAX(value) value
FROM your_table
GROUP BY COD
) gt ON your_table.COD = gt.COD and (your_table.value = gt.value OR gt.value IS NULL)
If you may have more than one non null value for a COD this will work
drop table MyTable
CREATE TABLE MyTable
(
COD INT,
ID INT,
VALUE VARCHAR(20)
)
INSERT INTO MyTable
VALUES (28,1, NULL),
(28,2,'Supermarket'),
(28,3,'School'),
(29,1,NULL),
(29,2,'School'),
(29,3,NULL),
(30,1,NULL);
WITH Dups AS
(SELECT COD FROM MyTable GROUP BY COD HAVING count (*) > 1 )
SELECT MyTable.COD,MyTable.ID,MyTable.VALUE FROM MyTable
INNER JOIN dups ON MyTable.COD = Dups.COD
WHERE value IS NOT NULL
UNION
SELECT MyTable.COD,MyTable.ID,MyTable.VALUE FROM MyTable
LEFT JOIN dups ON MyTable.COD = Dups.COD
WHERE dups.cod IS NULL

Fluently adding values in t-sql

I have a table like this:
Items Date Price
1 2016-01-01 10
1 2016-01-02 15
1 2016-01-03 null
1 2016-01-04 null
1 2016-01-05 8
1 2016-01-06 null
1 2016-01-07 null
1 2016-01-08 null
2 2016-01-01 14
2 2016-01-02 7
2 2016-01-03 null
2 2016-01-04 null
2 2016-01-05 16
2 2016-01-06 null
2 2016-01-07 null
2 2016-01-08 5
Now I want to update the null values. The difference between the price before and after null values must be evenly added.
Example:
1 2016-01-02 15 to
1 2016-01-05 8
15 to 8 = -7
-7 / 3 = -2,333333
1 2016-01-02 15
1 2016-01-03 12,6666
1 2016-01-04 10,3333
1 2016-01-05 8
Shouldn't be made with cursors. Helptables would be OK.
This is really where you want the ignore nulls option on lag() and lead(). Alas.
An alternative is to use outer apply:
select t.*,
coalesce(t.price,
tprev.price +
datediff(day, tprev.date, t.date) * (tnext.price - tprev.price) / datediff(day, tprev.date, tnext.date)
) as est_price
from t outer apply
(select top 1 t2.*
from t t2
where t2.item = t.item and
t2.date <= t.date and
t2.price is not null
order by t2.date desc
) tprev outer apply
(select top 1 t2.*
from t t2
where t2.item = t.item and
t2.date >= t.date and
t2.price is not null
order by t2.date asc
) tnext ;
The complex arithmetic is just calculating the difference, dividing by the number of days, and then allocating the days to the current day.
WITH T1 AS
(
SELECT *,
ROW_NUMBER() OVER (PARTITION BY Items ORDER BY Date) AS RN,
FORMAT(ROW_NUMBER() OVER (PARTITION BY Items ORDER BY Date),'D10') + FORMAT(Price,'0000000000.000000') AS RnPr
FROM YourTable
), T2 AS
(
SELECT *,
MAX(RnPr) OVER (PARTITION BY Items ORDER BY Date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS prev,
MIN(RnPr) OVER (PARTITION BY Items ORDER BY Date ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) AS next
FROM T1
), T3 AS
(
SELECT Items,
Date,
Price,
RnPr,
InterpolatedPrice = IIF(Price IS NOT NULL,prevPrice,prevPrice + (RN - prevRN) * (nextPrice - prevPrice)/NULLIF(nextRN - prevRN,0))
FROM T2
CROSS APPLY (VALUES(CAST(SUBSTRING(prev,11,17) AS decimal(16,6)),
CAST(LEFT(prev, 10) AS INT),
CAST(SUBSTRING(next,11,17) AS decimal(16,6)),
CAST(LEFT(next, 10) AS INT)
)) V(prevPrice,prevRN,nextPrice,nextRN)
)
--UPDATE T3 SET Price = InterpolatedPrice
SELECT *
FROM T3
ORDER BY Items,
Date
Which returns
the row_number and price are bundled together in a single column (RnPr above). The order of RnPr is the same as the order by row_number. MIN and MAX both ignore NULLS. So finding the MAX(RnPr) between UNBOUNDED PRECEDING AND CURRENT ROW will include the value of the previous NOT NULL price if the price in the current row is null. And similarly MIN(RnPr) will find the next with a frame between CURRENT ROW AND UNBOUNDED FOLLOWING.
This can then be cracked apart to get the price and row number as above.
If happy with the results the final SELECT can be removed and the UPDATE uncommented as in this demo.
Just replace "YourTable" (4 of them) with your actual table name.
If you are happy with the results, then comment out the select and un-comment the UPDATE and WHERE.
Select A.Items,A.Date,
--Update YourTable Set
Price = IsNull(A.Price,((DateDiff(DD,B.Date,A.Date)/(DateDiff(DD,B.Date,C.Date)+0.0))*(C.Price - B.Price)) + B.Price)
From YourTable A
Outer Apply (Select Top 1 Date,Price from YourTable Where Items=A.Items and Date<A.Date and Price is not Null and A.Price is null Order by Price Desc) B
Outer Apply (Select Top 1 Date,Price from YourTable Where Items=A.Items and Date>A.Date and Price is not Null and A.Price is null Order by Price) C
--Where Price is NULL
Returns
Now, you'll notice nulls between 01/06 and 01/08 for Items 1. This is because there is no cap to interpolate with.
You can do this by using a series of window functions in common table expressions.
T1 assigns a row number to each row (rn)
T2 finds the first non null Price row number before and after the current row (rnb / rna)
T3 calculates the adjusted Price by looking up the prices before and after
declare #T table (Items int, Date date, Price float)
insert into #T (Items, Date, Price) values
(1, '2016-01-01', 10), (1, '2016-01-02', 15), (1, '2016-01-03', null), (1, '2016-01-04', null), (1, '2016-01-05', 8), (1, '2016-01-06', null), (1, '2016-01-07', null), (1, '2016-01-08', null), (2, '2016-01-01', 14), (2, '2016-01-02', 7), (2, '2016-01-03', null), (2, '2016-01-04', null), (2, '2016-01-05', 16), (2, '2016-01-06', null), (2, '2016-01-07', null), (2, '2016-01-08', 5)
;with T1 as
(
select *,
row_number() over (order by Items, Date) as rn
from #T
),
T2 as
(
select *,
max(case when price is null then null else rn end) over (partition by Items order by Date) as rnb,
min(case when price is null then null else rn end) over (partition by Items order by Date desc) as rna
from T1
)
select Items, Date,
isnull(price,
lag(Price, rn-rnb, Price) over (order by rn) -
(
lag(Price, rn-rnb, Price) over (order by rn) -
lead(Price, rna-rn, Price) over (order by rn)
) / (rna-rnb) * (rn-rnb)
) as Price
from T2
order by Items, Date

Group by values that are in sequence

I have some table like this
row chequeNo
1 15
2 19
3 20
4 35
5 16
and I need to get the result like this
row from to
1 15 16
2 19 20
3 35 35
so I need groups of chequeNo where values would be sequential without any interruptions. chequeNo is unique column. Additionally it should be done with one sql select query, because I haven't permissions to create any sql structures except select queries.
So is it possible?
Would be grateful for any help
You can use Aketi Jyuuzou's technique called Tabibitosan here:
SQL> create table mytable (id,chequeno)
2 as
3 select 1, 15 from dual union all
4 select 2, 19 from dual union all
5 select 3, 20 from dual union all
6 select 4, 35 from dual union all
7 select 5, 16 from dual
8 /
Table created.
SQL> with tabibitosan as
2 ( select chequeno
3 , chequeno - row_number() over (order by chequeno) grp
4 from mytable
5 )
6 select row_number() over (order by grp) "row"
7 , min(chequeno) "from"
8 , max(chequeno) "to"
9 from tabibitosan
10 group by grp
11 /
row from to
---------- ---------- ----------
1 15 16
2 19 20
3 35 35
3 rows selected.
Regards,
Rob.
This should work with Oracle 10 (only tested with Oracle 11)
select group_nr + 1,
min(chequeno) as start_value,
max(chequeno) as end_value
from (
select chequeno,
sum(group_change_flag) over (order by rn) as group_nr
from (
select row_number() over (order by chequeno) as rn,
chequeno,
case
when chequeno - lag(chequeno,1,chequeno) over (order by chequeno) <= 1 then 0
else 1
end as group_change_flag
from foo
) t1
) t2
group by group_nr
order by group_nr
(it should work with any DBMS supporting standard SQL windowing functions, e.g. PostgreSQL, DB2, SQL Server 2012)
Here is a "plain vanilla" approach:
SELECT T1.chequeNo, T2.chequeNo
FROM Table1 AS T1 INNER JOIN Table1 AS T2 ON T2.chequeNo >= T1.chequeNo
WHERE
NOT EXISTS (SELECT T0.chequeNo FROM Table1 T0 WHERE T0.chequeNo IN ((T1.chequeNo-1), (T2.chequeNo+1)))
AND (SELECT COUNT(*) FROM Table1 T0 WHERE T0.chequeNo BETWEEN T1.chequeNo AND T2.chequeNo)=(T2.chequeNo - T1.chequeNo + 1)
ORDER BY 1,2
Please let me know if it's too inefficient for large data sets.
CREATE TABLE YOUR_TABLE (
chequeNo NUMBER PRIMARY KEY
);
INSERT INTO YOUR_TABLE VALUES (15);
INSERT INTO YOUR_TABLE VALUES (19);
INSERT INTO YOUR_TABLE VALUES (20);
INSERT INTO YOUR_TABLE VALUES (35);
INSERT INTO YOUR_TABLE VALUES (16);
SELECT T1.chequeNo "from", T2.chequeNo "to"
FROM
(
SELECT chequeNo, ROW_NUMBER() OVER (ORDER BY chequeNo) RN
FROM (
SELECT chequeNo, LAG(chequeNo) OVER (ORDER BY chequeNo) PREV
FROM YOUR_TABLE
)
WHERE PREV IS NULL OR chequeNo > PREV + 1
) T1
JOIN
(
SELECT chequeNo, ROW_NUMBER() OVER (ORDER BY chequeNo) RN
FROM (
SELECT chequeNo, LEAD(chequeNo) OVER (ORDER BY chequeNo) NEXT
FROM YOUR_TABLE
)
WHERE NEXT IS NULL OR chequeNo < NEXT - 1
) T2
USING (RN);
Result:
from to
---------------------- ----------------------
15 16
19 20
35 35
If we spice things up a little...
INSERT INTO YOUR_TABLE VALUES (17);
INSERT INTO YOUR_TABLE VALUES (18);
...we get:
from to
---------------------- ----------------------
15 20
35 35