Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Retrieve The Second Date Value

I am trying to get the second value based on date. Suppose, a user has three entries with date and the second date should be retrieved with the value as well. So my sample input is something like this:

UserId  Date                      Amount 
1001    2019-10-10 00:00:00.000   10000
1001    2018-01-01 00:00:00.000   20000
1001    2017-10-02 00:00:00.000   6000
1002    2017-10-10 00:00:00.000   1000
1002    2016-08-02 00:00:00.000   600
1003    2015-06-10 00:00:00.000   200

Expected output:

UserId  Date                      Amount 
1001    2018-01-01 00:00:00.000   20000
1002    2016-08-02 00:00:00.000   600
1003    2015-06-10 00:00:00.000   200

I hope, the above samples are informative enough to understand and tried the followings to make it work:

SELECT DISTINCT m.UserId, m.Amount FROM UserAmount m WHERE m.DatePosted =
(SELECT MAX(k.DatePosted) FROM UserAmount k WHERE 
k.DatePosted < (SELECT MAX(p.DatePosted) FROM UserAmount p));

SELECT DISTINCT m.UserId, m.Amount FROM UserAmount m WHERE m.UserId IN (SELECT q.UserId FROM DetailsUser q) AND m.DatePosted =
(SELECT MAX(k.DatePosted) FROM UserAmount k WHERE k.UserId IN (SELECT r.UserId FROM DetailsUser r) AND 
k.DatePosted < (SELECT MAX(p.DatePosted) FROM UserAmount p WHERE p.UserId IN (SELECT s.UserId FROM DetailsUser s)));

Unfortunately, I am getting result for the first id say 1001 from table as follows:

UserId Amount
1001   20000

Anything skipped or doing wrong in the query? Would expect some valuable suggestions to make it work.

Script:

USE [DbName]
GO
/****** Object:  Table [dbo].[UserAmount]    Script Date: 04/16/2019 23:42:15 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[UserAmount](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [UserId] [nvarchar](20) NULL,
    [DatePosted] [datetime] NULL,
    [Amount] [float] NULL,
 CONSTRAINT [PK_UserAmount] PRIMARY KEY CLUSTERED 
(
    [Id] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
SET IDENTITY_INSERT [dbo].[UserAmount] ON
INSERT [dbo].[UserAmount] ([Id], [UserId], [DatePosted], [Amount]) VALUES (1, N'1001', CAST(0x0000AAE200000000 AS DateTime), 10000)
INSERT [dbo].[UserAmount] ([Id], [UserId], [DatePosted], [Amount]) VALUES (2, N'1001', CAST(0x0000A85B00000000 AS DateTime), 20000)
INSERT [dbo].[UserAmount] ([Id], [UserId], [DatePosted], [Amount]) VALUES (3, N'1001', CAST(0x0000A80000000000 AS DateTime), 6000)
INSERT [dbo].[UserAmount] ([Id], [UserId], [DatePosted], [Amount]) VALUES (4, N'1002', CAST(0x0000A80800000000 AS DateTime), 1000)
INSERT [dbo].[UserAmount] ([Id], [UserId], [DatePosted], [Amount]) VALUES (5, N'1002', CAST(0x0000A65600000000 AS DateTime), 600)
INSERT [dbo].[UserAmount] ([Id], [UserId], [DatePosted], [Amount]) VALUES (6, N'1003', CAST(0x0000A4B300000000 AS DateTime), 200)
SET IDENTITY_INSERT [dbo].[UserAmount] OFF
/****** Object:  Table [dbo].[DetailsUser]    Script Date: 04/16/2019 23:42:15 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[DetailsUser](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [UserId] [nvarchar](20) NULL,
 CONSTRAINT [PK_DetailsUser] PRIMARY KEY CLUSTERED 
(
    [Id] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
SET IDENTITY_INSERT [dbo].[DetailsUser] ON
INSERT [dbo].[DetailsUser] ([Id], [UserId]) VALUES (1, N'1001')
INSERT [dbo].[DetailsUser] ([Id], [UserId]) VALUES (2, N'1002')
INSERT [dbo].[DetailsUser] ([Id], [UserId]) VALUES (3, N'1003')
SET IDENTITY_INSERT [dbo].[DetailsUser] OFF

N.B: Sample query could be done using any of the query languages - MS SQL or Oracle.

like image 637
AT-2017 Avatar asked Jun 14 '26 12:06

AT-2017


1 Answers

A simple way would be to use window functions and pick the second record.

Given your above setup:

SELECT s1.UserID, s1.Amount, s1.DatePosted
FROM (
    SELECT du.UserID, ua.Amount, ua.DatePosted
        , ROW_NUMBER() OVER ( PARTITION BY ua.UserID ORDER BY ua.DatePosted DESC ) AS rn
        , COUNT(*) OVER ( PARTITION BY ua.UserID) AS theCount
    FROM DetailsUser du
    LEFT OUTER JOIN UserAmount ua ON du.userID = ua.UserID
) s1
WHERE s1.rn = 2 OR s1.theCount <=1

https://dbfiddle.uk/?rdbms=sqlserver_2012&fiddle=7035366e57188a3508e7348f0fe0ce8b

That will work on SQL Server and Oracle, but unfortunately not on MySQL 5.x (since it didn't introduce window functions until 8). PostgreS has had window functions for a while. I'm not sure which other flavors of SQL have them, but the same functionality can be duplicate in standard SQL.

like image 120
Shawn Avatar answered Jun 17 '26 02:06

Shawn



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!