querying SQL Server 2005 - sql

I have a table that is time and milemarkers:
08:00 101.2
08:45 109.8
09:15 109.8
09:30 111.0
10:00 114.6
I need output that looks like this:
08:00-08:45 101.1-109.8
08:45-09:15 109.8-109.8
09:15-09:30 109.8-111.0
09:30-10:00 111.0-114.6
I figure I need 2 identical recordsets and somehow tie the first record of one to the second record of the other, but am clueless on how to accomplish that (or how to ask the question). Any help would be greatly appreciated.
Thanks in advance,
Ginny

The following query will get the next values:
select tm.*,
(select top 1 time
from timemilemarkers tm2
where tm2.time > tm.time
order by 1 desc
) as nexttime,
(select top 1 milemarker
from timemilemarkers tm2
where tm2.time > tm.time
order by 1 desc
) as nextmilemarker
from timemilemarkers tm;
You can put them into the form you want with something like:
select concat_ws('-', milemarker, nextmilemarker), concat_ws('-', time, nexttime)
from (select tm.*,
(select top 1 time
from timemilemarkers tm2
where tm2.time > tm.time
order by 1 desc
) as nexttime,
(select top 1 milemarker
from timemilemarkers tm2
where tm2.time > tm.time
order by 1 desc
) as nextmilemarker
from timemilemarkers tm
) tm
where nextmilemarker is not null;

Other way to do it is:
SQLFiddle
select cast(A.TIME_COL as varchar) + ' - ' + cast(B.TIME_COL as varchar),
cast(A.MILES as varchar) + ' - ' + cast(B.MILES as varchar)
from (select row_number() OVER (order by time_col) ID, * from TABLE_A) A
inner join (select row_number() OVER (order by time_col) ID, * from TABLE_A) B
on A.ID = B.ID - 1
UPDATE: this query will only works for SQL Server 2008 and upwards and obviously not answer your question. I will not erase the answer cause it can be helpful for othe people.
UPDATE2: It works on SQL Server 2005.

try this,
Declare #t table (times time(0), milemarkers decimal(5,2))
insert into #t
select '08:00','101.2' union all
select'08:45','109.8' union all
select'09:15','109.8' union all
select'09:30','111.0' union all
select'10:00','114.6'
;With cte1 as
(select *,ROW_NUMBER()over(order by times)rn from #t
)
,cte2 as
(select max(rn) rn1 from cte1)
, cte as
(select
(select times from cte1 where rn=1)lowerlimit,(select times from cte1 where rn=2)upperlimit,
(select milemarkers from cte1 where rn=1)lowerlimit1,(select milemarkers from cte1 where rn=2)upperlimit1
,1 rn from cte1
union all
select upperlimit,(select times from cte1 where rn=a.rn+2)
,upperlimit1,(select milemarkers from cte1 where rn=a.rn+2)
,rn+1
from cte a where a.rn<(select rn1 from cte2)
)
select distinct cast(lowerlimit as varchar(10))+'-'+cast(upperlimit as varchar(10)) ,
cast(lowerlimit1 as varchar(10))+'-'+cast(upperlimit1 as varchar(10))
from cte a where a.rn<(select rn1 from cte2)

Using CTE we can get the OutPut it is also other way to do Find below query
DECLARE #TABLE_A table(time_col time, miles float)
insert into #TABLE_A values ('08:00',101.2)
insert into #TABLE_A values ('08:45',109.8)
insert into #TABLE_A values ('09:15',109.8)
insert into #TABLE_A values ('09:30',111.0)
insert into #TABLE_A values ('10:00',114.6)
;WITH CTE AS
(
SELECT t.time_col,t.miles,t.RN FROM
(
Select ROW_NUMBER()OVER(ORDER BY time_col )RN,* FRom #TABLE_A
)t
INNER JOIN (
Select ROW_NUMBER()OVER(ORDER BY time_col )RN,* FRom #TABLE_A
)tt
ON t.RN = tt.RN
)
,CTE2(TimeSpan,Miles) AS
(
Select CONVERT(VARCHAR,c.time_col,108) +'-'+
(Select CONVERT(VARCHAR,time_col,108) FROM CTE WHERE RN = cc.RN + 1) As TimeSpan,
CAST(c.miles AS VARCHAR) +' - '+ (Select CAST(miles AS VARCHAR) FROM CTE WHERE RN = CC.RN + 1)AS Miles FROM CTE c
INNER JOIN CTE CC
ON CC.miles = c.miles
AND CC.time_col = c.time_col
)
Select TimeSpan,Miles from CTE2
WHERE TimeSpan IS NOT NULL

