SQL - How can I sum up a column after the results have been grouped and filtered in the having clause? - sql

Here is my current query: The objective is to find accounts that have received at least $500 in deposits within 30 days of their first deposit. Some accounts have been closed and re-opened, hence the first line of the 'WHERE' clause.
select Deposits.accountNumber,
min(Deposits.transDate) as "first deposit",
Deposits.transDate,
CAST(DATEADD(d,30,min(Deposits.transDate)) as date) as "30 days",
sum(Deposits.amount) as "sum",
Deposits.amount,
Members.accountOpenDate
from Deposits
inner join Members on Deposits.accountNumber = members.accountNumber
where Deposits.transDate >= members.accountOpenDate
and Deposits.accountNumber = 123456
group by Deposits.accountNumber
having Deposits.transDate between min(Deposits.transDate) and DATEADD('d',30,min(Deposits.transDate))
and sum(Deposits.amount) >= 500
The problem I am running into, is that the last line of the HAVING statement:
and sum(Deposits.amount) >= 500
is including all of the transactions for the account, as if there was no 'HAVING' clause. It is factoring in transactions that are excluded from the first line of the 'HAVING':
having Deposits.transDate between min(Deposits.transDate) and DATEADD('d',30,min(Deposits.transDate))
Here is what my data looks like (without grouping by account number):
accountNumber amount sum
123456 $100 $6,500
123456 $50 $6,500
123456 $50 $6,500
And here is what I am trying to get to:
accountNumber amount sum
123456 $100 $200
123456 $50 $200
123456 $50 $200
Thanks in advance. My DBMS is Intersystems-Cache. A link to their reference can be found Here.

You can try something like that:
select filtered.accountNumber,
min(filtered.transDate) as "first deposit",
filtered.transDate,
CAST(DATEADD(d,30,min(filtered.transDate)) as date) as "30 days",
sum(filtered.amount) as "sum",
filtered.amount,
filtered.accountOpenDate
from
(
select * from Deposits
inner join Members on Deposits.accountNumber = members.accountNumber
where Deposits.transDate >= members.accountOpenDate
and Deposits.accountNumber = 123456
having Deposits.transDate between min(Deposits.transDate) and DATEADD('d',30,min(Deposits.transDate))
) as filtered
group by filtered.accountNumber
having sum(filtered.amount) >= 500
With a query like that one you are first filtering your data applying the transDate condition then you can operate the filter on the sum of the amount

We need clarification:
1. Are the 3 transactions you show all within the 30 day window? If yes, then the total is less than $500. So, this account should be skipped.
2. Since $6500 is the total of all trans greater than the open date, why even calculate it? You only care about the 30 day window.
Besides that, I think the disconnect is the date calculation in the HAVING clause. You use MIN in the SELECT, but use a totally different aggregate date calculation in the HAVING. I think you should take the calculation out of the HAVING and make it part of the WHERE.
Of course, once you do that, you'll have to take the MIN out of the SELECT.

Related

SQL inner query trying to use alias in the where clause

