SQL How to subtract from only the MIN value - sql-server-2016

My query is pretty simple:
SELECT SUM(rate), SUM(nights), SUM(rate) * SUM(nights) AS subtotal, (SUM(rate) * SUM(nights)) * MIN(tax) AS tax,
SUM(adults), SUM(fee), ((SUM(rate) * SUM(nights)) * MIN(tax)) + SUM(fee) AS total
FROM table
WHERE group = #group_code
GROUP BY rate
The results of my query:
rate nights subtotal tax adults fee total
0.00 14 0.00 0.00 21 105 105.00
154.00 226 34804.00 5373.04 141 705 40882.04
254.00 6 1524.00 235.27 4 20 1779.27
I want to be able to subtract 2 adults from whatever the MIN(rate) row, in this case the row with a rate of $0.00, but leave the other rows alone.
Anyone able to help?

If I understood properly this would do the trick:
with s as (
select
SUM(rate) rate,
SUM(nights) nights,
SUM(rate) * SUM(nights) subtotal,
(SUM(rate) * SUM(nights)) * MIN(tax) tax,
SUM(adults) adults,
SUM(fee) fee,
((SUM(rate) * SUM(nights)) * MIN(tax)) + SUM(fee) total,
from table
where group = #group_code
group by rate
), sorted as (
select s.rate, s.nights, s.subtotal, s.tax, s.adults, s.fee, s.total, ROW_NUMBER() over(order by s.rate) lp
from s
)
select sorted.rate,
sorted.nights,
sorted.subtotal,
sorted.tax,
sorted.adults + IIF(lp = 1, -2, 0),
sorted.fee,
sorted.total
from sorted

Related

Calculative cumulative returns using SQL

