SQL: looking for rounded duplicates - sql

I have a table that possibly seems to have transactions that are rounded to the nearest dollar before being reversed off
Given something like this:
AccountID Transaction
1 20.05
1 -20.00
1 17.00
2 32.35
3 40.78
3 -41.00
4 15.00
4 -15.00
5 10.03
5 10.00
I want to see how many accounts have this pattern of a value, and a rounded negative counter-part. So, I want to pull AccountIDs 1 and 3 from the table above, for example.
I am not interested in AccountID 4, as that is an exact (absolute value) match, or AccountID 5, which has a "rounded duplicate" but is not a negative counter-part.
Any one know how I might accomplish this in SQL?

Just compare with rounded values.
SELECT
T.*
FROM
Transactions AS T
WHERE
EXISTS (
SELECT
'inverted rounded operation detected'
FROM
Transactions AS N
WHERE
T.AccountID = N.AccountID AND
ROUND(T.[Transaction], 0) = -1 * ROUND(N.[Transaction], 0))

You may have multiple transactions that match. Hence, you should use row_number() for the matching:
with t as (
select t.*,
row_number() over (partition by accountid, floor(transaction) order by transaction) as seqnum
from transactions t
)
select t.*
from t
where exists (select 1
from t t2
where t2.accountid = t.accountid and
floor(t2.transaction) = floor(t.transaction) and
t2.seqnum = t.seqnum
);

Round all the positive transaction values and find all the accounts that have an equal negative value.
select acct.accountid
from
acct
inner join (
select
accountid,
round(transaction, 0) as whole_trans
from
acct
where
transaction > 0
and transaction != round(transaction, 0)
) as unround on unround.accountid = acct.accountid
where
unround.whole_trans = -1 * acct.transaction;

Related

Oracle SQL Group by and sum with multiple conditions

I attached a capture of two tables:
- the left table is a result of others "Select" query
- the right table is the result I want from the left table
The right table can be created following the next conditions:
When the same Unit have all positive or all negative
energy values, the result remain the same
When the same Unit have positive and negative energy values then:
Make a sum of all Energy for that Unit(-50+15+20 = -15) and then take the maximum of absolut value for the Energy.e.g. max(abs(energy))=50 and take the price for that value.
I use SQL ORACLE.
I realy appreciate the help in this matter !
http://sqlfiddle.com/#!4/eb85a/12
This returns desired result:
signs CTE finds out whether there are positive/negative values, as well as maximum ABS energy value
then, there's union of two selects: one that returns "original" rows (if count of distinct signs is 1), and one that returns "calculated" values, as you described
SQL> with
2 signs as
3 (select unit,
4 count(distinct sign(energy)) cnt,
5 max(abs(energy)) max_abs_ene
6 from tab
7 group by unit
8 )
9 select t.unit, t.price, t.energy
10 from tab t join signs s on t.unit = s.unit
11 where s.cnt = 1
12 union all
13 select t.unit, t2.price, sum(t.energy)
14 from tab t join signs s on t.unit = s.unit
15 join tab t2 on t2.unit = s.unit and abs(t2.energy) = s.max_abs_ene
16 where s.cnt = 2
17 group by t.unit, t2.price
18 order by unit;
UNIT PRICE ENERGY
-------------------- ---------- ----------
A 20 -50
A 50 -80
B 13 -15
SQL>
Though, what do you expect if there was yet another "B" unit row with energy = +50? Then two rows would have the same MAX(ABS(ENERGY)) value.
A union all might be the simplest solution:
with t as (
select t.*,
max(energy) over (partition by unit) as max_energy,
min(energy) over (partition by unit) as min_energy
from t
)
select unit, price, energy
from t
where max_energy > 0 and min_energy > 0 or
max_energy < 0 and min_enery < 0
union all
select unit,
max(price) keep (dense_rank first order by abs(energy)),
sum(energy)
from t
where max_energy > 0 and min_energy < 0
group by unit;

SQL Get closest value to a number

