SQL MIN Datetime based on first occuranceof a value in another column - sql

This is what I have
ID Name DateTime Value Group
1 Mark 1/1/2010 0 1
2 Mark 1/2/2010 1 1
3 Mark 1/3/2010 0 1
4 Mark 1/4/2010 0 2
40 Mark 1/5/2010 1 2
5 Mark 1/9/2010 1 2
6 Mark 1/6/2010 1 2
7 Kelly 1/1/2010 0 3
8 Kelly 1/2/2010 1 3
9 Kelly 1/3/2010 1 3
10 Nancy 1/4/2010 0 4
11 Nancy 1/5/2010 0 4
12 Nancy 1/6/2010 1 5
13 Nancy 1/7/2010 0 5
What I want is to get the rows per "name" per "group" with minimum datetime after the value becomes 1. From the above example, I would need to get
3 Mark 1/3/2010 0 1
6 Mark 1/6/2010 1 2
9 Kelly 1/3/2010 1 3
13 Nancy 1/7/2010 0 5

Based on the description of your rules, I believe the output will actually be a bit different since 2010-01-05 was the first DateTime where the Value = 1 for Group 2 for Mark.
ID Name DateTime Value Group
3 Mark 2010-01-03 0 1
6 Mark 2010-01-06 1 2
9 Kelly 2010-01-03 1 3
13 Nancy 2010-01-07 0 5
The below code will work as demonstrated in this SQLFiddle.
SELECT sub.ID
, sub.Name
, sub.[DateTime]
, sub.Value
, sub.[Group]
FROM
(SELECT t.ID
, t.Name
, t.[DateTime]
, t.Value
, t.[Group]
, SequentialOrder = ROW_NUMBER() OVER
(PARTITION BY t.Name, t.[Group]
ORDER BY t.[DateTime])
FROM Test t
JOIN
(SELECT Name
, [Group]
, MinimumDateTime = MIN([DateTime])
FROM Test
WHERE Value = 1
GROUP BY Name
, [Group]) mint
ON t.Name = mint.Name
AND t.[Group] = mint.[Group]
WHERE t.[DateTime] > mint.MinimumDateTime) sub
WHERE sub.SequentialOrder = 1
ORDER BY ID;

Below is my query and it goes on assumption that records are received in order of their dates
WITH TBL_1 AS
(
SELECT A.*, ROW_NUMBER() OVER(PARTITION BY NAME, GROUP ORDER BY DATE) AS RN
FROM TABLE
WHERE (NAME, GROUP) IN
(SELECT NAME, GROUP FROM TABLE WHERE VALUE = 1)
),
TBL_2 AS
(
SELECT * FROM TBL_1 WHERE VALUE = 1
),
TBL_3 AS
(
SELECT A.*
FROM TBL_1 AS A
INNER JOIN TBL_2 AS B
ON B.NAME = A.NAME
AND B.GROUP = A.GROUP
AND A.RN > B.RN
)
SELECT *
FROM TBL_3
WHERE (NAME, GROUP, DATE) IN
(SELECT NAME, GROUP, MIN(DATE) FROM TBL_3 GROUP BY NAME, GROUP)

In SQL Server 2012 you can do this:
SELECT * FROM (
SELECT DISTINCT
ID,
Name,
DateTime,
Value,
Gr,
LAG(ID) OVER (PARTITION BY Name, Gr ORDER BY DateTime) F
FROM (
SELECT
ID,
Name,
DateTime,
Value,
Gr,
CASE WHEN LAG(Value) OVER (PARTITION BY Name, Gr ORDER BY DateTime) = 1 THEN 1 ELSE 0 END F
FROM
T
) TT
WHERE F = 1
) TT WHERE F IS NULL
ORDER BY Gr, Name, DateTime
Fiddle: http://www.sqlfiddle.com/#!6/5a0fa2/19

