Insert multiple comma separated columns as a single row in SQL server - sql-server

i have a table like this
CREATE TABLE #tbl(PackId NVARCHAR(MAX),AmntRemain NVARCHAR(MAX),AmntUsed NVARCHAR(MAX),IsCount NVARCHAR(MAX),IsValue NVARCHAR(MAX))
INSERT INTO #tbl VALUES('1,2','10,20','10,20','1,0','0,1')
above table output is
my concern is how to get output like below
how to insert data into a table of above table data as all columns individual value as a independent row

You should never store your data like this. You should really fix your etl processes and database schema per all the comments on your question.
Using cross apply(values ...) to unpivot your data, splitting the strings, and using conditional aggregation to pivot the data back to rows:
In SQL Server 2016+ you can use string_split().
In SQL Server pre-2016, using a CSV Splitter table valued function by Jeff Moden:
;with cte as (
select
Id = row_number() over (order by (select null)) /* adding an id to uniquely identify rows */
, *
from #tbl
)
select
cte.Id
, s.ItemNumber
, PackId = max(case when u.column_name = 'PackId' then s.item end)
, AmntRemain = max(case when u.column_name = 'AmntRemain' then s.item end)
, AmntUsed = max(case when u.column_name = 'AmntUsed' then s.item end)
, IsCount = max(case when u.column_name = 'IsCount' then s.item end)
, IsValue = max(case when u.column_name = 'IsValue' then s.item end)
from cte
cross apply (values ('PackId',PackId),('AmntRemain',AmntRemain),('AmntUsed',AmntUsed),('IsCount',IsCount),('IsValue',IsValue)) u (column_name,column_value)
cross apply dbo.delimitedsplit8K(u.column_value,',') s
group by cte.Id, s.ItemNumber
rextester demo: http://rextester.com/ZIFFQX41171
returns:
+----+------------+--------+------------+----------+---------+---------+
| Id | ItemNumber | PackId | AmntRemain | AmntUsed | IsCount | IsValue |
+----+------------+--------+------------+----------+---------+---------+
| 1 | 1 | 1 | 10 | 10 | 1 | 0 |
| 1 | 2 | 2 | 20 | 20 | 0 | 1 |
+----+------------+--------+------------+----------+---------+---------+
splitting strings reference:
Tally OH! An Improved SQL 8K “CSV Splitter” Function - Jeff Moden
Splitting Strings : A Follow-Up - Aaron Bertrand
Split strings the right way – or the next best way - Aaron Bertrand
string_split() in SQL Server 2016 : Follow-Up #1 - Aaron Bertrand
Ordinal workaround for **string_split()** - Solomon Rutzky

You want to insert into 2 rows. Try:
INSERT INTO #tbl VALUES
('1','10','10','1','0')
,('2','20','20','0','1')