I need to find the closet value of each number in column Divide from the column Quantity and put the value found in the Value column for both Quantities.
Example:
In the column Divide the value of 5166 would be closest to Quantity column value 5000. To keep from using those two values more than once I need to place the value of 5000 in the value column for both numbers, like the example below. Also, is it possible to do this without a loop?
Quantity Divide Rank Value
15500 5166 5 5000
1250 416 5 0
5000 1666 5 5000
12500 4166 4 0
164250 54750 3 0
5250 1750 3 0
6250 2083 3 0
12250 4083 3 0
1750 583 2 0
17000 5666 2 0
2500 833 2 0
11500 3833 2 0
1250 416 1 0
There are a couple of answers here but they both use ctes/complex subqueries. There is a much simpler/faster way by just doing a couple of self joins and a group-by
https://www.db-fiddle.com/f/rM268EYMWuK7yQT3gwSbGE/0
select
min(min.quantity) as minQuantityOverDivide
, t1.divide
, max(max.quantity) as maxQuantityUnderDivide
, case
when
(abs(t1.divide - coalesce(min(min.quantity),0))
<
abs(t1.divide - coalesce(max(max.quantity),0)))
then max(max.quantity)
else min(min.quantity) end as cloestQuantity
from t1
left join (select quantity from t1) min on min.quantity >= t1.divide
left join (select quantity from t1) max on max.quantity < t1.divide
group by
t1.divide
If I understood the requirements, 5166 is not closest to 5000 - it's closes to 5250 (delta of 166 vs 84)
The corresponding query, without loops, shall be (fiddle here: https://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=be434e67ba73addba119894a98657f17).
(I added a Value_Rank as it's not sure if you want Rank to be kept or recomputed)
select
Quantity, Divide, Rank, Value,
dense_rank() over(order by Value) as Value_Rank
from
(
select
Quantity, Divide, Rank,
--
case
when abs(Quantity_let_delta) < abs(Quantity_get_delta) then Divide + Quantity_let_delta
else Divide + Quantity_get_delta
end as Value
from
(
select
so.Quantity, so.Divide, so.Rank,
-- There is no LessEqualThan, assume GreaterEqualThan
max(isnull(so_let.Quantity, so_get.Quantity)) - so.Divide as Quantity_let_delta,
-- There is no GreaterEqualThan, assume LessEqualThan
min(isnull(so_get.Quantity, so_let.Quantity)) - so.Divide as Quantity_get_delta
from
SO so
left outer join SO so_let
on so_let.Quantity <= so.Divide
--
left outer join SO so_get
on so_get.Quantity >= so.Divide
group by so.Quantity, so.Divide, so.Rank
) so
) result
Or, if by closest you mean the previous closest (fiddle here: https://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=b41fb1a3fc11039c7f82926f8816e270).
select
Quantity, Divide, Rank, Value,
dense_rank() over(order by Value) as Value_Rank
from
(
select
so.Quantity, so.Divide, so.Rank,
-- There is no LessEqualThan, assume 0
max(isnull(so_let.Quantity, 0)) as Value
from
SO so
left outer join SO so_let
on so_let.Quantity <= so.Divide
group by so.Quantity, so.Divide, so.Rank
) result
You don't need a loop, basically you need to find which is lowest difference between the divide and all the quantities (first cte). Then use this distance to find the corresponding record (second cte) and then join with your initial table to get the converted values (final select)
;with cte as (
select t.Divide, min(abs(t2.Quantity-t.Divide)) as ClosestQuantity
from #t1 as t
cross apply #t1 as t2
group by t.Divide
)
,cte2 as (
select distinct
t.Divide, t2.Quantity
from #t1 as t
cross apply #t1 as t2
where abs(t2.Quantity-t.Divide) = (select ClosestQuantity from cte as c where c.Divide = t.Divide)
)
select t.Quantity, cte2.Quantity as Divide, t.Rank, t.Value
from #t1 as t
left outer join cte2 on t.Divide = cte2.Divide

How to SELECT top N rows that sum to a certain amount?

Suppose:
MyTable
--
Amount
1
2
3
4
5
MyTable only has one column, Amount, with 5 rows. They are not necessarily in increasing order.
How can I create a function, which takes a #SUM INT, and returns the TOP N rows that sum to this amount?
So for input 6, I want
Amount
1
2
3
Since 1 + 2 + 3 = 6. 2 + 4 / 1 + 5 won't work since I want TOP N ROWS
For 7/8/9/10, I want
Amount
1
2
3
4
I'm using MS SQL Server 2008 R2, if this matters.
Saying "top N rows" is indeed ambiguous when it comes to relational databases.
I assume that you want to order by "amount" ascending.
I would add a second column (to a table or view) like "sum_up_to_here", and create something like that:
create view mytable_view as
select
mt1.amount,
sum(mt2.amount) as sum_up_to_here
from
mytable mt1
left join mytable mt2 on (mt2.amount < mt1.amount)
group by mt1.amount
or:
create view mytable_view as
select
mt1.amount,
(select sum(amount) from mytable where amount < mt1.amount)
from mytable mt1
and then I would select the final rows:
select amount from mytable_view where sum_up_to_here < (some value)
If you don't bother about performance you may of course run it in one query:
select amount from
(
select
mt1.amount,
sum(mt2.amount) as sum_up_to_here
from
mytable mt1
left join mytable mt2 on (mt2.amount < mt1.amount)
group by mt1.amount
) t where sum_up_to_here < 20
One approach:
select t1.amount
from MyTable t1
left join MyTable t2 on t1.amount > t2.amount
group by t1.amount
having coalesce(sum(t2.amount),0) < 7
SQLFiddle here.
In Sql Server you can use CDEs to make it pretty simple to read.
Here is a CDE I did to sum up totals used in sequence. The CDE is similar to the joins above, and holds the total up to any given index. Outside of the CDE I join it back to the original table so I can select it along with other fields.
;with summrp as (
select m1.idx, sum(m2.QtyReq) as sumUsed
from #mrpe m1
join #mrpe m2 on m2.idx <= m1.idx
group by m1.idx
)
select RefNum, RefLineSuf, QtyReq, ProjectedDate, sumUsed from #mrpe m
join summrp on summrp.idx=m.idx
In SQL Server 2012 you can use this shortcut to get a result like Grzegorz's.
SELECT amount
FROM (
SELECT * ,
SUM(amount) OVER (ORDER BY amount ASC) AS total
from demo
) T
WHERE total <= 6
A fiddle in the hand... http://sqlfiddle.com/#!6/b8506/6

Query to select last order (entry) of every product belonging to user that's not returned

I am stuck with rather confusing query.
Assume I have a ProductLending table that tracks what product each user has borrowed, when it was renewed, was it returned or not etc.. Given a user, I want to be able to select, all unique products that are still with the user.
table example:
userid DateRenewed ProductId isReturned
````````````````````````````````````````````````
1 2011-07-21 15 0
1 2011-08-20 16 0
1 2011-09-21 15 1
2 2011-09-21 17 0
1 2011-09-21 15 0
This is a mock up so sorry if it's not accurate.
Now, given userId = 1, I want to select just unique productId that are NOT returned, but are with the user. So this should give me 15, 16 as result, as even though 15 was returned, it was re-borrowed. If we delete the last row, then the result would just be 16, since user has only 16 with him.
I tried ordering by dateRenewed and selecting top 1 but it did totally something else.. how do I construct a query for this please?
If product is not returned by user, then the sum of bought products must be larger than sum of returned products
SELECT userid,ProductId FROM <table>
GROUP BY userid,ProductId HAVING SUM(CASE CAST(isReturned AS INT) WHEN 0 THEN 1 ELSE 0 END)-SUM(CAST(isReturned AS INT))>0
Try this:
;WITH qry AS
(
SELECT *,
ROW_NUMBER() OVER(PARTITION BY userID, ProductID ORDER BY DateRenewed DESC) rn
FROM YourTable
)
SELECT *
FROM qry
WHERE rn = 1 AND isReturned = 0;
select distinct ProductId
from TABLE_NAME t1
where UserId= #UserId
and IsReturned = 0
and not exists
(
select *
from TABLE_NAME t2
where t2.UserId = t1.UserId
and t2.ProductId = t1.ProductId
and t2.IsReturned = 1
and t2.DateRenewed > t1.DateRenewed
)

SQL select row-wise increase in amount of running total column

Suppose I have a table with columns (DayId, RunningTotal):
DayId RunningTotal
---------------------
1 25
3 50
6 100
9 200
10 250
How can I select the DayId and the amount the RunningTotal has increased from the previous day? i.e. how can I select:
DayId DayTotal
---------------------
1 25
3 25
6 50
9 100
10 50
The only current method I know is with a while loop I am trying to factor out. Also, the DayId has no regular rules, just that it is some increasing integer value, but it increases by an irregular amount as shown in the example table.
EDIT: using MS SQL Server 2005
with cte as (
select dayid, runningtotal, row_number() over (order by dayid asc) as row_index
from #the_table
)
select cur.dayid, cur.runningtotal - coalesce(prev.runningtotal, 0) as daytotal
from cte cur
left join cte prev on prev.row_index = cur.row_index - 1
(I really wish they'd implemented support for the lead and lag functions in SQL Server :|)
There is probably a more succinct way than this, but try:
select t3.DayId,
case when t4.DayId is null then t3.RunningTotal else t3.RunningTotal - t4.RunningTotal end as DayTotal
from (
select t1.DayId, max(t2.DayId) as PreviousDayId as
from MyTable t1
left outer join MyTable t2 on t2.DayId < t1.DayId
group by t1.DayId
) a
inner join MyTable t3 on a.DayId = t3.DayId
left outer join MyTable t4 on a.PreviousDayId = t4.DayId