I want first and second inserted value with condition - sql

Table abc:
Consgno Name Entrydatetime
111 A 01/03/2017 10:10:15
111 A 01/03/2017 10:20:15
111 A 01/03/2017 11:10:20
222 B 02/03/2017 10:10:25
333 C 06/03/2017 10:10:25
333 C 07/03/2017 10:10:12
444 D 04/03/2017 10:10:41
444 D 04/03/2017 01:10:20
444 D 06/03/2017 10:10:32
555 E 05/04/2017 10:10:15
One Consgno has entered ONE more than one time.
When one Consgno is only once, then the first value should come, otherwise the second entered value should come.
I want to output like this:
Consgno Name Entrydatetime
111 A 01/03/2017 10:20:15
222 B 02/03/2017 11:10:36
333 C 07/03/2017 10:10:12
444 D 04/03/2017 01:10:20
555 E 05/04/2017 10:10:15

You can use a query like the following:
;WITH MyWindowedTable AS (
SELECT Consgno, Name, Entrydatetime,
ROW_NUMBER() OVER (PARTITION BY Consgno
ORDER BY Entrydatetime) AS rn,
COUNT(*) OVER (PARTITION BY Consgno) AS cnt
FROM mytable
)
SELECT Consgno, Name, Entrydatetime
FROM MyWindowedTable
WHERE (cnt = 1 AND rn = 1) OR (cnt > 1 AND rn = 2)
Using windowed version of COUNT:
COUNT(*) OVER (PARTITION BY Consgno)
returns the population, cnt, of each Consgno partition. We can use cnt to properly filter the records returned: in partitions with a population of 1 we get the single record of the partition, whereas in the rest of the cases we get the one having rn = 2.

Use ROW_NUMBER and COUNT built in functions :
CREATE TABLE #table1 ( Consgno INT, Name VARCHAR(1), Entrydatetime
DATETIME)
INSERT INTO #table1 ( Consgno , Name , Entrydatetime )
SELECT 111,'A','01/03/2017 10:10:15' UNION ALL
SELECT 111,'A','01/03/2017 10:20:15' UNION ALL
SELECT 111,'A','01/03/2017 11:10:20' UNION ALL
SELECT 222,'B','02/03/2017 10:10:25' UNION ALL
SELECT 333,'C','06/03/2017 10:10:25' UNION ALL
SELECT 333,'C','07/03/2017 10:10:12' UNION ALL
SELECT 444,'D','04/03/2017 10:10:41' UNION ALL
SELECT 444,'D','04/03/2017 01:10:20' UNION ALL
SELECT 444,'D','06/03/2017 10:10:32' UNION ALL
SELECT 555,'E','05/04/2017 10:10:15'
SELECT Consgno , Name , Entrydatetime
FROM
(
SELECT Consgno , Name , Entrydatetime , ROW_NUMBER() OVER (PARTITION BY
Consgno ORDER BY Entrydatetime) RNo , COUNT(*) OVER (PARTITION BY
Consgno) AS _Count
FROM #table1
) A WHERE ( RNo = 1 AND _Count = 1) OR (_Count > 1 AND RNo = 2 )

Related

SQL - Multiple values to one

