Get difference between the value in one row and the next - sql

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)

Related

Showing two temp tables data in a single table

I have one temp table having data like below in sql server
Table 1
**Data**
ISD-I987330
PSD-I987330
KSD-I987330
JSD-I987330
RSD-I987330
QSD-I987330
QSD-I987359
And another temp table having data like below
Table 2
**Data**
BRA-22310
BRA-22319
BRA-22316
BRA-22313
BRA-22317
I am trying to display both these tables data in a single table.Like below
But I am getting cross joins data.
Below is my query
declare #TempResults table
(
Tickets1 varchar(50),
Tickets2 varchar(50)
)
insert into #TempResults
Select distinct ti.Tickets1,
tr.Tickets2
FROM #Table1 ti,#table2 tr
select * from #TempResults
You can't really get the output you want without each table having a separate column which maintains the order of each record, in each table. Absent that, we could use ROW_NUMBER to generate an order, and then join:
WITH cte1 AS (
SELECT Data, ROW_NUMBER() OVER (ORDER BY Data) rn
FROM Table1
),
cte2 AS (
SELECT Data, ROW_NUMBER() OVER (ORDER BY Data) rn
FROM Table2
)
SELECT
t1.Data,
t2.Data
FROM cte1 t1
FULL OUTER JOIN cte2 t2
ON t1.rn = t2.rn;
you can join using row_number()
with cte1 as
(select *,row_number() over(order by data) rn
from table1
),
cte2 as
(
select *,row_number() over(order by data) rn
) select cte1.data,cte2.data from cte1 left join ct2 on cte1.rn=cte2.rn
The most basic solution but not necessarily the best, is to make the union of tables in a temporary table with different names for each of the columns
select * Into #TempResults From
(
SELECT ti.Tickets1 as 'Tickets1',' ' as 'Tickets2'
FROM #Table1 ti
UNION
SELECT ' ' as 'Tickets1', tr.Tickets2 as 'Tickets2'
FROM #table2 tr
) T

How to generate Tree path using traverse CTE

Suppose my data in table like in attached picture without having path column.
i want to generate a column, like "Path" in picture using traverse CTE in sql
Picture example:
Use a recursive CTE to solve this:
WITH recCTE
AS (
SELECT id,
parentid,
id AS original_id,
parentid AS original_parentid,
name as original_name,
1 AS depth,
CAST(name AS VARCHAR(5000)) AS path
FROM yourtable
UNION ALL
SELECT yourtable.id,
yourtable.parentid,
recCTE.original_id,
recCTE.original_parentID,
recCTE.original_name,
recCTE.depth + 1,
CAST(recCTE.path + '-' + yourtable.name as VARCHAR(5000))
FROM recCTE
INNER JOIN yourtable
ON recCTE.parentid = yourtable.id
WHERE depth < 20 /*prevent cycling*/
)
SELECT original_id as id, original_parentid as parentid, original_name as name, depth, path
FROM recCTE t1
WHERE depth = (SELECT max(depth) FROM recCTE WHERE t1.original_id = recCTE.original_id)
sqlfiddle example
That CTE has two parts:
The "Anchor Member" which is the first selection from the table. This defines the output (which columns and column type are in the output).
The "Recursive Member" which selects from the CTE in which it's contained at is performed iteratively until the join fails.
In this example we capture the path by concatenating the path to the name over and over again in the recursive member. We also track Depth (how many recursions have been performed) and track the current id and parentid as well as the original id and original parentid so they can be selected in the final SELECT statement.
try this,
;with cte(id,parentId,name,path,cnt)
AS
(
select id,parentid,name,cast(name as VARCHAR(1024)) as path, 1 as cnt from test_cte
union all
select a.id,a.parentid,a.name,CAST((a.name + '-' +path ) as VARCHAR(1024)), case when a.parentid is null then 0 else cnt + 1 end as cnt from test_cte a join cte c on c.id = a.parentid where c.cnt is not null
)
select id,parentid,name,path from (select id,parentid,name,path, row_number() over(partition by id order by cnt desc) as rank from cte) a where a.rank = 1 order by 1 asc ;

SQL - Return previous record details as column by date

I am trying to get a list of records showing changes in location and dates to display as one row for each record showing previous location.
Basically a query to take data like:
And display it like:
I tried using lag, but it mixes up some of the records. Would anyone be able suggest a good way to do this?
Thanks!
DECLARE #TABLE TABLE
(ID INT ,NAME VARCHAR(20), LOCATIONDATE DATETIME, REASON VARCHAR(20))
INSERT INTO #TABLE
(ID,NAME, LOCATIONDATE, REASON)
VALUES
( 1,'abc',CAST('2016/01/01' AS SMALLDATETIME),'move'),
( 2,'def',CAST('2016/02/01' AS SMALLDATETIME),'move'),
( 1,'abc',CAST('2016/06/01' AS SMALLDATETIME),'move'),
( 2,'def',CAST('2016/07/01' AS SMALLDATETIME),'move'),
( 1,'abc',CAST('2016/08/01' AS SMALLDATETIME),'move'),
( 3,'ghi',CAST('2016/08/01' AS SMALLDATETIME),'move')
select s.*
,t1.*
from
(
select t.*,
row_number() over (partition by id order by locationdate desc) rn
from #table t
) s
left join
(
select t.*,
row_number() over (partition by id order by locationdate desc) rn
from #table t
) t1
on t1.id = s.id and t1.rn = s.rn + 1
You can try it:
SELECT a.id,a.name,a.location as currentLocation,a.locationdatedate as currrentLocate,b.location as preLocation,b.locationdatedate as prevLocate,a.changereason
FROM test as a
JOIN test as b ON a.name = b.name
where a.locationdatedate > b.locationdatedate
group by a.name
Pleas try this one. it works here
SELECT l.id,
,l.name
,l.location as currentLocation
,l.locationdatedate as currrentLocate
,r.location as preLocation
,r.locationdatedate as prevLocate
,r.changereason
FROM tableName AS l inner join
tableName AS r ON r.id=l.id
WHERE l.locationdatedate !=r.locationdatedate AND l.locationdatedate > r.locationdatedate

