Getting previous row Amount field - sql

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;

Related

SQL Server = Creating a stored procedure

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.

CountDistinct() is counting value twice when grouped in SSRS

-- 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.

SQL Query > sysdate <Sysdate + 1 year

I am trying to write a query that will find the max expiration date but what i noticed is when I am doing this I get no results if I have a expiration date lets say 30-Dec-16 and for the same part I also have an expiration date of 01-Jan-2099 (which is the default date if nothing is filled in) below is my query how could I rewrite the expiration_date query to get the correct date.
SELECT
Part,
price,
effective_date,
expiration_date
FROM a.Table
WHERE Part IN ('&Part')
AND PRICE IN ('somewere')
AND expiration_date IN (SELECT
MAX(expiration_date)
FROM table
WHERE expiration_date > SYSDATE
AND part IN ('&Part)
AND PRICE IN (Somewere))
AND to_date(effective_date) IN (SELECT
MAX(EFFECTIVE_DATE) FROM b.table
WHERE expiration_date > SYSDATE
AND Part IN ('&Part)
AND price IN (somewere)
AND EFFECTIVE_DATE < SYSDATE + 1)
I would use ROW_NUMBER. https://docs.oracle.com/cd/B19306_01/server.102/b14200/functions137.htm
Here is the query:
SELECT
part
,price
,effective_date
,expieration_date
FROM (
SELECT
part
,price
,effective_date
,expieration_date
,ROW_NUMBER() OVER (PARTITION BY part ORDER BY expieration_date DESC) AS "row"
FROM #tbl
WHERE effective_date < SYSDATE + 1
) tbl
WHERE "row" = 1
Here is what I used to populate #tbl.
DECLARE #tbl TABLE (
part NVARCHAR(MAX)
,price FLOAT
,effective_date DATETIME2(3)
,expieration_date DATETIME2(3)
)
INSERT #tbl (part, PRICE, EFFECTIVE_DATE, EXPIERATION_DATE)
VALUES ('Apples',7.95,'2016-12-01','2016-12-30')
,('Apples',7.95,'2016-11-01','2016-11-30')
,('Apples',7.95,'2016-12-30','2099-01-01')

Retrieve value from the closest available date

I have a system which stores the history of a balance in a history table.
The table has the account number, sort code, balance, balance start date, balance end date.
When a balance is updated an entry is created in the history table which shows what the balance, what the date was when that balance first started and the date which shows when the balances changed. So for example the table will show a balance of $100.00 and this balance ran from 07/10/2013 to 07/15/2013.
What I'm trying to do is get the sum of the balances for all sort codes on a specific day however the balance may not have changed on this date so I would need to return the closest prior date but I'm failing.
This is what I tried so far.
declare #sdate datetime
set #sdate = '06/08/2012' --mm/dd/yyyy
SELECT CONVERT(varchar(20),MAX(StartDate),103) as "Closest Start Date", Sort, SUM(Balance) AS "Sum of balances"
FROM BalanceHistory
WHERE StartDate <= convert(smalldatetime ,#sdate) AND SortCode <> 'ABC123456'
GROUP BY SortCode
SELECT FROM BalanceHistory would produce something like
AccountNumber, SortCode, Balance, StartDate, EndDate, RECID
00000001, srt010203, 100.00, 06/01/2013, 06/02/2013, RECID
00000001, srt010203, 110.00, 06/02/2013, 06/03/2013, RECID
00000001, srt010203, 120.00, 06/03/2013, 06/04/2013, RECID
00000002, srt010204, 200.00, 06/01/2013, 06/02/2013, RECID
00000003, srt010204, 300.00, 06/01/2013, 06/02/2013, RECID
00000004, srt010205, 400.00, 06/01/2013, 06/02/2013, RECID
00000005, srt010205, 500.00, 06/01/2013, 06/02/2013, RECID
You can do this without a JOIN by using the ROW_NUMBER() function (assuming SQL Server 2005 or newer):
DECLARE #sdate DATE
SET #sdate = '2012-06-08'
SELECT SortCode, SUM(Balance)'Sum of Balances'
FROM (SELECT AccountNumber,SortCode, Balance,ROW_NUMBER() OVER (PARTITION BY AccountNumber ORDER BY StartDate DESC)'RowRank'
FROM BalanceHistory
WHERE StartDate <= #sdate AND SortCode <> 'ABC123456'
)sub
WHERE RowRank = 1
GROUP BY SortCode
Demo: SQL Fiddle
The ROW_NUMBER() function in the subquery assigns a 'RowRank' to the balance for each accountnumber, we order by StartDate DESC to get rank of '1' for the most recent balance for each accountnumber, the WHERE criteria limits it to most recent balance from the date you set in your variable. Then you use that rank in the outer query to limit only to that one balance.
This should work
Declare #table as table
(AccountNo int,
Balance money,
DateEntered datetime)
Declare #dt datetime
set #dt = '2013-07-01'
Insert into #table values(1, 100, '2013-04-01')
Insert into #table values(2, 200, '2013-04-01')
Insert into #table values(2, 300, '2013-05-01')
Insert into #table values(2, 400, '2013-06-01')
--select AccountNo, Max(Balance), Max(DateEntered) MaxDateEntered From #table where DateEntered <= #dt group by AccountNo
Select Sum(t.Balance) From #table t
inner join (select AccountNo, Max(Balance) Balance, Max(DateEntered) MaxDateEntered From #table where DateEntered <= #dt group by AccountNo) tm
on t.AccountNo = tm.AccountNo and t.DateEntered = tm.MaxDateEntered
enter code here

SQL count exposure of life time by age

(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...