I have book stock table like :
---------------------------------------------------------------------
| BOOK_ID | WAREHOUSE | UNITS | QTY_IN | QTY_OUT |
---------------------------------------------------------------------
| 1 | W01 | PCS | 5 | 0 |
---------------------------------------------------------------------
| 2 | W02 | BOX | 1 | 0 |
---------------------------------------------------------------------
| 1 | W01 | PCS | 20 | 0 |
---------------------------------------------------------------------
| 1 | W01 | BOX | 2 | 0 |
---------------------------------------------------------------------
| 1 | W01 | PCS | 0 | 2 |
---------------------------------------------------------------------
I want to get final quantity for each book item, eg:
for BOOK_ID 1 TOTAL QTY IS 2 BOX, 23 PCS
for BOOK_ID 2 TOTAL QTY is 1 BOX
so on..
This is I had tried so far :
select BOOK_ID, UNITS, WAREHOUSE, sum(QTY_IN) as QTY_IN, sum(QTY_OUT) as QTY_OUT from
(select BOOK_ID, UNITS, WAREHOUSE, QTY_IN, QTY_OUT
from BOOK_STOCK) d
group by BOOK_ID, UNITS, WAREHOUSE
Please help me how to grouping and sum the quantity?
Thank you.
Use case expressions to do conditional aggregation:
select BOOK_ID,
sum(case when units = 'BOX' then QTY_IN - QTY_OUT else 0 end) as QTY_BOX,
sum(case when units = 'PCS' then QTY_IN - QTY_OUT else 0 end) as QTY_PCS
from BOOK_STOCK
group by BOOK_ID
Below code should do the trick for you.
select BOX.book_id, BOX.TotalQty as BoxFinalQuantity, PCS.TotalQty as PCSFinalQuantity
from
(
select book_id, units, sum(qty_in-qty_out) as TotalQty from #Books
where units like 'BOX'
group by book_id, units) BOX
full outer join (
select book_id, units, sum(qty_in-qty_out) as TotalQty from #Books
where units like 'PCS'
group by book_id, units) PCS ON BOX.book_id = Pcs.book_id
Related
Accounts (Master List of Accounts with link to their parent (same table) )
(Accounts table is maintained using nested_set plugin, so the left, right, depth are available in the table and are maintained while adding/editing accounts)
| id | name | parent_id |
|----|----------------|-----------|
| 1 | Assets | null |
| 2 | Current Assets | 1 |
| 3 | Fixed Assets | 1 |
| 4 | Bank | 2 |
| 5 | Bank One | 4 |
| 6 | Bank Two | 4 |
| 7 | Revenue | null |
| 8 | Sales | 7 |
| 9 | Expenses | null |
| 10 | Rent | 9 |
Entries (where the date and description of each transaction stored)
| entry_id | date | description |
|----------|------------|--------------------|
| 1 | Mar 3 2020 | Cash Sales |
| 2 | Mar 3 2020 | Cash Paid For Rent |
| 3 | Apr 1 2020 | Owner Withdrawal |
Amounts (where the double entry transactions are stored)
| entry_id | account_id | type | amount |
|----------|------------|--------|--------|
| 1 | 5 | debit | 10000 |
| 1 | 8 | credit | 10000 |
| 2 | 10 | debit | 1000 |
| 2 | 5 | credit | 1000 |
| | | | |
Given the above structure, here is my requirements
Arrange the accounts in Tree(hierarchical) structure and calculate the individual account balances (balances can be debit_balance or credit_balance)
Hierarchical account balances, rolling up child balances to the parent accounts
PS:
I do have the solution for the req 1 above using a combination of
WITH RECURSIVE sql function on the accounts table and arranging the rows hierarchially and then joining the result set with amounts table that is summed up on amount column (after grouping on type) for each accounts.
I am keen to see how the folks over here will solve this. (lemme know if you would like to see what I got so far though)
here is the query that gets me the first result set. (i've omitted the details like normal_credit_blance flag etc for brevity in the original question)
select id, parent_id, name, newdepth as depth, debit_amount, credit_amount, type,
CASE WHEN normal_credit_balance = true THEN credit_amount - debit_amount END as credit_balance,
CASE WHEN normal_credit_balance = false THEN debit_amount - credit_amount END as debit_balance
from
(
WITH RECURSIVE children AS (
SELECT id, parent_id, display_name, lft, rgt, type, normal_credit_balance, 0 as newdepth
FROM accounts
WHERE parent_id is null
UNION
SELECT op.id, op.parent_id, op.display_name, op.lft, op.rgt, op.type, op.normal_credit_balance, newdepth + 1
FROM accounts op
JOIN children c ON op.parent_id = c.id
)
SELECT *
FROM children
) accounts_tbl
left join
( SELECT account_id,
SUM( CASE WHEN am.type = 'debit' THEN COALESCE( AMOUNT , 0.0 ) ELSE 0.0 END ) AS debit_amount ,
SUM( CASE WHEN am.type = 'credit' THEN COALESCE( AMOUNT , 0.0 ) ELSE 0.0 END ) AS credit_amount
FROM amounts am
join accounts ac on ac.id = am.account_id
group by account_id, ac.name, ac.type )
as amount_tbl
on accounts_tbl.id = amount_tbl.account_id order by lft
sample result based on the amounts table entries, the rollup should look like this:
| id | name | balance |
|----|----------------|-----------|
| 1 | Assets | 9000 |
| 2 | Current Assets | 9000 |
| 3 | Fixed Assets | 0 |
| 4 | Bank | 9000 |
| 5 | Bank One | 9000 |
| 6 | Bank Two | 0 |
| 7 | Revenue | 10000 |
| 8 | Sales | 10000 |
| 9 | Expenses | 1000 |
| 10 | Rent | 1000 |
I would start by computing the "direct" balance of each account, with a left join and aggregation. Then goes the recursive query: you just need to traverse the tree from the leafs to the root, conslidating the balance as you go. The final step is aggregation.
with recursive
data (id, name, parent_id, balance) as (
select
ac.*,
coalesce(sum(case am.type when 'debit' then - amount when 'credit' then amount end), 0) balance
from accounts ac
left join amounts am on am.account_id = ac.id
group by ac.id
),
cte (id, name, parent_id, balance) as (
select d.* from data d
union all
select d.id, d.name, d.parent_id, d.balance + c.balance
from cte c
inner join data d on d.id = c.parent_id
)
select id, name, sum(balance) from cte group by id, name
I don't get how all the accounts in your resultset end up with a positive balance, while some have more debits than credit (and vice-versa). The query treats debits as negative amounts and credits as positive.
Demo on DB Fiddle:
id | name | sum
-: | :------------- | ----:
1 | Assets | -9000
2 | Current Assets | -9000
3 | Fixed Assets | 0
4 | Bank | -9000
5 | Bank One | -9000
6 | Bank Two | 0
7 | Revenue | 10000
8 | Sales | 10000
9 | Expenses | -1000
10 | Rent | -1000
If you were using a closure table instead of nested sets (like I do in https://stackoverflow.com/a/38701519/5962802) then you could use simple JOINs like
SELECT
accounts.id,
accounts.title,
SUM(COALESCE(debits.amount,0)) AS debit,
SUM(COALESCE(credit.amount,0)) AS credit
FROM account_tree
LEFT JOIN accounts ON ancestor_id = accounts.id
LEFT JOIN balances AS debits ON account_id = child_id AND type = 'debit'
LEFT JOIN balances AS credits ON account_id = child_id AND type = 'credit'
GROUP BY accounts.id,accounts.title
As a side note I would recommend you to keep debits and credits on the same row - as 2 columns in table BALANCES.
I am still very new to SQL and Tableau however I am trying to work myself towards achieving a personal project of mine.
Table A; shows a table which contains the defect quantity per product category and when it was raised
+--------+-------------+--------------+-----------------+
| Issue# | Date_Raised | Category_ID# | Defect_Quantity |
+--------+-------------+--------------+-----------------+
| PCR12 | 11-Jan-2019 | Product#1 | 14 |
| PCR13 | 12-Jan-2019 | Product#1 | 54 |
| PCR14 | 5-Feb-2019 | Product#1 | 5 |
| PCR15 | 5-Feb-2019 | Product#2 | 7 |
| PCR16 | 20-Mar-2019 | Product#1 | 76 |
| PCR17 | 22-Mar-2019 | Product#2 | 5 |
| PCR18 | 25-Mar-2019 | Product#1 | 89 |
+--------+-------------+--------------+-----------------+
Table B; shows the consumption quantity of each product by month
+-------------+--------------+-------------------+
| Date_Raised | Category_ID# | Consumed_Quantity |
+-------------+--------------+-------------------+
| 5-Jan-2019 | Product#1 | 100 |
| 17-Jan-2019 | Product#1 | 200 |
| 5-Feb-2019 | Product#1 | 100 |
| 8-Feb-2019 | Product#2 | 50 |
| 10-Mar-2019 | Product#1 | 100 |
| 12-Mar-2019 | Product#2 | 50 |
+-------------+--------------+-------------------+
END RESULT
I would like to create a table/bar chart in tableau that shows that Defect_Quantity/Consumed_Quantity per month, per Category_ID#, so something like this below;
+----------+-----------+-----------+
| Month | Product#1 | Product#2 |
+----------+-----------+-----------+
| Jan-2019 | 23% | |
| Feb-2019 | 5% | 14% |
| Mar-2019 | 89% | 10% |
+----------+-----------+-----------+
WHAT I HAVE TRIED SO FAR
Unfortunately i have not really done anything, i am struggling to understand how do i get rid of the duplicates upon joining the tables based on Category_ID#.
Appreciate all the help I can receive here.
I can think of doing left joins on both product1 and 2.
select to_char(to_date(Date_Raised,'d-mon-yyyy'),'mon-yyyy')
, (p2.product1 - sum(case when category_id='Product#1' then Defect_Quantity else 0 end))/p2.product1 * 100
, (p2.product2 - sum(case when category_id='Product#2' then Defect_Quantity else 0 end))/p2.product2 * 100
from tableA t1
left join
(select to_char(to_date(Date_Raised,'d-mon-yyyy'),'mon-yyyy') Date_Raised
, sum(Comsumed_Quantity) as product1 tableB
where category_id = 'Product#1'
group by to_char(to_date(Date_Raised,'d-mon-yyyy'),'mon-yyyy')) p1
on p1.Date_Raised = t1.Date_Raised
left join
(select to_char(to_date(Date_Raised,'d-mon-yyyy'),'mon-yyyy') Date_Raised
, sum(Comsumed_Quantity) as product2 tableB
where category_id = 'Product#2'
group by to_char(to_date(Date_Raised,'d-mon-yyyy'),'mon-yyyy')) p2
on p2.Date_Raised = t1.Date_Raised
group by to_char(to_date(Date_Raised,'d-mon-yyyy'),'mon-yyyy')
By using ROW_NUMBER() OVER (PARTITION BY ORDER BY ) as RN, you can remove duplicate rows. As of your end result you should extract month from date and use pivot to achieve.
I would do this as:
select to_char(date_raised, 'YYYY-MM'),
(sum(case when product = 'Product#1' then defect_quantity end) /
sum(case when product = 'Product#1' then consumed_quantity end)
) as product1,
(sum(case when product = 'Product#2' then defect_quantity end) /
sum(case when product = 'Product#2' then consumed_quantity end)
) as product2
from ((select date_raised, product, defect_quantity, 0 as consumed_quantity
from a
) union all
(select date_raised, product, 0 as defect_quantity, consumed_quantity
from b
)
) ab
group by to_char(date_raised, 'YYYY-MM')
order by min(date_raised);
(I changed the date format because I much prefer YYYY-MM, but that is irrelevant to the logic.)
Why do I prefer this method? This will include all months where there is a row in either table. I don't have to worry that some months are inadvertently filtered out, because there are missing production or defects in one month.
I request your collaboration because pivot on a table and separating the records by null, but still leave the fields at 0 with NVL
Table
product | value
----------+-------
Shirts | 1200
Caps | 0
Stocks | 0
Glasses | 100
Shoes | 0
Código pivot
select * from products
PIVOT (sum(value)
for titles in ('product', 'value')) AS pivot_product
Result:
product | Shirts | Caps | Stocks | Glasses | Shoes
---------+-----------+--------+-------------+---------+----------
value | NULL | NULL | NULL | 100 | NULL
value | 1200 | NULL | NULL | NULL | NULL
Expected result:
product | Shirts | Caps | Stocks | Glasses | Shoes
---------+-----------+--------+-------------+-------+----------
valor | 1200 | NULL | NULL | 100 | NULL
Optional
product | Shirts | Caps | Stocks | Glasses | Shoes
---------+-----------+--------+-------------+-------+----------
valor | 1200 | 0 | 0 | 100 | 0
You need to put the column values in the pivot list:
Oracle Setup:
CREATE TABLE test_data ( product, value ) AS
SELECT 'Shirts', 1200 FROM DUAL UNION ALL
SELECT 'Caps', 0 FROM DUAL UNION ALL
SELECT 'Stocks', 0 FROM DUAL UNION ALL
SELECT 'Glasses', 100 FROM DUAL UNION ALL
SELECT 'Shoes', 0 FROM DUAL
Query:
SELECT 'value' AS product,
p.*
FROM test_data
PIVOT ( SUM( value ) FOR product IN (
'Shirts' AS Shirts,
'Caps' AS Caps,
'Stocks' AS Stocks,
'Glasses' AS Glasses,
'Shoes' AS Shoes
) ) p
Output:
PRODUCT | SHIRTS | CAPS | STOCKS | GLASSES | SHOES
:------ | -----: | ---: | -----: | ------: | ----:
value | 1200 | 0 | 0 | 100 | 0
db<>fiddle here
Just use conditional aggregation. It is more flexible:
select 'valor' as product,
sum(case when product = 'Shirts' then value end) as shirts,
sum(case when product = 'Caps' then value end) as caps,
sum(case when product = 'Stocks' then value end) as stockes,
sum(case when product = 'Shirts' then value end) as shirts,
sum(case when product = 'Glasses' then value end) as glasses,
sum(case when product = 'Shoes' then value end) as shoes
from test_data;
I have grouped sales from a sales view with sales below using
Select id, name, Count(*) as [Sales], product, amount
from vwSales
Group by
id,name, product, amount
ID | Name | Sales | Product | Amount
1 | Bob | 4 | Fridge | 40
1 | Bob | 12 | Washer | 120
2 | Anne | 5 | Fridge | 50
2 | Anne | 4 | Washer | 40
Is it possible to group these in to one row without using a join? So table looks something like
ID | Name | Fridge Sales | fridge Amt | Washer sales | washer amt
1 | Bob | 4 | 40 | 12 | 120
2 | Anne | 5 | 50 | 4 | 40
You can do conditional aggregation :
select id, name,
sum(case when Product = 'Fridge' then 1 else 0 end) as [Fridge Sales],
sum(case when Product = 'Fridge' then Amount else 0 end) as [fridge Amt],
sum(case when Product = 'Washer' then 1 else 0 end) as [Washer Sales],
sum(case when Product = 'Washer' then Amount else 0 end) as [Washer Amt]
from vwSales
Group by id, name;
shops
id_shop | id_prod_ty | position
and
products
id | price | id_prod_ty | distance
table shops contains 2 shops for each id_prod_ty (product type) and a position.
table products contains many records with different prices and a distance
basically i need to have a query that selects price low and high for each product and for each shops grouping by closer prices (and that are not closer to other shops)
so for example
shops
id_shop | id_prod_ty | position
1 | 1 | 3
2 | 1 | 7
3 | 2 | 8
4 | 2 | 4
....
products
id | price | id_prod_ty | distance
1 | 10 | 1 | 1
2 | 04 | 1 | 2
3 | 02 | 1 | 4
4 | 44 | 1 | 2
5 | 09 | 1 | 1
6 | 13 | 1 | 7
7 | 15 | 1 | 8
8 | 09 | 2 | 5
9 | 12 | 2 | 8
10 | 17 | 2 | 1
11 | 32 | 2 | 13
12 | 22 | 2 | 2
...
result shout be like this
id_prod_ty | id_price_low | id_price_high | id_shop
1 | 3 (02) | 4 (44) | 1
1 | 6 (13) | 7 (15) | 2
2 | 8 (09) | 12 (22) | 4
2 | 9 (12) | 11 (32) | 3
...
thanks
I think I understand the problem. For each price, you are trying to assign the shop that is nearest, based on the distance and the position.
The approach starts by joining the positions and shops. It then calculates the difference between the position and distance -- that seems to be the measure you are using. Each price id will appear twice (once for each shop). Using a window function, it calculates the minimum difference for each id.
With this information, the query can determine which is the closer shop. This version returns the prices, rather than the ids of the lowest prices. Here is the final, untested, query:
select id_prod_ty,
MIN(case when diff = minDiff then price) as price_low,
MAX(case when diff = minDiff then price) as price_high,
s.id_shop
from (select p.id_prod_ty,
s.id_shop,
p.price,
(p.distance - s.position) as diff,
MIN(p.distance - s.position) over (partition by p.id) as minDiff
from products p join
shops s
on p.id_prod_ty = s.id_prod_ty
) ps
group by id_prod_ty, id_shop
Here is a variation that gets the ids as well. It uses a window function to find the min and max price, and then compares the price to these values to get the ids:
select id_prod_ty,
MIN(minPrice) as price_low,
MIN(case when price = minPrice then id end) id_price_low,
MAX(maxprice) as price_high,
MIN(case when price = maxPrice then id end) id_price_high,
id_shop
from (select *,
MIN(IsThisShopPrice) over (partition by id_prod_ty, id_shop) as minPrice,
MIN(IsThisShopPrice) over (partition by id_prod_ty, id_shop) as maxPrice,
from (select p.id_prod_ty, s.id_shop, p.price, p.id,
(p.distance - s.position) as diff,
(case when (p.distance - s.position) = MIN(p.distance - s.position) over (partition by p.id)
then 'Y'
else 'N'
end) as IsThisShop
(case when (p.distance - s.position) = MIN(p.distance - s.position) over (partition by p.id)
then price
end) as IsThisShopPrice
from products p join
shops s
on p.id_prod_ty = s.id_prod_ty
) ps
) ps
group by id_prod_ty, id_shop