sql multiplying a sum - sql

I'm struggling with the below code. i'm trying to work out the total balance of a folio by using its total charges and total payments. i'm almost there, however with the below code the payments is incorrect, it seems to be multiplying the total sum of payments by the number of entries in charges. i'm guessing this is because I've connected the payments and charges, which I needed to do to filter out the checkin date which is only on pms sales.
can anyone help?
thanks
SELECT DISTINCT 'PMS AD' AS APP,
FOLIO_ID,
SUM(TOTAL_CHARGES) as TOTAL_CHARGES,
SUM(TOTAL_PAYMENTS) as TOTAL_PAYMENTS
FROM ((SELECT DISTINCT P1.FOLIO_ID AS FOLIO_ID, SUM(P1.CHARGE_CODE_AMOUNT) AS TOTAL_CHARGES, 0 AS TOTAL_PAYMENTS
FROM DEV.VR_PMS_SALES P1
WHERE P1.CHARGE_CODE_AMOUNT <> 0 AND
P1.ITEM_OPERATING_DAY IS NOT NULL
AND P1.ITEM_OPERATING_DAY <= '03-DEC-2014'
AND P1.CHECKIN_DATE <= '03-DEC-2014'
GROUP BY P1.FOLIO_ID
) UNION ALL
(SELECT DISTINCT P2.FOLIO_ID AS FOLIO_ID, 0 AS TOTAL_CHARGES, SUM(P2.AMOUNT) AS TOTAL_PAYMENTS
FROM DEV.VR_PMS_PAYMENTS P2,
DEV.VR_PMS_SALES P3
WHERE P2.FOLIO_ID = P3.FOLIO_ID
AND P2.AMOUNT <> 0
AND P2.PMS_OPERATING_DAY <= '03-DEC-2014'
AND P3.CHECKIN_DATE <= '03-DEC-2014'
GROUP BY P2.FOLIO_ID
)
) F
GROUP BY FOLIO_ID
EDIT:
Sorry I didn't provide examples. table data below
VR_PMS_PAYMENTS
VR_PMS_SALES
The issue I am having is that when running the sql it is multiplying the sum of p1.amount by the number of entries in VR_PMS_SALES. eg folio 4 is returning as 165 instead of 55. I need it to return the below...
desired outcome
I hope this is clearer.
thank you

Related

Return data where the running total of amounts 30 days from another row is greater than or equal to the amount for that row

Let's say I a table that contains the date, account, product, purchase type, and amount like below:
Looking at this table, you can see that for any particular account/product combination, there are buys and sells. Essentially, what I'd like to write is a SQL query that flags the following: Are there accounts that bought at a certain amount and then sold the same aggregate amount or more 30 days from that buy?
So for example, we can see account 1 bought product A for 20k on 8/1. If we look at the running sum of sells by account 1 for product A over the next 30 days, we see they sold a total of 20k - the same as the initial buy:
Ideally, the query would return results that flag all of these instances: for each individual buy, find all sells for that product/account 30 days from that buy, and only return rows where the running total of sells is greater than or equal to that initial buy.
EDIT: Using the sample data provided, the desired should look more or less look like the following:
You'll see that the buy on 8/2 for product B/account 2 is not returned because the running sum of sells for that product/account/buy combination over the next 30 days does not equal or exceed the buy amount of 35k but it does return rows for the buy on 8/3 for product B/ account 2 because the sells do exceed the buy amount of 10k.
I know I need to self join the sells against the buys, where the accounts/products equal and the datediff is less than or equal 30 and I basically have that part structured. What I can't seem to get working is the running total part and only returning data when that total is greater than or equal to that buy. I know I likely need to use the over/partition by clauses for the running sum but I'm struggling to produce the right results/optimize properly. Any help on this would be greatly appreciated - just looking for some general direction on how to approach this.
Bonus: Would be even more powerful to stop returning the sells once the running total passes the buy, so for example, the last two rows in the desired output I provided aren't technically needed - since the first two sells following the buy had already eclipsed the buy amount.
In SQL Server, one option uses a lateral join:
select
t.*,
case when t.amount = x.amount then 1 else 0 end as is_returned
from mytable t
cross apply (
select sum(amount) amount
from mytable t1
where
t1.purchase_type = 'Sell'
and t1.account = t.account
and t1.product = t.product
and t1.date >= t.date
and t1.date <= dateadd(day, 30, t.date)
) x
where t.purchase_type = 'Buy'
The lateral join sums the amount of "sells" of the same account and product within the following 30 days, which you can then compare with the amount of the buy. The query gives you one row per buy, with a boolean flag that indicates if the amounts match.
In databases that support the range specification to window functions, this would be more efficiently expressed with a window sum:
select *
from (
select
t.*,
case when amount = sum(case when purchase_type = 'Sell' then amount end) over(
partition by account, product
order by date
range between current row and interval '30' day following
) then 1 else 0 end as is_returned
from mytable t
) t
where purchase_type = 'Buy'
Edit: this would generate a resultset similar to the third table in your question:
select t.*, x.*
from mytable t
cross apply (
select
t1.date sale_date,
t1.amount sell_amount,
sum(t1.amount) over(order by t1.date) running_sell_amount,
sum(t1.amount) over() total_sell_amount
from mytable t1
where
t1.purchase_type = 'Sell'
and t1.account = t.account
and t1.product = t.product
and t1.date >= t.date
and t1.date <= dateadd(day, 30, t.date)
) x
where t.purchase_type = 'Buy' and t.amount = x.total_sell_amount

