2.5 percent increase of previous field? - sql

I have a table with a series of IDs. Each ID has dates ranging up to year 2025 from current year. Each year for each ID has a specific price.
http://i.imgur.com/srplSDo.jpg
Once I get to a certain point with each ID, it no longer has a specific price. So what I am wanting to do is take the previous years price and increase it by 2.5 percent. I have figured a way to grab the previous years price with this
SELECT a.*,
(CASE
WHEN a.YEARLY_PRICING is not null
THEN a.YEARLY_PRICING
ELSE (SELECT b.YEARLY_PRICING
FROM #STEP3 b
WHERE (a.id = b.id) AND (b.YEAR = a.YEAR-1))*1.025
END) AS TEST
FROM #STEP3 a
which would provide these results:
http://imgur.com/MJutM99
but the problem I am having is after the first null year, it is still recognizing the previous yearly_pricing as null, which gives me the null results, so obviously this method won't work for me. Any other suggestions for improvement?
Thanks

WITH CTE AS
(
SELECT ID, Year, Price, Price AS Prev
FROM T A
WHERE Year = (SELECT min(year) FROM T WHERE T.ID = A.ID GROUP BY T.ID)
UNION ALL
SELECT T.ID, T.Year, T.Price, ISNULL(T.Price, 1.025*Prev)
FROM T JOIN CTE ON T.ID = CTE.ID
AND T.Year - 1 = CTE.YEAR
)
SELECT * FROM CTE
ORDER BY ID, Year
SQL Fiddle Demo

What you want is a way to find not just the previous year (year - 1), but instead the year that is previous and also has a not-null price. To query for such a year (without solving your problem), you would do something like this:
select a.*
, (select max(year)
from step3 b
where a.id=b.id and a.year>b.year and b.yearly_pricing is not null
) PRIOR_YEAR
from step3 a
Since SQL-Server allows common-table expressions, you can call the above query "TMP", and then approach it this way. The CALC_PRICE in any year will be the price from the "PRIOR_YEAR" found as per the above query, multiplied by factor. That factor will be 1.025 to the POWER of the number of years from "PRIOR_YEAR" to the current year.
You would end up with SQL like this:
with TMP AS (
select a.*
, (select max(year)
from step3 b
where a.id=b.id and a.year>b.year and b.yearly_pricing is not null
) PRIOR_YEAR
from step3 a
)
select t.*,
c.yearly_pricing As prior_price,
c.yearly_pricing * POWER(1.025 , (t.year-t.prior_year)) calc_price
from tmp t
left join step3 c
on t.id=c.id and t.prior_year = c.year
It still has nulls, etc. but those are easily handled with COALESCE() or CASE expressions like you had in your question.
Here's an SQL Fiddle which shows how it works: http://sqlfiddle.com/#!3/296a4/21

Related

What is the most efficient way to find the first and last entry of an entity in SQL?