Please help me, the question is probably stupid, but I'm at a dead end. There is a table with the following data:
Num
Start number
Date start
111
225
11.11.22
111
223
9.11.22
111
220
9.11.22
222
347
11.11.22
222
345
11.11.22
222
343
10.11.22
I would like to come to something like this, so that Num is displayed in one cell, the first and last values are displayed in the Start number and Date start fields, respectively, and their number is also counted in one cell:
Num
Start number
Date start
Count
111
225
11.11.22
3
220
9.11.22
222
347
11.11.22
3
343
10.11.22
What you want is literally done in this query:
with t(Num, Start_number, Date_start) as (
select 111 , 225 , '11.11.22' from dual union all
select 111 , 223 , '9.11.22' from dual union all
select 111 , 220 , '9.11.22' from dual union all
select 222 , 347 , '11.11.22' from dual union all
select 222 , 345 , '11.11.22' from dual union all
select 222 , 343 , '10.11.22' from dual
)
select case when p.is_first = 1 then p.num end as num
, p.start_number
, p.date_start
, case when p.is_first = 1 then p.cnt end as num
from (
select t.*
, case when start_number = max(start_number) over (partition by num) then 1 else 0 end as is_first
, case when start_number = min(start_number) over (partition by num) then 1 else 0 end as is_last
, count(*) over (partition by num) as cnt
from t
) p
where p.is_first = 1 or p.is_last = 1
fiddle
However I agree with commenters this should be done at GUI level rather than SQL level. It does not make sense to alternate null and nonnull values (is it for some report?). You can utilize the p ("precomputation") subquery anyway, just change outer query then.
You can try the following SQL code:
SELECT
Num,
StartNumber,
DateStart,
[Count]
FROM
(
SELECT
Num,
[Count],
(SELECT MAX(StartNumber) FROM table AS t2 WHERE (t2.Num = t1.Num) AND (t2.DateStart = t1.MaxDateStart)) AS StartNumber,
MaxDateStart AS DateStart
FROM
(
SELECT
Num,
COUNT(*) AS [Count],
MAX(DateStart) AS MaxDateStart
FROM
table
GROUP BY
Num
) AS t1
UNION
SELECT
Num,
[Count],
(SELECT MIN(StartNumber) FROM table AS t2 WHERE (t2.Num = t1.Num) AND (t2.DateStart = t1.MinDateStart)) AS StartNumber,
MinDateStart AS DateStart
FROM
(
SELECT
Num,
COUNT(*) AS [Count],
MIN(DateStart) AS MinDateStart
FROM
table
GROUP BY
Num
) AS t1
) AS t
ORDER BY
Num, DateStart DESC
This code works without OVER for different RDBMS.

Create episode for each value with new Begin and End Dates

