Getting the wrong values after SUM() query - sql

I'm executing the following query:
SELECT
MAX(table1.id) as id,
clients.Username as account,
table1.clientid,
SUM(table1.symbols) as symbols,
SUM(table1.tickets) as tickets,
SUM(table1.cash) as cash,
(SUM(CASE WHEN table2.memo = 'Withdraw' THEN amount ELSE 0 END)) AS withdraw,
(SUM(CASE WHEN table2.memo = 'Depos' THEN amount ELSE 0 END)) AS depos,
FROM
table1
LEFT JOIN
(
clients
LEFT JOIN
table2
ON
clients.Fidx = table2.clientid
AND
table2.date >= '01-09-2016'
AND
table2.date <= '01-09-2017'
)
ON
clients.Fidx = table1.clientid
WHERE
table1.tradedate >= '01-09-2016'
AND
table1.tradedate <= '01-09-2017'
GROUP BY
table1.clientid, clients.Username, table2.clientid
ORDER BY
clients.Username;
And I want to get a simple result table combined of three tables:
+---------+--------+---------+
| account |withdraw| depos |
+---------+--------+---------+
| adaf | 300 | 0 |
| rich | 1000 | 355 |
| call | 0 | 45 |
| alen | 0 | 0 |
| courney| 0 | 106 |
| warren | 0 | 0 |
+---------+--------+---------+
What's the problem? - I'm getting the wrong values in the result table. Exactly in withdraw and depos. They're in 4 times more, than they should be. For example, for some client SUM(depos) should be 500, but in my result table this value gets 2000. I guess, the problem is in a GROUP BY method, cause when I'm executing the following query, the result seems to look OK:
SELECT clientid, SUM(case when memo = 'Withdraw' then amount else 0 end) as withdraw, SUM(case when memo = 'Depos' then amount else 0 end) as depos
from clients
LEFT JOIN
table2
ON
clients.Fidx = table2.clientid
WHERE table2.date >= '01-09-2016' and table2.date<='01-09-2017' GROUP BY clientid ORDER BY clientid;
What can be a reason of such a wrong result? I'm in trouble and need your help, guys.

You pretty much answered this yourself with the bottom query. You need to do the summing before the join, in a derived table or subquery, just like you have in the bottom query. This will ensure you join on a many-to-one relationship, instead of a many-to-many, which must be causing your current duplication (or 'multiplied' sums).
SELECT
MAX(table1.id) as id,
clients.Username as account,
table1.clientid,
SUM(table1.symbols) as symbols,
SUM(table1.tickets) as tickets,
SUM(table1.cash) as cash,
withdraw,
depos,
FROM
table1
LEFT JOIN
(SELECT clientid,
SUM(case when memo = 'Withdraw' then amount else 0 end) as withdraw,
SUM(case when memo = 'Depos' then amount else 0 end) as depos
FROM clients
LEFT JOIN table2 ON clients.Fidx = table2.clientid
AND table2.date >= '01-09-2016'
AND table2.date<='01-09-2017'
GROUP BY clientid) clients
ON
clients.clientID = table1.clientid
WHERE
table1.tradedate >= '01-09-2016'
AND
table1.tradedate <= '01-09-2017'
GROUP BY
table1.clientid, clients.Username, table2.clientid, clients.depos, clients.withdraw
ORDER BY
clients.Username;
Further example:
Table1
id | someInfo
1 | a
1 | b
1 | c
Table2
id | value
1 | 5
1 | 10
This query:
SELECT t1.id, SUM(t2.Value)
FROM table1 t1
JOIN table2 t2 on t1.id = t2.id --This will be many-to-many
GROUP BY t1.id
Will result in this:
Results
id | value
1 | 45 --sum of 45 because the `table2` values are triplicated from the join
Where this query:
SELECT DISTINCT t1.id, Value
FROM table1 t1
JOIN (SELECT id, SUM(Value) value
FROM table2
GROUP BY id) t2 on t1.id = t2.id --This will be many-to-one
Will result in this:
Results
id | value
1 | 15 --sum of 15 because the `table2` values are not triplicated from the join

Aggregate before joining:
select
t1.id,
c.Username as account,
c.clientid,
t1.symbols,
t1.tickets,
t1.cash,
coalesce(t2.withdraw, 0) as withdraw,
coalesce(t2.depos, 0) as depos
from clients c
join
(
select
clientid,
max(id) as id,
sum(symbols) as symbols,
sum(tickets) as tickets,
sum(cash) as cash
from table1
where date >= '20160901' and date <= '20170901'
group by clientid
) t1 on t1.clientid = c.fidx
left join
(
select
clientid,
sum(case when memo = 'Withdraw' then amount end) as withdraw,
sum(case when memo = 'Depos' then amount end) as depos
from table2
where date >= '20160901' and date <= '20170901'
group by clientid
) t2 on t2.clientid = c.fidx;