concatenate recursive cross join

I need to concatenate the name in a recursive cross join way. I don't know how to do this, I have tried a CTE using WITH RECURSIVE but no success.
I have a table like this:
group_id | name
---------------
13 | A
13 | B
19 | C
19 | D
31 | E
31 | F
31 | G
Desired output:
combinations
------------
ACE
ACF
ACG
ADE
ADF
ADG
BCE
BCF
BCG
BDE
BDF
BDG
Of course, the results should multiply if I add a 4th (or more) group.
Native Postgresql Syntax:
SqlFiddleDemo
WITH RECURSIVE cte1 AS
(
SELECT *, DENSE_RANK() OVER (ORDER BY group_id) AS rn
FROM mytable
),cte2 AS
(
SELECT
CAST(name AS VARCHAR(4000)) AS name,
rn
FROM cte1
WHERE rn = 1
UNION ALL
SELECT
CAST(CONCAT(c2.name,c1.name) AS VARCHAR(4000)) AS name
,c1.rn
FROM cte1 c1
JOIN cte2 c2
ON c1.rn = c2.rn + 1
)
SELECT name as combinations
FROM cte2
WHERE LENGTH(name) = (SELECT MAX(rn) FROM cte1)
ORDER BY name;
Before:
I hope if you don't mind that I use SQL Server Syntax:
Sample:
CREATE TABLE #mytable(
ID INTEGER NOT NULL
,TYPE VARCHAR(MAX) NOT NULL
);
INSERT INTO #mytable(ID,TYPE) VALUES (13,'A');
INSERT INTO #mytable(ID,TYPE) VALUES (13,'B');
INSERT INTO #mytable(ID,TYPE) VALUES (19,'C');
INSERT INTO #mytable(ID,TYPE) VALUES (19,'D');
INSERT INTO #mytable(ID,TYPE) VALUES (31,'E');
INSERT INTO #mytable(ID,TYPE) VALUES (31,'F');
INSERT INTO #mytable(ID,TYPE) VALUES (31,'G');
Main query:
WITH cte1 AS
(
SELECT *, rn = DENSE_RANK() OVER (ORDER BY ID)
FROM #mytable
),cte2 AS
(
SELECT
TYPE = CAST(TYPE AS VARCHAR(MAX)),
rn
FROM cte1
WHERE rn = 1
UNION ALL
SELECT
[Type] = CAST(CONCAT(c2.TYPE,c1.TYPE) AS VARCHAR(MAX))
,c1.rn
FROM cte1 c1
JOIN cte2 c2
ON c1.rn = c2.rn + 1
)
SELECT *
FROM cte2
WHERE LEN(Type) = (SELECT MAX(rn) FROM cte1)
ORDER BY Type;
LiveDemo
I've assumed that the order of "cross join" is dependent on ascending ID.
cte1 generate DENSE_RANK() because your IDs contain gaps
cte2 recursive part with CONCAT
main query just filter out required length and sort string
The recursive query is a bit simpler in Postgres:
WITH RECURSIVE t AS ( -- to produce gapless group numbers
SELECT dense_rank() OVER (ORDER BY group_id) AS grp, name
FROM tbl
)
, cte AS (
SELECT grp, name
FROM t
WHERE grp = 1
UNION ALL
SELECT t.grp, c.name || t.name
FROM cte c
JOIN t ON t.grp = c.grp + 1
)
SELECT name AS combi
FROM cte
WHERE grp = (SELECT max(grp) FROM t)
ORDER BY 1;
The basic logic is the same as in the SQL Server version provided by #lad2025, I added a couple of minor improvements.
Or you can use a simple version if your maximum number of groups is not too big (can't be very big, really, since the result set grows exponentially). For a maximum of 5 groups:
WITH t AS ( -- to produce gapless group numbers
SELECT dense_rank() OVER (ORDER BY group_id) AS grp, name AS n
FROM tbl
)
SELECT concat(t1.n, t2.n, t3.n, t4.n, t5.n) AS combi
FROM (SELECT n FROM t WHERE grp = 1) t1
LEFT JOIN (SELECT n FROM t WHERE grp = 2) t2 ON true
LEFT JOIN (SELECT n FROM t WHERE grp = 3) t3 ON true
LEFT JOIN (SELECT n FROM t WHERE grp = 4) t4 ON true
LEFT JOIN (SELECT n FROM t WHERE grp = 5) t5 ON true
ORDER BY 1;
Probably faster for few groups. LEFT JOIN .. ON true makes this work even if higher levels are missing. concat() ignores NULL values. Test with EXPLAIN ANALYZE to be sure.
SQL Fiddle showing both.

querying SQL Server 2005

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