CREATE TABLE dbo.HighScores
(
[User] varchar(255),
Score int,
DateAdded datetime
)
INSERT INTO dbo.HighScores
VALUES ('Bob', 2500, '2 Jan 2013 13:13'),
('Jon', 1500, '2 Jan 2013 13:15'),
('Amy', 3500, '2 Jan 2013 13:18'),
('Joe', 1750, '2 Jan 2013 13:23'),
('Don', 500, '2 Jan 2013 13:33'),
('Ann', 800, '2 Jan 2013 14:03'),
('Mav', 1200, '2 Jan 2013 15:13'),
('Ken', 2600, '2 Jan 2013 15:32'),
('Ace', 2500, '2 Jan 2013 16:45'),
('Tom', 2700, '2 Jan 2013 16:59'),
('Leo', 300, '2 Jan 2013 17:33'),
('Jay', 1000, '2 Jan 2013 18:03'),
('Roy', 1200, '2 Jan 2013 18:13'),
('Vic', 2100, '2 Jan 2013 19:32'),
('Ted', 1800, '2 Jan 2013 20:45'),
('Pat', 1400, '2 Jan 2013 20:59')
create a stored procedure called pr_GetHighScoreList
We now need to add highscores to the table using pr_PutHighScoreList
• You can only appear on the high score list once, and only your highest score must be stored.
• We would also need to record the movement in the highscore table, when players move up in position.
Example:
pr_PutHighScoreList 'Ann', 2750
Will increase Ann's score from 800 to 2750 and we need to record that she moved from position 14 to 2 in a separate table.
• Create the proc, and any other objects that may be required to store this.
This is what i did so far:
ALTER PROCEDURE [dbo].[pr_PutHighScoreList]
(
#name as nvarchar(20),
#score as int
)
AS
BEGIN
IF EXISTS
(SELECT * from [HighScores]
WHERE [User]=#name
AND [Score]>#score)
UPDATE [HighScores]
SET [User] = #name
,[Score] = #score
,[DateAdded] = getdate()
WHERE [User]=#name
ELSE
(SELECT * from [HighScores]
WHERE [User]!= #name)
INSERT INTO [HighScores]
([User]
,[Score]
,[DateAdded])
VALUES
(#name
,#score
,getdate())
END
Perhaps this will get you started
;with cte as (
Select *
,RN = row_number() over (order by score desc,dateAdded desc)
From HighScores
)
Select * From cte where RN<=5
Union
Select A.*
From cte A
Join (Select R1=RN-1,R2=RN+1 from cte where [User]='Ann' ) B on A.RN between B.R1 and B.R2
Order by RN
Results
I would use lag() and lead() for Ann and her neighbors. This is what a query looks like:
select *
from (select hs.*,
row_number() over (order by score desc, [User]) as seqnum,
lag([User]) over (order by score desc, [User]) as prev_user,
lead([User]) over (order by score desc, [User]) as next_user
from highscores hs
) hs
where seqnum <= 5 or
'Ann' in ([User], prev_user, next_user)
order by seqnum;
You can adapt it for a stored procedure.
Here is a db<>fiddle.
Related
I have a table with a few columns PersonID, Amount and StartDate, have provided 1 person as a sample. What I'm trying to do is
calculate the BeforeAmountCalculatedColumn automatically, basically picking up what the previous rows (by date)
amount was. How could I do this?
Create Table #Results
(
PersonID int,
Amount money,
StartDate datetime,
BeforeAmountCalculatedColumn money
)
insert into #Results
(
PersonID,
Amount,
StartDate,
BeforeAmountCalculatedColumn
)
select
1,
'163.45',
'30 Jan 2019',
'202.70'
union all
select
1,
'202.70',
'23 Nov 2018',
'189.45'
union all
select
1,
'189.45',
'28 Aug 2018',
'0'
Are you looking for lag()?
select r.*,
lag(amount) over (partition by personid order by startdate) as BeforeAmountCalculatedColumn
from #Results r;
Below is my table structure. I need to calculate rent for length of lease for each properties:
Let's look at PropertyID = 12077:
Area = 1280
StartDate = 2023-02-01
EndDate = 2027-10-31
BaseRent = 21.53
RentIncreasePercent = .04 (4 percent)
IncreaseRepeatMonths = 12 months (NOTE: First 12 months there won't be any increase)
Since this property lease starts and ends between year 2023 and 2028, I'd like to know (in separate row per year) amount of rent to be collected each year. This would take percent increase every 12 months (compound rent increase) into consideration.
Example:
21.53 * 1280 would give rent for first 12 months. However, lease started in February so year 2023 total rent amount would be = ((21.23 * 1280)/12) * 11
For year 2024, first month rent would be = (21.23 * 1280)/12 because rent only increases every 12 months. For next 11 months of 2024, rent would be ((12.23 * 1.04 * 1280)/12)* 11.
For year 2025, first month rent would be (12.23 * 1.04 *1280)/12). However, next 11 months of 2025 would be ((12.72 * 1.04 * 1280)/12)*11. 12.72 comes from compound increase.
How would I go about coming up with a view to do this? Most confusing part to me is not knowing how to accommodate for lease start date when it is not starting on January.
declare #table table
(
PropertyID int,
area int,
StartDate date,
EndDate date,
BaseRent decimal(12,2),
RentIncreaseBasis varchar(30),
RentIncreasePercent decimal(5,2),
IncreaseRepeatMonths int
)
insert #table values (12076, 5627, '2024-01-01', '2028-12-31', '16.52', '% Increase', 0.03, 12)
insert #table values (12077, 1280, '2023-02-01', '2027-10-31', '21.53', '% Increase', 0.04, 12)
insert #table values (12078, 1000, '2017-03-01', '2025-11-30', '23.52', '% Increase', 0.01, 12)
insert #table values (12079, 2000, '2020-02-01', '2024-09-30', '15.57', '% Increase', 0.05, 12)
insert #table values (12080, 3000, '2018-05-01', '2020-08-31', '18.58', '% Increase', 0.04, 12)
insert #table values (12081, 4000, '2019-08-01', '2020-12-31', '22.56', '% Increase', 0.03, 12)
insert #table values (12082, 5000, '2017-02-01', '2028-03-31', '19.53', '% Increase', 0.02, 12)
select * from #table
I recommend to use a calendar table which containts all the months from your table.
I hope my example will work in SQL 2008.
-- here is your code
-- the calendar table
DECLARE #MonthCalendar table(
[Month] date PRIMARY KEY
)
DECLARE #MinDate date,#MaxDate date
-- get min and max date
SELECT
#MinDate=MIN(StartDate),
#MaxDate=MAX(EndDate)
FROM #table
-- fill the calendar table
;WITH monthCTE AS(
SELECT CAST(#MinDate AS date) [Month]
UNION ALL
SELECT DATEADD(MONTH,1,[Month])
FROM monthCTE
WHERE [Month]<#MaxDate
)
INSERT #MonthCalendar([Month])
SELECT [Month]
FROM monthCTE
OPTION(MAXRECURSION 0);
-- final query
SELECT
*,
(BaseRent*Area*(1+RentIncreasePercent*IncreaseCount))/12 MonthRentAmount,
(1+RentIncreasePercent*IncreaseCount) TotalPercent
FROM
(
SELECT *,(ROW_NUMBER()OVER(PARTITION BY t.PropertyID ORDER BY m.[Month])-1)/12 IncreaseCount
FROM #table t
JOIN #MonthCalendar m ON m.[Month] BETWEEN t.StartDate AND t.EndDate
--WHERE t.PropertyID=12077
) q
-- query for total amounts by PropertyIDs and Years
SELECT
PropertyID,
YEAR(StartDate) [Year],
SUM((BaseRent*Area*(1+RentIncreasePercent*IncreaseCount))/12) YearRentAmount
FROM
(
SELECT *,(ROW_NUMBER()OVER(PARTITION BY t.PropertyID ORDER BY m.[Month])-1)/12 IncreaseCount
FROM #table t
JOIN #MonthCalendar m ON m.[Month] BETWEEN t.StartDate AND t.EndDate
--WHERE t.PropertyID=12077
) q
GROUP BY PropertyID,YEAR(StartDate)
ORDER BY PropertyID,[Year]
-- Sample data.
declare #Table1 as Table ( RegisterId Int Identity, UnitId Int, DateRegistered date);
declare #Table2 as Table ( Id Int Identity, RegisterId Int, Rep1 int, Rep2 int, DateCreated Date );
declare #Table3 as Table ( UnitId int Identity, UnitName varchar(40), SquadName varchar(40))
insert into #Table1 ( UnitId, DateRegistered )
values
( 1, '20160115' );
insert into #Table2 ( RegisterId, Rep1, Rep2, DateCreated )
values
( 1, 3, 4, '20160122' ), ( 1, 10, 4, '20160129' ), ( 1, 32, 45, '20160210' );
insert into #Table3 ( UnitName )
values
( 'Tango', 'West' ), ( 'Lima', 'West' ), ( 'Foxtrot', 'West' );
SELECT t3.UnitName
, t2.RegisterId
, t2.DateCreated
, t2.Rep1 + t2.Rep2 as 'TotalReps'
, DateName(month, t2.DateCreated) as 'Month'
, DateName(year, t2.DateCreated) as 'Year'
FROM #Table1 t1
INNER JOIN #Table2 t2 ON t1.RegisterId = t2.RegisterId
INNER JOIN #Table3 t3 ON t1.UnitId = t3.UnitId
Building a report in SSRS, the above is my query. Report parameters are a start date, enddate and UnitId(s).
In the report I have 3 Row Groups - Month, Year, SquadName. In the report I am using the TotalReps for totalreps, CountDistinct(Field!RegisterId.Value) for the ConfirmedRegisters and Count(Field!RegisterId.Value) for CheckIn. THe TOTALs are just SUMS of the expressions, SUM(CountDistinct(Field!RegisterId.Value)).
The report shows like:
TotalReps ConfirmedRegisters CheckIns
WEST
2016
Jan
21 1 2
Feb
77 1 1
TOTAL 98 1 3
Some definitions. A ConfirmedRegister means the Id exists in Table1 AND Table2. A Checkin is just a count of Table2 Ids. So to be a checkin, there must be a row in Table2 and a ConfirmedREgister can ONLY BE COUNTED ONCE, regardless of the number of checkins and when they happen. So if a Table1 register occurs in Jan 2016 and there are checkins off the registerid in Jan and Feb 2016 as our test data suggests, the report should show a zero in the ConfirmedRegisters columns for Feb because the RegisterId was counted in Jan.
Should be:
TotalReps ConfirmedRegisters CheckIns
WEST
2016
Jan
21 1 2
Feb
77 0 1
TOTAL 98 1 3
Notice the TOTAL Confirmed Registers is showing correct, I guess because it is totalling the whole date range. But The MONTHLY totals are incorrect for the CONFIRMEDREGISTERS columns because it is counting RegisterID for Jan and Feb where it should only count the Jan and put nothing or 0 for Feb.
Not sure if I need do fix this in the query or the report.
I fixed this by using a CTE with a windowing function (row_number() over(partition ...) to mimic a 'First()' type function so I could then count the first occurrence of each RegisterId from Table2.
Here is a table...
ID QTY DATE CURRENT_STOCK
----------------------------------
1 1 Jan 30
2 1 Feb 30
3 2 Mar 30
4 6 Apr 30
5 8 May 30
6 21 Jun 30
I need to return the newest rows whose summed qty equal or exceed the current stock level, excluding any additional rows once this total has been reached, so I am expecting to see just these rows...
ID QTY DATE CURRENT_STOCK
----------------------------------
4 6 Apr 30
5 8 May 30
6 21 Jun 30
I am assuming I need a CTE (Common Table Expression) and have looked at this question but cannot see how to translate that to my requirement.
Help!?
Declare #YourTable table (ID int,QTY int,DATE varchar(25), CURRENT_STOCK int)
Insert Into #YourTable values
(1 ,1 ,'Jan' ,30),
(2 ,1 ,'Feb' ,30),
(3 ,2 ,'Mar' ,30),
(4 ,6 ,'Apr' ,30),
(5 ,8 ,'May' ,30),
(6 ,21 ,'Jun' ,30)
Select A.*
From #YourTable A
Where ID>= (
Select LastID=max(ID)
From #YourTable A
Cross Apply (Select RT = sum(Qty) from #YourTable where ID>=A.ID) B
Where B.RT>=CURRENT_STOCK
)
Returns
ID QTY DATE CURRENT_STOCK
4 6 Apr 30
5 8 May 30
6 21 Jun 30
One way to do it with your provided data set
if object_id('tempdb..#Test') is not null drop table #Test
create table #Test (ID int, QTY int, Date_Month nvarchar(5), CURRENT_STOCK int)
insert into #Test (ID, QTY, Date_Month, CURRENT_STOCK)
values
(1, 1, 'Jan', 30),
(2, 1, 'Feb', 30),
(3, 2, 'Mar', 30),
(4, 6, 'Apr', 30),
(5, 8, 'May', 30),
(6, 21, 'Jun', 30)
if object_id('tempdb..#Finish') is not null drop table #Finish
create table #Finish (ID int, QTY int, Date_Month nvarchar(5), CURRENT_STOCK int)
declare #rows int = (select MAX(ID) from #Test)
declare #stock int = (select MAX(CURRENT_STOCK) from #Test)
declare #i int = 1
declare #Sum int = 0
while #rows > #i
BEGIN
select #Sum = #Sum + QTY from #Test where ID = #rows
IF (#SUM >= #stock)
BEGIN
set #i = #rows + 1 -- to exit loop
END
insert into #Finish (ID, QTY, Date_Month, CURRENT_STOCK)
select ID, QTY, Date_Month, CURRENT_STOCK from #Test where ID = #rows
set #rows = #rows - 1
END
select * from #Finish
Setup Test Data
-- Setup test data
CREATE TABLE #Stock
([ID] int, [QTY] int, [DATE] varchar(3), [CURRENT_STOCK] int)
;
INSERT INTO #Stock
([ID], [QTY], [DATE], [CURRENT_STOCK])
VALUES
(1, 1, 'Jan', 30),
(2, 1, 'Feb', 30),
(3, 2, 'Mar', 30),
(4, 6, 'Apr', 30),
(5, 8, 'May', 30),
(6, 21, 'Jun', 30)
;
Solution for SQL Server 2012+
If you have a more recent version of SQL server which supports full window function syntax, you can do it look this:
-- Calculate a running total of qty by Id descending
;WITH stock AS (
SELECT *
-- This calculates the SUM over a 'window' of rows based on the first
-- row in the result set through the current row, as specified by the
-- ORDER BY clause
,SUM(qty) OVER(ORDER BY Id DESC
ROWS BETWEEN UNBOUNDED PRECEDING
AND CURRENT ROW) AS TotalQty
FROM #Stock
),
-- Identify first row in mininum set that matches or exceeds CURRENT_STOCK
first_in_set AS (
SELECT TOP 1 *
FROM stock
WHERE TotalQty >= CURRENT_STOCK
)
-- Fetch matching set
SELECT *
FROM #stock
WHERE Id >= (SELECT Id FROM first_in_set)
Solution for SQL Server 2008
For SQL Server 2008, which only has basic support for window functions, you can calculate the running total using CROSS APPLY:
-- Calculate a running total of qty by Id descending
;WITH stock AS (
SELECT *
-- This window function causes the results of this query
-- to be sorted in descending order by Id
,ROW_NUMBER() OVER(ORDER BY Id DESC) AS sort_order
FROM #Stock s1
-- CROSS APPLY 'applies' the query (or UDF) to every row in a result set
-- This CROSS APPLY query produces a 'running total'
CROSS APPLY (
SELECT SUM(Qty) AS TotalQty
FROM #Stock s2
WHERE s2.Id >= s1.id
) total_calc
WHERE TotalQty >= s1.CURRENT_STOCK
),
-- Identify first row in mininum set that matches or exceeds CURRENT_STOCK
first_in_set AS (
SELECT TOP 1 Id
FROM stock
WHERE sort_order = 1
)
-- Fetch matching set
SELECT *
FROM #stock
WHERE Id >= (SELECT Id
FROM first_in_set)
(Using SQL Server 2008)
I need some help visualizing a solution. Let's say I have the following simple table for members of a pension scheme:
[Date of Birth] [Date Joined] [Date Left]
1970/06/1 2003/01/01 2007/03/01
I need to calculate the number of lives in each age group from 2000 to 2009.
NOTE: "Age" is defined as "age last birthday" (or "ALB") on 1 January of each of those yeasrs. e.g. if you are exactly 41.35 or 41.77 etc. years old on 1/1/2009 then you would be ALB 41.
So if the record above were the only entry in the database, then the output would be something like:
[Year] [Age ] [Number of Lives]
2003 32 1
2004 33 1
2005 34 1
2006 35 1
2007 36 1
(For 2000, 2001, 2002, 2008 and 2009 there are no lives on file since the sole member only joined on 1/1/2003 and left on 1/3/2007)
I hope I am making myself clear enough.
Anyone have any suggestions?
Thanks, Karl
[EDIT]
Adding another layer to the problem:
What if I had:
[Date of Birth] [Date Joined] [Date Left] [Gender] [Pension Value]
1970/06/1 2003/01/01 2007/03/01 'M' 100,000
and I want the output to be:
[Year] [Age ] [Gender] sum([Pension Value]) [Number of Lives]
2003 32 M 100,000 1
2004 33 M 100,000 1
2005 34 M 100,000 1
2006 35 M 100,000 1
2007 36 M 100,000 1
Any ideas?
WITH years AS
(
SELECT 1900 AS y
UNION ALL
SELECT y + 1
FROM years
WHERE y < YEAR(GETDATE())
),
agg AS
(
SELECT YEAR(Dob) AS Yob, YEAR(DJoined) AS YJoined, YEAR(DLeft) AS YLeft
FROM mytable
)
SELECT y, y - Yob, COUNT(*)
FROM agg
JOIN years
ON y BETWEEN YJoined AND YLeft
GROUP BY
y, y - Yob
OPTION (MAXRECURSION 0)
People born on same year always have the same age in your model
That's why if they go at all, they always go into one group and you just need to generate one row per year for the period they stay in the program.
You can try something like this
DECLARE #Table TABLE(
[Date of Birth] DATETIME,
[Date Joined] DATETIME,
[Date Left] DATETIME
)
INSERT INTO #Table ([Date of Birth],[Date Joined],[Date Left]) SELECT '01 Jun 1970', '01 Jan 2003', '01 Mar 2007'
INSERT INTO #Table ([Date of Birth],[Date Joined],[Date Left]) SELECT '01 Jun 1979', '01 Jan 2002', '01 Mar 2008'
DECLARE #StartYear INT,
#EndYear INT
SELECT #StartYear = 2000,
#EndYear = 2009
;WITH sel AS(
SELECT #StartYear YearVal
UNION ALL
SELECT YearVal + 1
FROM sel
WHERE YearVal < #EndYear
)
SELECT YearVal AS [Year],
COUNT(Age) [Number of Lives]
FROM (
SELECT YearVal,
YearVal - DATEPART(yy, [Date of Birth]) - 1 Age
FROM sel LEFT JOIN
#Table ON DATEPART(yy, [Date Joined]) <= sel.YearVal
AND DATEPART(yy, [Date Left]) >= sel.YearVal
) Sub
GROUP BY YearVal
Try the following sample query
SET NOCOUNT ON
Declare #PersonTable as Table
(
PersonId Integer,
DateofBirth DateTime,
DateJoined DateTime,
DateLeft DateTime
)
INSERT INTO #PersonTable Values
(1, '1970/06/10', '2003/01/01', '2007/03/01'),
(1, '1970/07/11', '2003/01/01', '2007/03/01'),
(1, '1970/03/12', '2003/01/01', '2007/03/01'),
(1, '1973/07/13', '2003/01/01', '2007/03/01'),
(1, '1972/06/14', '2003/01/01', '2007/03/01')
Declare #YearTable as Table
(
YearId Integer,
StartOfYear DateTime
)
insert into #YearTable Values
(1, '1/1/2000'),
(1, '1/1/2001'),
(1, '1/1/2002'),
(1, '1/1/2003'),
(1, '1/1/2004'),
(1, '1/1/2005'),
(1, '1/1/2006'),
(1, '1/1/2007'),
(1, '1/1/2008'),
(1, '1/1/2009')
;WITH AgeTable AS
(
select StartOfYear, DATEDIFF (YYYY, DateOfBirth, StartOfYear) Age
from #PersonTable
Cross join #YearTable
)
SELECT StartOfYear, Age, COUNT (1) NumIndividuals
FROM AgeTable
GROUP BY StartOfYear, Age
ORDER BY StartOfYear, Age
First some preparation to have something to test with:
CREATE TABLE People (
ID int PRIMARY KEY
,[Name] varchar(50)
,DateOfBirth datetime
,DateJoined datetime
,DateLeft datetime
)
go
-- some data to test with
INSERT INTO dbo.People
VALUES
(1, 'Bob', '1961-04-02', '1999-01-01', '2007-05-07')
,(2, 'Sadra', '1960-07-11', '1999-01-01', '2008-05-07')
,(3, 'Joe', '1961-09-25', '1999-01-01', '2009-02-11')
go
-- helper table to hold years
CREATE TABLE dimYear (
CalendarYear int PRIMARY KEY
)
go
-- fill-in years for report
DECLARE
#yr int
,#StartYear int
,#EndYear int
SET #StartYear = 2000
SET #EndYear = 2009
SET #yr = #StartYear
WHILE #yr <= #EndYear
BEGIN
INSERT INTO dimYear (CalendarYear) values(#yr)
SET #yr =#yr+1
END
-- show test data and year tables
select * from dbo.People
select * from dbo.dimYear
go
Then a function to return person's age for each year, if the person is still an active member.
-- returns [CalendarYear], [Age] for a member, if still active member in that year
CREATE FUNCTION dbo.MemberAge(#DateOfBirth datetime, #DateLeft datetime)
RETURNS TABLE
AS
RETURN (
SELECT
CalendarYear,
CASE
WHEN DATEDIFF(dd, cast(CalendarYear AS varchar(4)) + '-01-01',#DateLeft) > 0
THEN DATEDIFF(yy, #DateOfBirth, cast(CalendarYear AS varchar(4)) + '-01-01')
ELSE -1
END AS Age
FROM dimYear
);
go
And the final query:
SELECT
a.CalendarYear AS "Year"
,a.Age AS "Age"
,count(*) AS "Number Of Lives"
FROM
dbo.People AS p
CROSS APPLY dbo.MemberAge(p.DateOfBirth, p.DateLeft) AS a
WHERE a.Age > 0
GROUP BY a.CalendarYear, a.Age
Deal with this in pieces (some random thoughts) - create views to test you dev steps if you can:
ALB - do a query that, for a given year, gives you your memeber's ALB
Member in year - another bit of query that tell you whether a member was a member in a given year
Put those two together and you should be able to create a query that says whether a person was a member in a given year and what their ALB was for that year.
Hmm, tricky - following this chain of thought what you'd then want to do is generate a table that has all the years the person was a member and their ALB in that year (and a unique id)
From 4. select year, alb, count(id) group by year, alb
I'm not sure I'm going in the right direction from about 3 though it should work.
You may find a (temporary) table of years helpful - joining things to a table of dates makes all kinds of things possible.
Not really an answer, but certainly some direction...