SQL showing summary after each column value changes - sql

I have sales data for branches. I want a SQL which will give me summary of sales data for each branchId as below, of course BranchId(s) are huge so I have to make it dynamic (I can't use Union). I am stuck how to add a summary row after every branch change dynamically.
+ ---------+--------+---------+-----------+
| BranchId | CashIn | CashOut | CardSales |
+ ---------+--------+---------+-----------+
| 1 | 1000 | 500 | 50 |
| 1 | 500 | 2500 | 100 |
| 1 | 1000 | 200 | 200 |
| Totals | 2500 | 3200 | 350 |
| 5 | 100 | 500 | 500 |
| Totals | 100 | 500 | 500 |
| 7 | 100 | 100 | 100 |
| 7 | 200 | 300 | 400 |
| Totals | 300 | 400 | 500 |
+ ---------+--------+---------+-----------+

A brute force method is to do the aggregation and then interleave the results:
select (case when is_base = 1 then to_char(BranchId)
else replace('Total ([BranchId])', '[BranchId]', BranchId)
end) as BranchId, CashIn, CashOut, CardSales
from ((select BranchId, CashIn, CashOut, CardSales, 1 as is_base
from t
) union all
(select BranchId, sum(CashIn), sum(CashOut), sum(CardSales), 0 as is_base
from t
group by BranchId
)
) t
order by t.BranchId, is_base desc;
Here is a db<>fiddle.

Related

find next values satisfying a condition in SQL

Suppose that I have a dataframe as:
| ID | Value | Time |
|---------|-------|------|
| 101 | 100 | 1 |
| 101 | 0 | 2 |
| 101 | 200 | 4 |
| 101 | 200 | 7 |
| 101 | 0 | 10 |
| 102 | 100 | 2 |
| 102 | 0 | 3 |
| 102 | 200 | 5 |
For each non-zero Value, I would like to find the next Time that Value=0 for the same ID. So my desired output will be
| ID | Value | Time | NextTime |
|---------|-------|------|----------|
| 101 | 100 | 1 | 2 |
| 101 | 0 | 2 | Null |
| 101 | 200 | 4 | 10 |
| 101 | 200 | 7 | 10 |
| 101 | 0 | 10 | Null |
| 102 | 100 | 2 | 3 |
| 102 | 0 | 3 | Null |
| 102 | 200 | 5 | Null |
I have tried to use the following subquery:
SELECT *, CASE WHEN Value=0 THEN NULL ELSE (SELECT MIN(Time) FROM Table1 sub
WHERE sub.ID = main.ID AND sub.Time > main.Time AND sub.Value=0) END as NextTime
FROM Table1 AS main
ORDER BY
ID,
Time
This query should work, but the problem is that I am working with a extremely large dataframe (millions records), so this query can not be finished in a reasonable time. Could any one help with a more efficient way to get the desired result? Thanks.
You want a cumulative minimum:
select t.*,
min(case when value = 0 then time end) over
(partition by id
order by time
rows between 1 following and unbounded following
) as next_0_time
from t;
EDIT:
If you want values on the 0 rows to be NULL, then use a case expression:
select t.*,
(case when value <> 0
then min(case when value = 0 then time end) over
(partition by id
order by time
rows between 1 following and unbounded following
)
end) as next_0_time
from t;
Here is a db<>fiddle.

SQL select a row by highest value from a group

I'm trying to select the highest value(calculated) of a group with distinct selection of other columns
according to the table-data below, i want to select the rows, which have the highest amount (Qty-Plan) and a distinct selection of Len and Wid
Table data is as follow
+-----------+-----------+---------+---------+------------+---------+
| Ident | Name | Len | Wid | Qty | Plan |
+-----------+-----------+---------+---------+------------+---------+
| 12345 | Name1 | 1500 | 1000 | 20 | 5 |
| 23456 | Name1 | 1500 | 1000 | 30 | 13 |
| 34567 | Name1 | 2500 | 1000 | 10 | 2 |
| 45678 | Name1 | 2500 | 1000 | 10 | 4 |
| 56789 | Name1 | 1500 | 1200 | 20 | 3 |
| 00001 | Name2 | 1500 | 1200 | 10 | 6 |
| 00002 | Name2 | 1500 | 1200 | 20 | 7 |
| 00003 | Name3 | 1500 | 1200 | 30 | 5 |
| 00004 | Name3 | 1500 | 1200 | 40 | 4 |
+-----------+-----------+---------+---------+------------+---------+
with my query i cant erase the "lower" values:
select a.Ident ,a.Name, a.Len,a.Wid, a.Qnt-a.Plan as Amount
from table a
join (select ident, max(Qnt - Plan) Amount
from table
where Name = 'Name1'
group by Ident, Len, Wid) b
on b.Ident = a.Ident and b.Amount = a.Qnt-a.Plan
order by Amount desc
off-topic question: why i cant use -> where b.Amount = a.Amount (he does not know a.Amount ) ???
my desired select should look like:
+-----------+-----------+---------+---------+------------+
| Ident | Name | Len | Wid | Amount |
+-----------+-----------+---------+---------+------------+
| 56789 | Name1 | 1500 | 1200 | 18 |
| 23456 | Name1 | 1500 | 1000 | 17 |
| 34567 | Name1 | 2500 | 1000 | 8 |
+-----------+-----------+---------+---------+------------+
thanks a lot in advance
It's not clear what kind of database you're using, but this solution should work on any DB:
SELECT tab.Ident,
tab.Name,
tab.Len,
tab.Wid,
(tab.Qty - tab.Plan) AS Amount
FROM (SELECT Name,
Len,
Wid,
MAX(Qty-Plan) AS Amount
FROM my_table
GROUP BY Name,
Len,
Wid
) AS grouped
JOIN my_table tab
ON grouped.Name = tab.Name
AND grouped.Len = tab.Len
AND grouped.Wid = tab.Wid
AND grouped.Amount = (tab.Qty - tab.Plan)
AND tab.Name = 'Name1'
Another approach, using window functions to simplify things:
SELECT ident, name, len, wid, qnt - [plan] AS amount
FROM (SELECT *, row_number() OVER (PARTITION BY len, wid ORDER BY qnt - [plan] DESC) AS rn
FROM test WHERE name = 'Name1') AS sq
WHERE rn = 1
ORDER BY amount DESC;
SQL Fiddle example.

Return the row with the value of the previous row within the same group (Oracle Sql)

I have a tabel that looks like this:
|--------+------+---------|------|
| Head | ID | Amount | Rank |
|--------+------+---------|------|
| 1 | 10 | 1000 | 1 |
| 1 | 11 | 1200 | 2 |
| 1 | 12 | 1500 | 3 |
| 2 | 20 | 3400 | 1 |
| 2 | 21 | 3600 | 2 |
| 2 | 22 | 4200 | 3 |
| 2 | 23 | 1700 | 4 |
|--------+------+---------|------|
I want a new column (New_column) that does the following:
|--------+------+---------|------|------------|
| Head | ID | Amount | Rank | New_column |
|--------+------+---------|------|------------|
| 1 | 10 | 1000 | 1 | 1000 |
| 1 | 11 | 1200 | 2 | 1000 |
| 1 | 12 | 1500 | 3 | 1200 |
| 2 | 20 | 3400 | 1 | 3400 |
| 2 | 21 | 3600 | 2 | 3400 |
| 2 | 22 | 4200 | 3 | 3600 |
| 2 | 23 | 1700 | 4 | 4200 |
|--------+------+---------|------|------------|
Within each Head number, if rank is not 1, takes the amount of row within the Head number with Rank number before it (Rank 2 takes the amount of Rank 1 within the same Head and Rank 3 takes the amount of Rank 2 within the same Head and so on...)
I know how to fix it with a For loop in other programming languages but Don't know how to do it with SQL.
I think you basically want lag():
select t.*,
lag(amount, 1, amount) over (partition by head order by rank) as new_column
from t;
The three-argument form of lag() allows you to provide a default value.
You can join the same table(subquery) on rank-1 of derived table.
select t1.*,case when t1.rank=1 then amount else t2.amount new_amount
from your_table t1 left join (select Head,ID,Amount,Rank from your_table) t2
on t1.head=t2.head and t1.rank=t2.rank-1
You can use this update:
UPDATE your_table b
SET New_column = CASE WHEN rank = 1 then Amount
ELSE (select a.Amount FROM your_table a where a.ID = b.ID and a.rank = b.rank-1) END

postgresql - Change single row to multiple rows

I have a table named payment_info, with the following records.
paymentid | customercode | previousbalance | paymentamount | remainingbalance
-----------------------------------------------------------------------------
PID0001 | CUST024 | 10000 | 2500 | 7500
PID0002 | CUST031 | 8500 | 3500 | 5000
PID0003 | CUST005 | 12000 | 1500 | 10500
Then what I want is to create a 3 rows per row of the above table.
I want my results to look like this.
Payment Group | Payment Line Item | Payment ID | Customer Code | Type | Amount
--------------------------------------------------------------------------------------------------
1 | 1 | PID0001 | CUST024 | PREVIOUS BALANCE | 10000.00
1 | 2 | | | PAYMENT AMOUNT | 2500.00
1 | 3 | | | REMAINING BALANCE | 7500.00
2 | 1 | PID0002 | CUST031 | PREVIOUS BALANCE | 8500.00
2 | 2 | | | PAYMENT AMOUNT | 3500.00
2 | 3 | | | REMAINING BALANCE | 5000.00
3 | 1 | PID0003 | CUST005 | PREVIOUS BALANCE | 12000.00
3 | 2 | | | PAYMENT AMOUNT | 1500.00
3 | 3 | | | REMAINING BALANCE | 10500.00
Here is the query I've started. But it did not return results same as above.
select row_number() over() as id,paymentid,customercode,'PREVIOUS BALANCE' as type,previousbalance from payment_info
union
select row_number() over() as id,'','','PAYMENT AMOUNT' as type,paymentamount from payment_info
union
select row_number() over() as id,'','','REMAINING BALANCE' as type,remainingbalance from payment_info
Is there other ways, where I will not use UNION Keyword? Cause in the real table, I will be using 30+ columns, querying thousands of records.
I also don't know how to create auto generated number (id) from payment group (per payment id) and Payment Line Item (per group).
thanks
version with whitespace (empty text)
The unnest function can do this for you.
And if you want the empty text then you can use this
SELECT ROW_NUMBER() OVER (ORDER BY paymentid) AS "group",
unnest(array[1, 2, 3]) AS "line item",
unnest(array[paymentid, '', '']) AS "paymentid",
unnest(array[customercode, '', '']) AS "customercode",
unnest(array['PREVIOUS BALANCE', 'PAYMENT AMOUNT', 'REMAINING BALANCE']) AS "type",
unnest(array[previousbalance, paymentamount, remainingbalance]) AS "amount"
FROM payment_info
ORDER BY 1, 2 ;
To get this
group | line item | paymentid | customercode | type | amount
-------+-----------+-----------+--------------+-------------------+--------
1 | 1 | PID0001 | CUST024 | PREVIOUS BALANCE | 10000
1 | 2 | | | PAYMENT AMOUNT | 2500
1 | 3 | | | REMAINING BALANCE | 7500
2 | 1 | PID0002 | CUST031 | PREVIOUS BALANCE | 8500
2 | 2 | | | PAYMENT AMOUNT | 3500
2 | 3 | | | REMAINING BALANCE | 5000
3 | 1 | PID0003 | CUST005 | PREVIOUS BALANCE | 12000
3 | 2 | | | PAYMENT AMOUNT | 1500
3 | 3 | | | REMAINING BALANCE | 10500
If you want to have, for example points or other text, or arrows in the empty text columns, you can do this easily with unnest.
You can control the 4 empty text values individually.
SELECT ROW_NUMBER() OVER (ORDER BY paymentid) AS "group",
unnest(array[1, 2, 3]) AS "line item",
unnest(array[paymentid, ' a', ' c']) AS "paymentid",
unnest(array[customercode, ' b', ' d']) AS "customercode",
unnest(array['PREVIOUS BALANCE', 'PAYMENT AMOUNT', 'REMAINING BALANCE']) AS "type",
unnest(array[previousbalance, paymentamount, remainingbalance]) AS "amount"
FROM payment_info
ORDER BY 1, 2 ;
to generate
group | line item | paymentid | customercode | type | amount
-------+-----------+-----------+--------------+-------------------+--------
1 | 1 | PID0001 | CUST024 | PREVIOUS BALANCE | 10000
1 | 2 | a | b | PAYMENT AMOUNT | 2500
1 | 3 | c | d | REMAINING BALANCE | 7500
2 | 1 | PID0002 | CUST031 | PREVIOUS BALANCE | 8500
2 | 2 | a | b | PAYMENT AMOUNT | 3500
2 | 3 | c | d | REMAINING BALANCE | 5000
3 | 1 | PID0003 | CUST005 | PREVIOUS BALANCE | 12000
3 | 2 | a | b | PAYMENT AMOUNT | 1500
3 | 3 | c | d | REMAINING BALANCE | 10500
It's a very flexible solution, you know.
It isn't necessary to always use union queries. Here for example you can use 3 rows and a cross join instead. This has the advantage of only a single pass over the source table.
drop table if exists Table1;
CREATE TABLE Table1
("paymentid" varchar(7), "customercode" varchar(7)
, "previousbalance" int, "paymentamount" int, "remainingbalance" int)
;
INSERT INTO Table1
("paymentid", "customercode", "previousbalance", "paymentamount", "remainingbalance")
VALUES
('PID0001', 'CUST024', 10000, 2500, 7500),
('PID0002', 'CUST031', 8500, 3500, 5000),
('PID0003', 'CUST005', 12000, 1500, 10500)
;
select
paymentid
, customercode
, rn
, typeof
, case when rn = 1 then previousbalance
when rn = 2 then paymentamount
when rn = 3 then remainingbalance
end as Amount
from Table1
cross join (select 1 rn , 'previousbalance' typeof
union all
select 2 , 'paymentamount'
union all
select 3, 'remainingbalance'
) rns
That data/query produces this result:
+----+-----------+--------------+----+------------------+--------+
| | paymentid | customercode | rn | typeof | amount |
+----+-----------+--------------+----+------------------+--------+
| 1 | PID0001 | CUST024 | 1 | previousbalance | 10000 |
| 2 | PID0001 | CUST024 | 2 | paymentamount | 2500 |
| 3 | PID0001 | CUST024 | 3 | remainingbalance | 7500 |
| 4 | PID0002 | CUST031 | 1 | previousbalance | 8500 |
| 5 | PID0002 | CUST031 | 2 | paymentamount | 3500 |
| 6 | PID0002 | CUST031 | 3 | remainingbalance | 5000 |
| 7 | PID0003 | CUST005 | 1 | previousbalance | 12000 |
| 8 | PID0003 | CUST005 | 2 | paymentamount | 1500 |
| 9 | PID0003 | CUST005 | 3 | remainingbalance | 10500 |
+----+-----------+--------------+----+------------------+--------+
Please then note that SQL isn't a "report writer" so blanks in columns for "layout" are not a good fit for SQL which wants to repeat information (like you see above in the result) so that you can sort and filter as needed.

how to adjust the sum() of a group using the sum() of a subgroup()

I am trying to get the Output shown in the third table below using the tables "Assets" and "Transactions".
I am trying to group by Cmpy, Acct and AssetID and get the Sum(cost). But each cost has to be adjusted from the Transactions table before being summed. Not sure how to do it.
Table: Assets
+----------+------+---------+--------+
| Cpny | Acct | AssetID | Cost |
+----------+------+---------+--------+
| 50 | 120 | 109 | 100.00 |
| 50 | 120 | 109 | 200.00 |
| 50 | 120 | 110 | 300.00 |
| 50 | 120 | 110 | 20.00 |
| 50 | 121 | 107 | 150.00 |
| 50 | 121 | 201 | 200.00 |
+----------+------+---------+--------+
Table: Transactions
+------+---------+--------+
| Cpny | AssetID | Amt |
+------+---------+--------+
| 50 | 109 | -50.00 |
| 50 | 110 | 50.00 |
| 50 | 110 | -20.00 |
| 50 | 201 | -50.00 |
+------+---------+--------+
OUTPUT
+------+------+--------+
| Cpny | Acct | Total |
+------+------+--------+
| 50 | 120 | 600.00 |
| 50 | 121 | 300.00 |
+------+------+--------+
This one should give you an accurate answer:
SELECT a.Cpny,
a.Acct,
SUM(a.Cost + ISNULL(t.Adjustment, 0)) AS Total
FROM Assets a
LEFT JOIN (SELECT Cpny,
AssetID,
SUM(Amt) AS Adjustment
FROM Transactions
GROUP BY Cpny, AssetID) t
ON t.Cpny = a.Cpny AND t.AssetID = a.AssetID
GROUP BY a.Cpny, a.Acct
Associated SQLFiddle here.
Essentially, SUM the adjustment amounts in the transactions table, then join this to the main results list, summing the cost plus the adjustment for each asset in each account.
If the "relationship" between Acct and AssetID values are 1 to many then you could use this query (which is not so efficient):
SELECT x.Cpny,x.Acct, SUM( ISNULL(x.Total,0) + ISNULL(y.Total,0) ) AS Total
FROM
(
SELECT a.Cpny,a.Acct,a.AssetID, SUM(a.Cost) AS Total
FROM dbo.Assets a
GROUP BY a.Cpny,a.Acct,a.AssetID
) x
LEFT JOIN
(
SELECT t.Cpny,t.AssetID, SUM(t.Cost) AS Total
FROM dbo.Transactions t
GROUP BY t.Cpny,t.AssetID
) y ON x.Cpny=y.Cpny AND x.AssetID=y.AssetID
GROUP BY x.Cpny,x.Acct;