Related

How to use CTE recursion to get a running total of a column

I have been researching CTE recursion, but I still seem to be confused.
I have table: utb(date, b_id, v_id, b_vol)
I need to get the running total of the column: b_vol.
The catch is that I need to divide if the row_number is even, and multiply if it is odd, so essentially:
P1= b_vol1, P2 = b_vol1/b_vol2, P3= b_vol1/b_vol2*b_vol3,
P4= b_vol1/b_vol2*b_vol3/b_vol4
So, it is basically P(n)= (P(n-1)(*OR/(b_vol(n))
I can't seem to figure out how to put that into a query.
Thanks for taking the time to read this. I hope you can help.
My sample table only has 1 column B_VOL. Try this
DECLARE #SampleData AS TABLE ( B_VOL int)
INSERT INTO #SampleData VALUES (50), (50), ( 50), (50) ,(155), (255)
;WITH temp AS
(
SELECT sd.*, row_number() OVER(ORDER BY (SELECT null)) - 1 AS RowIndex FROM #SampleData sd
)
,cte AS
(
SELECT TOP 1 t.RowIndex, CAST( t.B_VOL AS decimal(18,7)) AS sv FROM temp t ORDER BY t.RowIndex ASC
UNION ALL
SELECT t.RowIndex, CAST(cte.sv * (CASE WHEN t.RowIndex % 2 = 1 THEN CAST(1 AS decimal(18,7))/t.B_VOL ELSE t.B_VOL END) AS decimal(18,7)) AS sv
FROM cte
INNER JOIN temp t ON cte.RowIndex + 1 = t.RowIndex
)
SELECT t.*, c.sv
FROM temp t
INNER JOIN cte c ON t.RowIndex = c.RowIndex
OPTION(MAXRECURSION 0)
First, a CTE is not the best way to solve this problem. But . . .
with u as (
select u.*, row_number() over (order by date) - 1 as seqnum
from utb u
),
cte as (
select seqnum, b_vol as result
from u
union all
select cte.seqnum, cte.result * (case when seqnum % 2 = 1 then 1.0 / b_vol else b_vol end) as result
from cte join
u
on u.seqnum = cte.seqnum + 1
)
select *
from cte;
you should try this,
DECLARE #SampleData AS TABLE ( B_VOL int)
INSERT INTO #SampleData VALUES (50), (50), ( 50), (50) ,(155), (255)
;with CTE as
(
select S.B_VOL,ROW_NUMBER()over(order by ((select null)))rn
from #SampleData S
)
,CTE1 AS
(
select b_vol,cast(b_vol as float) sv,1 rn1
from cte where rn=1
union ALL
select c.b_vol,
case when (c1.rn1+1)%2=0
THEN c1.sv/cast(c.b_vol as float)
ELSE
c1.sv*cast(c.b_vol as float)
end
,c1.rn1+1
from CTE C
inner join CTE1 c1
on c.rn=c1.rn1+1
where c1.rn1<7
)
select * from cte1

convert row to column using Pivot without any clause

I have a table like below.
I need to get the data like below.
I have created two temp tables and achieved the result like this. Please help me to do the same with PIVOT.
At least I wouldn't use pivot for that, to my mind this is simpler to do with group by and row_number:
select UserId, max(starttime) as starttime, max(endtime) as endtime
from (
select UserId,
case when StartOrEnd = 'S' then time end as starttime,
case when StartOrEnd = 'E' then time end as endtime,
row_number() over (partition by UserID order by time asc)
+ case when StartOrEnd = 'S' then 1 else 0 end as GRP
from table1
) X
group by UserId, GRP
order by starttime
The derived table splits the time into start / end time columns (to handle cases where only one exists) and uses a trick with row number to group the S / E items together. The outer select just groups the rows into the same row.
Example in SQL Fiddle
Not a efficient solution as JamesZ but should work
create table #tst (userid int,start_end char(1),times datetime)
insert #tst values
(1,'S','07-27-2015 16:45'),
(1,'E','07-27-2015 16:46'),
(2,'S','07-27-2015 16:47'),
(2,'E','07-27-2015 16:48'),
(1,'S','07-27-2015 16:49'),
(1,'E','07-27-2015 16:50')
WITH cte
AS (SELECT Row_number()OVER(ORDER BY times) rn,*
FROM #tst),
cte1
AS (SELECT a.userid,
a.start_end,
a.times,
CASE WHEN a.userid = b.userid THEN 0 ELSE 1 END AS com,
a.rn
FROM cte a
LEFT OUTER JOIN cte b
ON a.rn = b.rn + 1),
cte2
AS (SELECT userid,
start_end,
times,
(SELECT Sum(com)
FROM cte1 b
WHERE b.rn <= a.rn) AS row_num
FROM cte1 a)
SELECT USERID,
starttime=Min(CASE WHEN start_end = 's' THEN times END),
endtime=Max(CASE WHEN start_end = 'e' THEN times END)
FROM cte2
GROUP BY USERID,
row_num
Here is another method
declare #t table(userid int, StartOrEnd char(1), time datetime)
insert into #t
select 1,'S','2015-07-27 16:45' union all
select 1,'E','2015-07-27 16:46' union all
select 2,'S','2015-07-27 16:47' union all
select 2,'E','2015-07-27 16:48' union all
select 1,'S','2015-07-27 16:49' union all
select 1,'E','2015-07-27 16:50'
select userid,min(time) as minimum_time, max(time) as maximum_time from
(
select *, row_number() over (partition by cast(UserID as varchar(10))
+StartOrEnd order by time asc) as sno
from #t
) as t
group by userid,sno
Result
userid minimum_time maximum_time
----------- ----------------------- -----------------------
1 2015-07-27 16:45:00.000 2015-07-27 16:46:00.000
2 2015-07-27 16:47:00.000 2015-07-27 16:48:00.000
1 2015-07-27 16:49:00.000 2015-07-27 16:50:00.000

Get difference between the value in one row and the next

I have some students with report marks over a number of reporting periods. I use the following SQL statement to get those report marks
SELECT DISTINCT ID, thePeriod, MeritRank
FROM
(
SELECT ID,
cast(fileyear as varchar) + cast(filesemester as varchar) as thePeriod,
MeritRank
FROM uNCStudentMeritList) usm
ORDER BY ID, thePeriod asc
This gives me the following data
I would love to have another column which has the difference between each row, partitioned by the ID number. For example
Note: the first value item for each StudentId is left blank as this is the first report mark they have received. From then on, I would like to see the difference between one report mark and the next. If they have received a worse report mark then it should be a negative figure as shown. I don't have row ID numbers or anything in the table - I have seen other similar types of questions answered using row ID numbers. How can I get the sort of results I am after>
Any help would be much appreciated.
Try this :
;WITH cte_getdata
AS (SELECT ID,
Cast(fileyear AS VARCHAR(10))
+ Cast(filesemester AS VARCHAR(10)) AS thePeriod,
MeritRank,
ROW_NUMBER()
OVER (
partition BY id
ORDER BY Cast(fileyear AS VARCHAR(10)) ASC, Cast(filesemester AS VARCHAR(10)) ASC) AS rn
FROM uNCStudentMeritList)
SELECT t1.*,
t1.MeritRank - t2.MeritRank
FROM cte_getdata t1
LEFT JOIN cte_getdata t2
ON t1.rn = t2.rn + 1
AND t1.id = t2.id
The Row_Number() function will assign unique number to each record in partitions of id and order of fileyear,filesemester then you can left join the resultset with itself and match the current row with its previous row having same id. Using Left Join will give you all the rows regardless of matching condition. Here is a same approach with two CTE :
;WITH cte1
AS (SELECT ID,
Cast(fileyear AS VARCHAR)
+ Cast(filesemester AS VARCHAR) AS thePeriod,
MeritRank
FROM uNCStudentMeritList),
cte2
AS (SELECT *,
ROW_NUMBER()
OVER (
partition BY ID
ORDER BY thePeriod) rn
FROM cte1)
SELECT *
FROM cte2 c1
LEFT JOIN cte2 c2
ON c1.id = c2.id
AND c1.rn = c2.rn + 1
Try this.
;WITH cte
AS (SELECT Row_number()OVER (partition BY id ORDER BY theperiod) rn,
*
FROM tablename)
SELECT a.id,
a.theperiod,
a.MeritRank,
a.MeritRank - b.MeritRank
FROM cte A
LEFT JOIN cte b
ON a.rn = b.rn + 1
Or if you are using SQL SERVER 2012+ try this.
SELECT id,
theperiod,
MeritRank,
Lag(MeritRank)
OVER (
partition BY id
ORDER BY theperiod) - MeritRank
FROM tablename
This should also work:
SELECT usm1.ID, usm1.thePeriod, usm1.MeritRank, usm2.difference
FROM (SELECT ID, cast(fileyear as varchar) + cast(filesemester as varchar) as thePeriod, MeritRank
FROM uNCStudentMeritList)usm1 LEFT OUTER JOIN
(SELECT usm3.ID as ID, usm3.thePeriod as thePeriod, max(usm4.thePeriod), (usm3.MeritRank - usm4.MeritRank) as difference
FROM (SELECT ID, cast(fileyear as varchar) + cast(filesemester as varchar) as thePeriod, MeritRank
FROM uNCStudentMeritList)usm3 JOIN
(SELECT ID, cast(fileyear as varchar) + cast(filesemester as varchar) as thePeriod, MeritRank
FROM uNCStudentMeritList)usm4 USING (ID)
WHERE usm3.thePeriod > usm4.thePeriod
GROUP BY usm3.ID, usm3.thePeriod)usm2 USING (ID, thePeriod)

Get average time between record creation

So I have data like this:
UserID CreateDate
1 10/20/2013 4:05
1 10/20/2013 4:10
1 10/21/2013 5:10
2 10/20/2012 4:03
I need to group by each user get the average time between CreateDates. My desired results would be like this:
UserID AvgTime(minutes)
1 753.5
2 0
How can I find the difference between CreateDates for all records returned for a User grouping?
EDIT:
Using SQL Server 2012
Try this:
SELECT A.UserID,
AVG(CAST(DATEDIFF(MINUTE,B.CreateDate,A.CreateDate) AS FLOAT)) AvgTime
FROM #YourTable A
OUTER APPLY (SELECT TOP 1 *
FROM #YourTable
WHERE UserID = A.UserID
AND CreateDate < A.CreateDate
ORDER BY CreateDate DESC) B
GROUP BY A.UserID
This approach should aslo work.
Fiddle demo here:
;WITH CTE AS (
Select userId, createDate,
row_number() over (partition by userid order by createdate) rn
from Table1
)
select t1.userid,
isnull(avg(datediff(second, t1.createdate, t2.createdate)*1.0/60),0) AvgTime
from CTE t1 left join CTE t2 on t1.UserID = t2.UserID and t1.rn +1 = t2.rn
group by t1.UserID;
Updated: Thanks to #Lemark for pointing out number of diff = recordCount - 1
since you're using 2012 you can use lead() to do this
with cte as
(select
userid,
(datediff(second, createdate,
lead(CreateDate) over (Partition by userid order by createdate)
)/60) datdiff
From table1
)
select
userid,
avg(datdiff)
from cte
group by userid
Demo
Something like this:
;WITH CTE AS
(
SELECT
ROW_NUMBER() OVER (PARTITION BY UserID ORDER BY CreateDate) RN,
UserID,
CreateDate
FROM Tbl
)
SELECT
T1.UserID,
AVG(DATEDIFF(mi, ISNULL(T2.CreateDate, T1.CreateDate), T1.CreateDate)) AvgTime
FROM CTE T1
LEFT JOIN CTE T2
ON T1.UserID = T2.UserID
AND T1.RN = T2.RN - 1
GROUP BY T1.UserID
With SQL 2012 you can use the ROW_NUMBER function and self-join to find the "previous" row in each group:
WITH Base AS
(
SELECT
ROW_NUMBER() OVER (PARTITION BY UserID ORDER BY CreateDate) RowNum,
UserId,
CreateDate
FROM Users
)
SELECT
B1.UserID,
ISNULL(
AVG(
DATEDIFF(mi,B2.CreateDate,B1.CreateDate) * 1.0
)
,0) [Average]
FROM Base B1
LEFT JOIN Base B2
ON B1.UserID = B2.UserID
AND B1.RowNum = B2.RowNum + 1
GROUP BY B1.UserId
Although I get a different answer for UserID 1 - I get an average of (5 + 1500) / 2 = 752.
This only works in 2012. You can use the LEAD analytic function:
CREATE TABLE dates (
id integer,
created datetime not null
);
INSERT INTO dates (id, created)
SELECT 1 AS id, '10/20/2013 4:05' AS created
UNION ALL SELECT 1, '10/20/2013 4:10'
UNION ALL SELECT 1, '10/21/2013 5:10'
UNION ALL SELECT 2, '10/20/2012 4:03';
SELECT id, isnull(avg(diff), 0)
FROM (
SELECT id,
datediff(MINUTE,
created,
LEAD(created, 1, NULL) OVER(partition BY id ORDER BY created)
) AS diff
FROM dates
) as diffs
GROUP BY id;
http://sqlfiddle.com/#!6/4ce89/22

Define ranges to cover gaps in a number sequence (T-SQL)

Simplifying my problem down - I have 6-digit field which assigns numbers to customers starting from 1 and ending to 999999. Most numbers are sequentially assigned, but numbers can be assigned manually by users, and this feature has been used in an unpredicatable pattern throughout the range.
We now need to identify numbers that have not been assigned (easy) - and then convert this into a number of ranges (seems complex).
For example given the following numbers have been assigned
1,2,3,4,5,
1001,1002,1003,1004,1005,
999101,999102,999103,999104,999105
I need a resulting set of ranges like
Start End
6 1000
1006 999100
999106 999999
My thinking so far is this is probably too complex to write in queries - and best achieved by looping from 1 to 999999, and adding ranges to a temp table.
Interested to hear ideas as I can imagine there are a few approaches. I'm using SQL Server 2008 R2. This is a one-off exercise so even a non-SQL solution might be appropriate, if this were for example easily done in Excel.
Try this
declare #t table (num int)
insert #t values (2),(3),(6),(7),(9),(10),(11)
select
MIN(number) as rangestart,
MAX(number) as rangeend
from
(
select *,
ROW_NUMBER() over (order by number) -
ROW_NUMBER() over (order by num,number) grp
from
(
select number from master..spt_values where type='p' and number between 1 and 15
) numbers
left join #t t
on numbers.number = t.num
) v
where num is null
group by grp
Reference : gaps and islands by itzik ben-gan
To create a numbers query upto 999999
select p1.number + p2.number * 2048 as number
from
(select * from master..spt_values where type='p' ) p1,
(select * from master..spt_values where type='p' and number<489) p2
where p1.number + p2.number * 2048 <=999999
declare #t table (num int)
insert #t values
(2),(3),(4),(5),
(1001),(1002),(1003),(1004),(1005),
(999101),(999102),(999103),(999104),(999105)
;with cte as
(
select num,(ROW_NUMBER() OVER(ORDER BY num)) + 1 as idx from #t
union
select 0 [num],1 [idx] --start boundary
union
select 1000000 [num],COUNT(num) + 2 [idx] from #t --end boundary
)
select c1.num + 1 [Start], c2.num - 1 [End]
from cte c1
inner join cte c2 on c2.idx = c1.idx + 1
where c2.num != c1.num + 1
create table #temp (id int)
insert into #temp (id)
values (1),(2),(3),(1000),(1001),(1002),(2000)
--drop table #temp
with cte as
(
select *, ROW_NUMBER() over(order by id) as rn
from #temp a
)
select a.id + 1, b.id - 1
from cte a join cte b on a.rn = b.rn - 1 and a.id <> b.id -1
it wont include tail ranges, like 2001-9999
Here is SQLFiddle demo
select
case when max(n1)=0 then 1 else max(n1)end,
case when max(n2)=0 then 999999 else max(n2)end
from
(
select t.n+1 as n1,0 n2,
row_number() over(order by t.n)
+isnull((select 0 from t where n=1),1)
rn
from t
left join t t2 on t.n+1=t2.n
where t2.n is null
union all
select 0 n1, t.n-1 as n2 ,
row_number() over(order by t.n) rn
from t
left join t t2 on t.n-1=t2.n
where t2.n is null
and t.n>1
) t3
group by rn
declare #t table(id int)
insert #t values
(1),(2),(3),(4),(5),(1001),(1002),(1003),(1004),(1005),
(999101),(999102),(999103),(999104),(999105)
select t1.id+1 [start], coalesce(t3.[end], 999999) [end]
from #t t1
left join #t t2 on t1.id +1 = t2.id
cross apply
(select min(id)-1 [end] from #t where t1.id < id
) t3
where t2.id is null
if you have a table called "kh" for example with a column "myval" which is your list of integers you could try this SELECT.
SELECT MAX(t1.myval+1) AS 'StartRange',t3.myval-1 AS 'EndRange'
FROM kh t1, kh t3
WHERE t1.myval+1 NOT IN (SELECT myval FROM kh t2 ORDER BY myval)
AND t3.myval-1 NOT IN (SELECT myval FROM kh t4 ORDER BY myval)
AND t1.myval < t3.myval
GROUP BY t3.myval
ORDER BY StartRange