Related

SQL request with multiple joins condition on a list of transactions (postgreSQL)

I have a transactions table with a this structure of data, showing the most important fields to keep in mind.
It's a list of transactions, where type=1 transactions are account deposit, while type=2 are account withdrawals.
login | type | value | date.
1234 | 1 | 100 | 25/09/2021
1234. | 2. | 250. | 26/09/2021
4321. | 2. | 234. | 13/09/2021
4321. | 1. | 342. | 14/08/2021
...
...
What I'm trying to get is the list of the accounts, where their balance during the period is bigger than > some amount, X, and no active deposits after 16/09/2021.
SELECT t.date,
a.login, t.account, sum(d.value) as deposits, sum(w.value) as withdrawals, sum(d.value) - sum(w.value) as balance
FROM b.transactions AS t
INNER JOIN b.accounts as a ON t.account=a.id
INNER JOIN b.transactions AS d ON t.account = d.account and d.date=t.date and d.type=t.type
INNER JOIN b.transactions AS w ON t.account = w.account and w.date=t.date and w.type=t.type
WHERE
d.value - w.value > 5000 and
d.type = '1' and
w.type = '2' and
d.date<='2021-09-29' and d.date>='2021-09-29' and
w.date<='2021-09-29' and w.date>='2021-09-29' and
t.date<='2021-09-29' and t.date>='2021-09-29' and
t.account not in
(
SELECT t.account
FROM b.transactions AS t
where t.type in (1) and
t.date>='2021-09-16' and
t.value>0
group by t.account
)
I'm getting some output, but it looks like seriously underestimated (at least 10x- fold)... Can not find the mistake, where should I look?
Thanks in advance..
You may be overthinking this — I don't think it is necessary to join the same table three times:
SELECT t.date,
a.login,
t.account,
sum(CASE WHEN t.type = '1'
THEN t.value
WHEN t.type = '2'
THEN -t.value
END) AS balance
FROM b.transactions AS t
INNER JOIN b.accounts AS a
ON t.account = a.id
WHERE t.date BETWEEN '2021-09-29' AND '2021-09-29'
GROUP BY t.account
HAVING sum(CASE WHEN t.type = '1'
THEN t.value
WHEN t.type = '2'
THEN -t.value
END) > 5000;
First, to compute the running balance you can use a window function. For example:
select
login, date, type, value,
sum(
case when type = 1 then value when type = 2 then -value end
) over(partition by login order by date) as running_balance
from t
order by login, date;
Result:
login date type value running_balance
------ ------------------------- ----- ------ ---------------
1234 2021-09-25T00:00:00.000Z 1 100 100
1234 2021-09-26T00:00:00.000Z 2 250 -150
4321 2021-09-13T00:00:00.000Z 2 234 -234
4321 2021-09-14T00:00:00.000Z 1 342 108
See running example at DB Fiddle - Simple Running Balance.
Then you can use the logic above to compute more complex values so you can use them to filter data, as in:
select distinct login
from (
select
login, date, type, value,
sum(
case when type = 1 then value when type = 2 then -value end
) over(partition by login order by date) as running_balance,
sum(
case when type = 1 and date > date '2021-09-16' then 1 else 0 end
) over(partition by login order by date) as extra_deposits
from t
) x
where running_balance >= 100 and extra_deposits = 0
Result:
login
-----
4321
See running example at DB Fiddle - Full Query.

Oracle Group by multiple condition

