finding rows against summed value of specific id's in sql - sql

I have a table like below--
Id| Amount|DateAdded |
--|-------|-----------|
1 20 20-Jun-2018
1 10 05-Jun-2018
1 4 21-May-2018
1 5 15-May-2018
1 15 05-May-2018
2 25 15-Jun-2018
2 25 12-Jun-2018
2 65 05-Jun-2018
2 65 20-May-2018
Here If I sum up the Amount of Id = 1 then I will get 54 as the sum result. I want to find those rows of Id = 1 whose sum is not greater then exact 35 or any given value
In case of given value 35 the expected Output for id = 1 should be--
Id| Amount|DateAdded |
--|-------|-----------|
1 20 20-Jun-2018
1 10 05-Jun-2018
1 4 21-May-2018
1 5 15-May-2018
In case of given value 50 the expected Output for Id = 2 should be--
Id| Amount|DateAdded |
--|-------|-----------|
2 25 15-Jun-2018
2 25 12-Jun-2018

You would use a cumulative sum. To get all the rows:
select t.*
from (select t.*,
sum(amount) over (partition by id order by dateadded) as running_amount
from t
) t
where t.running_amount - amount < 35;
To get just the row that passes the mark:
where t.running_amount - amount < 35 and
t.running_amount >= 35

Related

How to do this in SQL (PostgreSQL Window Function?)

I have a situation in SQL (PostgreSQL specifically) that I'm struggling with. The schema/model that I'm working with is not under my control and not something I'm able to alter, so I am trying to figure out the best way to deal with the cards I've been dealt.
First, the schema, simplified for this question, but essentially it's invoice (Type = T) and transaction (Type <> T) lines combined into the same table. There can and will be n-number of tranaction lines per invoice and n-number of invoices per client.
Id
Type
InvoiceNo
ClientId
100
I
100
1
99
X
0
1
98
S
0
1
97
T
0
1
96
I
99
1
95
X
0
1
94
S
0
1
What I ultimately would like to end up with is something like the below, with the Invoice (Type = I) records removed and the Transaction (Type <> T) records that fall after each Invoice record populated with it's corresponding InvoiceId value.
Id
Type
InvoiceNo
ClientId
99
X
100
1
98
S
100
1
97
T
100
1
95
X
99
1
94
S
99
1
So far, the closest I've been able to get, which isn't very close, is using the below SQL:
select
t1.Id,
t1.Type,
t2.InvoiceNo,
t1.ClientId
from table AS t1
join (select
Id,
InvoiceNo,
ClientId
from table
where type = 'I') as t2
on t1.ClientId = t2.ClientId
where t1.ClientId = t2.ClientId and t1.Id <= t2.Id and t1.Type <> 'I'
The result of that looks something like the below, which works fine for the first invoice per client and then creates extra transaction records for each invoice
Id
Type
InvoiceNo
ClientId
99
X
100
1
98
S
100
1
97
T
100
1
95
X
100
1
95
X
99
1
94
S
100
1
94
S
99
1
Any help or guidance is much appreciated!
** Updated with more complex example **
Source:
Id
Type
InvoiceNo
ClientId
1
X
0
1
2
I
97
1
3
S
0
2
4
X
0
2
5
S
0
1
6
I
98
2
7
S
0
1
8
X
0
1
9
I
99
1
10
T
0
1
11
S
0
1
12
X
0
1
13
I
100
1
Playing with the answer below, I came up with:
select * from (select t.*,
max(InvoiceNo) filter (where type = 'I') over (partition by clientid order by id DESC) as imputed_invoiceno
from t) as x
where Type <> 'I';
Which gets me close:
Id
Type
InvoiceNo
ClientId
imputed_invoiceno
12
X
0
1
100
11
S
0
1
100
10
T
0
1
100
8
X
0
1
99
7
S
0
1
99
5
S
0
1
99
1
X
0
1
99
4
X
0
2
98
3
S
0
2
98
Best case result:
Id
Type
InvoiceNo
ClientId
12
X
100
1
11
S
100
1
10
T
100
1
8
X
99
1
7
S
99
1
5
S
99
1
1
X
97
1
4
X
98
2
3
S
98
2
Based on your sample data, you can use a cumulative window function:
select t.*,
min(invoiceno) filter (where type = 'I') over (order by id desc) as imputed_invoiceno
from t;

Decreasing of the sum by percentage (subtraction of percentage) SQL