This is in reference to below Question
Loop through each value to the seq num
But now Client want to see the data differently and started a new thread for this question.
below is the requirement.
This is the data .
ID seqNum DOS Service End Date
1 1 1/1/2017 1/15/2017
1 2 1/16/2017 1/16/2017
1 3 1/17/2017 1/21/2017
1 4 1/22/2017 2/13/2017
1 5 2/14/2017 3/21/2017
1 6 2/16/2017 3/21/2017
Expected outPut:
ID SeqNum DOSBeg DOSEnd
1 1 1/1/2017 1/30/2017
1 2 1/31/2017 3/1/2017
1 3 3/2/2017 3/31/2017
For each DOSBeg, add 29 and that is DOSEnd. then Add 1 to DOSEnd (1/31/2017) is new DOSBeg.
Now add 29 to (1/31/2017) and that is 3/1/2017 which is DOSEnd . Repeat this untill DOSend >=Max End Date i.e 3/21/2017.
Basically, we need episode of 29 days for each ID.
I tried with this code and it is giving me duplicates.
with cte as (
select ID, minDate as DOSBeg,dateadd(day,29,mindate) as DOSEnd
from #temp
union all
select ID,dateadd(day,1,DOSEnd) as DOSBeg,dateadd(day,29,dateadd(day,1,DOSEnd)) as DOSEnd
from cte
)
select ID,DOSBeg,DOSEnd
from cte
OPTION (MAXRECURSION 0)
Here mindate is Minimum DOS for this ID i.e. 1/1/2017
I came up with below logic and this is working fine for me. Is there any better way than this ?
declare #table table (id int, seqNum int identity(1,1), DOS date, ServiceEndDate date)
insert into #table
values
(1,'20170101','20170115'),
(1,'20170116','20170116'),
(1,'20170117','20170121'),
(1,'20170122','20170213'),
(1,'20170214','20170321'),
(1,'20170216','20170321'),
(2,'20170101','20170103'),
(2,'20170104','20170118')
select * into #temp from #table
--drop table #data
select distinct ID, cast(min(DOS) over (partition by ID) as date) as minDate
,row_Number() over (partition by ID order by ID, DOS) as SeqNum,
DOS,
max(ServiceEndDate) over (partition by ID)as maxDate
into #data
from #temp
--drop table #StartDateLogic
with cte as
(select ID,mindate as startdate,maxdate
from #data
union all
select ID,dateadd(day,30,startdate) as startdate,maxdate
from cte
where maxdate >= dateadd(day,30,startdate))
select distinct ID,startdate
into #StartDateLogic
from cte
OPTION (MAXRECURSION 0)
--final Result set
select ID
,ROW_NUMBER() over (Partition by ID order by ID,StartDate) as SeqNum
,StartDate
,dateadd(day,29,startdate) as EndDate
from #StartDateLogic
You were on the right track wit the recursive cte, but you forgot the anchor.
declare #table table (id int, seqNum int identity(1,1), DOS date, ServiceEndDate date)
insert into #table
values
(1,'20170101','20170115'),
(1,'20170116','20170116'),
(1,'20170117','20170121'),
(1,'20170122','20170213'),
(1,'20170214','20170321'),
(1,'20170216','20170321'),
(2,'20170101','20170103'),
(2,'20170104','20170118')
;with dates as(
select top 1 with ties id, seqnum, DOSBeg = DOS, DOSEnd = dateadd(day,29,DOS)
from #table
order by row_number() over (partition by id order by seqnum)
union all
select t.id, t.seqNum, DOSBeg = dateadd(day,1,d.DOSEnd), DOSEnd = dateadd(day,29,dateadd(day,1,d.DOSEnd))
from dates d
inner join #table t on
d.id = t.id and t.seqNum = d.seqNum + 1
)
select *
from dates d
where d.DOSEnd <= (select max(dateadd(month,1,ServiceEndDate)) from #table where id = d.id)
order by id, seqNum

How can I select an ID each month with the highest Mark

I am fairly new to SQL. My table is
id mark datetimes
------|-----|------------
1001 | 10 | 2011-12-20
1002 | 11 | 2012-01-10
1005 | 12 | 2012-01-10
1003 | 10 | 2012-01-10
1004 | 11 | 2018-10-10
1006 | 12 | 2018-10-19
1007 | 13 | 2018-03-12
1008 | 15 | 2018-03-13
I need to select an ID with the highest mark at the end of each month (Year also matters) and ID can be repeated
My desired output would be
id mark
-----|----
1001 | 10
1005 | 12
1006 | 12
1008 | 15
So far I've Only able to get the highest value in each month
Select Max(Mark)'HighestMark'
From StudentMark
Group BY Year(datetimes), Month(datetimes)
When I tried to
Select Max(Mark)'HighestMark', ID
From StudentMark
Group BY Year(datetimes), Month(datetimes), ID
I get
Id HighestMark
----------- ------------
1001 10
1002 11
1003 12
1004 10
1005 11
1006 12
1007 13
1008 15
You can try like following.
Using ROW_NUMBER()
SELECT * FROM
(
SELECT *,
ROW_NUMBER() OVER(PARTITION BY YEAR(DATETIMES)
,MONTH(DATETIMES) ORDER BY MARK DESC) AS RN
FROM [MY_TABLE]
)T WHERE RN=1
Using WITH TIES
SELECT TOP 1 WITH TIES ID, mark AS HighestMarks
FROM [MY_TABLE]
ORDER BY ROW_NUMBER() OVER (PARTITION BY YEAR(datetimes)
,MONTH(datetimes) ORDER BY mark DESC)
Example:
WITH MY AS
(
SELECT
* FROM (VALUES
(1001 , 10 , '2011-12-20'),
(1002 , 11 , '2012-01-10'),
(1005 , 12 , '2012-01-10'),
(1003 , 10 , '2012-01-10'),
(1004 , 11 , '2018-10-10'),
(1006 , 12 , '2018-10-19'),
(1007 , 13 , '2018-03-12'),
(1008 , 15 , '2018-03-13')
) T( id , mark , datetimes)
)
SELECT ID,Mark as HighestMark FROM
(
SELECT *,
ROW_NUMBER() OVER(PARTITION BY YEAR(DATETIMES),MONTH(DATETIMES) ORDER BY MARK DESC) AS RN
FROM MY
)T WHERE RN=1
Output:
ID HighestMark
1001 10
1005 12
1008 15
1006 12
I don't see a way of doing this in a single query. But we can easily enough use one subquery to find the final mark in the month for each student, and another to find the student with the highest final mark.
WITH cte AS (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY ID, CONVERT(varchar(7), datetimes, 126)
ORDER BY datetimes DESC) rn
FROM StudentMark
)
SELECT ID, Mark AS HighestMark
FROM
(
SELECT *,
RANK() OVER (PARTITION BY CONVERT(varchar(7), datetimes, 126)
ORDER BY Mark DESC) rk
FROM cte
WHERE rn = 1
) t
WHERE rk = 1
ORDER BY ID;
Demo
In below query you have included ID column for Group By, because of this, it is considering all data for all ID.
Select Max(Mark)'HighestMark', ID From StudentMark Group BY Year(datetimes), Month(datetimes), ID
Remove ID column from this script and try again.
Use RANK in case there are more than 1 student having the same highest mark.
select id, mark
from
(select *,
rank() over( partition by convert(char(7), datetimes, 111) order by mark desc) seqnum
from studentMark ) t
where seqnum = 1
this should work:
select s.ID, t.Mark, t.[Month year] from Studentmark s
inner join (
Select
Max(Mark)'HighestMark'
,cast(Year(datetimes) as varchar(10)) +
cast(Month(datetimes) as varchar(10)) [month year]
From StudentMark
Group BY cast(Year(datetimes) as varchar(10))
+ cast(Month(datetimes) as varchar(10))) t on t.HighestMark = s.mark and
t.[month year] = cast(Year(s.datetimes) as varchar(10)) + cast(Month(s.datetimes) as varchar(10))
If for some reason you abhor subqueries, you can actually do this as:
select distinct
first_value(id) over (partition by year(datetimes), month(datetime) order by mark desc) as id
max(mark) over (partition by year(datetimes), month(datetime))
from StudentMark;
Or:
select top (1) with ties id, mark
from StudentMark
order by row_number() over (partition by year(datetimes), month(datetime) order by mark desc);
In this case, you can get all students in the event of ties by using rank() or dense_rank() instead of row_number().

