Update a column and refer back it in the same query - sql

I have a table in SQL Server 2014 and need to recursively update a column based on its previous value. For e.g.
---------------------------------------
ID | price | diff_with_prev_price |
---------------------------------------
1 | 29 | 0 |
2 | 25 | 0 |
3 | 20 | 0 |
4 | 35 | 0 |
5 | 40 | 0 |
--------------------------------------|
I want to recursively update third column like below
---------------------------------------
ID | price | diff_with_prev_price |
---------------------------------------
1 | 29 | 0 |
2 | 25 | 25 |
3 | 20 | 5 |
4 | 35 | -30 |
5 | 40 | 10 |
--------------------------------------|
It is the summation of previous value of third column with next value of 'price'.
Can someone please give some hint to do this either using CTE or LEAD/LAG, but without using cursors. I have to update million rows.

You can try this:
SELECT 1 AS ID , 29 AS price, 0 AS diff_with_prev_prive
INTO #tmp
UNION SELECT 2 AS ID , 25 AS price, 0 AS diff_with_prev_prive
UNION SELECT 3 AS ID , 20 AS price, 0 AS diff_with_prev_prive
UNION SELECT 4 AS ID , 35 AS price, 0 AS diff_with_prev_prive
UNION SELECT 5 AS ID , 40 AS price, 0 AS diff_with_prev_prive
WITH cte AS
(
SELECT
ID
, price
, diff_with_prev_prive
, price - ISNULL(LAG(price) OVER (ORDER BY ID),0) AS new_value
FROM #tmp
)
UPDATE t
SET diff_with_prev_prive = t.new_value
FROM cte t
SELECT * FROM #tmp

Related

Oracle Aggregate(SUM function) after self-join

I have a table which contains ID, PARENT_ID AND COUNT.
EX)
+-----+-----------------------+--------------------------+
| ID | PARENT_ID | COUNT |...
+-----+-----------------------+--------------------------+
| 1 | NULL | 40 |...
| 2 | 1 | 10 |...
| 3 | 1 | 20 |...
| 4 | NULL | 35 |...
+-----+-----------------------+--------------------------+
And, i want result the sum of parent and sibling's count.
ID 1's count = ID 1's count + ID 2's count + ID 3's count
RESULT)
+-----+-----------------------+--------------------------+
| ID | PARENT_ID | COUNT |...
+-----+-----------------------+--------------------------+
| 1 | NULL | 70 |...
| 2 | 1 | 10 |...
| 3 | 1 | 20 |...
| 4 | NULL | 35 |...
+-----+-----------------------+--------------------------+
I used connect by to get the desired result, but I want to change the method as the above method uses too much oracle cpu.
Is there any way I can do this using sum function?
You can use the self join as follows:
SQL> with dataa (ID, PARENT_ID, CNT) as
2 (SELECT 1 , NULL, 40 FROM DUAL UNION ALL
3 SELECT 2 , 1 , 10 FROM DUAL UNION ALL
4 SELECT 3 , 1 , 20 FROM DUAL UNION ALL
5 SELECT 4 , NULL, 35 FROM DUAL)
6 -- your query starts from here
7 SELECT D1.ID, D1.PARENT_ID, D1.CNT + COALESCE(SUM(D2.CNT),0)
8 FROM DATAA D1 LEFT JOIN DATAA D2
9 ON D1.ID = D2.PARENT_ID
10 GROUP BY D1.ID, D1.PARENT_ID, D1.CNT
11 ORDER BY D1.ID;
ID PARENT_ID D1.CNT+COALESCE(SUM(D2.CNT),0)
---------- ---------- ------------------------------
1 70
2 1 10
3 1 20
4 35
SQL>

select rows based on equal columns values

consider we have a table with this columns
Id
fk_newsId
fk_NewsGroupId
fk_NewsZoneId
I need to select all records with same fk_NewsGroup and fk_NewsZone
something like this
+----+-----------+--------------+-------------+
| Id | fk_NewsId | fk_NewsGroup | fk_NewsZone |
+----+-----------+--------------+-------------+
| 1 | 60 | 5 | 8 |
| 2 | 30 | 5 | 8 |
| 3 | 31 | 9 | 20 |
| 4 | 5 | 9 | 20 |
| 5 | 12 | 9 | 20 |
| 6 | 1000 | 20 | 11 |
| 7 | 21 | 20 | 11 |
| 8 | 6 | 20 | 11 |
+----+-----------+--------------+-------------+
how can do that?
I tride group by like this
but it dosnt give desired output
select fk_NewsId, fk_NewsGroup,fk_NewsZone from tbl_test
group by fk_NewsGroup,fk_NewsZone,fk_NewsId
You can try to use COUNT with window function, to get the count by fk_NewsGroup and fk_NewsZone columns.
then get count greater than one.
SELECT *
FROM (
SELECT *,COUNT(*) OVER(PARTITION BY fk_NewsGroup,fk_NewsZone ORDER BY fk_NewsZone) cnt
FROM tbl_test
)t1
where t1.cnt > 1
dbfiddle
Not absolutely clear as to what you mean, but something like so:
SELECT t.Id, t.fk_NewsId, t.fk_NewsGroup, t.fk_NewsZone FROM tbl_test t
INNER JOIN (
SELECT fk_NewsGroup,fk_NewsZone, COUNT(*) AS Counted FROM tbl_test
GROUP BY fk_NewsGroup,fk_NewsZone
HAVING COUNT(*) > 1) g
ON t.fk_NewsGroup = g.fk_NewsGroup
AND t.fk_NewsZone = g.fk_NewsZone
DBFiddle example
I would use Group by and do it like:
select max(id) as Id, Max(fk_NewsId) as fk_NewsId, fk_NewsGroup,fk_NewsZone from #temp
group by fk_NewsGroup,fk_NewsZone