Substraction of percentage
I have got a table where is one record. Table looks like:
cust_code, SUM
1 25
I need to calculate a subtraction like this.
cust_code, ID, SUM
1 1 25
1 2 23
1 3 21.16
1 4 19.47
1 5 17.91
1 6 16.48
1 7 15.16
1 8 13.95
. . .
. . .
. . .
1 15 7.78
where value of sum in record 2 is subtracted by 8% of record 1,
value of sum in record 3 is subtracted by 8% of record 2,
value of sum in record 4 is subtracted by 8% of record 3, etc.
Max ID will be 15.
It should be single query, I can use any additional external table (containing for example simple counter from 0 to 15).
Best regards,
Volcano
Easy to do with a recursive CTE:
WITH cte AS
(SELECT cust_code, 1 AS id, sum FROM cust
UNION ALL
SELECT cust_code, id + 1, sum * 0.92 FROM cte WHERE id < 15)
SELECT * FROM cte ORDER BY id;
cust_code id sum
---------- ---------- ----------
1 1 25.0
1 2 23.0
1 3 21.16
1 4 19.4672
1 5 17.909824
1 6 16.4770380
1 7 15.1588750
1 8 13.9461650
1 9 12.8304718
1 10 11.8040340
1 11 10.8597113
1 12 9.99093444
1 13 9.19165969
1 14 8.45632691
1 15 7.77982076

Can't use case & aggregation correctly

I have the following table
Cash_table
ID Cash Rates Amount
1 50 3 16
2 100 4 25
3 130 10 7
3 130 10 6
4 13 7 1.8
5 30 8 2.5
5 30 10 1
6 10 5 2
What I want as a result is to cumulate all the entries that have a Count(id)>1 like this:
ID New_Cash New_Rates New_Amount
1 50 3 16
2 100 4 25
3 130 10+10 130/(10+10)
4 13 7 1.8
5 30 8+10 30/(8+10)
6 10 5 2
So I only want to change the rows where Count(id)>1 and leave the rest like it was.
For the rows with count(id)>1 I want to sum up the rates and take the cash and divide it by the sum of the rates. The Rates alone aren't a problem since I can sum them up and group by id and get the desired result.
The problem is with the New_Amount column:
I am trying to do it with a case statement but it isn't working:
select id,
cash as new_cash,
sum(rates) as new_rates,
(case count(id)
when 1 then amount
else cash/sum(nvl(rates,null))
end) as new_amount
from Cash_table
group by id
As the cash value is always the same for an ID, you can group by that as well:
select id,
cash as new_cash,
sum(rates) as new_rates,
case count(id)
when 1 then max(amount)
else cash/sum(rates)
end as new_amount
from cash_table
group by id, cash
order by id
ID NEW_CASH NEW_RATES NEW_AMOUNT
---------- ---------- ---------- ----------
1 50 3 16
2 100 4 25
3 130 20 6.5
4 13 7 1.8
5 30 18 1.66666667
6 10 5 2
The first branch of the case expression needs an aggregate because you aren't grouping by amount; and the sum(nvl(rates,null)) can just be sum(rates). If you're expecting any null rates then you need to decide how you want the amount to be handled, but nvl(rates,null) isn't doing anything.
You can do the same thing without a case expression if you prefer, manipulating all the values - which might be more expensive:
select id,
cash as new_cash,
sum(rates) as new_rates,
sum(amount * rates)/sum(rates) as new_amount
from cash_table
group by id, cash
order by id

sql best strategy to partition same values based on temporal sequence