Just about any parse/split function will do. The one supplied below also returns an Item Sequence Number which can be used to join and the individual elements in an appropriate row.
The number of elements are NOT fixed, one record could have 2 while another has 5.
I should add, if you can't use or want a UDF, it would be a small matter to create an in-line approach.
Example
Declare #Staging TABLE (PackId NVARCHAR(MAX),AmntRemain NVARCHAR(MAX),AmntUsed NVARCHAR(MAX),IsCount NVARCHAR(MAX),IsValue NVARCHAR(MAX))
INSERT INTO #Staging VALUES
('1,2','10,20','10,20','1,0','0,1')
Select B.*
From #Staging A
Cross Apply (
Select PackId = B1.RetVal
,AmntRemain = B2.RetVal
,AmntUsed = B3.RetVal
,IsCount = B4.RetVal
,IsValue = B5.RetVal
From [dbo].[udf-Str-Parse-8K](A.PackId,',') B1
Join [dbo].[udf-Str-Parse-8K](A.AmntRemain,',') B2 on B1.RetSeq=B2.RetSeq
Join [dbo].[udf-Str-Parse-8K](A.AmntUsed,',') B3 on B1.RetSeq=B3.RetSeq
Join [dbo].[udf-Str-Parse-8K](A.IsCount,',') B4 on B1.RetSeq=B4.RetSeq
Join [dbo].[udf-Str-Parse-8K](A.IsValue,',') B5 on B1.RetSeq=B5.RetSeq
) B
Returns
PackId AmntRemain AmntUsed IsCount IsValue
1 10 10 1 0
2 20 20 0 1
The UDF if interested
CREATE FUNCTION [dbo].[udf-Str-Parse-8K] (#String varchar(max),#Delimiter varchar(25))
Returns Table
As
Return (
with cte1(N) As (Select 1 From (Values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N(N)),
cte2(N) As (Select Top (IsNull(DataLength(#String),0)) Row_Number() over (Order By (Select NULL)) From (Select N=1 From cte1 a,cte1 b,cte1 c,cte1 d) A ),
cte3(N) As (Select 1 Union All Select t.N+DataLength(#Delimiter) From cte2 t Where Substring(#String,t.N,DataLength(#Delimiter)) = #Delimiter),
cte4(N,L) As (Select S.N,IsNull(NullIf(CharIndex(#Delimiter,#String,s.N),0)-S.N,8000) From cte3 S)
Select RetSeq = Row_Number() over (Order By A.N)
,RetVal = LTrim(RTrim(Substring(#String, A.N, A.L)))
From cte4 A
);
--Orginal Source http://www.sqlservercentral.com/articles/Tally+Table/72993/
--Much faster than str-Parse, but limited to 8K
--Select * from [dbo].[udf-Str-Parse-8K]('Dog,Cat,House,Car',',')
--Select * from [dbo].[udf-Str-Parse-8K]('John||Cappelletti||was||here','||')

You can't, unless your inserting data from another table you will have to create individual insert statements for each row you want to create.

Related

Recurcive cte to identify circular reference in data

I am trying to identify recursive/circular reference in my data for which I need recursive cte.
For example I have table that contains Product_ID and Inner_Product_ID. I want results when Product_ID A is inner to Product_ID B, which is inner to Product_ID C, which is inner to Product_ID A.
Sample data
PRODUCT_ID INNER_PRODUCT_ID
12 36
24 12
36 24
1 2
3 4
Expected output
PRODUCT_ID INNER_PRODUCT_ID
12 36
24 12
36 24
I have tried basic query with cte but not sure how to implement recursive cte for this problem:
;WITH RNCTE
AS ( SELECT *,
ROW_NUMBER() OVER (PARTITION BY pr1.PRODUCT_ID
ORDER BY pr1.PRODUCT_ID
) rn
FROM
TableName pr1),
cte
AS ( SELECT *
FROM RNCTE
WHERE RNCTE.rn = 1
UNION ALL
SELECT *
FROM cte c
JOIN RNCTE r
ON r.PRODUCT_ID = c.PRODUCT_ID
AND r.rn = c.rn + 1)
SELECT *
FROM cte;
try this - it walks through the linked records, and finds if the 'walk' eventually terminates, or not. If it lasts for more than the number of records in the table, then it must be a loop. 'Efficient' I am not sure of that!
;WITH UCNT AS (SELECT count(0) c from products),
RNCTE
AS (SELECT 1 as Levle, Product_ID, INNER_PRODUCT_ID FROM Products
UNION ALL
SELECT levle + 1, P.Product_ID, P.INNER_PRODUCT_ID
FROM RNCTE R
JOIN Products P
ON P.PRODUCT_ID = R.INNER_PRODUCT_ID
WHERE levle <= (SELECT c + 2 FROM UCNT))
--when the recursion count levle exceeds the count of records in the table,
--we must have recursion, because
--termination has to otherwise occur. The most extreme case is
--that all records are linked, with termination
--after this, we have to be in a 'loop'
SELECT TOP 1 with ties * FROM RNCTE order by levle desc
option (maxrecursion 0)
I think you don't need to use CTE or RECUSRIVE CTE :
SELECT pr1.*
FROM TableName pr1
WHERE EXISTS (SELECT 1 FROM TableName pr2 WHERE pr2.INNER_PRODUCT_ID = pr1.PRODUCT_ID);

How to find the cumulative sum in SubQuery? [duplicate]

declare #t table
(
id int,
SomeNumt int
)
insert into #t
select 1,10
union
select 2,12
union
select 3,3
union
select 4,15
union
select 5,23
select * from #t
the above select returns me the following.
id SomeNumt
1 10
2 12
3 3
4 15
5 23
How do I get the following:
id srome CumSrome
1 10 10
2 12 22
3 3 25
4 15 40
5 23 63
select t1.id, t1.SomeNumt, SUM(t2.SomeNumt) as sum
from #t t1
inner join #t t2 on t1.id >= t2.id
group by t1.id, t1.SomeNumt
order by t1.id
SQL Fiddle example
Output
| ID | SOMENUMT | SUM |
-----------------------
| 1 | 10 | 10 |
| 2 | 12 | 22 |
| 3 | 3 | 25 |
| 4 | 15 | 40 |
| 5 | 23 | 63 |
Edit: this is a generalized solution that will work across most db platforms. When there is a better solution available for your specific platform (e.g., gareth's), use it!
The latest version of SQL Server (2012) permits the following.
SELECT
RowID,
Col1,
SUM(Col1) OVER(ORDER BY RowId ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS Col2
FROM tablehh
ORDER BY RowId
or
SELECT
GroupID,
RowID,
Col1,
SUM(Col1) OVER(PARTITION BY GroupID ORDER BY RowId ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS Col2
FROM tablehh
ORDER BY RowId
This is even faster. Partitioned version completes in 34 seconds over 5 million rows for me.
Thanks to Peso, who commented on the SQL Team thread referred to in another answer.
For SQL Server 2012 onwards it could be easy:
SELECT id, SomeNumt, sum(SomeNumt) OVER (ORDER BY id) as CumSrome FROM #t
because ORDER BY clause for SUM by default means RANGE UNBOUNDED PRECEDING AND CURRENT ROW for window frame ("General Remarks" at https://msdn.microsoft.com/en-us/library/ms189461.aspx)
Let's first create a table with dummy data:
Create Table CUMULATIVESUM (id tinyint , SomeValue tinyint)
Now let's insert some data into the table;
Insert Into CUMULATIVESUM
Select 1, 10 union
Select 2, 2 union
Select 3, 6 union
Select 4, 10
Here I am joining same table (self joining)
Select c1.ID, c1.SomeValue, c2.SomeValue
From CumulativeSum c1, CumulativeSum c2
Where c1.id >= c2.ID
Order By c1.id Asc
Result:
ID SomeValue SomeValue
-------------------------
1 10 10
2 2 10
2 2 2
3 6 10
3 6 2
3 6 6
4 10 10
4 10 2
4 10 6
4 10 10
Here we go now just sum the Somevalue of t2 and we`ll get the answer:
Select c1.ID, c1.SomeValue, Sum(c2.SomeValue) CumulativeSumValue
From CumulativeSum c1, CumulativeSum c2
Where c1.id >= c2.ID
Group By c1.ID, c1.SomeValue
Order By c1.id Asc
For SQL Server 2012 and above (much better performance):
Select
c1.ID, c1.SomeValue,
Sum (SomeValue) Over (Order By c1.ID )
From CumulativeSum c1
Order By c1.id Asc
Desired result:
ID SomeValue CumlativeSumValue
---------------------------------
1 10 10
2 2 12
3 6 18
4 10 28
Drop Table CumulativeSum
A CTE version, just for fun:
;
WITH abcd
AS ( SELECT id
,SomeNumt
,SomeNumt AS MySum
FROM #t
WHERE id = 1
UNION ALL
SELECT t.id
,t.SomeNumt
,t.SomeNumt + a.MySum AS MySum
FROM #t AS t
JOIN abcd AS a ON a.id = t.id - 1
)
SELECT * FROM abcd
OPTION ( MAXRECURSION 1000 ) -- limit recursion here, or 0 for no limit.
Returns:
id SomeNumt MySum
----------- ----------- -----------
1 10 10
2 12 22
3 3 25
4 15 40
5 23 63
Late answer but showing one more possibility...
Cumulative Sum generation can be more optimized with the CROSS APPLY logic.
Works better than the INNER JOIN & OVER Clause when analyzed the actual query plan ...
/* Create table & populate data */
IF OBJECT_ID('tempdb..#TMP') IS NOT NULL
DROP TABLE #TMP
SELECT * INTO #TMP
FROM (
SELECT 1 AS id
UNION
SELECT 2 AS id
UNION
SELECT 3 AS id
UNION
SELECT 4 AS id
UNION
SELECT 5 AS id
) Tab
/* Using CROSS APPLY
Query cost relative to the batch 17%
*/
SELECT T1.id,
T2.CumSum
FROM #TMP T1
CROSS APPLY (
SELECT SUM(T2.id) AS CumSum
FROM #TMP T2
WHERE T1.id >= T2.id
) T2
/* Using INNER JOIN
Query cost relative to the batch 46%
*/
SELECT T1.id,
SUM(T2.id) CumSum
FROM #TMP T1
INNER JOIN #TMP T2
ON T1.id > = T2.id
GROUP BY T1.id
/* Using OVER clause
Query cost relative to the batch 37%
*/
SELECT T1.id,
SUM(T1.id) OVER( PARTITION BY id)
FROM #TMP T1
Output:-
id CumSum
------- -------
1 1
2 3
3 6
4 10
5 15
Select
*,
(Select Sum(SOMENUMT)
From #t S
Where S.id <= M.id)
From #t M
You can use this simple query for progressive calculation :
select
id
,SomeNumt
,sum(SomeNumt) over(order by id ROWS between UNBOUNDED PRECEDING and CURRENT ROW) as CumSrome
from #t
There is a much faster CTE implementation available in this excellent post:
http://weblogs.sqlteam.com/mladenp/archive/2009/07/28/SQL-Server-2005-Fast-Running-Totals.aspx
The problem in this thread can be expressed like this:
DECLARE #RT INT
SELECT #RT = 0
;
WITH abcd
AS ( SELECT TOP 100 percent
id
,SomeNumt
,MySum
order by id
)
update abcd
set #RT = MySum = #RT + SomeNumt
output inserted.*
For Ex: IF you have a table with two columns one is ID and second is number and wants to find out the cumulative sum.
SELECT ID,Number,SUM(Number)OVER(ORDER BY ID) FROM T
Once the table is created -
select
A.id, A.SomeNumt, SUM(B.SomeNumt) as sum
from #t A, #t B where A.id >= B.id
group by A.id, A.SomeNumt
order by A.id
The SQL solution wich combines "ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW" and "SUM" did exactly what i wanted to achieve.
Thank you so much!
If it can help anyone, here was my case. I wanted to cumulate +1 in a column whenever a maker is found as "Some Maker" (example). If not, no increment but show previous increment result.
So this piece of SQL:
SUM( CASE [rmaker] WHEN 'Some Maker' THEN 1 ELSE 0 END)
OVER
(PARTITION BY UserID ORDER BY UserID,[rrank] ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS Cumul_CNT
Allowed me to get something like this:
User 1 Rank1 MakerA 0
User 1 Rank2 MakerB 0
User 1 Rank3 Some Maker 1
User 1 Rank4 Some Maker 2
User 1 Rank5 MakerC 2
User 1 Rank6 Some Maker 3
User 2 Rank1 MakerA 0
User 2 Rank2 SomeMaker 1
Explanation of above: It starts the count of "some maker" with 0, Some Maker is found and we do +1. For User 1, MakerC is found so we dont do +1 but instead vertical count of Some Maker is stuck to 2 until next row.
Partitioning is by User so when we change user, cumulative count is back to zero.
I am at work, I dont want any merit on this answer, just say thank you and show my example in case someone is in the same situation. I was trying to combine SUM and PARTITION but the amazing syntax "ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW" completed the task.
Thanks!
Groaker
Above (Pre-SQL12) we see examples like this:-
SELECT
T1.id, SUM(T2.id) AS CumSum
FROM
#TMP T1
JOIN #TMP T2 ON T2.id < = T1.id
GROUP BY
T1.id
More efficient...
SELECT
T1.id, SUM(T2.id) + T1.id AS CumSum
FROM
#TMP T1
JOIN #TMP T2 ON T2.id < T1.id
GROUP BY
T1.id
Try this
select
t.id,
t.SomeNumt,
sum(t.SomeNumt) Over (Order by t.id asc Rows Between Unbounded Preceding and Current Row) as cum
from
#t t
group by
t.id,
t.SomeNumt
order by
t.id asc;
Try this:
CREATE TABLE #t(
[name] varchar NULL,
[val] [int] NULL,
[ID] [int] NULL
) ON [PRIMARY]
insert into #t (id,name,val) values
(1,'A',10), (2,'B',20), (3,'C',30)
select t1.id, t1.val, SUM(t2.val) as cumSum
from #t t1 inner join #t t2 on t1.id >= t2.id
group by t1.id, t1.val order by t1.id
Without using any type of JOIN cumulative salary for a person fetch by using follow query:
SELECT * , (
SELECT SUM( salary )
FROM `abc` AS table1
WHERE table1.ID <= `abc`.ID
AND table1.name = `abc`.Name
) AS cum
FROM `abc`
ORDER BY Name

How to get the latest entry for each item for in a Month with a single SQL query [duplicate]

This question already has answers here:
Fetch the rows which have the Max value for a column for each distinct value of another column
(35 answers)
Closed 6 years ago.
I am trying to write a query to pick one entry for each item for each month but the latest in the month from the following table:
Name | Date | Value
a |2015-01-01 | 1
a |2015-01-02 | 2
b |2015-01-03 | 1
b |2015-01-04 | 1
b |2015-01-03 | 3
c |2015-01-02 | 2
c |2015-01-29 | 10
a |2015-02-10 | 2
a |2015-02-20 | 1
c |2015-02-10 | 2
c |2015-02-22 | 23
b |2015-02-25 | 1
b |2015-02-19 | 2
return should be:
a |2015-01-02 | 2
b |2015-01-04 | 1
c |2015-01-29 | 10
a |2015-02-20 | 1
b |2015-02-25 | 1
c |2015-02-22 | 23
I wonder how would this be achieved instead of sending multiple queries to SQL server for each month I would like to load all the values with one query then filter the collection on the memory. Otherwise I would end up writing a query as below:
SELECT Name,Date, Value FROM MyTable mt
INNER JOIN (
select max(Date) as MaxDate
FROM [MyTable] m WHERE YEAR(Date) =YEAR(#date)
AND MONTH(Date)=MONTH(#date)) mx ON t.Date = mx.MaxDate)
And this query needs to be run for each month.
Any better idea to return all entries with a single query?
Thanks,
Try grouping by year and month in the derived table:
SELECT t1.Name, t1.[Date], t1.Value
FROM MyTable t1
INNER JOIN (
SELECT Name, YEAR(Date) AS y, MONTH([Date]) AS m, MAX([Date]) as MaxDate
FROM MyTable
GROUP BY Name, YEAR(Date), MONTH([Date])
) t2 ON t1.Name = t2.Name AND
YEAR(t1.[Date]) = t2.y AND MONTH(t1.[Date]) = t2.m AND
t1.[Date] = t2.MaxDate
SELECT *
FROM (
SELECT NAME, DATE, VALUE,
ROW_NUMBER() OVER (PARTITION BY NAME, YEAR(Date), MONTH(Date)
ORDER BY Date DESC) rn
FROM MyTable) AS t
WHERE t.rn = 1
Assuming that you are using a SQL Server version that supports it, you can use the ROW_NUMBER() windowing function to return a sequence number for each row, then you can subsequently use that to restrict to only the rows that you require.
SELECT [Name],[Date],[Value]
,ROW_NUMBER() OVER (PARTITION BY [Name] ORDER BY [Date] DESC) AS [Seq]
FROM myTable
Things to consider:
What happens when there is a tie? ROW_NUMBER will always return a sequence number, but if your data has > 1 row at the same Date value, the order will be arbritrary. To solve this add additional tie-break ORDER BY entries
How do I filter this? Put it into a Common Table Expression, Inline View or Real View
I think you need a correlated query once you have a set of distinct (Name, Month). There are various ways of doing this, one is to use cross apply:
select *
from (select distinct Name, Month(Date) as Month
from theTable) itemMonths
cross apply (select Max(value)
from theTable t
where Month(t.Date) = itemMonths.Month
and t.Name = itemMonths.Name)
You could try the following:
WITH MyTable AS
(SELECT 'a' AS name, GETDATE() AS date, 1 AS value
UNION ALL
SELECT 'a', GETDATE()+1, 2
)
, res AS (
SELECT Name,date,MAX(Date) OVER(PARTITION BY Name, DATEPART(yyyy,date), DATEPART(mm, date)) AS max_date , Value FROM MyTable
)
SELECT name,date,res.value FROM res WHERE date=max_date
You still need a filter though as the Max Over will return all rows.
If you were using Teradata I'd suggest using the Qualify Clause but Itzik hasn't had any luck getting this ported to SQL server!
https://connect.microsoft.com/SQLServer/feedback/details/532474
Use Cross apply
SELECT b.*
FROM mytable mt
CROSS apply (SELECT TOP 1 NAME, date, value
FROM [mytable] m
WHERE m.NAME = mt.NAME
AND Month(m.date) = Month(mt.date)
AND Year(m.date) = Year(mt.date)
ORDER BY m.date DESC) b

SQL Server - Select most recent records with condition

I have a table like this.
Table :
ID EnrollDate ExitDate
1 4/1/16 8/30/16
2 1/1/16 null
2 1/1/16 7/3/16
3 2/1/16 8/1/16
3 2/1/16 9/1/16
4 1/1/16 12/12/16
4 1/1/16 12/12/16
4 1/1/16 12/12/16
4 1/1/16 null
5 5/1/16 11/12/16
5 5/1/16 11/12/16
5 5/1/16 11/12/16
Need to select the most recent records with these conditions.
One and only one record has the most recent enroll date - select that
Two or more share same most recent enroll date and one and only one record has either a NULL Exit Date or the most recent Exit Date - Select the record with null. If no null record pick the record with recent exit date
Two or more with same enroll and Exit Date - If this case exists, don't select those record
So the expected result for the above table should be :
ID EnrollDate ExitDate
1 4/1/16 8/30/16
2 1/1/16 null
3 2/1/16 9/1/16
4 1/1/16 null
I wrote the query with group by. I am not sure how to select with the conditions 2 and 3.
select t1.* from table t1
INNER JOIN(SELECT Id,MAX(EnrollDate) maxentrydate
FROM table
GROUP BY Id)t2 ON EnrollDate = t2.maxentrydate and t1.Id=t2.Id
Please let me know what is the best way to do this.
Using the rank() window function, I think it's possible.
This is untested, but it should work:
select t.ID, t.EnrollDate, t.ExitDate
from (select t.*,
rank() over(
partition by ID
order by EnrollDate desc,
case when ExitDate is null then 1 else 2 end,
ExitDate desc) as rnk
from tbl t) t
where t.rnk = 1
group by t.ID, t.EnrollDate, t.ExitDate
having count(*) = 1
The basic idea is that the rank() window function will rank the most "recent" rows with a value of 1, which we filter on in the outer query's where clause.
If more than one row have the same "most recent" data, they will all share the same rank of 1, but will get filtered out by the having count(*) = 1 clause.
Use ROW_NUMBER coupled with CASE expression to achieve the desired result:
WITH Cte AS(
SELECT t.*,
ROW_NUMBER() OVER(
PARTITION BY t.ID
ORDER BY
t.EnrollDate DESC,
CASE WHEN t.ExitDate IS NULL THEN 0 ELSE 1 END,
t.ExitDate DESC
) AS rn
FROM Tbl t
INNER JOIN (
SELECT
ID,
COUNT(DISTINCT CHECKSUM(EnrollDate, ExitDate)) AS DistinctCnt, -- Count distinct combination of EnrollDate and ExitDate per ID
COUNT(*) AS RowCnt -- Count number of rows per ID
FROM Tbl
GROUP BY ID
) a
ON t.ID = a.ID
WHERE
(a.DistinctCnt = 1 AND a.RowCnt = 1)
OR a.DistinctCnt > 1
)
SELECT
ID, EnrollDate, ExitDate
FROM Cte c
WHERE Rn = 1
The ORDER BY clause in the ROW_NUMBER takes care of conditions 2 and 3.
The INNER JOIN and the WHERE clause take care of 1 and 4.
ONLINE DEMO
with B as (
select id, enrolldate ,
exitdate,
row_number() over (partition by id order by enrolldate desc, case when exitdate is null then 0 else 1 end, exitdate desc) rn
from ab )
select b1.id, b1.enrolldate, b1.exitdate from b b1
left join b b2
on b1.rn = b2.rn -1 and
b1.id = b2.id and
b1.exitdate = b2.exitdate and
b1.enrolldate = b2.enrolldate
where b1.rn = 1 and
b2.id is nULL
The left join is used to fullfill the 3) requirement. When record is returned then we don't want it.

Finding which COLUMN has a max( value per row

MSSQL
Table looks like so
ID 1 | 2 | 3 | 4 | 5
AA1 1 | 1 | 1 | 2 | 1
any clues on how I could make a query to return
ID | MaxNo
AA1 | 4
, usign the above table example? I know I could write a case blah when statement, but I have a feeling there's a much simpler way of doing this
You can use UNPIVOT to get these comparable items, correctly1, into the same column, and then use ROW_NUMBER() to find the highest valued row2:
declare #t table (ID char(3) not null,[1] int not null,[2] int not null,
[3] int not null,[4] int not null,[5] int not null)
insert into #t (ID,[1],[2],[3],[4],[5]) values
('AA1',1,1,1,2,1)
;With Unpivoted as (
select *,ROW_NUMBER() OVER (ORDER BY Value desc) rn
from #t t UNPIVOT (Value FOR Col in ([1],[2],[3],[4],[5])) u
)
select * from Unpivoted where rn = 1
Result:
ID Value Col rn
---- ----------- ------------------------- --------------------
AA1 2 4 1
1 If you have data from the same "domain" appearing in multiple columns in the same table (such that it even makes sense to compare such values), it's usually a sign of attribute splitting, where part of your data has, incorrectly, been used to form part of a column name.
2 In your question, you say "per row", and yet you've only given a one row sample. If we assume that ID values are unique for each row, and you want to find the maximum separately for each ID, you'd write the ROW_NUMBER() as ROW_NUMBER() OVER (PARTITION BY ID ORDER BY Value desc) rn, to get (I hope) the result you're looking for.
You can use a cross apply where you do max() over the columns for one row.
select T1.ID,
T2.Value
from YourTable as T1
cross apply
(
select max(T.Value) as Value
from (values (T1.[1]),
(T1.[2]),
(T1.[3]),
(T1.[4]),
(T1.[5])) as T(Value)
) as T2
If you are on SQL Server 2005 you can use union all in the derived table instead of values().
select T1.ID,
T2.Value
from YourTable as T1
cross apply
(
select max(T.Value) as Value
from (select T1.[1] union all
select T1.[2] union all
select T1.[3] union all
select T1.[4] union all
select T1.[5]) as T(Value)
) as T2
SQL Fiddle

Resources