Find the reversal transactions in my table - sql

I have a table data like the bellow
Date Amount
01022017 300
01052017 -300
03042016 200
06112016 400
05042016 -200
30012016 150
I need only the list like below (the negatives are reversals happen to those transactions).
My expected result like
Date Amount
06112016 400
30012016 150
I need to avoid the reversal transactions. But here in my table I don't have any reference column to indicate the transaction as reversal or normal transaction.

You can achieve this with a LEFT JOIN:
SELECT
m.Date
, m.Amount
FROM myTable m
LEFT JOIN myTable m2 on m.Amount = (-1 * m2.Amount)
WHERE m2.Date IS NULL

As noted in the comments, relying in the amount seems flaky to me but here is a try:
SELECT t.date, t.amount
FROM TableName t
WHERE NOT EXISTS (SELECT 1
FROM TableName t1 INNER JOIN TableName t2
ON T1.amount = -t2.amount and T1.date<=t2.date
WHERE (t.date=t1.date or t.date=t2.date) and t.amount=t1.amount
Note that this will not display amounts that appear more than twice.

based on your Assumed table data using Lag function we can do this
Sample Data :
DECLARE #Table1 TABLE
(Dates int, Amount int)
;
INSERT INTO #Table1
(Dates, Amount)
VALUES
(01022017, 300),
(01052017, -300),
(03042016, 200),
(06112016, 400),
(05042016, -200),
(30012016, 150)
;
Script :
Select t.Amount from (
select amount,
coalesce(lag(amount) over (PARTITION BY RIGHT (dates,4) order by (select null)), 0) + amount as diff
from #Table1) T
WHERE NOT EXISTS (select 1 from #Table1 where T.diff = Amount ) and T.diff <> 0

Try this one for oracle, The Problem with the below query is it reads the main table twice.
select Date,Amount from table where amount>0
minus
select Date,-1*amount as Amount from table where amount<0;

Related

Subtract two values of same column based on another column and save the result in a new row

I have following table rows in SQL server.
Now, I want an end result like this (Available quantity = (Total Quantity - Rented out Quantity) WHERE YYYYMM AND BOOKNAME IS SAME)
To generate the 2 required rows, you can use a GROUP BY query
SELECT t.YYYYMM, STATUS = 'AVAILABLE', t.BOOKNAME,
SUM (CASE WHEN t.STATUS = 'TOTAL' THEN +t.QUANTITY ELSE -t.QUANTITY END) AS QUANTITY
FROM a_table t
GROUP BY t.YYYYMM, t.BOOKNAME
You can insert that back into the table
Or
can use UNION ALL in a SELECT query when you need to display it
INSERT INTO table (yyyymm, status, bookname, quantity)
SELECT t1.yyyymm, 'AVAILABLE', t1.bookname, t1.quantity - t2.quantity
FROM table t1
JOIN table t2 USING (yyyymm, bookname)
WHERE t1.status = 'TOTAL'
AND t2.status = 'RENTED OUT'
table (yyyymm, status, bookname) must be unique by proper constraint/index.
The record will be inserted only when both source records are present. If it is possible that 'RENTED OUT' is absent and its value must be assumed as zero, use LEFT JOIN ans COALESCE additionally (and move the condition by t2.status to ON clause).
Can you please be kind enough to edit your answer to include the assumption when 'RENTED OUT' is absent?
INSERT INTO table (yyyymm, status, bookname, quantity)
SELECT t1.yyyymm, 'AVAILABLE', t1.bookname, t1.quantity - COALESCE(t2.quantity, 0)
FROM table t1
LEFT JOIN table t2 ON t1.yyyymm = t2.yyyymm
AND t1.bookname = t2.bookname
AND t2.status = 'RENTED OUT'
WHERE t1.status = 'TOTAL'
This query will definitely works for you please see screen shot below Thank you
create table #temp (YYYYMM varchar(10),Status varchar(20),BOOKNAME
varchar(20),QUANTITY INT)
INSERT INTO #temp VALUES ('201912','TOTAL','PHYSICS101',260)
INSERT INTO #temp VALUES ('201912','RENTED OUT','PHYSICS101',100)
INSERT INTO #temp VALUES ('201912','TOTAL','CHEMISTRY101',250)
INSERT INTO #temp VALUES ('201912','RENTED OUT','CHEMISTRY101',50)
;WITH CTE AS (
SELECT YYYYMM,Status,BOOKNAME,QUANTITY,
CASE WHEN BOOKNAME IN('CHEMISTRY101','PHYSICS101') AND STATUS='TOTAL' THEN QUANTITY
END TOTAL
,CASE WHEN BOOKNAME IN('CHEMISTRY101','PHYSICS101') AND STATUS='RENTED OUT' THEN
QUANTITY END RENTED_OUT
FROM #temp
)
SELECT YYYYMM,Status,BOOKNAME,QUANTITY FROM #temp
UNION ALL
SELECT YYYYMM,'AVAILABLE' Status,BOOKNAME,SUM(ISNULL(TOTAL,0)-ISNULL(RENTED_OUT,0))
QUANTITY FROM CTE
GROUP BY YYYYMM,BOOKNAME
DROP TABLE #temp

SQL QUERY : multiple records against one column value . need to compare another column value to fetch one record

A sample table us like
STATUS INVOICE
=======================
processed 100
reconciled 100
reconciled 200
paid 300
paid 100
paid 200
Output should be
STATUS INVOICE
=======================
processed 100
reconciled 200
paid 300
Logic : If there are multiple statuses against an invoice number , then we should follow the below order to fetch .
Processed > reconciled > paid
Please help me with the SQL query statement for this requirement .
This is a prioritization query. You can handle it using row_number():
select t.*
from (select t.*,
row_number() over (partition by invoice
order by case status when 'Processed' then 1 when 'reconciled' then 2 when 'paid' then 3 else 4 end
) as seqnum
from t
) t
where seqnum = 1;
You need conditional ordering with row_number() :
select top (1) with ties t.*
from table t
order by row_number() over (partition by invoice
order by (case status
when 'Processed'
then 1
when 'reconciled'
then 2
when 'paid'
then 3
else 4
end)
);
Others answers are ok but I'm posting it for the cases there's hundres (or more) of categories because in this case populating a table variable is better than writing lots and lots of CASE statements.
The trick here is to populate a table variable or temp table with your ordering rules and just aggregate by it.
create table dbo.[SAMPLE]
(
[STATUS] varchar(30) not null
,[INVOICE] int not null
)
GO
insert into dbo.[SAMPLE]
values
('processed', 100)
,('reconciled', 100)
,('reconciled', 200)
,('paid', 300)
,('paid', 100)
,('paid', 200)
GO
The below is a partial result showing how it get grouped by your ordering rules
--Processed > reconciled > paid
declare #Ordering as table
(
[STATUS] varchar(30) not null
,[Order] smallint not null
)
insert into #Ordering
values
('processed', 3)
,('reconciled',2)
,('paid',1)
select sp.[INVOICE], max(ord.[Order]) as Precedence
from dbo.[SAMPLE] sp
join #Ordering ord on ord.[STATUS] = sp.[STATUS]
group by sp.[INVOICE]
and below the final query with the expected results
--Processed > reconciled > paid
declare #Ordering as table
(
[STATUS] varchar(30) not null
,[Order] smallint not null
)
insert into #Ordering
values
('processed', 3)
,('reconciled',2)
,('paid',1)
select ord.[STATUS], grouped.INVOICE
from
(
select sp.[INVOICE], max(ord.[Order]) as Precedence
from dbo.[SAMPLE] sp
join #Ordering ord on ord.[STATUS] = sp.[STATUS]
group by sp.[INVOICE]
) as grouped
join #Ordering ord on ord.[Order] = grouped.Precedence
It can be also a interesting solution from performance perspective (acid test required of course).
if you have a status table and the order of status like this
id desc
1 processed
2 reconcilied
3 paid
the better way is joining with this tatble, group by invoice and select max(id)
select i.invoice, max(s.id)
from status s left outer join invoice i
on s.desc = i.status
group by i.invoice
if you havn't this table you can use with to create a virtual table and do this or you can use the case then
https://modern-sql.com/feature/with
https://learn.microsoft.com/it-it/sql/t-sql/language-elements/case-transact-sql?view=sql-server-2017
You can try this.
DECLARE #SampleDate TABLE (STATUS VARCHAR(20), INVOICE INT)
INSERT INTO #SampleDate VALUES
('processed', 100),
('reconciled', 100),
('reconciled', 200),
('paid', 300),
('paid', 100),
('paid', 200)
SELECT STATUS, INVOICE FROM (
SELECT T.*, ROW_NUMBER() OVER(PARTITION BY INVOICE ORDER BY St.ID) AS RN FROM #SampleDate T
INNER JOIN (VALUES (1,'processed'), (2,'reconciled'), (3,'paid')) St(Id, Name) ON T.STATUS = St.Name
) AS X WHERE RN= 1
Result:
STATUS INVOICE
-------------------- -----------
processed 100
reconciled 200
paid 300
WITH TempData As (SELECT MAX(INVOICE) AS INVOICE, [STATUS], CASE WHEN [STATUS] = 'processed' THEN 1 WHEN [STATUS] = 'reconciled' THEN 2 WHEN [STATUS] = 'paid' THEN 3 ELSE 4 END AS SEQ
FROM SAMPLETEST GROUP BY [STATUS])
SELECT [STATUS], INVOICE FROM TempData ORDER BY TempData.SEQ;

TSQL: How do i detect and insert missing records

I have T-SQL Table below.
ID Cost MaxCost
-------------------------------
2 200 300
3 400 1000
6 20 100
The above table must have 10 rows with IDs 1 to 10. So its missing 7 rows. How do i insert missing rows with proper ID. The cost & maxcost for missing rows will be zero. Do i need to create a temp table that holds 1 to 10 numbers?
No need for temp table, simple tally derived table and LEFT OUTER JOIN are sufficient:
CREATE TABLE #tab(ID INT, Cost INT, MaxCost INT);
INSERT INTO #tab(ID, Cost, MaxCost)
VALUES (2, 200,300),(3, 400, 1000) ,(6, 20, 100);
DECLARE #range_start INT = 1
,#range_end INT = 10;
;WITH tally AS
(
SELECT TOP 1000 r = ROW_NUMBER() OVER (ORDER BY name)
FROM master..spt_values
)
INSERT INTO #tab(id, Cost, MaxCost)
SELECT t.r, 0, 0
FROM tally t
LEFT JOIN #tab c
ON t.r = c.ID
WHERE t.r BETWEEN #range_start AND #range_end
AND c.ID IS NULL;
SELECT *
FROM #tab
ORDER BY ID;
LiveDemo
EDIT:
Tally table is simply number table. There are many ways to achieve it with subquery:
recursive cte
ROW_NUMBER() from system table that holds many values (used here)
UNION ALL and CROSS JOIN
VALUES(...)
using OPENJSON (SQL Server 2016+)
...
The TOP 1000 will generate only 1000 records if you know that you need more you can use:
SELECT TOP 1000000 r = ROW_NUMBER() OVER (ORDER BY (SELECT 1))
FROM master..spt_values c
CROSS JOIN master..spt_values c2;
Since your number of rows is low you could just define the data explicitly...
CREATE TABLE Data(ID INT, Cost INT, MaxCost INT);
INSERT INTO Data(ID, Cost, MaxCost) VALUES(2, 200, 300);
INSERT INTO Data(ID, Cost, MaxCost) VALUES(3, 400, 1000);
INSERT INTO Data(ID, Cost, MaxCost) VALUES(6, 20, 100);
and the query...
select *
from (VALUES(1),(2),(3),(4),(5),(6),(7),(8),(9),(10)) RowNums (ID)
left outer join Data on RowNums.ID = Data.ID
The first part defines a column ID with rows 1-10, it then left outer joins to your data. The beauty of this is that it is very readable.
I like to google for new and better ways to do things.. so i stumbled over this post and...Well what worked good in SQL7 and works good in SQL2016 is to just use a outer join and look for NULL values(null is missing data) ....
insert into DestTable (keyCol1,col1,col2,col3...)
select keyCol1,col1,col2,col3,...)
from SouceTable as s
left outer join DestTable as d on d.KeyCol1=s.KeyCol1
where d.KeyCol1 is null
and ...
feel free to test it
wrap your statement in a transaction, delete a few rows and see them come back in the select statement that would normally insert the rows in the destination table...
BEGIN TRAN
--> delete a subset of the data, in this case 5 rows
set rowcount 5;
-->delete and show what is deleted
delete from DestTable;
OUTPUT deleted.*,'DELETD' as [Action]
--> Perform the select to see if the selected rows that are retured match the deleted rows
--insert into DestTable (keyCol1,col1,col2,col3...)
Select keyCol1,col1,col2,col3,...)
from SouceTable as s
left outer join DestTable as d on d.KeyCol1=s.KeyCol1
where d.KeyCol1 is null
and ...
ROLLBACK
another way would be a TSQL merge, google that if you need to also update and optionally delete...

