Oracle SQL - Getting Max Date with several joins - sql

I think I may be overthinking this, but I'm having an issue when trying to find a max date with several joins in an Oracle database as well as several where clauses. I've found many examples of simple max queries, but nothing specifically like this. The query works fine if I add a line in to find all records above a specific date (there are only a handful of results returned). However, I want to automatically get the most recent date for all records from the bill table. This database has an additional table where the actual bill amount is stored, so that adds another layer.
SELECT p.purchase_id, p.account_id, b.bill_date, bp.current_amount
FROM Purchases p
JOIN Bill_Purchases bp ON p.purchase_id = bp.purchase_id
JOIN Bills b ON bp.bill_id = b.bill_id
--NEED TO GET MOST RECENT DATE FROM BILL TABLE FOR EACH BILL
WHERE p.type != 'CASH'
AND bp.amount > '100.00'
AND p.status = 'Approved'
AND p.purchase_id IN ( ... list of purchases ...);
I have tried doing subqueries with Max functions in them, but am not having any luck. Each query returns the same amount of records as the original query. How would I rearrange this query to still retrieve all of the necessary columns and where clauses while still limiting this to only the most recent purchase that was billed?

Try like below this
SELECT p.purchase_id, p.account_id, b.bill_date, bp.current_amount
FROM Purchases p
JOIN Bill_Purchases bp ON p.purchase_id = bp.purchase_id
JOIN ( SELECT bill_id, MAX(bill_date) bill_date
FROM Bills
GROUP BY bill_id
)b ON bp.bill_id = b.bill_id
WHERE p.type != 'CASH'
AND bp.amount > '100.00'
AND p.status = 'Approved'
AND p.purchase_id IN ( ... list of purchases ...);

You may want to solve this problem using Rank() function,
SELECT p.purchase_id, p.account_id, , bp.current_amount, RANK() OVER ( partition by b.bill_id order by b.bill_date) as max_bill_date
FROM Purchases p
JOIN Bill_Purchases bp ON p.purchase_id = bp.purchase_id
JOIN Bills b ON bp.bill_id = b.bill_id
--NEED TO GET MOST RECENT DATE FROM BILL TABLE FOR EACH BILL
WHERE max_bill_date = 1
AND p.type != 'CASH'
AND bp.amount > '100.00'
AND p.status = 'Approved'`enter code here`
AND p.purchase_id IN ( ... list of purchases ...);

Do either of these work for you?
WITH data as (
SELECT
p.purchase_id, p.account_id, b.bill_date, bp.current_amount,
FROM
Purchases p
INNER JOIN Bill_Purchases bp ON p.purchase_id = bp.purchase_id
INNER JOIN Bills b ON b.bill_id = bp.bill_id
WHERE p.type <> 'CASH'
AND bp.amount > '100.00'
AND p.status = 'Approved'
AND p.purchase_id IN ( ... list of purchases ...)
), most_recent as (
SELECT max(bill_date) as bill_date FROM data
)
SELECT *
FROM data
WHERE bill_date = (select bill_date from most_recent);
SELECT purchase_id, account_id, bill_date, current_amount
FROM (
SELECT
p.purchase_id, p.account_id, b.bill_date, bp.current_amount,
dense_rank() over (order by b.bill_date desc) as dr
FROM
Purchases p
INNER JOIN Bill_Purchases bp ON p.purchase_id = bp.purchase_id
INNER JOIN Bills b ON b.bill_id = bp.bill_id
WHERE p.type <> 'CASH'
AND bp.amount > '100.00'
AND p.status = 'Approved'
AND p.purchase_id IN ( ... list of purchases ...)
) data
WHERE dr = 1;

I got this working pretty much right after posting, was a stupid mistake on my part. Data for the max function needed to be joined on the account_id, not the bill_id.

Related

How to query many cases in SQL CASE statement