Oracle select query to filter rows

Say I have a table with the following values
id (PK) a_num a_code effect_dt expire_dt
32 1234 abcd 01/01/2015 05/30/2015
9 1234 abcd 06/01/2015 12/31/2015
5 1234 efgh 01/01/2015 05/30/2015
14 1234 efgh 06/01/2015 12/31/2015
How can I select just one record from a_num,a_code pair. Either Id's 1,3 or 2,4? There may be scenarios where there are more than 2 records for a a_num,a_code pair.
UPDATE - ID will not necessarily always be in order, it is just a primary key.
This will give you rows 1 and 3
Select * from (
Select * , Row_number() Over(Partition by a_num, a_code order by id) r_num from Your_Table ) result
Where r_num = 1
Just use DESC in order by and you will get rows 2 and 4
Select * from (
Select * , Row_number() Over(Partition by a_num, a_code order by id desc) r_num from Your_Table ) result
Where r_num = 1
One way would be to use the row_number window function:
SELECT id, a_num, a_code, effect_dt, expire_dt
FROM (SELECT id, a_num, a_code, effect_dt, expire_dt,
ROW_NUMBER() OVER (PARTITION BY a_num, a_code
ORDER BY 1) AS rn
FROM mytable) t
WHERE rn = 1

SQL Server 2008 Group Based on a Sequence

