How to calculate credit and debit from single column 'Amount'? - sql

I have four columns as follows:
User_ID
Credit_Account
Debit_Account
AMOUNT
18595
PKR1000100013010
PKR1000023233010
1364500
16133
PKR1616100013001
125450528
1826
16387
PL52008
130984886
4560
18768
PKR1007000010025
PL64084
4000
18540
131014988
131013728
159092
18386
105090145
PKR1000100013010
167079
How do I calculate Total Credit Amount and Total Credit Amount?
I tried the following which is a mess I think:
SELECT USER_ID , SUM(CR_total) as Total_Credit
FROM
(
SELECT USER_ID
, Credit_Account
, AMOUNT as CR_total
FROM table
GROUP BY USER_ID
)CR
LEFT JOIN
(SELECT USER_ID , SUM(DR_total) as Total_Debit
FROM
(
SELECT USER_ID
, Debit_Account
, AMOUNT as DR_total
FROM table
GROUP BY DR.USER_ID
)DR
ON DR.USER_ID = CR.USER_ID
group by USER_ID
ORDER BY USER_ID
Expected Results:
User_ID
Credit_Account
Credit Amount against CA
Debit Amount against CA
Debit_Account
Credit Amount against DA
Debit Amount against DA
AMOUNT
18595
PKR1000100013010
PKR1000023233010
1364500
16133
PKR1616100013001
125450528
1826
And since there might be duplication of accounts in both columns(Credit_Account and Debit_Account), the required debit/credit amounts can be aggregated against each account.

You can use a CROSS APPLY( VALUES...) to reshape your data into Account, Credit_Amount, and Debit_Amount. Standard grouping and aggregation operations can then be performed.
SELECT
C.Account,
SUM(C.Credit_Amount) AS Total_Credit_Amount,
SUM(C.Debit_Amount) AS Total_Debit_Amount
FROM Data D
CROSS APPLY (
VALUES
(D.Credit_Account, D.AMOUNT, 0),
(D.Debit_Account, 0, D.AMOUNT)
) C(Account, Credit_Amount, Debit_Amount)
GROUP BY C.Account
See this db<>fiddle.
The zero amounts may also be replaced will NULLs if you prefer.
If accounts are user-specific, you can add D.User_ID to the group by and select list.

Related

Subtraction in same table in same row with where condition #SQL

Using a SQL query to subtract in same table:
Table1
grade record total_amount
------------------------------
DSP receipt 17258
DSP sales 16313
OPC43 receipt 95442
OPC43 sales 92856
OPC53 receipt 371752
OPC53 sales 368985
PPC receipt 156023
PPC sales 152803
vajram receipt 36100
vajram sales 36100
Answer needed
945 DSP
2586 OPC43
2767 OPC53
3220 PPC
0 vajram
Is there ever only two entries per grade? Always just a receipt and sales? If that's the case, then something like the following would do the trick
SELECT receipt.total_amount - sales.total_amount, grade
FROM
(SELECT * FROM yourtable WHERE record = 'receipt') receipt
INNER JOIN (SELECT * FROM yourtable WHERE record = 'sales') sales
ON receipt.grade = sales.grade
I'd try something like that:
select grade, sum(case when record = 'receipt' then total_amount else 0-total_amount end)
from table
group by grade

How to select 1000 customers who were the first to gain 1000 bonus points for purchases in categories "Taxi" and "Books"? (SQLite)

The BONUS table has attributes: client_id, bonus_date, the number of accrued bonuses (bonus_cnt), mcc code of the transaction for which added bonuses (mcc_code). The MCC_CATEGORIES table is a mcc code reference.
Attributes:
mcc-code (mcc_code), category (for example, supermarkets, transport, pharmacies, etc., mcc_category)
How to select 1000 customers who were the first to gain 1000 bonus points for purchases in
categories "Taxi" and "Books"?
BONUS table looks like:
CLIENT_ID BONUS_DATE BONUS_CNT MCC_CODE
1121 2020-01-02 23 5432
3421 2020-04-15 7 654
...
MCC_CATEGORIES table looks like:
MCC_CODE MCC_CATEGORY
5432 Taxi
3532 Music
...
I would use window functions and aggregation: first join the tables and compute the running sum of bonus per user and category. Then aggregate by user and category, and get the date when they reached a bonus of 1000. Finally, compute the date when each user reached the target on both categories, order by that, and limit:
select client_id, max(bonus_date) bonus_date
from (
select client_id, mcc_category, min(bonus_date) bonus_date
from (
select b.client_id, b.bonus_date, c.mcc_category,
sum(bonus_cnt) over(partition by b.client_id, c.mcc_category order by b.bonus_date) sum_bonus
from bonus b
inner join mcc_categories c on c.mcc_code = b.mcc_code
where mcc_category in ('Taxi', 'Books')
) t
where sum_bonus >= 1000
group by client_id, mcc_category
) t
group by client_id
having count(*) = 2
order by bonus_date
limit 1000
Window functions are available in SQLite starting version 3.25.
How to select 1000 customers who were the first to gain 1000 bonus points for purchases in categories "Taxi" and "Books"?
I am guessing you want to combine the bonuses for the two categories together. If so:
select client_id, min(bonus_date) as min_bonus_date
from (select b.client_id, b.bonus_date, b.bonus_cnt,
sum(b.bonus_cnt) over (partition by b.client_id order by b.bonus_date) as running_bonus_cnt
from bonus b join
mcc_categories c
on c.mcc_code = b.mcc_code
where mcc_category in ('Taxi', 'Books')
) bc
where running_bonus_cnt >= 1000 and
running_bonus_cnt - bonus_cnt < 1000
group by client_id
order by min_bonus_date
limit 1000;
Note how this works. The subquery calculates the running bonus amount. The where clause then gets the one row where the bonus count first exceeds 1000.
The rest is just aggregation.

Calculating average for each Reward Type

For each reward type, I am trying to calculate the average number of times rewards of that type has been included in a deposit. A constraint on it is that deposits that do not include reward type don't contribute to the average for that type instead of 0.
Below is the schema:
rewards(rewardId, rewardType, rewardValue);
deposit(depositId, depositDate, customerId);
details(depositId, rewardsId, numDeposit);
Here is my query:
select r.rewardsId, avg(dep.depositId)
from deposit dep join details det
on dep.depositId = det.depositId join rewards r
on r.rewardsId = det.rewardsId
group by r.rewardsId;
The answer I get doesn't seem right since the average is very high but when I count it manually, I get around 2 for each rewardType. Can someone point out what I'm doing wrong?
Since you want to count only deposits that do include reward type, then you can count them like this:
select t.rewardsId, avg(t.counter) from (
select
r.rewardsId,
sum(case when det.rewardsId is null then 0 else 1 end) counter
from deposit dep join details det
on dep.depositId = det.depositId join rewards r
on r.rewardsId = det.rewardsId
group by r.rewardsId, dep.depositId
) t
group by t.rewardsId
You definitely don't want the average of an id column.
That just doesn't make sense.
You may just want count(*):
select r.rewardsId, count(*) as num_rewards
from details det join
rewards r
on r.rewardsId = det.rewardsId
group by r.rewardsId;
From what I can tell, you don't need the deposit table. The deposit id is in the details.
Or, if you want rewards per deposit:
select r.rewardsId,
count(*) * 1.0 / count(distinct det.depositId) as num_rewards
from details det join
rewards r
on r.rewardsId = det.rewardsId
group by r.rewardsId;
Use nullif(dep.depositId, 0) to convert the zeros to null, which will not be accounted for in the average calculations.
Consider the following:
select avg(val) as avg_all,
avg(nullif(val, 0)) as avg_nonzero
from (values
(2), (4), (6), (0), (8), (0), (10)
) v(val)
which returns:
avg_all |avg_nonzero |
------------------|------------------|
4.2857142857142857|6.0000000000000000|
See nullif documentation

How to find net rows from sales and cancel rows using sql

I have a table with columns mentioned below:
transaction_type transaction_number amount
Sale 2016040433 50
Cancel R2016040433 -50
Sale 2016040434 50
Sale 2016040435 50
Cancel R2016040435 -50
Sale 2016040436 50
I want to find net number of rows with only sales which does not include canceled rows.
(Using SQL Only).
If you just want to count the sales and subtract the cancels (as suggested by your sample data), you can use conditional aggregation:
select sum(case when transaction_type = 'Sale' then 1
when transaction_type = 'Cancel' then -1
else 0
end)
from t;
SELECT count(transaction_type) FROM TBL_NAME GROUP BY count(transaction_type) HAVING transaction_type = 'Sale'
Just use the count function, and use HAVING with GROUP BY to filter
The list of row
select transaction_number, amount
from table
where transaction_type ='Sale';
or sum
select sum(amount)
from table
where transaction_type ='Sale';
or count
select count(*)
from table
where transaction_type ='Sale';
Since you have the transaction number, which i assume is a unique identifier for each transaction, i would use it to count the net sale rows.
Assuming then than every Cancelled transaction number is 'R'+ Sales transact number, then the query i propose is the following:
WITH CancelledSales_CTE (transaction_type, transaction_number, amount)
AS
(
SELECT transaction_type, RIGHT(transaction_number, LEN(transaction_number) - 1), amount
FROM Sales
WHERE transaction_type IS 'Cancel'
)
SELECT count(transaction_number)
FROM Sales
where transaction_number not in
(select distinct transaction_number from CancelledSales_CTE )
CancelledSales_CTE contains the list of cancelled rows, with the transaction number of the relative sale.
The count coming from
SELECT count(transaction_number)
FROM Sales
where transaction_number not in
(select distinct transaction_number from CancelledSales_CTE )
will exclude the transaction numbers which have been canceled

SQL - Restricting JOINed Records by Date

Using two tables in MSSQL:
One table, [CUSTOMER], containing information on donors.
- Relevent columns: [CustID], [Name]
One table, [DONATION], containing records for each donation given.
- Relevent columns: [CustID], [Amount], [Date]
The tables share a key, [CustID].
I want to aggregate the [Amounts] according to each [CustID]
SELECT DONATION.CUSTID
,PEOPLE.NAME
,SUM (DONATION.AMOUNT) as TOTAL_DONATION
FROM [dbo].[DONATION] INNER JOIN [dbo].[PEOPLE] ON DONATION.CUSTID = PEOPLE.CUSTID
GROUP BY
DONATION.CUSTID
,PEOPLE.NAME
HAVING SUM (DONATION.AMOUNT) > 100
This query works fine, even with adding the HAVING clause.
When I want to restrict the dates of donations to aggregate (adding to the SELECT,GROUP BY, and HAVING clauses) however...
SELECT DONATION.CUSTID
,PEOPLE.NAME,DONATION.DATE
,SUM (DONATION.AMOUNT) as TOTAL_DONATION
FROM [dbo].[DONATION] INNER JOIN [dbo].[PEOPLE] ON DONATION.CUSTID = PEOPLE.CUSTID
GROUP BY
DONATION.CUSTID
,PEOPLE.NAME
,DONATION.DATE
HAVING SUM (DONATION.AMOUNT) > 100
and DONATION.DATE > '1-1-2010'
The query no longer returns aggregate sums of each person's donations, but returns individual donations for each person, which meet the HAVING criteria.
How can I implement this date restriction? Is it how I'm joining or summing or....? Thanks.
Move it to the WHERE clause
SELECT DONATION.CUSTID
,PEOPLE.NAME
,SUM (DONATION.AMOUNT) as TOTAL_DONATION
FROM [dbo].[DONATION]
INNER JOIN [dbo].[PEOPLE] ON DONATION.CUSTID = PEOPLE.CUSTID
WHERE DONATION.DATE > '1-1-2010'
GROUP BY
DONATION.CUSTID
,PEOPLE.NAME
HAVING SUM (DONATION.AMOUNT) > 100
What this means:
- given the people who donated
- look only at data where donation date is in 2010 or later
- and within that data, show the people who donated a total of more than 100
Modify your first query like:
,SUM (CASE WHEN DONATION.DATE > '1-1-2010' THEN DONATION.AMOUNT END)
as TOTAL_DONATION
You can't group by date unless you want one row per customer per date.
How about doing it from the donation table and just looking up customer names?
select Donator=(select name from [people] where [people].custid=[donation].custid),
custid,SUM(amount)
from [donation]
where [donation].date between '1/1/2011' and '1/15/2011'
group by custid
having SUM(amount)>100