I currently generate a user's "monthly_return" between two months using the code below. How would I turn "monthly_return" into a cumulative "linked" return similar to the StackOverflow question linked below?
Similar question: Running cumulative return in sql
I tried:
exp(sum(log(1 + cumulative_return) over (order by date)) - 1)
But get the error:
PG::WrongObjectType: ERROR: OVER specified, but log is not a window function nor an aggregate function LINE 3: exp(sum(log(1 + cumulative_return) over (order by date)) - 1... ^ : SELECT portfolio_id, exp(sum(log(1 + cumulative_return) over (order by date)) - 1) FROM (SELECT date, portfolio_id, (value_cents * 0.01 - cash_flow_cents * 0.01) / (lag(value_cents * 0.01, 1) over ( ORDER BY portfolio_id, date)) - 1 AS cumulative_return FROM portfolio_balances WHERE portfolio_id = 16 ORDER BY portfolio_id, date) as return_data;
The input data would be:
1/1/2017: $100 value, $100 cash flow
1/2/2017: $100 value, $0 cash flow
1/3/2017: $100 value, $0 cash flow
1/4/2017: $200 value, $100 cash flow
The output would be:
1/1/2017: 0% cumulative return
1/2/2017: 0% cumulative return
1/3/2017: 0% cumulative return
1/4/2017: 0% cumulative return
My current code which shows monthly returns which are not linked (cumulative).
SELECT
date,
portfolio_id,
(value_cents * 0.01 - cash_flow_cents * 0.01) / (lag(value_cents * 0.01, 1) over ( ORDER BY portfolio_id, date)) - 1 AS monthly_return
FROM portfolio_balances
WHERE portfolio_id = 16
ORDER BY portfolio_id, date;
If you want a cumulative sum:
SELECT p.*,
SUM(monthly_return) OVER (PARTITION BY portfolio_id ORDER BY date) as running_monthly_return
FROM (SELECT date, portfolio_id,
(value_cents * 0.01 - cash_flow_cents * 0.01) / (lag(value_cents * 0.01, 1) over ( ORDER BY portfolio_id, date)) - 1 AS monthly_return
FROM portfolio_balances
WHERE portfolio_id = 16
) p
ORDER BY portfolio_id, date;
I don't see that this makes much sense, because you have the cumulative sum of a ratio, but that appears to be what you are asking for.

Add a column into a select for calculation

I have 2 columns from a table and i want to add a third column to output the result of a calculation
select statement at the moment is:
select revenue, cost
from costdata
my 2 columns are revenue and cost
table name: costdata
my formula is: = ((revenue - cost)/ revenue)*100
I want the third column to be named 'result'
any idea on how to do this in a select statement?
SELECT revenue
, cost
, ((revenue - cost) / revenue) * 100 As result
FROM costdata
You mentioned in the comments that you get a divide by zero error. This wll occur when revenue equals zero.
What you want to happen in this scenario is up to you but this should get you started
SELECT revenue
, cost
, CASE WHEN revenue = 0 THEN
0
ELSE
((revenue - cost) / revenue) * 100
END As result
FROM costdata
Query:
SELECT revenue,
cost,
CASE WHEN revenue <> 0
THEN ((revenue - cost) / revenue) * 100
ELSE 0 END As result
FROM costdata
Try,
select revenue, cost,((revenue - cost)/ revenue)*100 AS result
from costdata
SELECT revenue, cost, ((revenue - cost)/ revenue)*100 AS result FROM costdata

Summing and then getting min and max of top 70% in SQL

I have data for the purchases of a product formatted like this:
Item | Price | Quantity Bought
ABC 10.10 4
DEF 8.30 12
DEF 7.75 8
ABC 10.50 20
GHI 15.4 1
GHI 15.2 12
ABC 10.25 8
... ... ...
Where each row represents an individual purchasing a certain amount at a certain price. I would like to aggregate this data and eliminate the prices below the 30th percentile for total quantity bought from my table.
For example, in the above data set the total amount of product ABC bought was (4+20+8) = 32 units, with average price = (4*10.10 + 8*10.25 + 20*10.50)/32 = 10.39.
I would like to organize the above data set like this:
Item | VWP | Total Vol | 70th %ile min | 70th %ile max
ABC 10.39 32 ??? ???
DEF ... 20 ??? ???
GHI ... 13 ??? ???
Where VWP is the volume weighted price, and the 70th %ile min/max represent the minimum and maximum prices within the top 70% of volume.
In other words, I want to eliminate the prices with the lowest volumes until I have 70% of the total volume for the day contained in the remaining prices. I would then like to publish the min and max price for the ones that are left in the 70th %ile min/max columns.
I tried to be as clear as possible, but if this is tough to follow along with please let me know which parts need clarification.
Note: These are not the only columns contained in my dataset, and I will be selecting and calculating other values as well. I only included the columns that are relevant to this specific calculation.
EDIT:
Here is my code so far, and I need to incorporate my calculation into this (the variables with the '#' symbol before them are inputs that are given by the user:
SELECT Item,
SUM(quantity) AS Total_Vol,
DATEADD(day, -#DateOffset, CONVERT(date, GETDATE())) AS buyDate,
MIN(Price) AS MinPrice,
MAX(Price) AS MaxPrice,
MAX(Price) - MIN(Price) AS PriceRange,
ROUND(SUM(Price * quantity)/SUM(quantity), 6) AS VWP,
FROM TransactTracker..CustData
-- #DateOffset (Number of days data is offset by)
-- #StartTime (Time to start data in hours)
-- #EndTime (Time to stop data in hours)
WHERE DATEDIFF(day, TradeDateTime, GETDATE()) = (#DateOffset+1)
AND DATEPART(hh, TradeDateTime) >= #StartTime
AND HitTake = ''
OR DATEDIFF(day, TradeDateTime, GETDATE()) = #DateOffset
AND DATEPART(hh, TradeDateTime) < #EndTime
AND HitTake = ''
GROUP BY Item
EDIT 2:
FROM (SELECT p.*,
(SELECT SUM(quantity) from TransactTracker..CustData p2
where p2.Series = p.Series and p2.Size >= p.Size) as volCum
FROM TransactTracker..CustData p
) p
EDIT 3:
(case when CAST(qcum AS FLOAT) / SUM(quantity) <= 0.7 THEN MIN(Price) END) AS min70px,
(case when CAST(qcum AS FLOAT) / SUM(quantity) <= 0.7 THEN MAX(Price) END) AS max70px
FROM (select p.*,
(select SUM(quantity) from TransactTracker..CustData p2
where p2.Item = p.Item and p2.quantity >= p.quantity)
as qcum from TransactTracker..CustData p) cd
There is some ambiguity on how you define 70 % when something goes over the threshold. However, the challenge is two fold. After identifying the cumulative proportion, the query also needs to choose the appropriate row. This suggests using row_number() for selection.
This solution using SQL Server 2012 syntax calculates the cumulative sum. It then takes assigns a sequential value based on how close the ratio is to 70%.
select item,
SUM(price * quantity) / SUM(quantity) as vwp,
SUM(quantity) as total_vol,
min(case when seqnum = 1 then price end) as min70price,
max(case when seqnum = 1 then price end) as max70price
from (select p.*,
ROW_NUMBER() over (partition by item order by abs(0.7 - qcum/qtot) as seqnum
from (select p.*,
SUM(quantity) over (partition by item order by vol desc) as qcum,
SUM(quantity) over (partition by item) as qtot
from purchases p
) p
) p
group by item;
To get the largest value less than 70%, then you would use:
max(case when qcum < qtot*0.7 then qcum end) over (partition by item) as lastqcum
And then the case statements in the outer select would be:
min(case when lastqcum = qcum then price end) . .
In earlier versions of SQL Server, you can get the same effect with the correlated subquery:
select item,
SUM(price * quantity) / SUM(quantity) as vwp,
SUM(quantity) as total_vol,
min(case when seqnum = 1 then price end) as min70price,
max(case when seqnum = 1 then price end) as max70price
from (select p.*,
ROW_NUMBER() over (partition by item order by abs(0.7 - qcum/qtot) as seqnum
from (select p.*,
(select SUM(quantity) from purchases p2 where p2.item = p.item and p2.quantity >= p.quantity
) as qsum,
SUM(quantity) over (partition by item) as qtot
from purchases p
) p
) p
group by item
Here is the example with your code:
SELECT Item,
SUM(quantity) AS Total_Vol,
DATEADD(day, -#DateOffset, CONVERT(date, GETDATE())) AS buyDate,
MIN(Price) AS MinPrice,
MAX(Price) AS MaxPrice,
MAX(Price) - MIN(Price) AS PriceRange,
ROUND(SUM(Price * quantity)/SUM(quantity), 6) AS VWP,
min(case when seqnum = 1 then price end) as min70price,
max(case when seqnum = 1 then price end) as max70price
from (select p.*,
ROW_NUMBER() over (partition by item order by abs(0.7 - qcum/qtot) as seqnum
from (select p.*,
(select SUM(quantity) from TransactTracker..CustData p2 where p2.item = p.item and p2.quantity >= p.quantity
) as qsum,
SUM(quantity) over (partition by item) as qtot
from purchases TransactTracker..CustData
) p
) cd
-- #DateOffset (Number of days data is offset by)
-- #StartTime (Time to start data in hours)
-- #EndTime (Time to stop data in hours)
WHERE DATEDIFF(day, TradeDateTime, GETDATE()) = (#DateOffset+1)
AND DATEPART(hh, TradeDateTime) >= #StartTime
AND HitTake = ''
OR DATEDIFF(day, TradeDateTime, GETDATE()) = #DateOffset
AND DATEPART(hh, TradeDateTime) < #EndTime
AND HitTake = ''
GROUP BY Item

Improving Performance without Joins in Oracle

I have 3 tables.
- Tab_active(Contains active details )(Contains 30 million records)
- Tab_apr(Contains customer details for april) (contains 7 lakh
records)
- Tab_may(Contains customer details for may) (contains 7 lakh
records)
- Tab_jun(Contains customer details for june) (contains 7 lakh
records)
The table structure is the same for all the tables
CustNo Revenue
1000 54.55
Now I write a procedure to calculate
[(Revenue for June)/(Avg Revenue for Apr,May)-1]
for which I require who are active for 3 months(June,Apr and May) and also those who are present in Tab_active.
Considering the size of active customer records,using a join will degrade performance.
Is there a better way of doing this?
Thanks in advance.
I can think of one way to do this without a join, but I can't guarantee it'll be faster:
select sum(revJun) / (sum(RevMay)*0.5 + sum(RevApr)*0.5 - 1)
from (select CustNo, sum(revJun) as revJun, sum(revMay) as revMay, sum(revApr) as revApr,
count(*) as NumActiveMonths
from ((select Custno, Revenue, 'Now' as which, 0.0 as RevJun, 0.0 as RevMay, 0.0 as RevApr
from tab_active
) union all
(select Custno, Revenue, 'Jun', Revenue as RevJun, 0.0 as RevMay, 0.0 as RevApr
from tab_Jun
) union all
(select Custno, Revenue, 'May', 0.0 as RevJun, Revenue as RevMay, 0.0 as RevApr
from tab_May
) union all
(select Custno, Revenue, 'Apr', 0.0 as RevJun, 0.0 as RevMay, Revenue as RevApr
from tab_Apr
)
) t
group by CustNo
) t
where NumActiveMonths = 4
I don't know that this will be faster. You'll have to experiment.

T-SQL sorting by a calculated column

I have a table with these columns: ID, Price, IsMonthlyPrice
How can I select all and sort by weekly price, taking into account that:
if (IsMonthlyPrice = false) then Price is weekly
EDIT:
I need to sort by weekly price
weekly price of a monthly price would be: Price*12/52
You don't need to include the calculation twice. You can order on the column number
SELECT
ID,
Price,
IsMonthlyPrice,
CASE IsMonthlyPrice
WHEN 1 THEN Price * 12 / 52
ELSE price
END
FROM [TABLE]
order by
4
Not sure if this is what you look for but maybe try:
select
id,
case isMonthlyPrice when 'false' then (price*52/12) else price end normalizedPrice
from
yourTable
order by
normalizedPrice
You can sort calculated columns using two approaches. The more verbal approach (untested pseudocode) is:
select ID, CASE WHEN IsMonthlyPrice = 0 THEN Price * 52 / 12 ELSE Price END AS Price
ORDER BY CASE WHEN IsMonthlyPrice = 0 THEN Price * 52 / 12 ELSE Price END
The more concise approach is:
select * from
(
select ID, CASE WHEN IsMonthlyPrice = 0 THEN Price * 52 / 12 ELSE Price END AS Price
) as derived
ORDER BY derived.Price
You can use a case to calculate different values depending on whether the price is weekly or monthly.
This will calculate an approximate (assuming 31 days per month) price per day to sort on:
select ID, Price, IsMonthlyPrice
from TheTable
order by Price / case IsMonthlyPrice when 1 then 31.0 else 7.0 end
I generally wrap the select in a another one. then the column is no longer "calculated"
select
Price,
ID
from
(select
ID,
Price,
[calculate the IsMonthly Price] as 'IsMonthlyPrice'
from table
) order by IsMonthlyPrice