I am trying to solve an SQL online-challenge
I have three tables:
sales: customer_id, order_date, product_id.
members: join_date, member_id.
menu: product_id, product_name, price.
and one of the questions is: What is the total items and amount spent for each member before they became a member?
I think I got the write answer right the following query:
with cte as
(
SELECT
CASE WHEN s.customer_id = 'A' THEN count(s.product_id)*m.price END AS purchases_A,
CASE WHEN s.customer_id = 'B' THEN count(s.product_id)*m.price END AS purchases_B,
CASE WHEN s.customer_id = 'C' THEN count(s.product_id)*m.price END AS purchases_C,
case when s.customer_id = 'A' THEN count(s.product_id) END AS total_A,
case when s.customer_id = 'B' THEN count(s.product_id) END AS total_B,
case when s.customer_id = 'C' THEN count(s.product_id) END AS total_C
from sales s
join menu m on s.product_id = m.product_id
join members mb on mb.customer_id = s.customer_id and mb.join_date > s.order_date
group by s.customer_id, m.price
)
select
sum(purchases_A) as total_spendings_a,
sum (total_A) as total_items_A,
sum(purchases_B) as total_spendings_b,
sum (total_B) as total_items_B,
sum(purchases_C) as total_spendings_c,
sum (total_C) as total_items_C
from cte;
And my question is. is there a better way or more efficient way in writing this query? it feels too long and repetitive.
In this case I had only three customers: A,B, and C
what if I have 100 or 1000 customers?
Simply join the tables and aggregate by member:
select
m.member_id,
coalesce(count(*), 0) as total_amount,
coalesce(sum(price), 0) as total_price
from members m
left join sales s on s.customer_id = m.customer_id and s.order_date < m.join_date
left join menu p on p.product_id = s.product_id
group by m.member_id
order by by m.member_id;
You mention both members.member_id and members.customer_id, so I've used them both. If this was a typo in your request, then just change the wrong column name to the correct one in my query.

Return Records Based on Condition Comparing Aggregates of Two Child Tables

I have an invoices table that has many payments and has many invoice line items. The total of an invoice record is derived through invoice line items (quantity * price).
I'm trying to create a query that will return invoices with an outstanding balance.
SELECT inv.id, MAX(inv.invoice_total) as "InvoiceTotal", SUM(pt.amount) AS "TotalPayments" FROM (
SELECT
i.id,
SUM( ili.price * ili.quantity ) as "invoice_total"
FROM
invoices i
JOIN invoice_line_items ili
on i.id = ili.invoice_id
GROUP BY i.id
) inv
LEFT JOIN payment_transactions pt
ON pt.invoice_id = inv.id
GROUP BY inv.id
ORDER BY inv.id DESC
The last piece, I figured, was to add HAVING that returns only records where Total Payments is less than the Invoice Total, but it's not working.
How am I able to achieve this? Is there a simpler approach than mine?
This should work:
SELECT inv.id, MAX(inv.invoice_total) as InvoiceTotal, SUM(pt.amount) AS "TotalPayments"
FROM (SELECT i.id, SUM( ili.price * ili.quantity ) as invoice_total
FROM invoices i JOIN
invoice_line_items ili
ON i.id = ili.invoice_id
GROUP BY i.id
) inv LEFT JOIN
payment_transactions pt
ON pt.invoice_id = inv.id
GROUP BY inv.id
HAVING COALESCE(SUM(pt.amount), 0) < MAX(inv.invoice_total)
ORDER BY inv.id DESC;

Output of two different queries as one result in oracle