I have a join of 2 tables, that represent a list of payments that contracts have done.
Sample Query: (https://www.db-fiddle.com/f/iXGxgDTopsBBgXGUJsXpa/13)
That sample data consist of 5 contracts, some of them are running behind in payments, so I want to get the list of contracts that havent done a payment in the last 7 days, considering the current date to be: 9 of may of 2021.
For the example, contracts 121, 300, 321 and 400 have made a payment in the last 7 days, so any records from them should not appear in the final query. However:
Contract 321 despite of a payment in the last 7 days they had a reversal that was the total of the credits made by them in the last 7 days, this is equivalent to 0 payments, so I want this contract to appear in my final query.
Contract 121, I dont want to appear in the final result becuase despite of the reversal the is a total credits of 20 (100 credit - 80 reversal)
Contract 400 I want to appear in my results because one of the rows has as codename Special Delete.
In the fiddle I was able to create the query that Filters all records with payments in the last 7 days, but I need help adding the extra filtering:
If any contract that appear there if the sum of the credits and debits is 0, then appear it should appear in the final result (as it is like no payments have been send) this will be the case for the contract 321.
If the credits are positive but one of the rows has as codename "SpecialDelete" then display it in the final result (this is the case for the contract 400)
Total Debits against total credits greater than 0
I will be using this query with AWS Athena
I am guessing the part I need to ammend is (WHERE Payments.ContractID NOT IN ....):
SELECT PaymentID,
Payments.ContractID,
PaymentDate,
Credit,
Debit,
Code,
CodeName,
amount,
city
FROM Payments
LEFT JOIN Info ON Info.ContractID = Payments.ContractID
WHERE Payments.ContractID NOT IN (
SELECT Payments.ContractID
FROM Payments
WHERE PaymentDate >= '20210501'
)
ORDER BY PaymentDate DESC
;
you guess is correct, Here is what you need (if I didn't miss anything):
SELECT p.ContractID,PaymentDate,Credit,Debit,Code,CodeName,amount,city
FROM Payments p
LEFT JOIN Info ON Info.ContractID = p.ContractID
WHERE p.ContractID NOT IN (
SELECT p2.ContractID
FROM Payments p2
WHERE p2.PaymentDate >= '20210501'
group by p2.ContractID
having sum(p2.credit - p2.debit) > 0
) or codename = 'Special Delete'
ORDER BY PaymentDate DESC;

Performing math on SELECT result rows

I have a table that houses customer balances and I need to be able to see when accounts figures have dropped by a certain percentage over the previous month's balance per account.
My output consists of an account id, year_month combination code, and the month ending balance. So I want to see if February's balance dropped by X% from January's, and if January's dropped by the same % from December. If it did drop then I would like to be able to see what year_month code it dropped in, and yes I could have 1 account with multiple drops and I hope to see that.
Anyone have an ideas on how to perform this within SQL?
EDIT: Adding some sample data as requested. On the table I am looking at I have year_month as a column, but I do have access to get the last business day date per month as well
account_id | year_month | ending balance
1 | 2016-1 | 50000
1 | 2016-2 | 40000
1 | 2016-3 | 25
Output that I would like to see is the year_month code when the ending balance has at least a 50% decline from the previous month.
First I would recommend making Year_Month a yyyy-mm-dd format date for this calculation. Then take the current table and join it to itself, but the date that you join on will be the prior month. Then perform your calculation in the select. So you could do something like this below.
SELECT x.*,
x.EndingBalance - y.EndingBalance
FROM Balances x
INNER JOIN Balances y ON x.AccountID = y.AccountID
and x.YearMonth = DATEADD(month, DATEDIFF(month, 0, x.YearMonth) - 1, 0)

Select most current data in grouped set in Oracle

I am writing a procedure to query some data in Oracle and grouping it:
Account Amt Due Last payment Last Payment Date (mm/dd/yyyy format)
1234 10.00 5.00 12/12/2013
1234 35.00 8.00 12/12/2013
3293 15.00 10.00 11/18/2013
4455 8.00 3.00 5/23/2013
4455 14.00 5.00 10/18/2013
I want to group the data, so there is one record per account, the Amt due is summed, as well as the last payment. Unless the last payment date is different -- if the date is different, then I just want the last payment. So I would want to have a result of something like this:
Account Amt Due Last payment Last Payment Date
1234 45.00 13.00 12/12/2013
3293 15.00 10.00 11/18/2013
4455 22.00 5.00 10/18/2013
I was doing something like
select Account, sum (AmtDue), sum (LastPmt), Max (LastPmtDt)
from all my tables
group by Account
But, that doesn't work for the last record above, because the last payment was only the $5.00 on 10/18, not the sum of them on 10/18.
If I group by Account and LastPmtDt, then I get two records for the last, but I only want one per account.
I have other data I'm querying, and I'm using a CASE, INSTR, and LISTAGG on another field (if combining them gives me this substring and that, then output 'Both'; else if it only gives me this substring, then output the substring; else if it only gives me the other substring, then output that one). It seems like I may need something similar, but not by looking for a specific date. If the dates are the same, then sum (LastPmt) and max (LastPmtDt) works fine, if they are not the same, then I want to ignore all but the most recent LastPmt and LastPmtDt record(s).
Oh, and my LastPmt and LastPmtDt fields are already case statements within the select. They aren't fields that I already can just access. I'm reading other posts about RANK and KEEP, but to involve both fields, I'd need all that calculation of each field as well. Would it be more efficient to query everything, and then wrap another query around that to do the grouping, summing, and selecting fields I want?
Related: HAVING - GROUP BY to get the latest record
Can someone provide some direction on how to solve this?
Try this:
select Account,
sum ( Amt_Due),
sum (CASE WHEN Last_Payment_Date = last_dat THEN Last_payment ELSE 0 END),
Max (Last_Payment_Date)
from (
SELECT t.*,
max( Last_Payment_Date ) OVER( partition by Account ) last_dat
FROM table1 t
)
group by Account
Demo --> http://www.sqlfiddle.com/#!4/fc650/8
Rank is the right idea.
Try this
select a.Account, a.AmtDue, a.LastPmt, a.LastPmtDt from (
select Account, sum (AmtDue) AmtDue, sum (LastPmt) LastPmt, LastPmtDt,
RANK() OVER (PARTITION BY Account ORDER BY LastPmtDt desc) as rnk
from all my tables
group by Account, LastPmtDt
) a
where a.rnk = 1
I haven't tested this, but it should give you the right idea.
Try this:
select Account, sum(AmtDue), sum(LastPmt), LastPmtDt
from (select Account,
AmtDue,
LastPmt,
LastPmtDt,
max(LastPmtDt) over(partition by Account) MaxLastPmtDt
from your_table) t
where t.LastPmtDt = t.MaxLastPmtDt
group by Account, LastPmtDt

12 month moving average by person, date

I have a table [production] that contains the following structure:
rep (char(10))
,cyc_date (datetime) ---- already standardized to mm/01/yyyy
,amt (decimal)
I have data for each rep from 1/1/2011 to 8/1/2013. What I want to be able to do is create a 12 month moving average beginning 1/1/2012 for each rep, as follows:
rep cyc_dt 12moAvg
-------------------------
A 1/1/2012 10000.01
A 2/1/2012 13510.05
. ........ ........
A 8/1/2013 22101.32
B 1/1/2012 98328.22
B ........ ........
where each row represents the 12 month moving average for said rep at stated time. I found some examples that were vaguely close and I tried them to no avail. It seems the addition of a group by rep component is the major departure from other examples.
This is about as far as I got:
SELECT
rep,
cyc_date,
(
SELECT Avg([amt])
FROM production Q
WHERE Q.[cyc_date] BETWEEN DateAdd("yyyy",-1,[cyc_date]+1) AND [cyc_date]
) AS 12moavg
FROM production
That query seems to pull an overall average or sum, since there is no grouping in the correlated subquery. When I try to group by, I get an error that it can only return at most one row.
I think it may work with 2 adjustments to the correlated subquery.
Subtract 11 months in the DateAdd() expression.
Include another WHERE condition to limit the average to the same rep as the current row of the parent (containing) query.
SELECT
p.rep,
p.cyc_date,
(
SELECT Avg(Q.amt)
FROM production AS Q
WHERE
Q.rep = p.rep
AND
Q.cyc_date BETWEEN DateAdd("m", -11, p.cyc_date)
AND p.cyc_date
) AS [12moavg]
FROM production AS p;
Correlated subqueries can be slow. Make sure to index rep and cyc_date to limit the pain with this one.

Create SQL view column from result set

I'm new to SQL and attempting to revise a Create View script to add a new column from a select statement result set I've googled this quite a bit but haven't seen a good example.
Here's the select statement:
select lease_id, year(posting_date) as years1, SUM(amount) as Annual
from la_tbl_lease_projection
group by year(posting_date), lease_id
order by lease_id
The complicating factor is this. The Annual column in the result set is the Annual sum of expenses for a lease_id. However, in the view I'm adding the column to, expenses are listed monthly. So lease_id 100001 has 12 lines in 2010, 2011, etc. I want the view to have the new column show the Annual amount on each of the 12 monthly line items. The new Annual column should be to the right of the amount column and each line should contain the sum of the amount column for that year. e.g.:
Lease_id Posting_Date Amount Annual
100001 2010-01-01 $25 $300
100001 2010-02-01 $25 $300
etc...............
The view I'm adding to is a reasonably complex join and union from multiple tables. Instead of creating a new table for my result set, I'd like to access it using a stored procedure, unless there's a better option. MSDN says temp tables and table variables don't work in views so that's not an option.
I think this can be done by something like "when years1 = years1 AND lease_id = lease_id then [Annual] = resultset total, but can't seem to visualize it. Thanks in advance for your input.
Since you were looking at MSDN, I'm assuming SQL Server for this answer;
To get a yearly column that's a by year sum of amounts, you can use SUM() OVER ();
SELECT *, SUM(Amount) OVER (PARTITION BY YEAR(Posting_Date)) Yearly
FROM la_tbl_lease_projection;
An SQLfiddle to test with.
I think a derived table would do the trick for you something like:
select blah, blah2, blah3, ..., a.annual
from
<Long complicated set of joins>
join
(select lease_id, year(posting_date) as years1, SUM(amount) as Annual
from la_tbl_lease_projection
group by year(posting_date), lease_id
order by lease_id) a
on sometable.lease_id = a.lease_id and year(sometable .posting_date) = a.years1
Where <complex where conditions>