using window functions:
with cte as (
select
*,
row_number() over(partition by [Group], Name order by [DateTime]) as rn,
dense_rank() over(order by [Group], Name) as rnk
from Table1
)
select c1.*
from cte as c1
inner join cte as c2 on c2.rn = c1.rn - 1 and c2.rnk = c1.rnk and c2.Value = 1
where
not exists (select * from cte as c3 where c3.rn <= c1.rn - 2 and c3.rnk = c1.rnk and c3.Value = 1)
or apply:
select t1.*
from Table1 as t1
cross apply (
select top 1 t2.Value, t2.DateTime
from Table1 as t2
where
t2.[Group] = t1.[Group] and t2.Name = t1.Name and
t2.[DateTime] < t1.[DateTime]
order by t2.[Datetime] desc
) as t2
where
t2.Value = 1 and
not exists (
select *
from Table1 as t3
where
t3.[Group] = t1.[Group] and t3.Name = t1.Name and
t3.[DateTime] < t2.[DateTime] and t3.Value = 1
)
sql fiddle demo
update forgot to mention that your output seems to be incorrect - there should id = 6 instead of 5 in second row (see sql fiddle).

Related

sql numbering the partition of Numbers

I have a set of numbers like this
ID
===
1
2
3
1
2
1
1
2
3
4
5
...
I want to select a new row that increase when fetch next 1 like this
ID number
=== ========
1 1
2 1
3 1
1 2
2 2
1 3
1 4
2 4
3 4
4 4
5 4
Any suggestion ?
Assuming that you have a column o which specify the ordering then you can use a self-join like this:
select d1.o, d1.id, count(*)
from data d1
join data d2 on d1.o >= d2.o and d2.id = 1
group by d1.o, d1.id
DBFiddle DEMO
You can solve this with use of cte and window functions, as follows:
DECLARE #t TABLE (ID INT);
INSERT INTO #t VALUES (1),(2),(3),(1),(2),(1),(1),(2),(3),(4),(5);
WITH cte AS(
SELECT ID, ROW_NUMBER() OVER (ORDER BY (SELECT 1)) rn
FROM #t
),
cte1 AS(
SELECT ID, rn, ROW_NUMBER() OVER (ORDER BY rn) rn2
FROM cte
WHERE ID = 1
)
SELECT c.ID, MAX(rn2) OVER (ORDER BY c.rn) rn
FROM cte c
LEFT JOIN cte1 c1 ON c1.rn = c.rn
ORDER BY c.rn

SQL: How do I display all records per unique id, but not the first record ever recorded in SQL

Example:
id Pricemoney time/date
1 100 01/20/2017
1 10 01/21/2017
1 1000 01/21/20147
2 10 01/23/2017
2 100 01/24/2017
3 1000 01/19/2017
3 100 01/22/2017
3 10 01/24/2017
I want to run a SQL query where I can display all the Id and it's pricemoney BUT NOT include the first record (based on time/date) per unique
Just to clarify what I do not want to be displayed
userid Pricemoney issuedate
1 100 01/20/2017 -- not included
1 10 01/21/2017
1 1000 01/21/20147
2 10 01/23/2017 --- not inlcuded
2 100 01/24/2017
3 1000 01/19/2017 -- not included
3 100 01/22/2017
3 10 01/24/2017
Expected result:
id Pricemoney time/date
1 10 01/21/2017
1 1000 01/21/20147
2 100 01/24/2017
3 100 01/22/2017
3 10 01/24/2017
You can use row_number():
select t.*
from (select t.*,
row_number() over (partition by id order by time_date asc) as seqnum
from <tablename> t
) t
where seqnum > 1;
If you want to keep single rows, you can do:
select t.*
from (select t.*,
row_number() over (partition by id order by time_date asc) as seqnum,
count(*) over (partition by id) as cnt
from <tablename> t
) t
where seqnum > 1 and cnt > 1;
You may use EXISTS
select t1.*
from data t1
where exists (
select 1
from data t2
where t1.id = t2.id and t2.time_date < t1.time_date
)
you can try this :
select data1.id,data1.Date,data1.Pricemoney from data1
left join (
select id ,min(Date) date from data1
group by id
) as t
on data1.date= t.date and t.id = data1.id
where t.id is null
group by data1.id,data1.Date,data1.Pricemoney
above query not duplicated records also ignore, if want
not duplicated records then use having count(id) > 1 in left query e,g.
select data1.id,data1.Date,data1.Pricemoney from data1
left join (
select id ,min(Date) date from data1
group by id
having COUNT(id) > 1
) as t
on data1.date= t.date and t.id = data1.id
where t.id is null
group by data1.id,data1.Date,data1.Pricemoney

Getting the minimum of column on the basis of other field

