Subtract the value of a row from grouped result - sql

I have a table supplier_account which has five coloumns supplier_account_id(pk),supplier_id(fk),voucher_no,debit and credit. I want to get the sum of debit grouped by supplier_id and then subtract the value of credit of the rows in which voucher_no is not null. So for each subsequent rows the value of sum of debit gets reduced. I have tried using 'with' clause.
with debitdetails as(
select supplier_id,sum(debit) as amt
from supplier_account group by supplier_id
)
select acs.supplier_id,s.supplier_name,acs.purchase_voucher_no,acs.purchase_voucher_date,dd.amt-acs.credit as amount
from supplier_account acs
left join supplier s on acs.supplier_id=s.supplier_id
left join debitdetails dd on acs.supplier_id=dd.supplier_id
where voucher_no is not null
But here the debit value will be same for all rows. After subtraction in the first row I want to get the result in second row and subtract the next credit value from that.
I know it is possible by using temporary tables. The problem is I cannot use temporary tables because the procedure is used to generate reports using Jasper Reports.

What you need is an implementation of the running total. The easiest way to do it with a help of a window function:
with debitdetails as(
select id,sum(debit) as amt
from suppliers group by id
)
select s.id, purchase_voucher_no, dd.amt, s.credit,
dd.amt - sum(s.credit) over (partition by s.id order by purchase_voucher_no asc)
from suppliers s
left join debitdetails dd on s.id=dd.id
order by s.id, purchase_voucher_no
SQL Fiddle
Results:
| id | purchase_voucher_no | amt | credit | ?column? |
|----|---------------------|-----|--------|----------|
| 1 | 1 | 43 | 5 | 38 |
| 1 | 2 | 43 | 18 | 20 |
| 1 | 3 | 43 | 8 | 12 |
| 2 | 4 | 60 | 5 | 55 |
| 2 | 5 | 60 | 15 | 40 |
| 2 | 6 | 60 | 30 | 10 |

Related

How to order id's using subtotal from another column in PostgreSQL

I have a table returned by a select query. Example :
id | day | count |
-- | ------ | ----- |
1 | 71 | 3 |
1 | 70 | 2 |
1 |Subtotal| 5 |
2 | 70 | 5 |
2 | 71 | 2 |
2 | 69 | 2 |
2 |Subtotal| 9 |
3 | 69 | 1 |
3 | 70 | 1 |
3 |Subtotal| 2 |
the day column contains text values (so varchar)
subtotal is the sum of the counts for an id (e.g. id 2 has subtotal of 5 + 2 + 2 = 9)
I now want to order this table so the id’s with the lowest subtotal count come first, and then ordered by day with subtotal at the end (like before)
Expected output:
id | day | count |
-- | ------ | ----- |
3 | 69 | 1 |
3 | 70 | 1 |
3 |Subtotal| 2 |
1 | 70 | 2 |
1 | 71 | 3 |
1 |Subtotal| 5 |
2 | 69 | 2 |
2 | 70 | 5 |
2 | 71 | 2 |
2 |Subtotal| 9 |
I can't figure out how to order based on subtotal only ?
i've tried multiple order by (eg: ORDER BY day = 'Subtotal' & a mix of others) and using window functions but none are helping. Cheers !
Not sure if it's directly applicable to your source query (since you haven't included it) however the ordering you require on the sample data can be done with:
order by Max(count) over(partition by id), day
Note - ordering by day works with your sample data but as it's a string it will not honour numeric ordering, this should really be ordered by the source of the numerical value - again since we don't have your actual query I can't suggest anything more applicable but I'm sure you can substitute the correct column/expression.
I just crated table with 3 columns and tried to reproduce your expected result. I assume that there might be a problem ordering by day, subtotal would be always on top, but it seems as working solution.
create table test
(
id int,
day varchar(15),
count int
)
insert into test
values
(1,'71',3),
(1,'70',2),
(2,'70',5),
(2,'71',2),
(2,'69',2),
(3,'69',1),
(3,'70',1)
select id, day, count
from
(
select id, day, sum(count) as count
from test
group by id, rollup(day)
) as t
order by Max(count) over(partition by id), day

Find number of rows with each property value, taking into account only the most recent rows in SQL