I'm struggling to find if this is possible to use SQL Server 2008 to assign a sequence without having to use cursors. Let's say I have the following table which defines a driver's driving route going from one location to another (null means he is going from home):
RouteID SourceLocationID DestinationLocationID DriverID Created Updated
------- ---------------- --------------------- -------- ------- -------
1 NULL 219 1 10:20 10:23
2 219 266 1 10:21 10:24
3 266 NULL 1 10:22 10:25
4 NULL 54 2 10:23 10:26
5 54 NULL 2 10:24 10:27
6 NULL 300 1 10:25 10:28
7 300 NULL 1 10:26 10:29
I want to group the records between the rows where sourceLID is NULL and the destinationLID is null, so I get the following (generating a sequence number for each grouping set):
DriverID DestinationLocationID TripNumber
-------- --------------------- ----------
1 219 1 (his first trip)
1 266 1
1 300 2 (his second trip)
2 54 1
Is there a way I could use GROUP BY here rather than cursors?
a quick try:
with cte as
( select DestinationLocationID
, DriverID
, tripid = row_number()
over ( partition by driverid
order by DestinationLocationID)
from table1
where sourcelocationid is NULL
UNION ALL
select table1.DestinationLocationID
, table1.DriverID
, cte.tripid
from table1
join cte on table1.SourceLocationID=cte.DestinationLocationID
and table1.DriverID=cte.DriverID
where cte.DestinationLocationID is not null
)
select * from cte
Try this:
select driverid, destinationlocationid, count(destinationlocationid) from
(
select driverid, destinationlocationid from table1 where sourcelocationid is NULL
union all
select driverid, sourcelocationid from table1 where destinationlocationid is NULL
)A group by driverid, destinationlocationid
Try this,
Declare #t table(RouteID int, SourceLocationID int,DestinationLocationID int
,DriverID int,Created time, Updated time)
insert into #t
values(1, NULL, 219, 1, '10:20','10:23'),
(2 ,219,266, 1, '10:21','10:24'),
(3,266, NULL, 1, '10:22','10:25'),
(4, NULL, 54, 2, '10:23','10:26'),
(5,54, NULL, 2, '10:24','10:27'),
(6,NULL,300, 1, '10:25','10:28'),
(7,300,NULL, 1, '10:26','10:29')
;
WITH CTE
AS (
SELECT *
,ROW_NUMBER() OVER (
PARTITION BY DriverID ORDER BY Created
) RN
FROM #t
)
,CTE1
AS (
SELECT *
,1 TripNumber
FROM CTE
WHERE RN = 1
UNION ALL
SELECT A.*
,CASE
WHEN A.SourceLocationID IS NULL
THEN B.TripNumber + 1
ELSE B.TripNumber
END
FROM CTE1 B
INNER JOIN CTE A ON B.DriverID = A.DriverID
WHERE A.RN > B.RN
)
SELECT DISTINCT DestinationLocationID
,DriverID
,TripNumber
FROM CTE1
WHERE DestinationLocationID IS NOT NULL
ORDER BY DriverID
Use a correlated sub-query to count previous trips, plus 1 to get this trip number.
select DriverID,
DestinationLocationID,
(select count(*) + 1
from routes t2
where t1.DriverID = t2.DriverID
and t1.RouteID > t2.RouteID
and DestinationLocationID IS NULL) as TripNumber
from routes t1
where DestinationLocationID IS NOT NULL
order by DriverID, DestinationLocationID;
Executes like this:
SQL>select DriverID,
SQL& DestinationLocationID,
SQL& (select count(*) + 1
SQL& from routes t2
SQL& where t1.DriverID = t2.DriverID
SQL& and t1.RouteID > t2.RouteID
SQL& and DestinationLocationID IS NULL) as TripNumber
SQL&from routes t1
SQL&where DestinationLocationID IS NOT NULL
SQL&order by DriverID, DestinationLocationID;
DriverID DestinationLocationID TripNumber
=========== ===================== ============
1 219 1
1 266 1
1 300 2
2 54 1
4 rows found