SQL Select different column values into one row

I have a table with values something like this:
StoreID | ItemID | OpeningClosingBalance | Total
1 | 1 | O | 10
1 | 1 | C | 20
1 | 2 | O | 5
1 | 2 | C | 7
To the first row is an opening balance of 10 for a specific item. The second row is the closing balance of 20 for that same item. Row 3 is opening for another item, and then is closing balance ect. I would like a query with results displayed as follows:
StoreID | ItemID | Openingbalance | ClosingBalance
1 | 1 | 10 | 20
1 | 2 | 5 | 7
Can anyone please assist?
You can do this with conditional aggregation:
select StoreId, ItemId,
max(case when OpeningClosingBalance = 'O' then total end) as openingbalance,
max(case when OpeningClosingBalance = 'C' then total end) as closingbalance
from t
group by StoreId, ItemId;

Return all records if more than 2/3 satisfy a value

I have a table representing multiple transactions by customers in any given day. I need to return all transactions per customer if two thirds or more of the transactions per customer were cash instead of credit card.
In the example below I want to return all of customers' 1, 4 transactions as they were the only customers to have 2 thirds or more of their transactions as cash:
+----------------+-------------+-----------------+------------------+
| Transaction ID | CustomerNum | TransactionType | TransactionValue |
+----------------+-------------+-----------------+------------------+
| 1 | 1 | Cash | 11 |
| 2 | 1 | Card | 12 |
| 3 | 1 | Cash | 13 |
| 4 | 2 | Cash | 14 |
| 5 | 2 | Card | 15 |
| 6 | 3 | Cash | 15 |
| 7 | 3 | Card | 11 |
| 8 | 3 | Cash | 12 |
| 9 | 3 | Card | 13 |
| 10 | 4 | Cash | 14 |
| 11 | 4 | Cash | 15 |
| 12 | 4 | Cash | 15 |
+----------------+-------------+-----------------+------------------+
This seems to work with the sample data:
declare #t table (TranID int not null,CustomerNum int not null,
TranType varchar(17) not null,TranValue decimal(18,0) not null)
insert into #t(TranID,CustomerNum,TranType,TranValue) values
( 1,1,'Cash',11), ( 2,1,'Card',12), ( 3,1,'Cash',13),
( 4,2,'Cash',14), ( 5,2,'Card',15),
( 6,3,'Cash',15), ( 7,3,'Card',11), ( 8,3,'Cash',12), ( 9,3,'Card',13),
(10,4,'Cash',14), (11,4,'Cash',15), (12,4,'Cash',15)
;With Counted as (
select *,
COUNT(*) OVER (PARTITION BY CustomerNum) as cnt,
SUM(CASE WHEN TranType='Cash' THEN 1 ELSE 0 END)
OVER (PARTITION BY CustomerNum) as cashcnt
from #t
)
select * from Counted
where cashcnt * 3 >= cnt * 2
I've gone with simple multiplication at the end to keep all of the maths as integers and avoid having to think about float/decimal and the representation of 2/3.
Result:
TranID CustomerNum TranType TranValue cnt cashcnt
----------- ----------- ----------------- ----------- ----------- -----------
1 1 Cash 11 3 2
2 1 Card 12 3 2
3 1 Cash 13 3 2
10 4 Cash 14 3 3
11 4 Cash 15 3 3
12 4 Cash 15 3 3
Try this:
select t.*
from (select customernum
from transactions
group by customernum
having sum(case when TransactionType = 'Cash' then 1.0 else 0.0 end) / sum(1.0) > 0.6666) c
join transactions t on t.customernum = c.customernum

select the most recent in all groups of with the same value in one column

The question isn't very clear, but I'll illustrate what I mean, suppose my table is like such:
item_name | date added | val1 | val2
------------------------------------
1 | date+1 | 10 | 20
1 | date | 12 | 21
2 | date+1 | 5 | 6
3 | date+3 | 3 | 1
3 | date+2 | 5 | 2
3 | date | 3 | 1
And I want to select row 1, 3, 4 as they are the most recent entries for each item
Try this:
select *
from tableX t1
where t1.date_added = (select max(t2.date_added)
from tableX t2
where t2.item_name = t1.item_name )