I was asked this question in an interview. A table, trips, contains the following columns( customer_id, start_from, end_at, start_at_time, end_at_time), with data structured so that each trip is stored as a separate row and a part of the table looks like this: How would you find the list of all the customers who started yesterday from point A and ended yesterday at point P?
I provided solution using windowing functions that identified the list of all customers that started their day at A and then did an inner join of a list of these customers with the customers who ended their day at P( using the same windowing functions).
The solution I gave was this:
SELECT a.customer_id
FROM
(SELECT a.customer_id
FROM
(SELECT customer_id,
start_from,
row_number() OVER (PARTITION BY customer_id
ORDER BY start_at_time ASC) AS rnk
FROM trips
WHERE to_date(start_at_time)= date_sub(CURRENT_DATE, 1) ) AS a
WHERE a.rnk=1
AND a.start_from='A' ) AS a
INNER JOIN
(SELECT a.customer_id
FROM
(SELECT customer_id,
end_at,
row_number() OVER (PARTITION BY customer_id
ORDER BY end_at_time DESC) AS rnk
FROM trips
WHERE to_date(end_at_time)= date_sub(CURRENT_DATE, 1) ) AS a
WHERE a.rnk=1
AND a.end_at='P' ) AS b ON a.customer_id=b.customer_id
My interviewer said my solution was correct but there is a more efficient way to solve this problem. I've searching and trying to find a more efficient way but I could not find one so far. Can you suggest a more efficient way to solve this problem?
I might use first_value() for this:
select t.customer_id
from (select t.*,
first_value(start_from) over (partition by customer_id order by start_at_time) as first_start,
first_value(end_at) over (partition by customer_id order by start_at_time desc) as last_end
from t
where start_at_time >= date_sub(CURRENT_DATE, 1) and
start_at_time < CURRENT_DATE
) t
where first_start = start_from and -- just some filtering so select distinct is not needed
first_start = 'A' and
last_end = 'P';
I should add that many databases support an equivalent function for aggregation, and I would use that instead.
This assumes that starts are not repeated. To be safe, you can add select distinct, but there is a performance hit for that.
A generalized version of what I would probably have done:
SELECT fandl.a
FROM (
SELECT a, MIN(start) AS t0, MAX(start) AS tN
FROM someTable
WHERE start >= DATE_SUB(CURRENT_DATE, 1) AND start < CURRENT_DATE
GROUP BY a
) AS fandl
INNER JOIN someTable AS st0 ON fandl.a = st0.a AND fandl.t0 = st0.start
INNER JOIN someTable AS stN ON fandl.a = stN.a AND fandl.tN = stN.start
WHERE st0.b1 = 'A' AND stN.b2 = 'P'
;
Using the date function you did, since you did not specify sql dialect.
Note that, in many RDBMS, if there is an (a, start) index, the subquery and joins can be done with the index alone; actual table access would only be required for the final WHERE evaluation.

SQL Server : select max year [duplicate]

This question already has answers here:
Get top 1 row of each group
(19 answers)
Closed 4 years ago.
I have a fairly large table of data with multiple years worth of accounts with in it. I'm trying to write a query that will simple pull back the latest year.
However I have tried a number of queries, some from this site that seem to work for other but I can not get it to work on my data set.
The table data looks like so:
So in this example I need to bring back the 2018 year only. The query that I thought might work was:
SELECT *
FROM dbo.Accounts A
INNER JOIN
(SELECT [Account No], MAX(Year) AS MaxYear
FROM dbo.Accounts
GROUP BY [Account No]) MaxYear ON A.[Account No] = MaxYear.[Account No]
AND A.Year = MaxYear.MaxYear
However this still provides me three records on the Max part when I look for the example account number above.
Any help would be much appreciated.
Thanks in advance
PS: The Year datatype is Float and Account No is nvarchar(255)
Then don't group by the account number in the sub-query?
SELECT *
FROM dbo.Accounts A
INNER JOIN
(SELECT MAX(Year) AS MaxYear
FROM dbo.Accounts
) MaxYear
ON A.Year = MaxYear.MaxYear
By grouping by the account number (and then joining on it), you were getting each account's max year, rather than the entire table's max year.
Try below:
SELECT * FROM dbo.Accounts where Year = (Select Max(Year) FROM dbo.Accounts)
you can try like below
SELECT A.*
FROM dbo.Accounts A where A.Year= (select max(year) from dbo.Accounts)
use top
select TOP(1) WITH TIES * from dbo.Accounts
order by Year desc
As I understand year is part of the accountNo column meaning only the first part is the real account number and you want max (last) year per account
SELECT AccountNo, max_year
FROM Accounts
JOIN (SELECT substring(accountno, 1, 6) as accno, max(year) max_year
FROM accounts
group by accno) as max_acc ON max_acc.accno = substring(accountno, 1, 6) AND year = max_year

SQL query to get max homeruns for each year

