Related
I am trying to get the statement on fetching the previous and next rows of a selected row.
Declare #OderDetail table
(
Id int primary key,
OrderId int,
ItemId int,
OrderDate DateTime2,
Lookup varchar(15)
)
INSERT INTO #OderDetail
VALUES
(1, 10, 1, '2018-06-11', 'A'),
(2, 10, 2, '2018-06-11', 'BE'), --this
(3, 2, 1, '2018-06-04', 'DR'),
(4, 2, 2, '2018-06-04', 'D'), --this
(5, 3, 2, '2018-06-14', 'DD'), --this
(6, 4, 2, '2018-06-14', 'R');
DECLARE
#ItemId int = 2,
#orderid int = 10
Required output:
Input for the procedure is order id =10 and item id =2 and i need to check item-2 is in the any other order i.e only previous and next item of matched record/order as per order date
Is this what your after? (Updated to reflect edit [OrderDate] to question)
Declare #OderDetail table
(
Id int primary key,
OrderId int,
ItemId int,
OrderDate DateTime2,
Lookup varchar(15)
)
INSERT INTO #OderDetail
VALUES
(1, 10, 1, '2018-06-11', 'A'),
(2, 10, 2, '2018-06-11', 'BE'), --this
(3, 2, 1, '2018-06-04', 'DR'),
(4, 2, 2, '2018-06-04', 'D'), --this
(5, 3, 2, '2018-06-14', 'DD'), --this
(6, 4, 2, '2018-06-14', 'R');
declare #ItemId int=2 , #orderid int = 10;
Query
With cte As
(
Select ROW_NUMBER() OVER(ORDER BY OrderDate) AS RecN,
*
From #OderDetail Where ItemId=#ItemId
)
Select Id, OrderId, ItemId, [Lookup] From cte Where
RecN Between ((Select Top 1 RecN From cte Where OrderId = #orderid) -1) And
((Select Top 1 RecN From cte Where OrderId = #orderid) +1)
Order by id
Result:
Id OrderId ItemId Lookup
2 10 2 BE
4 2 2 D
5 3 2 DD
Another possible approach is to use LAG() and LEAD() functions, that return data from a previous and subsequent row form the same resul tset.
-- Table
DECLARE #OrderDetail TABLE (
Id int primary key,
OrderId int,
ItemId int,
OrderDate DateTime2,
Lookup varchar(15)
)
INSERT INTO #OrderDetail
VALUES
(1, 10, 1, '2018-06-11', 'A'),
(2, 10, 2, '2018-06-11', 'BE'), --this
(3, 2, 1, '2018-06-04', 'DR'),
(4, 2, 2, '2018-06-04', 'D'), --this
(5, 3, 2, '2018-06-14', 'DD'), --this
(6, 4, 2, '2018-06-14', 'R');
-- Item and order
DECLARE
#ItemId int = 2,
#orderid int = 10
-- Statement
-- Get previois and next ID for every order, grouped by ItemId, ordered by OrderDate
;WITH cte AS (
SELECT
Id,
LAG(Id, 1) OVER (PARTITION BY ItemId ORDER BY OrderDate) previousId,
LEAD(Id, 1) OVER (PARTITION BY ItemId ORDER BY OrderDate) nextId,
ItemId,
OrderId,
Lookup
FROM #OrderDetail
)
-- Select current, previous and next order
SELECT od.*
FROM cte
CROSS APPLY (SELECT * FROM #OrderDetail WHERE Id = cte.Id) od
WHERE (cte.OrderId = #orderId) AND (cte.ItemId = #ItemId)
UNION ALL
SELECT od.*
FROM cte
CROSS APPLY (SELECT * FROM #OrderDetail WHERE Id = cte.previousId) od
WHERE (cte.OrderId = #orderId) AND (cte.ItemId = #ItemId)
UNION ALL
SELECT od.*
FROM cte
CROSS APPLY (SELECT * FROM #OrderDetail WHERE Id = cte.nextId) od
WHERE (cte.OrderId = #orderId) AND (cte.ItemId = #ItemId)
Output:
Id OrderId ItemId OrderDate Lookup
2 10 2 11/06/2018 00:00:00 BE
4 2 2 04/06/2018 00:00:00 D
5 3 2 14/06/2018 00:00:00 DD
Update to given this data set: I see where you are going with this. Note that in SOME cases there IS no row before the given one - so it only returns 2 not 3. Here I updated the CTE version. Un-comment the OTHER row to see 3 not 2 as there is then one before the selected row with that Itemid.
Added a variable to demonstrate how this is better allowing you to get 1 before and after or 2 before/after if you change that number (i.e. pass a parameter) - and if less rows, or none are before or after it gets as many as it can within that constraint.
Data setup for all versions:
Declare #OderDetail table
(
Id int primary key,
OrderId int,
ItemId int,
OrderDate DateTime2,
Lookup varchar(15)
)
INSERT INTO #OderDetail
VALUES
(1, 10, 1, '2018-06-11', 'A'),
(2, 10, 2, '2018-06-11', 'BE'), --this
(3, 2, 1, '2018-06-04', 'DR'),
(4, 2, 2, '2018-06-04', 'D'), --this
(5, 3, 2, '2018-06-14', 'DD'), --this
(9, 4, 2, '2018-06-14', 'DD'),
(6, 4, 2, '2018-06-14', 'R'),
--(10, 10, 2, '2018-06-02', 'BE'), -- un-comment to see one before
(23, 4, 2, '2018-06-14', 'R');
DECLARE
#ItemId int = 2,
#orderid int = 2;
CTE updated version:
DECLARE #rowsBeforeAndAfter INT = 1;
;WITH cte AS (
SELECT
Id,
OrderId,
ItemId,
OrderDate,
[Lookup],
ROW_NUMBER() OVER (ORDER BY OrderDate,Id) AS RowNumber
FROM #OderDetail
WHERE
ItemId = #itemId -- all matches of this
),
myrow AS (
SELECT TOP 1
Id,
OrderId,
ItemId,
OrderDate,
[Lookup],
RowNumber
FROM cte
WHERE
ItemId = #itemId
AND OrderId = #orderid
)
SELECT
cte.Id,
cte.OrderId,
cte.ItemId,
cte.OrderDate,
cte.[Lookup],
cte.RowNumber
FROM ctE
INNER JOIN myrow
ON ABS(cte.RowNumber - myrow.RowNumber) <= #rowsBeforeAndAfter
ORDER BY OrderDate, OrderId;
You probably want the CTE method (See an original at the end of this) however:
Just to point out, this gets the proper results but is probably not what you are striving for since it is dependent on the row order and the item id not the actual row with those two values:
SELECT TOP 3
a.Id,
a.OrderId,
a.ItemId,
a.Lookup
FROM #OderDetail AS a
WHERE
a.ItemId = #ItemId
To fix that, you can use an ORDER BY and TOP 1 with a UNION, kind of ugly. (UPDATED with date sort and != on the id.)
SELECT
u.Id,
u.OrderId,
u.OrderDate,
u.ItemId,
u.Lookup
FROM (
SELECT
a.Id,
a.OrderId,
a.OrderDate,
a.ItemId,
a.Lookup
FROM #OderDetail AS a
WHERE
a.ItemId = #ItemId
AND a.OrderId = #orderid
UNION
SELECT top 1
b.Id,
b.OrderId,
b.OrderDate,
b.ItemId,
b.Lookup
FROM #OderDetail AS b
WHERE
b.ItemId = #ItemId
AND b.OrderId != #orderid
ORDER BY b.OrderDate desc, b.OrderId
UNION
SELECT top 1
b.Id,
b.OrderId,
b.OrderDate,
b.ItemId,
b.Lookup
FROM #OderDetail AS b
WHERE
b.ItemId = #ItemId
AND b.OrderId != #orderid
ORDER BY b.OrderDate asc, b.OrderId
) AS u
ORDER BY u.OrderDate asc, u.OrderId
I think its simple, you can check with min(Id) and Max(id) with left outer join or outer apply
like
Declare #ItemID int = 2
Select * From #OderDetail A
Outer Apply (
Select MIN(A2.Id) minID, MAX(A2.Id) maxID From #OderDetail A2
Where A2.ItemId =#ItemID
) I05
Outer Apply(
Select * From #OderDetail Where Id=minID-1
Union All
Select * From #OderDetail Where Id=maxID+1
) I052
Where A.ItemId =#ItemID Order By A.Id
Let me know if this helps you or you face any problem with it...
Regards,
I have a simple categories table as with the following columns:
Id
Name
ParentId
So, an infinite amount of Categories can be the child of a category. Take for example the following hierarchy:
I want, in a simple query that returns the category "Business Laptops" to also return a column with all it's parents, comma separator or something:
Or take the following example:
Recursive cte to the rescue....
Create and populate sample table (Please save us this step in your future questions):
DECLARE #T as table
(
id int,
name varchar(100),
parent_id int
)
INSERT INTO #T VALUES
(1, 'A', NULL),
(2, 'A.1', 1),
(3, 'A.2', 1),
(4, 'A.1.1', 2),
(5, 'B', NULL),
(6, 'B.1', 5),
(7, 'B.1.1', 6),
(8, 'B.2', 5),
(9, 'A.1.1.1', 4),
(10, 'A.1.1.2', 4)
The cte:
;WITH CTE AS
(
SELECT id, name, name as path, parent_id
FROM #T
WHERE parent_id IS NULL
UNION ALL
SELECT t.id, t.name, cast(cte.path +','+ t.name as varchar(100)), t.parent_id
FROM #T t
INNER JOIN CTE ON t.parent_id = CTE.id
)
The query:
SELECT id, name, path
FROM CTE
Results:
id name path
1 A A
5 B B
6 B.1 B,B.1
8 B.2 B,B.2
7 B.1.1 B,B.1,B.1.1
2 A.1 A,A.1
3 A.2 A,A.2
4 A.1.1 A,A.1,A.1.1
9 A.1.1.1 A,A.1,A.1.1,A.1.1.1
10 A.1.1.2 A,A.1,A.1.1,A.1.1.2
See online demo on rextester
I have a table with id and parent_id. I have seen many CTE on how to generate the tree. However I can't seem to filter the tree in order to retrieve a full tree given any node from it.
Given the values
1, NULL
2, 1
3, 2
4, 2
5, NULL
6, 5
7, NULL
If I filter by id any of the values 1,2,3 or 4 I should get the tree
1, NULL
2, 1
3, 2
4, 2
for 5 or 6
5, NULL
6, 5
for 7
7, NULL
Can this be achieved using CTE?
Use one CTE to locate the root of the tree, and then use a second CTE to explode the tree structure:
declare #T table (ID int not null, Parent int null)
insert into #T(ID,Parent) values
(1, NULL),
(2, 1 ),
(3, 2 ),
(4, 2 ),
(5, NULL),
(6, 5 ),
(7, NULL)
declare #Node int
set #node = 3
;With Root as (
select t.ID,t.Parent from #T t where t.ID = #Node or t.Parent = #Node
union all
select t.ID,t.Parent
from
Root r
inner join
#t t
on
t.ID = r.Parent
), Tree as (
select ID,Parent from Root where Parent is null
union all
select t.ID,t.Parent
from #T t
inner join
Tree tr
on tr.ID = t.Parent
)
select * from Tree
Result:
ID Parent
----------- -----------
1 NULL
2 1
3 2
4 2
Hopefully you can see how the two CTEs are working in opposite directions.
You could use recursive CTE like this.
CTE returns all tree with RootId, and you could get all tree nodes by its RootId
DECLARE #SampleData AS TABLE
(
NodeId int,
ParentNodeId int
)
INSERT INTO #SampleData
(
NodeId,
ParentNodeId
)
VALUES
( 1, NULL),
( 2, 1),
( 3, 2),
( 4, 2),
( 5, NULL),
( 6, 5),
( 7, NULL)
DECLARE #NodeId int = 4
-- temp returns all child nodes of rootid (1,5,7)
;WITH temp AS
(
SELECT sd.NodeId, sd.ParentNodeId, sd.NodeId AS RootId
FROM #SampleData sd
WHERE sd.ParentNodeId IS NULL
UNION ALL
SELECT sd.NodeId, sd.ParentNodeId, t.RootId
FROM temp t
INNER JOIN #SampleData sd ON t.NodeId = sd.ParentNodeId
)
SELECT t2.NodeId, t2.ParentNodeId
FROM temp t
INNER JOIN temp t2 ON t2.RootId = t.RootId
WHERE t.NodeId = #NodeId
OPTION (MAXRECURSION 0)
Demo link: http://rextester.com/PPPMXX4941
Is there a way to write a row based condition in Left Join.
If some row not exists based on column condition, then it should take the next first row.
I have the structure below,
create table Report
(
id int,
name varchar(10)
)
create table ReportData
(
report_id int references report(id),
flag bit,
path varchar(50)
)
insert into Report values (1, 'a');
insert into Report values (2, 'b');
insert into Report values (3, 'c');
insert into ReportData values (1, 0, 'xx');
insert into ReportData values (2, 0, 'yy');
insert into ReportData values (2, 1, 'yy');
insert into ReportData values (3, 1, 'zz');
insert into ReportData values (3, 1, 'mm');
I need some output like
1 a 0 xx
2 b 0 yy
3 c 1 zz
You can use ROW_NUMBER for this:
;WITH ReportDate_Rn AS (
SELECT report_id, flag, path,
ROW_NUMBER() OVER (PARTITION BY report_id ORDER BY path) AS rn
FROM ReportDate
)
SELECT t1.id, t1.name, t2.flag, t2.path
FROM Report AS t1
JOIN ReportDate_Rn AS t2 ON t1.id = t2.report_id AND t2.rn = 1
The above query regards as first record of each report_id slice, the one having the alphabetically smallest path. You may amend the ORDER BY clause of the ROW_NUMBER() window function as you wish.
SELECT id,name,flag,path
FROM
(
SELECT Report.id,Report.name,ReportData.flag,ReportData.path,
row_number() over(partition by ReportData.report_id order by flag) as rownum
FROM Report
JOIN ReportData on Report.id = ReportData.report_id
) tmp
WHERE tmp.rownum=1
A simpler alternative to the left join, using rowid and rownum
SELECT id, name, flag, path
FROM report, reportdata
WHERE reportdata.rowid = (SELECT rowid
FROM reportdata
WHERE id = report_id
AND rownum = 1);
Without using row_numner() you can achieve this.
Have a look at this SQL Fiddle
select r.id, r.name, d.flag, d.path from report r
inner join reportdata d
on r.id = d.report_id group by d.report_id
PS: I wasn't believing the result - I was just building the query - haven't used d.report_id in the select clause and it worked. Will be updating this answer once I get the reason why this query worked :)
Use Partition BY:
declare #Report AS table
(
id int,
name varchar(10)
)
declare #ReportData AS table
(
report_id int ,
flag bit,
path varchar(50)
)
insert into #Report values (1, 'a');
insert into #Report values (2, 'b');
insert into #Report values (3, 'c');
insert into #ReportData values (1, 0, 'xx');
insert into #ReportData values (2, 0, 'yy');
insert into #ReportData values (2, 1, 'yy');
insert into #ReportData values (3, 1, 'zz');
insert into #ReportData values (3, 1, 'mm');
;WITH T AS
(
Select
R.id,
r.name,
RD.flag,
RD.path,
ROW_NUMBER () OVER(PARTITION BY R.id ORDER BY R.id) AS PartNo
FROM #Report R
LEFT JOIN #ReportData RD ON R.id=RD.report_id
)
SELECT
T.id,
T.name,
T.flag,
T.path
FROM T WHERE T.PartNo=1
I am trying to map sub categories to a 'majority' parent category. Looking at this example:
IF OBJECT_ID(N'tempdb..#Temp') IS NOT NULL DROP TABLE #Temp
CREATE TABLE #Temp
(
ID INT,
SubCategory NVARCHAR(100),
Category NVARCHAR(100)
)
INSERT INTO #Temp
SELECT 1, N'SC1', N'C1'
UNION
SELECT 2, N'SC1', N'C1'
UNION
SELECT 3, N'SC1', N'C1'
UNION
SELECT 4, N'SC1', N'C2'
UNION
SELECT 5, N'SC1', N'C2'
sub category SC1 can belong to 2 'parent' categories: C1, C2 but C1 is assigned more to SC1 than C2 so it represents the majority. I am currently using a correlated sub query to map sun categories to their majority parent categroy like this:
IF OBJECT_ID(N'tempdb..#Temp') IS NOT NULL DROP TABLE #Temp
CREATE TABLE #Temp
(
ID INT,
SubCategory NVARCHAR(100),
Category NVARCHAR(100)
)
INSERT INTO #Temp
SELECT 1, N'SC1', N'C1'
UNION
SELECT 2, N'SC1', N'C1'
UNION
SELECT 3, N'SC1', N'C1'
UNION
SELECT 4, N'SC1', N'C2'
UNION
SELECT 5, N'SC1', N'C2'
SELECT
Id,
OuterQuery.SubCategory,
Category = (SELECT TOP 1 Category FROM #Temp AS InnerQuery
WHERE InnerQuery.SubCategory = OuterQuery.SubCategory
GROUP BY InnerQuery.SubCategory, InnerQuery.Category
ORDER BY COUNT(DISTINCT Id) DESC)
FROM #Temp AS OuterQuery
Is this a correct way of achieving want I want to?
I would do it like this:
DECLARE #t TABLE (
ID INT,
SubCategory NVARCHAR(100),
Category NVARCHAR(100)
)
INSERT INTO #t
VALUES
( 1, N'SC1', N'C1'),
(2, N'SC1', N'C1'),
(3, N'SC1', N'C1'),
(4, N'SC1', N'C2'),
(5, N'SC1', N'C2'),
(6, N'SC2', N'C2'),
(7, N'SC2', N'C2'),
(8, N'SC2', N'C3');
WITH a AS (
SELECT
subcategory,
category,
COUNT(id) cnt
FROM #t
GROUP BY
SubCategory,
category
), b as (
SELECT
subcategory,
category,
ROW_NUMBER() OVER
(PARTITION BY subcategory ORDER BY cnt DESC) row
FROM a
)
SELECT
subcategory,
category AS Majority
FROM b
WHERE row = 1
This produces the result:
subcategory Majority
SC1 C1
SC2 C2
There's this way with just subqueries;
Test Data (I've increased the range for this)
IF OBJECT_ID(N'tempdb..#Temp') IS NOT NULL DROP TABLE #Temp
CREATE TABLE #Temp
(ID INT, SubCategory NVARCHAR(100), Category NVARCHAR(100))
INSERT INTO #Temp (ID, SubCategory, Category)
VALUES
(1, N'SC1', N'C1')
,(2, N'SC1', N'C1')
,(3, N'SC1', N'C1')
,(4, N'SC1', N'C2')
,(5, N'SC1', N'C2')
,(6, N'SC2', N'C3')
,(7, N'SC2', N'C3')
,(8, N'SC2', N'C2')
,(9, N'SC2', N'C2')
,(10, N'SC2', N'C2')
,(11, N'SC2', N'C2')
Now the query;
SELECT DISTINCT
t.SubCategory
,t.Category
FROM #Temp t
JOIN
(
SELECT
SubCategory
,Category
,COUNT(SubCategory) Ct
FROM #Temp
GROUP BY SubCategory, Category
) Cont
ON t.SubCategory = cont.SubCategory
AND t.Category = cont.Category
JOIN
(
SELECT
a.SubCategory
,MAX(a.Ct) MaxCount
FROM
(
SELECT
SubCategory
,Category
,COUNT(SubCategory) Ct
FROM #Temp
GROUP BY SubCategory, Category
) a
GROUP BY a.SubCategory
) maxi ON t.SubCategory = maxi.SubCategory
AND cont.Ct = maxi.MaxCount