how to get nulls when number incremented - sql-server

i have a piece of code looks like this
declare #t table (record int,string varchar(MAX))
insert into #t (record,string)values (1,'ABC')
insert into #t (record,string)values (2,'DEF/123')
insert into #t (record,string)values (3,'GHI/456/XYZ')
i got a query where i can result like this
SELECT record,
RIGHT(LEFT(T.string,Number-1),CHARINDEX('/',REVERSE(LEFT('/' + T.string,number-1))))
FROM
master..spt_values,
#t T
WHERE
Type = 'P' AND Number BETWEEN 1 AND LEN(T.string)+1
AND
(SUBSTRING(T.string,Number,1) = '/' OR SUBSTRING(T.string,Number,1) = '')
getting output
record values
1 ABC
2 DEF
2 123
3 GHI
3 456
3 XYZ
how can i get output like this
record values
1 ABC
1 NULL
1 NULL
2 DEF
2 123
2 NULL
3 GHI
3 456
3 XYZ
it has been asked by some user .i excelled upto here and from there how can i achieve desire output

The idea is to generate a rows of record cross joined to 1,2,3 to produce combination of record with another column numbered 1,2,3, then use that combination to join to your splitted values. You must add a ROW_NUMBER for your splitted values first to be able to join it with the generated combinations.
;WITH CteThree(record, N) AS(
SELECT
t.record,
x.N
FROM (
SELECT DISTINCT record FROM #t
)t
CROSS JOIN(
SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3
)x(N)
),
CteSplitted AS(
SELECT
record,
ROW_NUMBER() OVER(PARTITION BY record ORDER BY Number) AS N,
RIGHT(LEFT(T.string,Number-1),CHARINDEX('/',REVERSE(LEFT('/' + T.string,number-1)))) AS str
FROM master..spt_values v
CROSS JOIN #t T
WHERE
Type = 'P'
AND Number BETWEEN 1 AND LEN(T.string)+1
AND (SUBSTRING(T.string,Number,1) = '/' OR SUBSTRING(T.string,Number,1) = '')
)
SELECT
t.record,
s.str
FROM CteThree t
LEFT JOIN CteSplitted s
ON s.record = t.record
AND s.N = t.N