I have data that looks like this, where there are multiple values for each ID that correspond to an ascending date variable:
ID LEVEL DATE
1 10 10/1/2000
1 10 11/20/2001
1 10 12/01/2001
1 30 02/15/2002
1 30 02/15/2002
1 20 05/17/2002
1 20 01/04/2003
1 30 07/20/2003
1 30 03/16/2004
1 30 04/15/2004
I want to acquire a count per each ID/LEVEL/DATE block that looks like this:
ID LEVEL COUNT
1 10 3
1 30 2
1 20 2
1 30 3
The problem is that if I use the count windows function and partition by level, it groups 30 together regardless of the temporal sequence. I want the count for level 30 both before and after 20 to be distinct. Does anyone know how to do that?
A standard gaps and islands solution using ROW_NUMBER(), if it's available on your particular DBMS...
WITH
ordered AS
(
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY id ORDER BY date) AS set_ordinal,
ROW_NUMBER() OVER (PARTITION BY id, level ORDER BY date) AS grp_ordinal
FROM
yourData
)
SELECT
id,
level,
set_ordinal - grp_ordinal,
MIN(date),
COUNT(*)
FROM
ordered
GROUP BY
id,
level,
set_ordinal - grp_ordinal
ORDER BY
id,
MIN(date)
Visualising the effect of the two row numbers...
ID LEVEL DATE set_ordinal grp_ordinal set-grp GROUP
-- ----- ---------- ----------- ----------- ------- --------
1 10 10/01/2000 1 1 0 1,10,0
1 10 11/20/2001 2 2 0 1,10,0
1 10 12/01/2001 3 3 0 1,10,0
1 30 02/15/2002 4 1 3 1,30,3
1 30 02/15/2002 5 2 3 1,30,3
1 20 05/17/2002 6 1 5 1,20,5
1 20 01/04/2003 7 2 5 1,20,5
1 30 07/20/2003 8 3 5 1,30,5
1 30 03/16/2004 9 4 5 1,30,5
1 30 04/15/2004 10 5 5 1,30,5

TSQL - divide rows into groups based on one field

This is modified version of my earlier question: TSQL equally divide resultset to groups and update them
I have my database with 2 tables like so:
Orders table has data like below:
OrderID OperatorID GroupID OrderDesc Status Cash ...
--------------------------------------------------------------------------
1 1 1 small_order 1 300
2 1 1 another_order 1 0
3 1 2 xxxxxxxxxxx 2 1000
5 2 2 yyyyyyyyyyy 2 150
9 5 1 xxxxxxxxxxx 1 50
10 NULL 2 xxxxxxxxxxx 1 150
11 NULL 3 xxxxxxxxxxx 1 -50
12 4 1 xxxxxxxxxxx 1 200
Operators table:
OperatorID Name GroupID Active
---------------------------------------
1 John 1 1
2 Kate 1 1
4 Jack 2 1
5 Will 1 0
6 Sam 3 0
I'm able to equally divide my recordset into equally groups using below query:
SELECT o.*, op.operatorName AS NewOperator, op.operatorID AS NewOperatorId
FROM (SELECT o.*, (ROW_NUMBER() over (ORDER BY newid()) % numoperators) + 1 AS randseqnum
FROM Orders o CROSS JOIN
(SELECT COUNT(*) AS numoperators FROM operators WHERE operators.active=1) op
WHERE o.status in (1,3)
) o JOIN
(SELECT op.*, ROW_NUMBER() over (ORDER BY newid()) AS seqnum
FROM Operators op WHERE op.active=1
) op
ON o.randseqnum = op.seqnum ORDER BY o.orderID
Demo available at: http://sqlfiddle.com/#!3/ff47b/1
Using script from above I can divide Orders to (almost) equal groups but based on number or Orders for Operator, but I need to modify it so that it will assign Operators to Orders based on sum or Cash for orders.
For example:
If I have 6 Orders with Cash values: 300, 0, 50, 150, -50, 200 they sum gives 650.
My script should assign to 3 Operators random 2 Orders with random sum of Cash for Orders.
What I would like to get is to assign for example 300,-50 to operator1, 200, 0 to second and 150, 50 to third.
Hope this sound clear :)
Here is example output that I expect to get:
ORDERID OPERATORID GROUPID DESCRIPTION STATUS CASH NEWOPERATORID
------------------------------------------------------------------------
1 1 1 small_order 1 300 2
2 1 1 another_order 1 0 1
9 5 1 xxxxxxxxxxx 1 50 4
10 (null) 2 xxxxxxxxxxx 1 150 4
11 (null) 3 xxxxxxxxxxx 1 -50 2
12 4 1 xxxxxxxxxxx 1 200 1
How can I (if I can at all) assign Operators to my Orders so that sum or Cash will be closest to average
If I'm understanding this right, could you get the result you want by ordering the Cash column by the biggest, then the smallest, then the next biggest, then the next smallest, etc. Like this:
ROW_NUMBER() over (order by CASE WHEN CashOrder % 2 = 1 then Cash else -Cash END) as splitCash
where you've provided CashOrder lower in the query with
ROW_NUMBER() over (ORDER by CASH) as CashOrder
Then you specify each of your operators depending on this split value, ie (for three operators):
splitCash%3 +1