how to get query value from 1 row to use to another row?

This is example query:
payment_Type payment_value cost_type cost value
Cost I 100 Registration 40
Cost I 100 books 40
Cost I 100 Lab 40
The COST I has 3 elements Cost_Type that have their own Cost_value.
I want to manipulate like this:
payment_Type payment_value cost_type cost value Payment_by_cost_type
Cost I 100 Registration 40 40
Cost I 100 books 40 40
Cost I 100 Lab 40 20
The point is the I want to divided the payment_value into each cost_value. In the example the payment_by_cost becomes 40, 40, 20 = 100.
The lab cost_value is 40 but it can assign value is 20 because remains from the divided 2 cost type above.
Is it possible that I can use the value from Payment_by_cost_type in the next row record? I have been trying to insert the value Payment_by_cost_type to a temporary table but select cannot have insert statement.
Does anyone have any ideas on how to solve this? I've been consulting to DWH he said it must using Store procedure it cannot done by query.
I guess your table contains not only "Cost I" but other values so here is a query to output results for all groups (by Payment_type) in the table:
;with table1 as
(select
t.*,
row_number()
OVER
(PARTITION BY payment_Type order by cost_type) rn
from t
)
,table2 as
( select t4.*,isnull((select sum(cost_value) from table1
where table1.payment_type=t4.payment_type and rn<t4.rn),0) CumSum
from table1 t4
)
select payment_type,payment_value,cost_type,cost_value,
case when cost_value+CumSum<=payment_value then cost_value
else
payment_value-Cumsum
end
from table2
order by Payment_type,rn;
SQLFIDDLE demo
You need to define some kind of order for your records to define in which order the payments should be applied
Once you have done that (i'm using ID in this example)...
select *
, case
when payment_value-(select isnull(SUM(cost_value),0) from yourtable t2 where t2.id<t1.id)<cost_value
then payment_value-(select isnull(SUM(cost_value),0) from yourtable t2 where t2.id<t1.id)
else cost_value
end
from yourtable t1
Doing it step by step using common table expressions.
declare #t table (
payment_type varchar(20),
payment_value int,
cost_type varchar(20),
cost_value int,
cost_id int --for the ordering
)
insert #t values
('Cost I',100,'Registration',40,1),
('Cost I',100,'books',40,2),
('Cost I',100,'Lab',40,3),
('Cost 2',100,'Registration',40,4),
('Cost 2',100,'books',50,5),
('Cost 2',100,'Lab',40,6)
--get count for each payment_type to determine last row
;with payment_value_cte(payment_type,payment_value,count) as
(
select payment_type,payment_value,COUNT(*) from #t group by payment_type,payment_value
),
--use sequential index for each row in payment type
payment_value_index_cte(
payment_type ,
payment_value,
cost_type,
cost_value,
cost_id,
row) as
(
select *,ROW_NUMBER() OVER(PARTITION BY payment_type ORDER BY cost_id) from #t --assumes order is by an id
),
--get sum of each row for payment type except last row
payment_value_sum_except_last_cte(
payment_type,
payment_value,
current_sum) as
(
select pi.payment_type,pi.payment_value,SUM(cost_value)
from payment_value_index_cte pi
inner join payment_value_cte pt on pt.payment_type = pi.payment_type
where pi.row < pt.count
group by pi.payment_type,pi.payment_value
)
select
pi.payment_type,pi.payment_value,pi.cost_type,pi.cost_value,
--if last row calculate difference, else use the cost_value
case when pi.row = pt.count then pt.payment_value - pe.current_sum else pi.cost_value end [Payment_by_cost_type]
from payment_value_index_cte pi
inner join payment_value_cte pt on pt.payment_type = pi.payment_type
inner join payment_value_sum_except_last_cte pe on pe.payment_type = pi.payment_type
SELECT payment_Type, payment_value, cost_type, cost_value,
CASE WHEN ROW_NUMBER() OVER (ORDER BY(SELECT 1)) = COUNT(*) OVER (PARTITION BY payment_Type)
THEN SUM(cost_value) OVER (PARTITION BY payment_Type) - payment_value
ELSE cost_value END AS Payment_by_cost_type
FROM dbo.your_table
Demo on SQLFiddle