I had a query to group types with sum of prices
SELECT t.type, sum(t.price) AS TOTAL
FROM table1 t
WHERE t.entry_date = & entryDate
GROUP BY t.type;
It works correctly and returns
+-----------+-----------+
| TYPE | TOTAL |
+-----------+-----------+
| | 741,5 |
| type1 | 108,54 |
| type2 | 216,35 |
+-----------+-----------+
In table some of the rows don't have a type name.
I want to add a case to check if exists add its price to type1 so
I tried something like
SELECT (CASE WHEN t.id = k.orderno AND length(k.invoice_no) < 12
THEN 'type1' ELSE t.type END
) AS TYPE,
SUM(t.price) AS TOTAL
FROM table1 t, table2 k
WHERE t.entry_date = & entryDate
GROUP BY TYPE;
It gives
ora-00937 not a single-group group function
I also tried group by when case but it didn't work either.
Without knowing your table definitions and what the columns are used for it is difficult to answer; however, it could be as simple as:
SELECT COALESCE( t.type, t.price ) AS type,
sum(t.price) AS TOTAL
FROM table1 t
WHERE t.entry_date = &entryDate
GROUP BY
t.type;
Or, if you want to join another table then you want to use a join condition (and, preferably, the ANSI join syntax rather the legacy comma join syntax):
SELECT CASE
WHEN COALESCE( LENGTH(k.invoice_no), 0 ) < 12
THEN 'type1' || t.price
ELSE t.type
END AS TYPE,
SUM(t.price) AS TOTAL
FROM table1 t
LEFT OUTER JOIN table2 k
ON ( t.id = k.orderno )
WHERE t.entry_date = &entryDate
GROUP BY
CASE
WHEN COALESCE( LENGTH(k.invoice_no), 0 ) < 12
THEN 'type1' || t.price
ELSE t.type
END;
You need to use the group by accordingly as follows:
select Case When t.id = k.orderno
and length(k.invoice_no) < 12
THEN 'type1'
ELSE t.type
END AS TYPE,
sum(t.price) as TOTAL
from table1 t, table2 k
where t.entry_date = &entryDate
group by Case When t.id = k.orderno
and length(k.invoice_no) < 12
THEN 'type1'
ELSE t.type
END;

SQL Select Distinct Records From Two Tables

I am trying to write a SQL statement that will return a set of Distinct set of CompanyNames from a table based on the most recent SaleDate withing a specified date range from another table.
T01 = Account
T02 = TransHeader
The fields of importance are:
T01.ID, T01.CompanyName
T02.AccountID, T02.SaleDate
T01.ID = T02.AccountID
What I want to return is the Max SaleDate for each CompanyName without any duplicate CompanyNames and only the Max(SaleDate) as LastSale. I will be using a Where Clause to limit the SaleDate range.
I tried the following but it returns all the records for all SalesDates in the range. This results in the same company being listed multiple times.
Current MS-SQL Query
SELECT T01.CompanyName, T02.LastSale
FROM
(SELECT DISTINCT ID, IsActive, ClassTypeID, CompanyName FROM Account) T01
FULL OUTER JOIN
(SELECT DISTINCT AccountID, TransactionType, MAX(SaleDate) LastSale FROM TransHeader group by AccountID, TransactionType, SaleDate) T02
ON T01.ID = T02.AccountID
WHERE ( ( T01.IsActive = 1 )AND
( (Select Max(SaleDate)From TransHeader Where AccountID = T01.ID AND TransactionType in (1,6) AND SaleDate is NOT NULL)
BETWEEN '01/01/2016' AND '12/31/2018 23:59:00' AND (Select Max(SaleDate)From TransHeader Where AccountID = T01.ID AND TransactionType in (1,6) AND SaleDate is NOT NULL) IS NOT NULL
)
)
ORDER BY T01.CompanyName
I thought the FULL OUTER JOIN was the ticket but it did not work and I am stuck.
Sample data Account Table (T01)
ID CompanyName IsActive ClassTypeID
1 ABC123 1 1
2 CDE456 1 1
3 EFG789 1 1
4 Test123 0 1
5 Test456 1 1
6 Test789 0 1
Sample data Transheader table (T02)
AccountID TransactionType SaleDate
1 1 02/03/2012
2 1 03/04/2013
3 1 04/05/2014
4 1 05/06/2014
5 1 06/07/2014
6 1 07/08/2015
1 1 08/09/2016
1 1 01/15/2016
2 1 03/20/2017
2 1 03/21/2017
3 1 03/04/2017
3 1 04/05/2018
3 1 05/27/2018
4 1 06/01/2018
5 1 07/08/2018
5 1 08/01/2018
5 1 10/11/2018
6 1 11/30/2018
Desired Results
CompanyName LastSale (Notes note returned in the result)
ABC123 01/15/2016 (Max(SaleDate) LastSale for ID=1)
CDE456 03/21/2017 (Max(SaleDate) LastSale for ID=2)
EFG789 05/27/2018 (Max(SaleDate) LastSale for ID=3)
Testing456 10/11/2018 (Max(SaleDate) LastSale for ID=5)
ID=4 & ID=6 are note returned because IsActive = 0 for these records.
One option is to select the maximum date in the select clause.
select
a.*,
(
select max(th.saledate)
from transheader th
where th.accountid = a.id
and th.saledate >= '2016-01-01'
and th.saledate < '2019-01-01'
) as max_date
from account a
where a.isactive = 1
order by a.id;
If you only want to show transaction headers with sales dates in the given date range, then you can just inner join the maximum dates with the accounts. In order to do so, you must group your date aggregation per account:
select a.*, th.max_date
from account a
join
(
select accountid, max(saledate) as max_date
from transheader
and saledate >= '2016-01-01'
and saledate < '2019-01-01'
group by accountid
) th on th.accountid = a.id
where a.isactive = 1
order by a.id;
select CompanyName,MAX(SaleDate) SaleDate from Account a
inner join Transheader b on a.id = b.accountid
group by CompanyName
order by 1