I have a scenario wherein I have
Id|rank| date
1 | 7 |07/08/2015
1 | 7 |09/08/2015
1 | 8 |16/08/2015
1 | 8 |17/08/2015
1 | 7 |19/08/2015
1 | 7 |15/08/2015
2 | 7 |01/08/2015
2 | 7 |02/08/2015
2 | 8 |16/08/2015
2 | 8 |17/08/2015
2 | 7 |26/08/2015
2 | 7 |28/08/2015
My desired solution is
1 | 7 |07/08/2015
1 | 8 |16/08/2015
1 | 7 |15/08/2015
2 | 7 |01/08/2015
2 | 8 |16/08/2015
2 | 7 |26/08/2015
i.e for each block of id and rank I want the minimum of date.
I have tried using while loop as there are thousands of records it is taking 2 hours to load.Is there any other way to do please suggest.
For each row give unique row number using necessary order. (As I get Id is more important than date and date is more important than rank).
Join resulting table to itself using row numbers shifted by one row (d1.RowNum = d2.RowNum+1).
Select only rows that are joined to "other block" rows (d1.Id <> d2.Id or d1.Rank <> d2.rank).
Depending on shifting direction and selected table either maximal or minimal date will be selected.
Don't forget "edge case" - row that due to shifting can't be joined (that's why not inner join and d1.RowNum = 1 condition used).
;WITH dataWithRowNums as (
select Id, Rank, Date,
RowNum = ROW_NUMBER() OVER (ORDER BY Id,date,rank)
from YourTable
)
select d1.Id, d1.Rank, d1.Date
from dataWithRowNums d1
left join dataWithRowNums d2
on d1.RowNum = d2.RowNum+1 and (d1.Id <> d2.Id or d1.Rank <> d2.rank)
where not d2.Id is null or d1.RowNum = 1
This code returns result bit different from yours:
Id Rank Date
1 7 2015-08-07
1 8 2015-08-16
1 7 2015-08-19 <-- you've got here 2015-08-15
2 7 2015-08-01
2 8 2015-08-16
2 7 2015-08-26
As block (Rank 8 Id 1) have started at 16/08 so row 15/08 for rank 7 is related to first block (rank7 Id1).
If you still need your sorting (so 15/08 rank 7 is related to second block (rank7 id1)) then you should provide your own RowSorting data and then ask here about another solution for another task )
Here is the query using row_number()
;WITH cte_rec
as (SELECT Id,Rank,Date
,ROW_NUMBER()OVER (partition by Id,Rank ORDER BY date) as RNO
FROM YourTable)
SELECT Id,Rank,Date
FROM cte_rec
WHERE RNO =1
This is what I have tried and is running as expected
create table #temp
(
iden int identity(1,1),
ID int,
[rank] int,
[date] date,
dr_id int,
rownum_id int,
grouprecord int
)
Insert into #temp(id,rank,date)
select 1 , 7 ,'07/08/2015'
union all select 1 , 7 ,'09/08/2015'
union all select 1 , 8 ,'08/16/2015'
union all select 1 , 8 ,'08/17/2015'
union all select 1 , 7 ,'08/19/2015'
union all select 1 , 7 ,'08/15/2015'
union all select 2 , 7 ,'08/01/2015'
union all select 2 , 7 ,'08/02/2015'
union all select 2 , 8 ,'08/16/2015'
union all select 2 , 8 ,'08/17/2015'
union all select 2 , 7 ,'08/26/2015'
union all select 2 , 7 ,'08/28/2015'
update t1
set dr_id = t2.rn
from #temp t1 inner join
(select iden, dense_rank() over(order by id) as rn from #temp) t2
on t1.iden = t2.iden
update t1
set rownum_id = t2.rn
from #temp t1 inner join
(select iden, row_number() over(partition by dr_id order by id) as rn from #temp) t2
on t1.iden = t2.iden
select *,row_number() over(order by iden)rn into #temp1 from
(
select t2.*
from #temp t1 inner join #temp t2
on (t1.dr_id = t2.dr_id or t2.dr_id = (t1.dr_id +1) ) and ( t1.rank<>t2.rank or t2.dr_id = (t1.dr_id +1) )
and t2.iden = t1.iden + 1
)a
declare #id int,#miniden int,#maxiden int,#maxid int
set #id = 1
select #maxid = max(iden) from #temp
while exists(select 1 from #temp1 where rn = #id)
begin
Select #miniden = iden from #temp1
where rn = #id
Select #maxiden = iden from #temp1
where rn = #id+1
update #temp
set grouprecord = #id +1
where iden between #miniden and #maxiden
IF(#maxiden IS NULL)
BEGIN
Update #temp
set grouprecord = #id +1
where iden between #miniden and #maxid
END
set #id = #id + 1
SET #miniden =NULL
SET #maxiden = NULL
end
UPDATE #TEMP
SET GROUPRECORD = 1
WHERE GROUPRECORD IS NULL
select min(date) as mindate,grouprecord from #temp
group by grouprecord
Thanks everyone the help :)

JOIN table when selecting a max value of columns

I have the below table structure that is successfully getting the max column value of a table for each CommentID. But I want to JOIN the table tbComment with tbCommentBreadcrumb where CommentID is the key.
tbComment
CommentID IsLocked
1 0
2 0
3 1
4 0
5 1
tbCommentBreadcrumb
CommentStatusID CommentID StatusTypeID
105 1 1
106 1 4
107 2 1
108 3 1
109 3 4
110 4 1
112 5 1
112 5 4
Here's what I have working:
SELECT *
FROM
(
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY CommentID ORDER BY CreateDateTime desc) as rn
FROM
tbCommentBreadCrumb
WHERE
CommentStatusTypeID = 4
AND CreateDateTime <= {ts '2014-02-09 09:44:57'}
) t
WHERE
t.rn = 1
ORDER BY
CommentStatusID DESC
This returns over a hundred records and I want to further refine the query by only getting the records where the above query is true but also that each of the CommentID in the table tbComment is locked. Basically not sure where to JOIN tbComment and put the AND tbComment.CommentIsLocked = 1
You are quite close :-)
SELECT t.*
FROM
(
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY CommentID ORDER BY CreateDateTime desc) as rn
FROM
tbCommentBreadCrumb
WHERE CommentStatusTypeID = 4
AND CreateDateTime <= {ts '2014-02-09 09:44:57'}
) t
JOIN tbComment c ON t.CommentID = c.CommentID
WHERE
t.rn = 1
AND c.CommentIsLocked = 1
ORDER BY CommentStatusID DESC
;With CTE
AS
(
SELECT *,
ROW_NUMBER() OVER (PARTITION BY CommentID ORDER BY CreateDateTime desc) as rn
FROM tbCommentBreadCrumb
WHERE CommentStatusTypeID = 4
AND CreateDateTime <= {ts '2014-02-09 09:44:57'}
)
SELECT *
FROM CTE C INNER JOIN tbComment TC
ON C.CommentID = TC.CommentID
WHERE C.rn = 1
ORDER BY C.CommentStatusID DESC