I have two different tables and I want to get these two different output in single result. Here I want to display the result of both queries in single report.
Query 1
Select name, sum(purchasqty)-sum (soldqty) as pending
from (select p.name, p.qty as purchasqty, s.qty as soldqty
from purchase p
left join sold s on p.id = s.id )
group by name;
Query 2
Select name, sum(qty) as damage
from purchase p
where con = 'c'
group by name
This will do
Select name, sum(purchasqty)-sum (soldqty) as pending, '' as damage
from (select p.name, p.qty as purchasqty, s.qty as soldqty
from purchase p
left join sold s on p.id = s.id )
group by name
union all
Select name, '' as pending, sum(qty) as damage
from purchase p
where con = 'c'
group by name
This is a little tricky because you seem to be aggregating at a level different from what you are joining on. I would recommend:
select p.name, (p.purchasqty - coalesce(ps.soldqty, 0) as pending ,
p.damage
from (select p.name, sum(qty) as purchase_qty,
sum(case when con = 'c' then qty else 0 end) as damage
from purchase p
group by p.name
) p left join
(select p.name, sum(s.qty) as soldqty
from purchase p join
sold s
on p.id = s.id
group by p.name
) ps
on ps.name = p.name;

Sum from the joined table in sql

I am trying to sum the amount from the tbl_paid where transaction_num has same number and tbl_client.c_id = tbl_paid.c_id.
Basically means I am trying to add the amount of 3359 and 1 where there c_id = 172 and transaction_num = 2 because they have the same record. Row 1 and 2 and then the other is as is.
tbl_client
tbl_paid
Look at the first picture to see the query I used to join table and get the output.
What I am trying to do with my query is to show all the records from tbl_client joined by tbl_paid who are accounts that are paid today. If the pay twice this day the record of the amount will be added to the existing payment.
You want to show clients with their today's total payment. So, select the total payment per client and then join this to the client table.
select c.*, p.total
from tbl_client c
join
(
select c_id, sum(amount) as total
from tbl_paid
where pay_date = current_date()
group by c_id
) p on p.c_id = c.c_id
order by c.c_id;
If you want to show clients with no current payment, too, then change the inner join to an outer join.
UPDATE: In the comments to ϻᴇᴛᴀʟ's answer you say you want to see the different transactions. If this is so, then you don't want the total per client, but the total per client and transaction, which is a very small change in the query:
select c.*, p.transaction_num, p.total
from tbl_client c
join
(
select c_id, transaction_num, sum(amount) as total
from tbl_paid
where pay_date = current_date()
group by c_id, transaction_num
) p on p.c_id = c.c_id
order by c.c_id;
sum() and group by is what you need.
select t3.*, t4.*
from tbl_client t4
inner join
(select sum(t2.amount) as amt, t1.transaction_num, t1.c_id
from tbl_client t1
inner join tbl_paid t2 on t1.c_id = t2.c_id
where t1.pay_date = current_date()
group by t1.transaction_num, t1.cid
) t3 on t3.c_id = t4.c_id
You can use SUM() :
select SUM(amount), t1.transaction_num
from tbl_client t1
left join tbl_paid t2 on t1.c_id = t2.c_id
where t1.transaction_num = 2 and t1.c_id = 172`

SQL Server : query with incorrect SUM result

My SQL Server query is supposed to get a count of each customer's number of orders, and the SUM of their reward points. For most customers the result is accurate (most people only have one or two orders). For a few people, the result is wildly off.
Here's the original query:
SELECT
c.email,
c.lastlogindate,
c.custenabled,
c.maillist,
d.GroupName,
COUNT(o.orderid) AS orders,
SUM(r.points) AS total_points
FROM
((customers c
LEFT JOIN orders o ON (c.contactid = o.ocustomerid AND o.ostep = 'step 5')
)
LEFT JOIN discount_group d ON c.discount = d.id
)
LEFT JOIN
customer_rewards r ON r.contactid = c.contactid
WHERE
c.last_update > '2014-02-01'
OR c.lastlogindate > '2014-02-01'
GROUP BY
c.email, c.custenabled, c.maillist, c.lastlogindate, d.GroupName;
For one example, customerid 1234 has placed 21 orders, totaling 2724 points. This will report that he has placed 441 orders (21 * 21) valued at 57204 points (2724 * 21). The raw data is fine, but each order row is being duplicated by the amount of orders they placed (but not for most customers...)
If I change the query to this:
SELECT
o.orderid,
c.email,
COUNT(o.orderid) AS orders,
SUM(r.points) AS total_points
FROM
((customers c
INNER JOIN orders o ON (c.contactid = o.ocustomerid AND o.ostep = 'step 5')
)
)
INNER JOIN
customer_rewards r ON r.contactid = c.contactid
WHERE
c.last_update > '2014-02-01'
OR c.lastlogindate > '2014-02-01'
GROUP BY
c.email, o.orderid;
The aggregate functions are calculated properly, but it will display one result for each order placed. So it will show "Customer 1234/21 orders/2724 points", 21 times.
I did remove the 'discount_group' join in the second query, but that was just to make it easier to read and change. That hasn't had any effect on results.
Here is a solution using common table expressions to aggregate your results.
Note: this will not show customers that have 0 orders or 0 rewards points. If you would like to show these, change the INNER JOINs to LEFT JOINs
WITH cteOrders AS
(
SELECT o.ocustomerid, orderCount = count(*)
FROM orders o
WHERE o.ostep = 'step 5'
GROUP BY o.ocustomerid
)
, cteRewards as
(
SELECT cr.contactid, total_points = SUM(cr.points)
FROM customer_rewards cr
GROUP BY cr.contactid
)
SELECT
c.email,
o.orderCount as orders,
r.total_points
FROM
customers c
INNER JOIN cteOrders o ON c.contactid = o.ocustomerid
INNER JOIN cteRewards r ON r.contactid = c.contactid
WHERE
c.last_update > '2014-02-01'
OR c.lastlogindate > '2014-02-01'
;
Or using subqueries:
SELECT
c.email,
o.orderCount as orders,
r.total_points
FROM
customers c
INNER JOIN
(
SELECT o.ocustomerid, orderCount = count(*)
FROM orders o
WHERE o.ostep = 'step 5'
GROUP BY o.ocustomerid
) o ON c.contactid = o.ocustomerid
INNER JOIN
(
SELECT cr.contactid, total_points = SUM(cr.points)
FROM customer_rewards cr
GROUP BY cr.contactid
) r ON r.contactid = c.contactid
WHERE
c.last_update > '2014-02-01'
OR c.lastlogindate > '2014-02-01'
;