how to query a table date against a series of dates on another table

I have two tables, INVOICES and INV_PRICES. I am trying to find the Invoice table's part price from the Inv_Prices based upon the Invoice_Dt on the Invoice table; if the Invoice_Dt is between (greater than, but less than) or greater than the max EFF_DT on the Inv_Prices, then return that part's price.
I have tired variations on the following code, but no luck. I either do not get all the parts or multiple records.
SELECT DISTINCT A.INVOICE_NBR, A.INVOICE_DT, A.PART_NO,
CASE WHEN TRUNC(A.INVOICE_DT) >= TRUNC(B.EFF_DT) THEN B.DLR_NET_PRC_AM
WHEN (TRUNC(A.INVOICE_DT)||ROWNUM >= TRUNC(B.EFF_DT)||ROWNUM) AND (TRUNC(B.EFF_DT)||ROWNUM <= TRUNC(A.INVOICE_DT)||ROWNUM) THEN B.DLR_NET_PRC_AM
/*MAX(B.EFF_DT) THEN B.DLR_NET_PRC_AM*/
ELSE 0
END AS PRICE
FROM INVOICES A,
INV_PRICES B
WHERE A.PART_NO = B.PART_NO
ORDER BY A.INVOICE_NBR
Can someone assist? I have a sample of each table if needed.
Doesn't it work to put the condition in the JOIN conditions? You can calculate the period when a price is valid using LEAD():
SELECT i.INVOICE_NBR, i.INVOICE_DT, i.PART_NO,
COALESCE(ip.DLR_NET_PRC_AM, 0) as price
FROM INVOICES i LEFT JOIN
(SELECT ip.*, LEAD(eff_dt) OVER (PARTITION BY PART_NO ORDER BY eff_dt) as next_eff_dt
FROM INV_PRICES ip
) ip
ON i.PART_NO = ip.PART_NO AND
i.invoice_dt >= ip.eff_dt AND
(i.invoice_dt < ip.next_eff_dt or ip.next_eff_dt is null)
ORDER BY i.INVOICE_NBR

What is wrong with my SQL query SUM group-by