how about this:
declare #t table (record int,string varchar(MAX));
declare #s char(1) = '/';
WITH counter as (
SELECT MAX(LEN(string)-LEN(REPLACE(string, #s, ''))) lines
) ,
splitter as (
SELECT record, string
, line = 1
, pos = h.pos
, value = CASE WHEN h.pos>0 THEN SUBSTRING(string,1,h.pos) ELSE string END
FROM #t
CROSS APPLY (SELECT CHARINDEX(#s, string) pos ) h
UNION ALL
SELECT record, string
, line = s.line + 1
, pos = CASE WHEN s.pos = 0 THEN 0 ELSE h.pos END
, value = CASE WHEN s.pos = 0 THEN null
WHEN h.pos > 0 THEN SUBSTRING(string,s.pos+1,h.pos-s.pos-1)
ELSE SUBSTRING(string,s.pos+1,99)
END
FROM splitter s
CROSS APPLY (SELECT CHARINDEX(#s, string, s.pos+1) pos ) h
WHERE s.line<=(SELECT lines FROM counter)
)
SELECT *
FROM splitter
ORDER BY record,line

try this
DECLARE #t TABLE
(
record INT ,
string VARCHAR(MAX)
)
INSERT INTO #t
( record, string )
VALUES ( 1, 'ABC' ),
( 2, 'DEF/123' ),
( 3, 'GHI/456/XYZ' );
WITH cte
AS ( SELECT Number = 1
UNION ALL
SELECT Number + 1
FROM cte
WHERE Number <= 100
),
NotNull
AS ( SELECT record ,
RIGHT(LEFT(T.string, Number - 1),
CHARINDEX('/',
REVERSE(LEFT('/' + T.string,
number - 1)))) string ,
ROW_NUMBER() OVER ( PARTITION BY T.record ORDER BY T.record ) AS RN
FROM cte
JOIN #t T ON Number <= ( LEN(T.string) + 1 )
AND SUBSTRING(T.string + '/', Number, 1) = '/'
)
SELECT template.record ,
NotNull.string
FROM ( SELECT *
FROM ( SELECT DISTINCT
RN
FROM NotNull
) AS A
CROSS JOIN ( SELECT Record
FROM NotNull
) AS B
) AS template
LEFT JOIN NotNull ON template.RN = NotNull.RN
AND template.Record = NotNull.Record

try this
declare #t table (record int,string varchar(MAX))
insert into #t (record,string)values (1,'ABC')
insert into #t (record,string)values (2,'DEF/123')
insert into #t (record,string)values (3,'GHI/456/XYZ')
declare #mx int
select #mx= len(string)-len(replace(string,'/','')) from #t
select record,t.c.value('.','varchar(max)') as col2 from
(select record,x=cast('<t>'+replace(left(string+'////////////////////',(len(string)+(#mx-(len(string)-len(replace(string,'/','')))))),'/','</t><t>') +'</t>' as xml) from #t)
a cross apply x.nodes('/t') t(c)

Related

Finding sequence of the last numeric value in a varchar variable

I have a column in a table which has incremented values like:
AAA0000001
AAA0000002
... and so on
I want to find if the values stored in this column are in proper sequential order or if any value is missing in between or is deleted.
How can i achieve this?
Assuming the pattern is always: AAA[0-9][0-9][0-9][0-9][0-9][0-9][0-9], you can do this with a Tally Table.
Sample Data:
CREATE TABLE Tbl(val VARCHAR(10))
INSERT INTO Tbl VALUES
('AAA0000001'), ('AAA0000002'), ('AAA0000004'), ('AAA0000011');
val
----------
AAA0000001
AAA0000002
AAA0000004
AAA0000011
SQL Fiddle
;WITH Cte AS(
SELECT *,
num = CAST(SUBSTRING(val, 4, LEN(val) - 3) AS INT)
FROM Tbl
),
E1(N) AS(
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
),
E2(N) AS(SELECT 1 FROM E1 a CROSS JOIN E1 b),
E4(N) AS(SELECT 1 FROM E2 a CROSS JOIN E2 b),
Tally(N) AS(
SELECT TOP(SELECT MAX(num) FROM Cte)
ROW_NUMBER() OVER(ORDER BY (SELECT NULL))
FROM E4
)
SELECT
N,
val = 'AAA' + RIGHT('0000000' + CAST(N AS VARCHAR(7)), 7)
FROM Tally
WHERE NOT EXISTS(
SELECT 1 FROM Cte WHERE num = N
)
RESULT
N val
-------------------- ----------
3 AAA0000003
5 AAA0000005
6 AAA0000006
7 AAA0000007
8 AAA0000008
9 AAA0000009
10 AAA0000010
Explanation:
The first CTE, named as Cte, extracts the numeric part of the strings and CASTs them to INT.
The succeeding CTEs, from E1 to Tally(N) generates a table with sequential values from 1 up to the MAX(num) - the INT return from the first CTE.
The final SELECT just checks for the non-existing num from the first CTE.
'AAA' + RIGHT('0000000' + CAST(N AS VARCHAR(7)), 7) transforms N so that it follows the pattern.
This is a Gaps problem. You can look into this article by Dwain Camps for more solutions on Gaps and Islands.
You can use ROW_NUMBER like this.
Sample Data
DECLARE #tab1 TABLE(id VARCHAR(20));
insert into #tab1 VALUES('AAA0000001'),('AAA0000002'),('AAA0000003'),('AAA0000004'),('AAA0000006'),('AAA0000007'),('AAA0000010');
Query
;WITH CTE as
(
SELECT convert(int,STUFF(id,1,3,'')) id,convert(int,STUFF(id,1,3,'')) - ROW_NUMBER()OVER(ORDER BY convert(int,STUFF(id,1,3,''))) rn
FROM #tab1
),CTE2 as
(
SELECT ROW_NUMBER()OVER(ORDER BY rn) as rn, MIN(id) series_start,MAX(id) series_end
FROM CTE
GROUP BY rn
)
SELECT C2.series_end,C1.series_start
FROM CTE2 C1
INNER JOIN CTE2 C2 ON C1.rn = C2.rn + 1;
SQL Fiddle
Explanation
Output of CTE is the difference of gaps between id values.
Output of CTE2 is the start and end of continuous series of numbers
Final Output gives the start and end of gaps within the series
Output
series_end series_start
4 6
7 10
If the schema is fixed then no need for complex queries. This works:
DECLARE #t TABLE ( v VARCHAR(100) );
INSERT INTO #t
VALUES ( 'AAA0000001' ),
( 'AAA0000002' ),
( 'AAA0000007' ),
( 'AAA0000008' ),
( 'AAA0000010' ),
( 'AAA0000011' ),
( 'AAA0000012' );
SELECT * FROM #t t1
CROSS APPLY(SELECT TOP 1 v FROM #t t2 WHERE t2.v > t1.v ORDER BY v) ca
WHERE RIGHT(t1.v, 7) <> RIGHT(ca.v, 7) - 1
Output:
v v
AAA0000002 AAA0000007
AAA0000008 AAA0000010
In sqlserver 2012, you can use LAG and LEAD
DECLARE #t table(col1 varchar(15))
INSERT #t values('AAA0000001'),('AAA0000002'),('AAA0000004')
SELECT
case when
stuff(lag(col1) over (order by col1), 1,3,'') + 1
= stuff(col1, 1,3,'') then 'Yes' else 'No' end previous_exists,
case when
stuff(lead(col1) over (order by col1), 1,3,'') - 1
= stuff(col1, 1,3,'') then 'Yes' else 'No' end next_exists,
col1
FROM #t
Result:
previous_exists next_exists col1
No Yes AAA0000001
Yes No AAA0000002
No No AAA0000004

Recursively calculate path for each item of the self referencial hierarchial table

I have a table like this (SQL Server 2008):
[ATTACHMENTS].[ID]
[ATTACHMENTS].[Name]
[ATTACHMENTS].[DocumentID],
[ATTACHMENTS].[ParentAttachmentID]
If [ParentAttachmentID] is NULL or 0 - then the record is top. Otherwise - the record is a child. Every child can be (and can't be) the parent of some other child elementS.
I need to calculate "path" for every record of the table. Path is:
ParentAttachment.Path + ' > ' + Attachment.Path (ParentAttachment.Path is recursive)
I try something like this:
WITH attachments AS (
SELECT *,
[ATTACHMENTS].[Name] AS Path
FROM [ATTACHMENTS]
WHERE [ATTACHMENTS].[ParentAttachmentID] IS NULL
OR [ATTACHMENTS].[ParentAttachmentID] = 0
UNION ALL
SELECT a.*,
c.Path + ' > ' + a.[Name]
FROM [ATTACHMENTS] a
INNER JOIN attachments c
ON a.[ParentAttachmentID] = c.[ID]
)
But it doesn't work properly (Path is invalid for some elements due to duplicates as I think). Where I made a mistake? Please, help me to fix this problem.
UPD 2: CSV with data from table [ATTACHMENTS] - http://pastebin.com/WMd6HJ7j
CSV with result of recursive query: http://pastebin.com/7pqs0dx1
Thanx!
What is the error you get?
I think that you are not specifying your datatypes explicitly enough. The size of Path is the size of the anchor start point: ATTACHMENTS.Name. Which probably would be a varchar(100) or so.
Then when you keep recursively growing your path, it might run out of space. The CTE itself looks fine. I had a go at it using explicit sizes, then it work fine.
CREATE TABLE #Attachments
(
Id INT PRIMARY KEY
NOT NULL ,
ParentId INT NULL ,
Name VARCHAR(50) NOT NULL
);
-- some sample data:
-------------------------------------
-- 100 top level attachments (lvl0)
INSERT INTO #Attachments
( Id ,
ParentId ,
Name
)
SELECT A.Rnum ,
NULL ,
'A' + CAST(rnum AS VARCHAR(10))
FROM ( SELECT TOP 100
ROW_NUMBER() OVER ( ORDER BY ( SELECT
1
) ) AS Rnum
FROM sys.messages
) A;
-- attachments linked to lvl0
INSERT INTO #Attachments
( Id ,
ParentId ,
Name
)
SELECT 1000 + A.Rnum ,
A.Rnum ,
'A' + CAST(( rnum + 1000 ) AS VARCHAR(10))
FROM ( SELECT TOP 1000
ROW_NUMBER() OVER ( ORDER BY ( SELECT
1
) ) AS Rnum
FROM sys.messages
) A;
-- attachments linked to lvl1
INSERT INTO #Attachments
( Id ,
ParentId ,
Name
)
SELECT 10000 + A.Rnum ,
1000 + A.Rnum ,
'A' + CAST(( rnum + 10000 ) AS VARCHAR(10))
FROM ( SELECT TOP 1000
ROW_NUMBER() OVER ( ORDER BY ( SELECT
1
) ) AS Rnum
FROM sys.messages
) A;
-- Query:
-------------------------------------
WITH Att ( Id, Name, ParentId, Lvl, NamePath, IdPath )
AS ( SELECT P.Id ,
P.Name ,
P.ParentId ,
CAST(0 AS INT) AS Lvl ,
CAST(P.[Name] AS VARCHAR(1000)) AS NamePath ,
CAST(P.[Id] AS VARCHAR(1000)) AS IdPath
FROM #ATTACHMENTS P
WHERE P.[ParentId] IS NULL
OR P.[ParentId] = 0
UNION ALL
SELECT A.Id ,
A.Name ,
A.ParentId ,
P.Lvl + 1 ,
CAST(P.NamePath + ' > ' + A.Name AS VARCHAR(1000)) ,
CAST(P.IdPath + '\' + CAST(A.Id AS VARCHAR(10)) AS VARCHAR(1000))
FROM #ATTACHMENTS A
INNER JOIN Att P ON A.ParentId = P.Id
)
SELECT *
FROM Att;
DROP TABLE #Attachments;
You have to set different names for table and the name after WITH, and use the second name for second part of the join. Consider attachments1 in below code:
WITH attachments1 AS (
-- anchor
SELECT *,
[ATTACHMENTS].[Name] AS [Path]
FROM ATTACHMENTS
WHERE [ATTACHMENTS].[ParentAttachmentID] IS NULL
OR [ATTACHMENTS].ParentAttachmentID = 0
UNION ALL
--recursive member
SELECT a.*,
cast((c.[Path] + N' > ' + a.[Name]) as nvarchar(500)) as [Path]
FROM ATTACHMENTS a
INNER JOIN attachments1 c
ON a.ParentAttachmentID = c.[ID]
)
SELECT * FROM attachments1
You can get the Path from the recursive member.
Your statement seems correct to me:
DECLARE #t TABLE
(
Code NVARCHAR(MAX) ,
ParentCode NVARCHAR(MAX)
)
INSERT INTO #t
VALUES ( '1', NULL ),
( '2', NULL ),
( '1.1', '1' ),
( '1.1.1', '1.1' ),
( '1.1.2', '1.1' ),
( '1.2', '1' ),
( '1.2.1', '1.2' ),
( '2.1', '2' ),
( '2.2', '2' );
WITH cte
AS ( SELECT Code ,
ParentCode ,
ISNULL(Code, '') AS Path
FROM #t
WHERE ParentCode IS NULL
UNION ALL
SELECT t.Code ,
t.ParentCode ,
Path + ISNULL(' > ' + t.Code, '') AS Path
FROM #t t
JOIN cte c ON c.Code = t.ParentCode
)
SELECT * FROM cte
Output:
Code ParentCode Path
1 NULL 1
2 NULL 2
2.1 2 2 > 2.1
2.2 2 2 > 2.2
1.1 1 1 > 1.1
1.2 1 1 > 1.2
1.2.1 1.2 1 > 1.2 > 1.2.1
1.1.1 1.1 1 > 1.1 > 1.1.1
1.1.2 1.1 1 > 1.1 > 1.1.2

SQL Server Median Function

CREATE FUNCTION [dbo].[f_Get_Average_Order_Size_Median]
(
#ITEM char(15)
)
RETURNS decimal(21,6)
AS
BEGIN
SELECT #Median = AVG(1.0 * QTYSHP)
FROM
(
SELECT o.QTYSHP, rn = ROW_NUMBER() OVER (ORDER BY o.QTYSHP), c.c
FROM dbo.tbl AS o
WHERE RQDATE >=DATEADD (mm,-6, GETDATE())
AND PRICE != '0'
AND SALESMN != 'WB'
AND item = #ITEM )
+
SELECT o.QTYSHP, rn = ROW_NUMBER() OVER (ORDER BY o.QTYSHP), c.c
FROM tbl
WHERE RQDATE >=DATEADD (mm,-6, GETDATE())
AND PRICE != '0'
AND SALESMN != 'WB'
AND item = #ITEM
CROSS JOIN (SELECT c = COUNT(*)
FROM dbo.tblS) AS c
WHERE RQDATE >=DATEADD (mm,-6, GETDATE())
AND PRICE != '0'
AND SALESMN != 'WB'
AND item = #ITEM
+
(SELECT c = COUNT(*)
FROM dbo.tblS) AS c
WHERE RQDATE >=DATEADD (mm,-6, GETDATE())
AND PRICE != '0'
AND SALESMN != 'WB'
AND item = #ITEM
) AS x
WHERE rn IN ((c + 1)/2, (c + 2)/2);
#Return = #Median
BEGIN
END
RETURN #Return
END TRANSACTION...
Is this the correct median function? Please correct me ..I'm learning
The median is the value that accumulates 50% of the values (the 50% percentile). So I think the simplest way to do it is:
Count the number of records (let's say this count is 'n')
Select the top n / 2 records (if n is even, round it to the next integer value), sorted by the column that holds the value for which you want to calculate the median. Read the biggest (last) value of this column.
I'm not quite familiar with SQL server, but in MySQL I would do it like this:
set #n = (select count(*) from yourTable);
set #med = ceil(#n / 2);
select yourColumn
from (
select yourColumn
from yourTable
order by yourColumn
limit #med
) as a
order by yourColumn desc
limit 1;
For SQL Server 2005+ you could try this solution:
DECLARE #MyTable TABLE
(
ID INT PRIMARY KEY,
Value NUMERIC(9,2)
);
INSERT #MyTable (ID, Value) VALUES (1, 10);
INSERT #MyTable (ID, Value) VALUES (2, 20);
INSERT #MyTable (ID, Value) VALUES (3, 30);
INSERT #MyTable (ID, Value) VALUES (4, 40);
-- Test #1: 4 rows => AVG(20,30)
SELECT AVG(y.Value) AS Median#1
FROM
(
SELECT *,
ROW_NUMBER() OVER(ORDER BY x.ID ASC) AS RowNumASC,
ROW_NUMBER() OVER(ORDER BY x.ID DESC) AS RowNumDESC
FROM #MyTable x
) y
WHERE y.RowNumASC = y.RowNumDESC
OR y.RowNumASC + 1 = y.RowNumDESC
OR y.RowNumASC - 1 = y.RowNumDESC;
-- End of Test #1
-- Test #2: 5 rows => AVG(30)
INSERT #MyTable (ID, Value) VALUES (5, 50);
SELECT AVG(y.Value) AS Median#2
FROM
(
SELECT *,
ROW_NUMBER() OVER(ORDER BY x.ID ASC) AS RowNumASC,
ROW_NUMBER() OVER(ORDER BY x.ID DESC) AS RowNumDESC
FROM #MyTable x
) y
WHERE y.RowNumASC = y.RowNumDESC
OR y.RowNumASC + 1 = y.RowNumDESC
OR y.RowNumASC - 1 = y.RowNumDESC;
-- End of Test #2
Results:
Median#1
---------
25.000000
Median#2
---------
30.000000

SQL Server 2008 : TSQL, select same data for different times based on column value

I am using mssql 2008 R2,
i have below structure
create table #temp (
product int,
[order] int,
ord_qnty int
)
insert #temp
select 10 ,3,4
now, if ord_qnty is 4 , i want to select same product,order four times but in all four rows thevalue of ord_qnty should be 1 , i.e.
out put should be
Product order ord_qnty
10 3 1
10 3 1
10 3 1
10 3 1
If you have a numbers table, you can use that. If not, you can generate one:
;with Numbers(n) as (
select ROW_NUMBER() OVER (ORDER BY object_id) from sys.objects
)
select product,[order],1 as ord_qnty
from #temp t inner join Numbers num
on t.ord_qnty >= num.n
(In my nearly empty scratch database, the ROW_NUMBER() generates 77 rows. If that's not going to be enough, you can introduce cross-joins or use other tricks to generate more numbers, or you can create and populate a permanent numbers table)
Try this one -
Query:
DECLARE #temp TABLE
(
product INT
, [order] INT
, ord_qnty INT
)
INSERT #temp(product, [order], ord_qnty)
SELECT 10, 3, 4
SELECT
t.product
, t.[order]
, ord_qnty = 1
FROM #temp t
JOIN [master].dbo.spt_values sv ON t.ord_qnty > sv.number
WHERE sv.[type] = 'p'
SELECT
t.product
, t.[order]
, ord_qnty = 1
FROM #temp t
JOIN (
SELECT number = ROW_NUMBER() OVER (ORDER BY (SELECT 1))
FROM sys.system_parameters p
) sv ON t.ord_qnty >= sv.number
Output:
product order ord_qnty
----------- ----------- -----------
10 3 1
10 3 1
10 3 1
10 3 1
Query Cost:
For any "millions value":
SET NOCOUNT ON;
DECLARE #numbers TABLE (number INT)
DECLARE #temp TABLE
(
product INT
, [order] INT
, ord_qnty INT
)
INSERT #temp(product, [order], ord_qnty)
SELECT 10, 3, 4
DECLARE
#i BIGINT = 1
, #max BIGINT = (
SELECT MAX(ord_qnty)
FROM #temp
)
WHILE (#i <= #max) BEGIN
INSERT INTO #numbers (number)
VALUES (#i), (#i+1), (#i+2), (#i+3), (#i+4), (#i+5), (#i+6), (#i+7), (#i+8), (#i+9)
SELECT #i += 10
END
SELECT
t.product
, t.[order]
, ord_qnty = 1
FROM #temp t
CROSS JOIN (
SELECT *
FROM #numbers
WHERE number < #max + 1
) t2

SQL Server get next previous rows from a table based on a date

I have the following SQL Server query and I am trying to get the next and previous row from a given Id. All works great unless there are two/more dates that are the same, then my logic breaks down.
If you set #currentId = 4 then I get back id 7 (it should be id 6) for the prev
If you set #currentId = 6 then I get back id 2 (it should be id 4) for the next
declare #t table (i int, d datetime)
insert into #t (i, d)
select 1, '17-Nov-2009 07:22:13' union
select 2, '18-Nov-2009 07:22:14' union
select 3, '17-Nov-2009 07:23:15' union
select 4, '20-Nov-2009 07:22:18' union
select 5, '17-Nov-2009 07:22:17' union
select 6, '20-Nov-2009 07:22:18' union
select 7, '21-Nov-2009 07:22:19'
--order that I want
select * from #t order by d desc, i
declare #currentId int; set #currentId = 4
--Get Prev
select TOP 1 * from #t where d > (select TOP 1 d from #t where i = #currentId order by d, i desc) order by d, i desc
--Get Next
select TOP 1 * from #t where d < (select TOP 1 d from #t where i = #currentId order by d desc) order by d desc
Can anyone help me work out how to guarantee get the next/prev row based on a given id, Note that it is important that I keep the this order, Date Desc, id ASC
Many thanks
EDIT: It should be noted that this is going to be used for SQL Server 2005.
You were nearly there ... try this instead. Effectively changed the date comparison to be "...-or-equals-to" and telling it not to match the current ID so it doesn't return the same row...
declare #currID int
set #currID = 4
select top 1 *
from (
select *
from #t
where d = (select d from #t where i = #currID)
and i > #currID
union ALL
select *
from #t
where d < (select d from #t where i = #currID)
) as t
order by d desc, i
select top 1 *
from (
select *
from #t
where d = (select d from #t where i = #currID)
and i < #currID
union ALL
select *
from #t
where d > (select d from #t where i = #currID)
) as t
order by d, i desc
Toy can try something like this
declare #t table (i int, d datetime)
insert into #t (i, d)
select 1, '17-Nov-2009 07:22:13' union
select 2, '18-Nov-2009 07:22:14' union
select 3, '17-Nov-2009 07:23:15' union
select 4, '20-Nov-2009 07:22:18' union
select 5, '17-Nov-2009 07:22:17' union
select 6, '20-Nov-2009 07:22:18' union
select 7, '21-Nov-2009 07:22:19'
--order that I want
select * from #t order by d desc, i
declare #currentId int;
set #currentId = 4
SELECT TOP 1 t.*
FROM #t t INNER JOIN
(
SELECT d CurrentDateVal
FROM #t
WHERE i = #currentId
) currentDate ON t.d <= currentDate.CurrentDateVal AND t.i != #currentId
ORDER BY t.d DESC
SELECT t.*
FROM #t t INNER JOIN
(
SELECT d CurrentDateVal
FROM #t
WHERE i = #currentId
) currentDate ON t.d >= currentDate.CurrentDateVal AND t.i != #currentId
ORDER BY t.d
You must be carefull, it can seem that 6 should be both prev and next.

Resources