In the database we have the table batter, which contains a record for each batter each year they played as well as their game stats for that year. How would I write a query to select each player that has the max number of homeruns out of all players?
I've been trying with
SELECT *
FROM master
WHERE batting.HR = (
SELECT MAX(batting.HR)
FROM batting
)
But have had no luck. How could I do this?
SELECT b.*
FROM batter b
INNER JOIN (
select year, Max(hr) as MaxHR
from batter
group by year
) y on b.year = y.year and b.HR = y.MaxHR
Note this is vulnerable to two players tying. It's not clear what you want to see in that case.
Assuming your RDBMS supports SQL2003, you can use a window function:
SELECT *
FROM
(
SELECT
batter,
year,
hr,
MAX(hr) OVER (PARTITION BY year) AS year_best_hr
FROM batting
)
WHERE hr = year_best_hr

In T-SQL, how can I collate positive and negative actions in order that they happened?

I have a table like this:
;WITH CTE AS
( SELECT *
FROM (VALUES(1,'BlueCar',NULL),
(2,'RedCar',NULL),
(3,NULL,'BlueCar'),
(4,'GreenCar',NULL),
(5,NULL,'RedCar'),
(6,'BlueCar',NULL)
) AS ValuesTable(Time,Buy,Sell)
)
SELECT *
FROM CTE
Time Buy Sell
1 BlueCar NULL
2 RedCar NULL
3 NULL BlueCar
4 GreenCar NULL
5 NULL RedCar
6 BlueCar NULL
How can I query this table to get the total number of cars still in stock? The Time column is days since the shop opened. The time that the car was purchased must be preserved
Note: The input data is such that there will never be a situation where there are multiple cars in the inventory.
Expected Output
Time Buy
4 GreenCar
6 BlueCar
In the query below, I do two separate aggregations to obtain the buy and sell counts for each car. I left join buys to sells, which should not run the risk of losing data assuming that the dealer did not short sell any inventory which does not actually exist.
Then I join that result to a CTE which finds the latest time for each car. This would then correspond to the time when the most recent car came into inventory, for each car type.
I also include the inventory count, which you did request, but it may be useful for you if you decide to expand the scope of your query later on.
WITH yourTable AS (
SELECT 1 AS Time, 'BlueCar' AS Buy, NULL AS Sell UNION ALL
SELECT 2,'RedCar',NULL UNION ALL
SELECT 3,NULL,'BlueCar' UNION ALL
SELECT 4,'GreenCar',NULL UNION ALL
SELECT 5,NULL,'RedCar' UNION ALL
SELECT 6,'BlueCar',NULL
),
cte AS (
SELECT Buy, Time
FROM
(
SELECT Buy, Time,
ROW_NUMBER() OVER (PARTITION BY Buy ORDER BY Time DESC) rn
FROM yourTable
) t
WHERE rn = 1
)
SELECT
t1.Buy,
t1.buy_cnt - COALESCE(t2.sell_cnt, 0) AS inventory,
t3.Time
FROM
(
SELECT Buy, COUNT(*) AS buy_cnt
FROM yourTable
GROUP BY Buy
) t1
LEFT JOIN
(
SELECT Sell, COUNT(*) AS sell_cnt
FROM yourTable
GROUP BY Sell
) t2
ON t1.Buy = t2.Sell
LEFT JOIN cte t3
ON t1.Buy = t3.Buy
WHERE
t1.Buy IS NOT NULL AND
t1.buy_cnt - COALESCE(t2.sell_cnt, 0) > 0
ORDER BY
t3.Time;
Output:
Demo here:
Rextester
You can do this with a not exists:
;WITH CTE AS
( SELECT *
FROM (VALUES(1,'BlueCar',NULL),
(2,'RedCar',NULL),
(3,NULL,'BlueCar'),
(4,'GreenCar',NULL),
(5,NULL,'RedCar'),
(6,'BlueCar',NULL)
) AS ValuesTable(Time,Buy,Sell)
)
SELECT
[Time], Buy
FROM CTE as T1
WHERE
NOT EXISTS (SELECT 1 FROM CTE as T2 WHERE T2.TIME > T1.TIME AND T1.Buy = T2.Sell) AND
BUY IS NOT NULL
Presumably, you want:
with cte as (
. . .
)
select count(buy) - count(sell)
from cte;
Note: This does not verify that what you sell is something that has already been bought. It just counts up the non-NULL values in each column and takes the difference.
To get the stock at a certain point in time you can do
SELECT car, SUM(Inc) total FROM
(SELECT ID, Buy car, 1 Inc FROM tbl WHERE Buy>''
UNION ALL
SELECT ID, Sell car, -1 Inc FROM tbl WHERE Sell>'') coll
WHERE ID < 20 -- some cut-off time
GROUP BY car
I combine the two columns Buy and Sell into one (= car) and add another column (inc) with the increment of each action (-1 or 1). The rest is simple: select with a group by [car] and summation over column inc.
Here is a little demo: http://rextester.com/LLQDW60692
It is Good Question. I like that. Time by time your expected outputs changes.Its ok.
check below simple query for your problem.
Using Joins and Rownumber() we can achieve this.
;with CTE as
(
select a.time,a.buy,a.rid,COALESCE(b.rid,0)rid2 ,coalesce(b.sell,a.buy)sell from
( select time,buy,ROW_NUMBER()over( partition by buy order by (select 1)) rid
from #tableName where buy is not null)a left join
( select time,sell, ROW_NUMBER()over( partition by sell order by (select 1)) rid
from #TableName
where sell is not null )b on a.buy=b.sell
)
select Time,Buy from CTE
where rid!=rid2
Sample Demo For All Your Expected outputs.
Demo Link : Click Here
ALL Required Outputs :

SELECT field value minus previous field value

I have a select query that gets a CarID, month, mileage and CO2 emission.
Now it gives for each month per car the mileage like this:
month 1: 5000
month 2: 5200
...
What I really need is that it takes the current value minus the previous one. I get data between a certain time frame and I already included a mileage point before that time frame. So it would be possible to get the total miles per month, I just don't know how. What I want is this.
pre timeframe: 5000
month 1: 200
month 2: 150
...
How would I do this?
edit: code, I have not yet tried anything as I have no clue how to start to do this.
resultlist as (
SELECT
CarID
, '01/01/2000' as beginmonth
, MAX(kilometerstand) as Kilometers
, MAX(Co2Emission) as CO2
FROM
totalmileagelist
GROUP BY CarID
UNION
SELECT
CarID
, beginmonth
, MAX(kilometerstand) as Kilometers
, MAX(Co2Emission) as CO2
FROM
resultunionlist
GROUP BY CarID, beginmonth
)
select * from resultlist
order by CarID, beginmonth
Edit2: explanation to the code
In the first part of the result list I grab the latest mileage per car. In the second part, after the union, I grab per month per car the latest mileage.
If you just want to subtract the previous milage, use the lag() function:
select ml.*,
(kilometerstand - lag(kilometerstand) over (partition by carid order by month)
) as diff
from totalmileagelist ml;
lag() is available in SQL Server 2012+. In earlier versions you can use a correlated subquery or outer apply.
(I missed the version because it is in the title and not on a tag.) In SQL Server 2008:
select ml.*,
(ml.mileage - mlprev.mileage) as diff
from totalmileagelist ml outer apply
(select top 1 ml2.*
from totalmileagelist ml2
where ml2.CarId = ml.CarId and
ml2.month < ml.month
order by ml2.month desc
) mlprev;
Try like this:
SELECT id, yourColumnValue,
COALESCE(
(
SELECT TOP 1 yourColumnValue
FROM table_name t
WHERE t.id> tbl.id
ORDER BY
rowInt
), 0) - yourColumnValue AS diff
FROM table_name tbl
ORDER BY
id
or like this using rank()
select rank() OVER (ORDER BY id) as 'RowId', mileage into temptable
from totalmileagelist
select t1.mileage - t2.mileage from temptable t1, temptable t2
where t1.RowId = t2.RowId - 1
drop table temptable