I have a database with tables that represents "edits" to "pages". Every edit has an ID and a timestamp and a "status", which has certain discrete values. Pages have IDs and also have "categories".
I wish to find the number of pages with each status within a given category, taking into account only the state as of the most recent edit.
Edits:
+---------+---------+-----------+--------+
| edit_id | page_id | edit_time | status |
+---------+---------+-----------+--------+
| 1 | 10 | 20210502 | 90 |
| 2 | 10 | 20210503 | 91 |
| 3 | 20 | 20210504 | 91 |
| 4 | 30 | 20210504 | 90 |
| 5 | 30 | 20210505 | 92 |
| 6 | 40 | 20210505 | 90 |
| 7 | 50 | 20210503 | 90 |
+---------+---------+-----------+--------+
Pages:
+---------+--------+
| page_id | cat_id |
+---------+--------+
| 10 | 100 |
| 20 | 100 |
| 30 | 100 |
| 40 | 200 |
+---------+--------+
I want to get, for category 100:
+--------+-------+
| stat | count |
+--------+-------+
| 90 | 1 |
| 91 | 2 |
| 92 | 1 |
+--------+-------+
Page 10 and 30 have two edits, but the later one "overrides" the first one, so only the edits with status 91 and 92 are counted. Pages 20 and 40 account for one of 91 and 90 each and page 50 is in the wrong category so it doesn't feature.
I have tried the following, but it doesn't seem to work. The idea was to select the max (i.e. latest) edit for each page with the right category. Then join that to the edit table and group by the status and count the rows:
SELECT stat, COUNT(*)
FROM edits as out_e
INNER JOIN (
SELECT edit_id, page_id, max(edit_time) as last_edit
FROM edits
INNER JOIN pages on edit_page_id = page_id
WHERE cat_id = 100
GROUP BY page_id
) in_e ON out_e.edit_id = in_e.edit_id
GROUP BY stat
ORDER BY stat;
"""
For example in this fiddle: http://sqlfiddle.com/#!9/42f2ed/1
The result is:
+--------+-------+
| stat | count |
+--------+-------+
| 90 | 3 |
| 91 | 1 |
+--------+-------+
What is the correct way to get this information?
SELECT cat_id, stat, COUNT(*) cnt
FROM pages
JOIN edits ON pages.page_id = edits.edit_page_id
JOIN ( SELECT edit_page_id, MAX(edit_time) edit_time
FROM edits
GROUP BY edit_page_id ) last_time ON edits.edit_page_id = last_time.edit_page_id
AND edits.edit_time = last_time.edit_time
GROUP BY cat_id, stat
Output:
cat_id
stat
cnt
100
90
1
100
91
2
100
92
1
200
90
1
https://dbfiddle.uk/?rdbms=mysql_5.6&fiddle=7592c7853481f6b5a9626c8d111c1d3b (the query is applicable to MariaDB 10.1).
Is it possible to join on the edit_id (which is unique key for each edit)? – Inductiveload
No, this is impossible. cnt=2 counts two different edit_id values - what value must be used?
But you may obtain concatenated values list - simply add GROUP_CONCAT(edit_id) into the output list.
https://dbfiddle.uk/?rdbms=mysql_5.6&fiddle=b2391972c3f7c4be4254e47514d0f1da
think you dont need the second join - see if the query helps.
select
t1.stat, count(*) count_
from
(
SELECT
e.edit_id, p.page_id, e.stat,
rank() over(partition by e.edit_page_id order by e.edit_time desc) edit_rank
FROM
edits e
INNER JOIN pages p on e.edit_page_id = p.page_id
WHERE
p.cat_id = 100
) t1
where
t1.edit_rank = 1
group by
t1.stat
fiddle url : (https://dbfiddle.uk/?rdbms=mariadb_10.3&fiddle=0f681dc8d93cc3eebf9a03e0c8d84850)
select e1.stat, count(e1.stat) as count
from edits e1
join (
select edit_page_id, max(edit_time) as edit_time
from edits
where edit_page_id in (
select page_id
from pages
where cat_id = 100
)
group by edit_page_id
) as e2
on e1.edit_page_id = e2.edit_page_id and e1.edit_time = e2.edit_time
group by e1.stat;
Here's the link to fiddle - http://sqlfiddle.com/#!9/42f2ed/40/0
Edit: updated to consider edit_time instead of stat to find latest record

SQL- Invalid query because of aggregate function with simple calculations for each date and ID

I very new to SQL (less than 100Hrs). Problem case is as mentioned below. Every time I try a query either get incorrect output or error that "not contained in either an aggregate function or the GROUP BY clause"
Have tried searching similar questions or example but no results. I am lost now.
Please help
I have three tables
Table Calc,
Source_id | date(yyyymmdd) | metric1 | metric 2
-------------------------------------------------
1 | 20201010 | 2 | 3
2 | 20201010 | 4 | 5
3 | 20201010 | 6 | 7
1 | 20201011 | 8 | 9
2 | 20201011 | 10 | 11
3 | 20201011 | 12 | 13
1 | 20201012 | 14 | 15
2 | 20201012 | 16 | 17
3 | 20201012 | 18 | 19
Table Source
Source_id | Description
------------------------
1 | ABC
2 | DEF
3 | XYZ
Table Factor
Date | Factor
-----------------
20201010 | .3
20201011 | .5
20201012 | .7
If selected dates by user is 20201010 to 20201012 then result will be
Required result
Source_id | Calculated Value
-------------------------------------------------------------------------------
ABC | (((2x3)x.3 + (8x9)x.5 + (14x15)x.7))/(No of dates selected in this case =3)
DEF | (((4x5)x.3+ (10x11)x.5 + (16x17)x.7))/(No of dates selected in this case =3)
XYZ | (((6x7)x.3+ (12x13)x.5 + (18x19)x.7))/(No of dates selected in this case =3)
Dates will be user defined input so the calculated value should be average of that many dates. Selected dates will always be defined in range rather than random multiple selection.
In table calc, source_id and date together will be unique.
Each date has factor which is to be multiplied with all source_id for that date.
If selected dates by user is from 20201010 to 20201011 then result will be
Source_id | Calculated Value
-------------------------------------------------------------------------------
ABC | ((2x3)x.3+(8x9)x.5)/2
DEF | ((4x5)x.3+(10x11)x.5)/2
XYZ | ((6x7)x.3+(12x13)x.5)/2
If selected dates by user is 20201012 then result will be
Source_id | Calculated Value
-------------------------------------------------------------------------------
ABC | (14x15)x.7
DEF | (16x17)x.7
XYZ | (18x19)x.7
Create a CTE to store the starting and ending dates and cross join it to the join of the tables, group by source and aggregate:
WITH cte AS (SELECT '20201010' min_date, '20201012' max_date)
SELECT s.Description,
ROUND(SUM(c.metric1 * c.metric2 * f.factor / (DATEDIFF(day, t.min_date, t.max_date) + 1)), 2) calculated_value
FROM cte t CROSS JOIN Source s
LEFT JOIN Calc c ON c.Source_id = s.Source_id AND c.date BETWEEN t.min_date AND t.max_date
LEFT JOIN Factor f ON f.date = c.date
GROUP BY s.Source_id, s.Description
See the demo.
Results:
> Description | calculated_value
> :---------- | ---------------:
> ABC | 61.60
> DEF | 83.80
> XYZ | 110.00

Subtract constant across database tables

I need to subtract a value, found in a different table, from values across different rows.
For example, the tables I have are:
ProductID | Warehouse | Locator | qtyOnHand
-------------------------------------------
100 | A | 123 | 12
100 | A | 124 | 12
100 | A | 124 | 8
101 | A | 126 | 6
101 | B | 127 | 12
ProductID | Sold
----------------
100 | 26
101 | 16
Result:
ProductID | Warehouse | Locator | qtyOnHand | available
-------------------------------------------------------
100 | A | 123 | 12 | 0
100 | A | 123 | 12 | 0
100 | A | 124 | 8 | 6
101 | A | 126 | 6 | 0
101 | B | 127 | 12 | 12
The value should only be subtracted from those in warehouse A.
Im using postgresql. Any help is much appreciated!
If I understand correctly, you want to compare the overall stock to the cumulative amounts in the first table. The rows in the first table appear to be ordered from largest to smallest. Note: This is an interpretation and not 100% consistent with the data in the question.
Use JOIN to bring the data together and then cumulative sums and arithmetic:
select t1.*,
(case when running_qoh < t2.sold then 0
when running_qoh - qtyOnHand < t2.sold then (running_qoh - t2.sold)
else qtyOnHand
end) as available
from (select t1.*,
sum(qtyOnHand) over (partition by productID order by qtyOnHand desc) as running_qoh
from table1 t1
) t1 join
table2 t2
using (ProductID)

Using lag() for dynamic rows postgresql

I have a table named 'quantity' as below
Table 1 :
----------------------------------------------------
Price | A_Quantity | B_Quantity | Remaining_quantity
----------------------------------------------------
30 | 17 | 2 | 0
32 | 17 | 4 | 0
33 | 17 | 4 | 0
----------------------------------------------------
I want the field - Remaining_quantity to be filled as below
Table 2 :
----------------------------------------------------
Price | A_Quantity | B_Quantity | Remaining_quantity
----------------------------------------------------
30 | 17 | 2 | 15
32 | 17 | 4 | 11
33 | 17 | 4 | 7
----------------------------------------------------
I have used the below query, but it doesn't give the expected result.
update quantity set remaining_quantity = remaining_quantity -
(lag(B_Quantity) OVER (ORDER BY price))
This is 1 way to do it.
Note: By looking at data, looks like there is no specific primary key, but combination of price and a_quantity looks unique, hence I used these 2 columns in join. If this is just your example data and in actual you do have a primary key, I suggest you to change the join condition to that primary key.
REXTESTER DEMO
update Table1
set remaining_quantity = upd_tbl.cum
from
(
select t.*
,a_quantity - sum(b_quantity) over (partition by a_quantity order by price) as cum
from table1 t
) upd_tbl
where table1.price=upd_tbl.price
and table1.a_quantity=upd_tbl.a_quantity;
Explanation:
sum(b_quantity) over (partition by a_quantity order by price) will give you cumulative sum for a partition (2,6,10..). Now you can subtract it from a_quantity to get your output.