Select and aggregate last records base on order

I have different versions of the charges in a table. I want to grab and sum the last charge grouped by Type.
So I want to add 9.87, 9.63, 1.65.
I want the Parent ID , sum(9.87 + 9.63 + 1.65) as the results of this query.
We use MSSQL
ID ORDER CHARGES TYPE PARENT ID
1 1 6.45 1 1
2 2 1.25 1 1
3 3 9.87 1 1
4 1 6.54 2 1
5 2 5.64 2 1
6 3 0.84 2 1
7 4 9.63 2 1
8 1 7.33 3 1
9 2 5.65 3 1
10 3 8.65 3 1
11 4 5.14 3 1
12 5 1.65 3 1
WITH recordsList
AS
(
SELECT Type, Charges,
ROW_NUMBER() OVER (PArtition BY TYPE
ORDER BY [ORDER] DESC) rn
FROM tableName
)
SELECT SUM(Charges) totalCharge
FROM recordsLIst
WHERE rn = 1
SQLFiddle Demo
Use row_number() to identify the rows to be summed, and then sum them:
select SUM(charges)
from (select t.*,
ROW_NUMBER() over (PARTITION by type order by id desc) as seqnum
from t
) t
where seqnum = 1
Alternatively you could use a window aggregate MAX():
SELECT SUM(Charges)
FROM (
SELECT
[ORDER],
Charges,
MaxOrder = MAX([ORDER]) OVER (PARTITION BY [TYPE])
FROM atable
) s
WHERE [ORDER] = MaxOrder
;
SELECT t.PARENT_ID, SUM(t.CHARGES)
FROM dbo.test73 t
WHERE EXISTS (
SELECT 1
FROM dbo.test73
WHERE [TYPE] = t.[TYPE]
HAVING MAX([ORDER]) = t.[ORDER]
)
GROUP BY t.PARENT_ID
Demo on SQLFiddle