I have the following table structures for which I am trying to obtain a totals and subtotals and show a rollup of the values.
ChartOfAccounts(AccountNumber, AccountDescription, ParentAccountNumber, IsControlAccount)
Ledger(LedgerId, JournalId, AccountNumber, IsDebit, Amount)
I have managed to use CTE to obtain the required Parent-Child relationships but am unsure how to use this get control account balances which rollup into parent accounts.
So far, I have managed to put the following query together which is not entirely what I want --> SQL Fiddle. The current query does not seem to rollup and group the parent-child totals correctly. (I have excluded the year,month columns from the fiddle)
Another way to describe the problem, would be to say that all control accounts should have the total of it's child accounts.
My required output is the following
(year, month, AccountNumber, AccountDescription, DebitBalance, CreditBalance, Balance)
|Account#|Acc Desc | DR | CR | BAL |
|1000 |Accounts Receivable |10000 |5000 |5000 |
|1200 |Buyer Receivables |5000 |0 |5000 |
|12001 |Buyer Receivables - Best Buy |5000 |0 |5000 |
|1500 |Offers |5000 |5000 |0 |
|4000 |Accounts Payable | |4475.06 |4475.06 |
|4100 |Supplier Invoice Payables | |4475.06 |4475.06 |
|41002 |Supplier Invoice Payables - Knechtel | |4475.06 |4475.06 |
|6000 |Revenue | |524.93 |524.93 |
|6100 |Membership Fees Revenue | | |0 |
|6200 |Processing Fees Revenue | |100 |100 |
|62002 |Processing Fees Revenue - Knechtel | |100 |100 |
|6300 |Fees Revenue | |424.93 |424.93 |
|63002 |Fees Revenue - Knechtel | |424.93 |424.93 |
Here is what I came up with and was able to get really close to matching your desired output
WITH CTEAcc
AS
(
SELECT
coa.accountDescription,coa.accountnumber,coa.accountnumber as parentaccount
FROM ChartOfAccounts coa
where iscontrolaccount=1
union all select c.accountdescription, coa.accountnumber, c.ParentAccount
from chartofaccounts coa
inner join cteacc c on coa.ParentAccountNumber=c.accountnumber
)
select parentaccount as [Account#], accountdescription as [Acc Desc],
sum(case when isdebit=1 then amount else 0 end) as DR,
sum(case when isdebit=0 then amount else 0 end) as CR,
sum(case when isdebit=1 then amount else 0 end)-sum(case when isdebit=0 then amount else 0 end) as BAL
from (select c.accountdescription, c.accountnumber,
c.parentaccount, l.isdebit, l.amount
from cteacc c
left join ledger l
on c.accountnumber=l.accountnumber
union all select c.accountdescription,
c.accountnumber, c.accountnumber as parentaccount,
l.isdebit, l.amount
from ChartOfAccounts c
inner join ledger l
on c.accountnumber=l.accountnumber where amount<>0) f
group by parentaccount, accountdescription
order by parentaccount
Here is the sql fiddle: http://www.sqlfiddle.com/#!3/d94bc/106
Yet another variation. Kept the hierarchy and iscontrol fields in just for reference. First it associates the account hierarchy with each account (the recursive cte). Then, for each account, computes sums of the ledger items for the account based on the hierarchy position (and whether it is a control account or not). Finally, wraps in another query to compute the balance of and strip off unused accounts from the output.
WITH AccountHierarchy AS (
SELECT AccountNumber
,AccountDescription
,CAST(AccountNumber AS VARCHAR(MAX))
+ '/' AS AccountHierarchy
,IsControlAccount
FROM ChartOfAccounts
WHERE ParentAccountNumber IS NULL
UNION ALL
SELECT c.AccountNumber
,c.AccountDescription
,CAST(h.AccountHierarchy AS VARCHAR(MAX))
+ CAST(c.AccountNumber AS VARCHAR(MAX))
+ '/' AS AccountHierarchy
,c.IsControlAccount
FROM ChartOfAccounts c
INNER JOIN AccountHierarchy h ON (c.ParentAccountNumber = h.AccountNumber)
WHERE ParentAccountNumber IS NOT NULL
)
SELECT AccountNumber
,AccountDescription
,AccountHierarchy
,IsControlAccount
,DR
,CR
,CASE WHEN (DR IS NULL AND CR IS NULL) THEN NULL
ELSE COALESCE(DR, 0) - COALESCE(CR, 0)
END AS BAL
FROM (SELECT h.AccountNumber
,h.AccountDescription
,h.AccountHierarchy
,h.IsControlAccount
,(SELECT SUM(l.Amount)
FROM Ledger l
INNER JOIN AccountHierarchy hd ON (l.AccountNumber = hd.AccountNumber)
WHERE l.IsDebit = 1
AND ( (h.IsControlAccount = 1 AND hd.AccountHierarchy LIKE h.AccountHierarchy + '%')
OR hd.AccountHierarchy = h.AccountHierarchy)
) AS DR
,(SELECT SUM(l.Amount)
FROM Ledger l
INNER JOIN AccountHierarchy hd ON (l.AccountNumber = hd.AccountNumber)
WHERE l.IsDebit = 0
AND ( (h.IsControlAccount = 1 AND hd.AccountHierarchy LIKE h.AccountHierarchy + '%')
OR hd.AccountHierarchy = h.AccountHierarchy)
) AS CR
FROM AccountHierarchy h
) x
WHERE NOT(CR IS NULL AND DR IS NULL)
ORDER BY AccountHierarchy
I used this question for a hierarchy example.
Output:
| ACCOUNTNUMBER | ACCOUNTDESCRIPTION | ACCOUNTHIERARCHY | ISCONTROLACCOUNT | DR | CR | BAL |
|----------------------|------------------------------------|-----------------------------------------------------------------|------------------|--------|-----------|------------|
| 1000 | Accounts Receivable | 1000 / | 1 | 10000 | 5000 | 5000 |
| 1200 | Buyer Receivables | 1000 /1200 / | 1 | 5000 | (null) | 5000 |
| 12001 | Buyer Receivables - Best Buy | 1000 /1200 /12001 / | 0 | 5000 | (null) | 5000 |
| 1500 | Offers | 1000 /1500 / | 0 | 5000 | 5000 | 0 |
| 4000 | Accounts Payable | 4000 / | 1 | (null) | 4475.0685 | -4475.0685 |
| 4100 | Supplier Payables | 4000 /4100 / | 1 | (null) | 4475.0685 | -4475.0685 |
| 41002 | Supplier Payables - Knechtel | 4000 /4100 /41002 / | 0 | (null) | 4475.0685 | -4475.0685 |
| 6000 | Revenue | 6000 / | 1 | (null) | 524.9315 | -524.9315 |
| 6200 | Processing Fees Revenue | 6000 /6200 / | 1 | (null) | 100 | -100 |
| 62002 | Processing Fees Revenue - Knechtel | 6000 /6200 /62002 / | 0 | (null) | 100 | -100 |
| 6300 | Fees Revenue | 6000 /6300 / | 1 | (null) | 424.9315 | -424.9315 |
| 63002 | Fees Revenue - Knechtel | 6000 /6300 /63002 / | 0 | (null) | 424.9315 | -424.9315 |
Starting with your desired output I came up with the following query that groups child accounts based on the ParentAccountNumber. The subquery is only needed since I assumed you want to convert any NULL value to 0 before summing up (in SQL, NULL + 42 = NULL).
with preresult as
(
select acc.ParentAccountNumber as AccountNumber,
acc.AccountDescription as "Acc Desc",
ISNULL(ld.Amount, 0) as DR,
ISNULL(lc.Amount, 0) as CR
from ChartOfAccounts acc
left outer join Ledger ld
on (ld.AccountNumber = acc.AccountNumber AND ld.IsDebit = 1)
left outer join Ledger lc
on (lc.AccountNumber = acc.AccountNumber AND lc.IsDebit = 0)
where acc.ParentAccountNumber is not null
)
select c.AccountNumber as "ACC",
c.AccountDescription as "ACC DESC",
sum(DR) as DR,
sum(CR) as CR,
sum(DR) - sum(CR) AS BL
from preresult p
join ChartOfAccounts c on (c.AccountNumber = p.AccountNumber)
group by c.AccountNumber, c.AccountDescription;
The sqlfiddle can be found here: http://www.sqlfiddle.com/#!3/d94bc/81/0
This seems to give you what you want:
;WITH recurs
AS
(
SELECT C.AccountNumber, C.IsControlAccount, C.ParentAccountNumber, C.AccountDescription,
COALESCE((SELECT SUM(Amount) FROM Ledger WHERE AccountNumber = C.AccountNumber and IsDebit = 1), 0) AS DR,
COALESCE((SELECT SUM(Amount) FROM Ledger WHERE AccountNumber = C.AccountNumber and IsDebit = 0), 0) AS CR,
COALESCE((SELECT SUM(CASE WHEN IsDebit = 0 THEN Amount * -1 ELSE Amount END) FROM Ledger WHERE AccountNumber = C.AccountNumber), 0) AS BAL
FROM ChartOfAccounts C
WHERE IsControlAccount = 0
UNION ALL
SELECT C.AccountNumber, C.IsControlAccount, C.ParentAccountNumber, C.AccountDescription,
r.DR, r.CR, R.BAL
FROM ChartOfAccounts C
INNER JOIN recurs r
ON r.ParentAccountNumber = c.AccountNumber
)
SELECT R.AccountNumber, R.AccountDescription, SUM(R.DR) AS DR, SUM(R.CR) AS CR, SUM(R.BAL) AS BAL
FROM recurs R
WHERE NOT (R.DR = 0 AND R.CR = 0 AND R.BAL = 0)
GROUP BY R.AccountNumber, R.AccountDescription
ORDER BY AccountNumber
SQL Fiddle here
Results:
| ACCOUNTNUMBER | ACCOUNTDESCRIPTION | DR | CR | BAL |
|----------------------|------------------------------------|-------|-----------|------------|
| 1000 | Accounts Receivable | 10000 | 5000 | 5000 |
| 1200 | Buyer Receivables | 5000 | 0 | 5000 |
| 12001 | Buyer Receivables - Best Buy | 5000 | 0 | 5000 |
| 1500 | Offers | 5000 | 5000 | 0 |
| 4000 | Accounts Payable | 0 | 4475.0685 | -4475.0685 |
| 4100 | Supplier Payables | 0 | 4475.0685 | -4475.0685 |
| 41002 | Supplier Payables - Knechtel | 0 | 4475.0685 | -4475.0685 |
| 6000 | Revenue | 0 | 524.9315 | -524.9315 |
| 6200 | Processing Fees Revenue | 0 | 100 | -100 |
| 62002 | Processing Fees Revenue - Knechtel | 0 | 100 | -100 |
| 6300 | Fees Revenue | 0 | 424.9315 | -424.9315 |
| 63002 | Fees Revenue - Knechtel | 0 | 424.9315 | -424.9315 |
Related
query which calculates the total amount in dollars of stolen goods for each month for restricted and neutral items.
I have 2 tables
first
| UPC | item | in_stock | price | ship_day | class |
1 | 101 | 'generator' | 16 | 5999 | '12-1-2065'| 'restricted'
2 | 102 | 'blank tape' | 30 | 3000 | '12-1-2065'| 'neutral'
second
| UPC | unit_stolen |
1 | 101 | 4 |
1 | 401 | 2 |
If I understand correctly, this is basically a join and group by:
select date_trunc('mon', f.ship_day) as yyyymm,
sum(f.price * s.unit_stolen) filter (where f.class = 'restricted'),
sum(f.price * s.unit_stolen) filter (where f.class = 'neutral')
from first f join
second s
on f.upc = s.upc
group by date_trunc('mon', f.ship_day)
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 have 2 tables that needs to be joined based on couple of parameters. One of the parameters is year. One table contains the current year but another year doesn't contain current year, so it must use the latest year and matched with other parameters.
Example
Product
-------------------------------------------------------------------------------
| product_id | category_id | sub_category_id | product_year | amount |
-------------------------------------------------------------------------------
| 504 | I | U | 2020 | 400 |
| 510 | I | U | 2019 | 100 |
| 528 | I | U | 2019 | 150 |
| 540 | I | U | 2018 | 1000 |
Discount
-----------------------------------------------------------------------------
| discount_year | category_id | sub_category_id | discount |
-----------------------------------------------------------------------------
| 2018 | I | U | 0.15 |
| 2017 | I | U | 0.35 |
| 2016 | I | U | 0.50 |
Output
-----------------------------------------------------------------------------
| product_id | category_id | sub_category_id | product_year | discount_year |
-----------------------------------------------------------------------------
| 504 | I | U | 2020 | 2018 |
| 510 | I | U | 2019 | 2018 |
| 528 | I | U | 2019 | 2018 |
| 540 | I | U | 2018 | 2017 |
The discount is always gotten from one year behind but if those rates aren't available, then it would keep going back a year until available.
I have tried the following:
SELECT
product_year, a.product_id, a.category_id, a.sub_category_id,
discount_year, amount, discount
FROM
Product a
INNER JOIN
Discount b ON a.category_id = b.category_id
AND a.sub_category_id = b.sub_category_id
AND product_ year = CASE
WHEN discount_year + 1 = product_year
THEN discount_year + 1
WHEN discount_year + 2 = product_year
THEN discount_year + 2
WHEN discount_year + 3 = product_year
THEN discount_year + 3
END
WHERE
product = 540
This return the following output:
--------------------------------------------------------------------------------------------------
| product_year | product_id | category_id | sub_category_id | discount_year | amount | discount |
--------------------------------------------------------------------------------------------------
| 2016 | 540 | I | U | 2017 | 1000 | 0.50 |
| 2017 | 540 | I | U | 2017 | 1000 | 0.35 |
Any help will be appreciated.
You can use OUTER APPLY and a subquery. In the subquery select the row with the maximum discount_year, that is less the product_year using TOP and ORDER BY.
SELECT p.product_year,
p.product_id,
p.category_id,
p.sub_category_id,
d.discount_year,
p.amount,
d.discount
FROM product p
OUTER APPLY (SELECT TOP 1
*
FROM discount d
WHERE d.category_id = p.category_id
AND d.sub_category_id = p.sub_category_id
AND d.discount_year < p.product_year
ORDER BY d.discount_year DESC) d;
instead of a CASE expression you can use a sub-query to select the TOP 1 related
discount_year that is less than your product_year, ORDER BY discount_year ASC.
Create a product to discount mapping using a CTE first. This contains the discount year pulled from discount table for every product year in the product table and corresponding product_id. Following this, you can easily join with relevant tables to get results and eliminate any nulls as needed
Simplified query.
;WITH disc_prod_mapper
AS
(
SELECT product_id, product_year,(SELECT MAX(discount_year) FROM #Discount b WHERE discount_year < product_year AND a.category_id = b.category_id AND a.sub_category_id = b.sub_category_id ) AS discount_year
FROM Product a
)
SELECT a.product_year, c.discount_year, a.amount, c.discount
FROM Product a
LEFT JOIN disc_prod_mapper b ON a.product_id = b.product_id
LEFT JOIN Discount c ON b.discount_year = c.discount_year
I need help with this.
Tbl: WarehouseInventory
Date | DelRec | ProductId | Quantity
2015-09-10 | 110 | 1 | 100
2015-09-12 | 111 | 1 | 100
2015-09-12 | 111 | 2 | 200
2015-09-12 | 111 | 3 | 300
Tbl: Withdrawals
Date | ID | ProductId | Quantity | CustomerId
2015-09-11 | 1 | 1 | 400 | 2
2015-09-12 | 1 | 1 | 100 | 1
2015-09-12 | 2 | 2 | 200 | 1
2015-09-12 | 3 | 3 | 300 | 1
Tbl: Customers
Customer Id | Name
1 | Somebody
2 | Someone
The output should be like this
DelRec | Date Added | ProductId | Stocked | Withdrawn | Customer
110 | 2015-09-10 | 1 | 100 | 0 | NULL
0 | 2015-09-11 | 1 | 0 | 400 | Someone
111 | 2015-09-12 | 1 | 100 | 100 | Somebody
111 | 2015-09-12 | 2 | 200 | 200 | Somebody
111 | 2015-09-12 | 3 | 300 | 300 | Somebody
This is what I have come up so far and it's giving me a wrong output
select wi.DateAdded as 'Date Added', max(wi.DeliveryReceipt) as 'Delivery Receipt', wi.ProductId as 'Product',
max(isnull(wi.Quantity, 0)) as 'Stocked', max(isnull(w.Quantity, 0)) as 'Withdrawn', e.Customers as 'Customer'
from WarehouseInventory wi
cross join Withdrawals w
cross join Customer e
group by wi.DateAdded, wi.ProductId, e.Customers, wi.DeliveryReceipt, w.ProductId
Basically, I need to join the two tables on the date and product and if there is a null value in one of the tables, just make it 0. I appreciate your help.
You can use a FULL OUTER JOIN:
SELECT DelRec,
COALESCE(wi.[Date], wd.[Date]) AS Date_Added,
COALESCE(wi.ProductId, wd.ProductId) AS ProductId,
COALESCE(wi.Quantity, 0) AS Stocked,
COALESCE(wd.Quantity, 0) AS Withdrawn,
c.Name AS Customer
FROM WarehouseInventory AS wi
FULL OUTER JOIN Withdrawals AS wd
ON wi.[Date] = wd.[Date] AND wi.ProductId = wd.ProductId
LEFT JOIN Customers AS c ON c.[Customer Id] = wd.CustomerId
ORDER BY Date_Added
You have a few inconsistencies between your example table and your query, but here's the basic gist:
You want to FULL OUTER JOIN your Warehouse delivery (A) and Withdrawal (B) tables on both product and date
Make sure you coalesce(A, B) for both date and product
Sum the quantities from each table, then coalesce outside each aggregate to get zeros (since one column can be all nulls).
Here:
select
coalesce(wi.DateAdded, w.date) as 'Date Added',
max(wi.DeliveryReceipt) as 'Delivery Receipt',
coalesce(wi.ProductId, w.productId) as 'Product',
coalesce(sum(wi.Quantity), 0) as 'Stocked',
coalesce(sum(w.Quantity), 0) as 'Withdrawn',
e.name as 'Customer'
from WarehouseInventory wi
full outer join Withdrawals w on w.date = wi.dateadded and w.productId = wi.productId
left join Customer e on e.customerId = w.customerId
group by
coalesce(wi.DateAdded, w.date),
coalesce(wi.ProductId, w.productId),
e.name