Return id when id value does not equal 0 at least once

I have 2 tables.
table_1
id | product
1 | a
2 | b
3 | c
4 | d
table_2
product_id | value
1 | 0
2 | 0
1 | 5
2 | 0
4 | 10
How can I return details from table_1 for ids that:
- are present in table_2 (table_1.id = table_2.product_id)
- do not have any associated value equal to 0 (for example id "1" should be excluded)
The correct result would be id "4" as none of its values equal to zero.
I have tried below query but it returns also id "3" that is not present in the table_2.
SELECT * FROM table_1
WHERE id NOT IN (
SELECT product_id FROM table_2
WHERE value = 0)
You can use two conditions:
SELECT t1.*
FROM table_1 t1
WHERE EXISTS (SELECT 1
FROM table_2 t2
WHERE t1.id = t2.product_id
) AND
NOT EXISTS (SELECT 1
FROM table_2 t2
WHERE t1.id = t2.product_id AND t2.value = 0
);
The naive approach:
-- Step 1: Select product IDs to ignore
SELECT product_id
FROM table_2
WHERE value = 0
-- Step 2: Select product IDs to include
SELECT product_id
FROM table_2
WHERE product_id NOT IN ( -- Use the result of Step 1
SELECT product_id
FROM table_2
WHERE value = 0
)
-- Final query: Select products
SELECT *
FROM table_1
WHERE product_id IN ( -- Use the result of Step 2
SELECT product_id
FROM table_2
WHERE product_id NOT IN ( -- Use the result of Step 1
SELECT product_id
FROM table_2
WHERE value = 0
)
)
One option, using aggregation:
SELECT
t1.id,
t1.product
FROM table_1 t1
INNER JOIN table_2 t2
ON t1.id = t2.product_id
GROUP BY
t1.id,
t1.product
HAVING
COUNT(CASE WHEN t2.value = 0 THEN 1 END) = 0;
In order for the HAVING clause to return true, the product must not have had any zero value in the second table. Also, the inner join filters off any product which does not appear at all in the second table.
You can get the ids you need to use for the IN clause, by grouping by product_id and putting the condition in the HAVING clause:
SELECT * FROM table_1
WHERE id IN (
SELECT product_id
FROM table_2
GROUP BY product_id
HAVING SUM(CASE WHEN value = 0 THEN 1 ELSE 0 END) = 0
)

Creating a new column for -ve values from the existing rows

How to write logic for this in SQL Server:
Voucher # Name Amount
-------------------------
123 ABC 910
123 ABC -910
224 XYZ 600
Expected output
Voucher # Name Amount - (Amount)
-------------------------------------------
123 ABC 910 -910
224 XYZ 600 -
Using conditional aggregation is really simple here. No need to query the same table over and over.
select Voucher
, Name
, Amount = sum(case when Amount > 0 then Amount else 0 end)
, [-Amount] = sum(case when Amount < 0 then Amount else 0 end)
from YourTable
group by Voucher
, Name
Here is the code, try it
Group by negative and positive amount and make a left join
DECLARE #tbl TABLE
(
Voucher varchar(10),
Name varchar(100),
Amount int
)
INSERT INTO #tbl
(
Voucher,
Name,
Amount
)
VALUES
(123,'ABC',910),
(123,'ABC',-910),
(224,'XYZ',600)
SELECT t1.Voucher, t1.Name, t1.Amount , ISNULL(t2.Amount,0) AS [(-Amount)] FROM (
SELECT t.Voucher, Name, Sum(t.Amount) Amount
FROM #tbl t
WHERE t.Amount > 0
GROUP BY t.Voucher, t.Name) t1 Left JOIN
(SELECT t.Voucher, Name, Sum(t.Amount) Amount
FROM #tbl t
WHERE t.Amount < 0
GROUP BY t.Voucher, t.Name) t2 ON t1.Voucher = t2.Voucher
If for each (Voucher, Name) there exists only 1 positive Amount and 0 or 1 negative Amount then with a self join:
select t.*, tt.amount NegativeAmount
from tablename t left join tablename tt
on tt.voucher = t.voucher and tt.name = t.name and tt.amount < 0
where t.amount > 0
See the demo.
Results:
> voucher | name | amount | NegativeAmount
> ------: | :--- | -----: | -------------:
> 123 | ABC | 910 | -910
> 224 | XYZ | 600 |