How to select info from row above?

I want to add a column to my table that is like the following:
This is just an example of how the table is structured, the real table is more than 10.000 rows.
No_ Name Account_Type Subgroup (New_Column)
100 Sales 3
200 Underwear 0 250 *100
300 Bikes 0 250 *100
400 Profit 3
500 Cash 0 450 *400
So for every time there is a value in 'Subgroup' I want the (New_Column) to get the value [No_] from the row above
No_ Name Account_Type Subgroup (New_Column)
100 Sales 3
150 TotalSales 3
200 Underwear 0 250 *150
300 Bikes 0 250 *150
400 Profit 3
500 Cash 0 450 *400
There are cases where the table is like the above, where two "Headers" are above. And in that case I also want the first above row (150) in this case.
Is this a case for a cursor or what do you recommend?
The data is ordered by No_
--EDIT--
Starting from the first line and then running through the whole table:
Is there a way I can store the value for [No_] where [Subgroup] is ''?
And following that insert this [No_] value in the (New_Column) in each row below having value in the [Subgroup] row.
And when the [Subgroup] row is empty the process will keep going, inserting the next [No_] value in (New_Column), that is if the next line has a value in [Subgroup]
Here is a better image for what I´m trying to do:
SQL Server 2012 suggests using Window Offset Functions.
In this case : LAG
Something like this:
SELECT [No_]
,[Name]
,[Account_Type]
,[Subgroup]
,LAG([No_]) OVER(PARTITION BY [Subgroup]
ORDER BY [No_]) as [PrevValue]
FROM table
Here is an example from MS:
http://technet.microsoft.com/en-us/library/hh231256.aspx
The ROW_NUMBER function will allow you to find out what number the row is, but because it is a windowed function, you will have to use a common table expression (CTE) to join the table with itself.
WITH cte AS
(
SELECT [No_], Name, Account_Type, Subgroup, [Row] = ROW_NUMBER() OVER (ORDER BY [No_])
FROM table
)
SELECT t1.*, t2.[No_]
FROM cte t1
LEFT JOIN cte t2 ON t1.Row = t2.Row - 1
Hope this helps.
Next query will return Name of the parent row instead of the row itself, i.e. Sales for both Sales, Underwear, Bikes; and Profit for Profit, Cash:
select ISNULL(t2.Name, t1.Name)
from table t1
left join table t2 on t1.NewColumn = t2.No
So in SQL Server 2008 i created test table with 3 values in it:
create table #ttable
(
id int primary key identity,
number int,
number_prev int
)
Go
Insert Into #ttable (number)
Output inserted.id
Values (10), (20), (30);
Insert in table, that does what you need (at least if understood correctly) looks like this:
declare #new_value int;
set #new_value = 13; -- NEW value
Insert Into #ttable (number, number_prev)
Values (#new_value,
(Select Max(number) From #ttable t Where t.number < #new_value))
[This part added] And to work with subgroup- just modify the inner select to filter out it:
Select Max(number) From #ttable t
Where t.number < #new_value And Subgroup != #Subgroup
SELECT
No_
, Name
, Account_Type
, Subgroup
, ( SELECT MAX(above.No_)
FROM TableX AS above
WHERE above.No_ < a.No_
AND above.Account_Type = 3
AND a.Account_Type <> 3
) AS NewColumn
FROM
TableX AS a