Hello i have a sql query and it does not count my one row. which is called Spend, you can see it in the fiddle. what is wrong with my code?
I just need basic table
Month ID GOT SPEND
1 1 100 50
2 1 500 200
1 2 200 50
I have created the fiddle http://sqlfiddle.com/#!9/3623b1/2
Could you please help me?
Here is the query:
select
keliones_lapas.Vairuot_Id,
MONTH(keliones_lapas.Data_darbo),
sum(keliones_lapas.uzdarbis) as Got,
coalesce(Suma, 0) AS Spend,
(sum(keliones_lapas.uzdarbis) - coalesce(Suma, 0)) Total
from keliones_lapas
left join (
select Vairuotas,
MONTH(Data_islaidu) as Data_islaidu,
sum(Suma) as Suma
from islaidos
group by Vairuotas, MONTH(Data_islaidu)) islaidos
on keliones_lapas.Vairuot_Id = islaidos.Vairuotas
and MONTH(keliones_lapas.Data_darbo) = MONTH(islaidos.Data_islaidu)
group by keliones_lapas.Vairuot_Id, MONTH(keliones_lapas.Data_darbo), Suma
order by keliones_lapas.Vairuot_Id, MONTH(keliones_lapas.Data_darbo);
TRY THIS: You are taking already month in your subquery then again using MONTH to retrieve from month in the join so it's returning NULL and not matching with any month of keliones_lapas
SELECT
keliones_lapas.Vairuot_Id,
MONTH(keliones_lapas.Data_darbo),
SUM(keliones_lapas.uzdarbis) AS Got,
COALESCE(Suma, 0) AS Spend,
(SUM(keliones_lapas.uzdarbis) - COALESCE(Suma, 0)) Total
FROM keliones_lapas
LEFT JOIN (
SELECT Vairuotas,
MONTH(Data_islaidu) AS Data_islaidu, --It's already in MONTH
SUM(Suma) AS Suma
FROM islaidos
GROUP BY Vairuotas, MONTH(Data_islaidu)) islaidos
ON keliones_lapas.Vairuot_Id = islaidos.Vairuotas
AND MONTH(keliones_lapas.Data_darbo) = Data_islaidu --No need to use MONTH or `vice versa`
GROUP BY keliones_lapas.Vairuot_Id, MONTH(keliones_lapas.Data_darbo), Suma
ORDER BY keliones_lapas.Vairuot_Id, MONTH(keliones_lapas.Data_darbo)

SQL Server - logic to allow me to do a calculation in the where statement

I have searched for an answer for ages with no luck for my particular problem.
I have been asked to create a script to extract data for a mail-merge. Our billing team need to identify users who we have sent final warning notices to but have still not paid their bills.
The biggest challenge that I have been unable to overcome is that some of the customers may have done one of the following:
not made any payments for the outstanding amount
paid the outstanding amount in full in one payment
paid the outstanding amount in full with multiple payments
paid part of the outstanding amount in one or multiple payments
The data that was used to generate the final warning notices is in a table with the outstanding amount appearing as a positive number, and any payments that have been made are in a different table as a negative number.
In my query identifying customers from scenario 1 and 2 would be fairly easy to identify, but 3 and 4 are causing me headaches. I had the idea of adding the outstanding amount to a sum of any payments made. If the sum total is > 0 then the customer has not paid enough to cover the outstanding amount. I have developed a sub-query to get a total of all payments made after a certain date but I can't get the query to add that to the outstanding amount and then check to see if the balance is greater than 0. Error I get is "Cannot perform an aggregate function on an expression containing an aggregate or a subquery."
Can someone recommend a different approach to this query?
SELECT
finalwarning.table_no AS [Property No],
finalwarning.value_1 AS [Arrears Balance],
finalwarning.date_1 AS [Notice Date]
FROM
finalwarning
WHERE
finalwarning.table_no = 172030
AND finalwarning.ref_3 = 'Final'
AND ((SELECT
CASE
WHEN EXISTS (SELECT 1
FROM paymentsmade
WHERE PROPERTY_NO = 1234
AND TRANSACTION_DESC = 'Receipt'
AND TRANSACTION_DATE > '2017-05-01 00:00:00.000'
GROUP BY TRANSACTION_DESC)
THEN (SELECT SUM(amount)
FROM paymentsmade
WHERE paymentsmade.PROPERTY_NO = 1234
AND paymentsmade.TRANSACTION_DESC = 'Receipt'
AND paymentsmade.TRANSACTION_DATE > '2017-05-01 00:00:00.000'
GROUP BY paymentsmade.TRANSACTION_DESC)
ELSE '0'
END) + finalwarning.value_1) < 0
CTE are our friends and really help get the hard work done for us and allow for easier writing of the core SQL. The example below takes your structure and applies the concept of a payments CTE which will aggregate payments by property no and provide for all of the calculations you need to perform in your evaluation. I was uncertain about how the date came into play and the join might be wrong because there were 2 different numbers provided for property_no in the example but I assumed they were one and the same.
WITH paymentCTE (property_no, amount, transaction_desc, cntpayments)
as
(select property_no, sum(amount) as amount, transaction_desc, count(*)
from paymentsmade
where TRANSACTION_DATE > > '2017-05-01 00:00:00.000'
group by property_no, transaction_desc
)
SELECT fw.table_no AS [Property No]
, fw.value_1 AS [Arrears Balance]
, fw.date_1 AS [Notice Date]
, coalesce(p1.amount,0) as [Payments Made]
, fw.value_1+coalesce(p1.amount,0) as [Outstanding Balance]
, coalesce(p1.cntpayments, 0) as [Number of Payments]
FROM finalwarning fw
left outer join paymentCTE p1
on fw.table_no = p1.property_no
and p1.Transaction_desc = 'Receipt'
WHERE p1.cntpayments is null
or (coalesce(p1.cntpayments, 0) = 1 and fw.value_1 + coalesce(p1.amount,0) = 0)
or (coalesce(p1.cntpayments, 0) > 1 and fw.value_1 + coalesce(p1.amount,0) = 0)
or (coalesce(p1.cntpayments, 0) > 0 and fw.value_1 + coalesce(p1.amount,0)<> 0)

SQL query to identify seasonal sales items

I need a SQL query that will identify seasonal sales items.
My table has the following structure -
ProdId WeekEnd Sales
234 23/04/09 543.23
234 30/04/09 12.43
432 23/04/09 0.00
etc
I need a SQL query that will return all ProdId's that have 26 weeks consecutive 0 sales. I am running SQL server 2005. Many thanks!
Update: A colleague has suggested a solution using rank() - I'm looking at it now...
Here's my version:
DECLARE #NumWeeks int
SET #NumWeeks = 26
SELECT s1.ProdID, s1.WeekEnd, COUNT(*) AS ZeroCount
FROM Sales s1
INNER JOIN Sales s2
ON s2.ProdID = s1.ProdID
AND s2.WeekEnd >= s1.WeekEnd
AND s2.WeekEnd <= DATEADD(WEEK, #NumWeeks + 1, s1.WeekEnd)
WHERE s1.Sales > 0
GROUP BY s1.ProdID, s1.WeekEnd
HAVING COUNT(*) >= #NumWeeks
Now, this is making a critical assumption, namely that there are no duplicate entries (only 1 per product per week) and that new data is actually entered every week. With these assumptions taken into account, if we look at the 27 weeks after a non-zero sales week and find that there were 26 total weeks with zero sales, then we can deduce logically that they had to be 26 consecutive weeks.
Note that this will ignore products that had zero sales from the start; there has to be a non-zero week to anchor it. If you want to include products that had no sales since the beginning, then add the following line after `WHERE s1.Sales > 0':
OR s1.WeekEnd = (SELECT MIN(WeekEnd) FROM Sales WHERE ProdID = s1.ProdID)
This will slow the query down a lot but guarantees that the first week of "recorded" sales will always be taken into account.
SELECT DISTINCT
s1.ProdId
FROM (
SELECT
ProdId,
ROW_NUMBER() OVER (PARTITION BY ProdId ORDER BY WeekEnd) AS rownum,
WeekEnd
FROM Sales
WHERE Sales <> 0
) s1
INNER JOIN (
SELECT
ProdId,
ROW_NUMBER() OVER (PARTITION BY ProdId ORDER BY WeekEnd) AS rownum,
WeekEnd
FROM Sales
WHERE Sales <> 0
) s2
ON s1.ProdId = s2.ProdId
AND s1.rownum + 1 = s2.rownum
AND DateAdd(WEEK, 26, s1.WeekEnd) = s2.WeekEnd;