Get Numbers Range from Table in MSSQL - sql-server

I have a table in MSSQL 2008R2:
ID | PinAddress
-------------------------------------
1 | 1
1 | 2
1 | 3
1 | 4
1 | 5
1 | 6
1 | 16
1 | 31
2 | 55
2 | 56
2 | 57
2 | 81
2 | 82
2 | 83
2 | 84
3 | 101
3 | 102
3 | 103
3 | 107
3 | 108
3 | 109
What I want is when I search for ID = 1,I want result like
1-6,16,31
When I search for ID = 2,I want result like
55-57,81-84
When I search for ID = 3,I want result like
101-103,107-109
You can use below script to create table and data:
CREATE TABLE PinAddress(ID INT,PinAddress INT)
INSERT INTO PinAddress values(1,1)
INSERT INTO PinAddress values(1,2)
INSERT INTO PinAddress values(1,3)
INSERT INTO PinAddress values(1,4)
INSERT INTO PinAddress values(1,5)
INSERT INTO PinAddress values(1,6)
INSERT INTO PinAddress values(1,16)
INSERT INTO PinAddress values(1,31)
INSERT INTO PinAddress values(2,55)
INSERT INTO PinAddress values(2,56)
INSERT INTO PinAddress values(2,57)
INSERT INTO PinAddress values(2,81)
INSERT INTO PinAddress values(2,82)
INSERT INTO PinAddress values(2,83)
INSERT INTO PinAddress values(2,84)
INSERT INTO PinAddress values(3,101)
INSERT INTO PinAddress values(3,102)
INSERT INTO PinAddress values(3,103)
INSERT INTO PinAddress values(3,107)
INSERT INTO PinAddress values(3,108)
INSERT INTO PinAddress values(3,109)
Thanks

This is a gaps and islands problem, and the key is identifying your continuous ranges, which is done using ROW_NUMBER(). So for ID 3, you have:
ID PinAddress RowNumber
---------------------------
3 101 1
3 102 2
3 103 3
3 107 4
3 108 5
3 109 6
And deducting the row number from the pin address will give you a constant value for each continuous range:
ID PinAddress RowNumber (PinAddress - RowNumber)
---------------------------------------------------
3 101 1 100
3 102 2 100
3 103 3 100
---------------------------------------------------
3 107 4 103
3 108 5 103
3 109 6 103
The query thus far is simply:
SELECT ID,
PinAddress,
GroupingSet = PinAddress - ROW_NUMBER() OVER(PARTITION BY ID ORDER BY PinAddress)
FROM dbo.PinAddress;
Then you can group by your constant value and ID, and use MIN and MAX to get the start and end of each range:
WITH RankedData AS
( SELECT ID,
PinAddress,
GroupingSet = PinAddress - ROW_NUMBER() OVER(PARTITION BY ID ORDER BY PinAddress)
FROM PinAddress
)
SELECT ID,
RangeStart = MIN(PinAddress),
RangeEnd = MAX(PinAddress),
RangeText = CONVERT(VARCHAR(10), MIN(PinAddress)) +
CASE WHEN MIN(PinAddress) = MAX(PinAddress) THEN ''
ELSE ' - ' + CONVERT(VARCHAR(10), MAX(PinAddress))
END
FROM RankedData
GROUP BY ID, GroupingSet;
Which, for ID 3 gives:
ID RangeStart RangeEnd RangeText
-----------------------------------------
3 101 103 101 - 103
3 107 109 107 - 109
Finally, you need to concatenate the RangeText values into a single row, which can be done using SQL Server's XML Extensions.
WITH RankedData AS
( SELECT ID,
PinAddress,
GroupingSet = PinAddress - ROW_NUMBER() OVER(PARTITION BY ID ORDER BY PinAddress)
FROM PinAddress
)
SELECT p.ID,
Ranges = STUFF((SELECT ', ' + CONVERT(VARCHAR(10), MIN(PinAddress)) +
CASE WHEN MIN(PinAddress) = MAX(PinAddress) THEN ''
ELSE ' - ' + CONVERT(VARCHAR(10), MAX(PinAddress))
END
FROM RankedData AS rd
WHERE rd.ID = p.ID
GROUP BY ID, GroupingSet
FOR XML PATH(''), TYPE).value('.', 'VARCHAR(MAX)'), 1, 2, '')
FROM (SELECT DISTINCT ID FROM PinAddress) AS p;
Which gives:
ID Ranges
------------------------------
1 1 - 6, 16 - 16, 31 - 31
2 55 - 57, 81 - 84
3 101 - 103, 107 - 109

Try this code
DECLARE #values VARCHAR(8000)
DECLARE #prevseq int
SET #values = ''
SELECT #values = #values +
(CASE WHEN #values = '' OR #values like '%,' THEN cast(PinAddress as varchar) --first value or new after sequence
WHEN PinAddress - 1 = #prevseq THEN ''
ELSE '-' + cast (#prevseq as varchar) + ',' + cast(PinAddress as varchar)
END),
#prevseq = coalesce(PinAddress, -1)
FROM PinAddress
WHERE ID = 1
ORDER BY PinAddress ASC
SELECT #values = #values +
(CASE WHEN #values not like '%' + cast(#prevseq as varchar) THEN '-' + cast(#prevseq as varchar) ELSE '' END)
PRINT #values

#GarethD your logic with ROW_NUMBER was excellent and works like a charm.
I took help of your query and changed it little bit to get my desired output:
WITH RankedData AS
(
SELECT ID,
PinAddress,
GroupingSet = PinAddress - ROW_NUMBER() OVER(PARTITION BY ID ORDER BY PinAddress)
FROM dbo.PinAddress
WHERE ID = 1
)
SELECT p.ID,
Ranges = STUFF(
(
SELECT CASE WHEN MIN(pinaddress) = MAX(PINADDRESS) THEN
', ' + CONVERT(VARCHAR(10), MIN(PinAddress))
ELSE
', ' + CONVERT(VARCHAR(10), MIN(PinAddress)) + '-' +
CONVERT(VARCHAR(10), MAX(PinAddress))
END
FROM RankedData AS rd
WHERE rd.ID = p.ID
GROUP BY
ID,
GroupingSet
FOR XML PATH(''),
TYPE
).value('.', 'VARCHAR(MAX)'),
1,
2,
''
)
FROM (
SELECT DISTINCT ID
FROM RankedData
) AS p;

Basic Version, If you want to fetch the result set for all ID's then create a scalar function.
declare #id int = 1
declare #formed varchar(max)
Select #Formed = ISNULL(#formed+',','')+Formed
from
(
Select
Formed = convert(varchar,MIN([PinAddress]))
+case when MIN([PinAddress]) != MAX([PinAddress]) then '-'
+convert(varchar,MAX([PinAddress] )) else '' end
from PinAddress where ID = #id
group by [PinAddress]/case when [#PinAddress]/10= 0 then 10 else 5 end)t
select #formed

Related

Pivot Table With multiple Values for a number of x columns

i have a problem with pivot table in SQL Server:
I have a table with the next information (i dont know how much different values could have column1):
Column1 Value
---------------
PRODUCT_4 1
PRODUCT_4 2
PRODUCT_4 3
PRODUCT_6 10
PRODUCT_6 20
PRODUCT_6 30
PRODUCT_8 100
PRODUCT_8 200
PRODUCT_8 300
... ...
PRODUCT_X 1
PRODUCT_X 2
PRODUCT_X 3
I want to transform this into a pivot table to get the next output:
product_4 product_6 product_8 ... product_x
1 10 100 1
2 20 200 2
3 30 300 3
I was using the next query:
DECLARE
#cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(column1)
from dbo.Context_Table
group by column1
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
print #cols
set #query = N'SELECT ' + #cols + N' from
(
select value, Column1
from dbo.Context_Table
) x
pivot
(
max(value)
for Column1 in (' + #cols + N')
) p '
exec sp_executesql #query;
But the ouput is not that i expected...
PRODUCT_4 PRODUCT_6 PRODUCT_8 PRODUCT_X
3 30 300 3
My question is... how can i get all the values without using the aggregate function MAX?
Thanks all!
add ROW_NUMBER() to your dynamic pivot:
set #query = N'SELECT ' + #cols + N' from
(
select ROW_NUMBER() OVER (PARTITION BY Column1 ORDER BY value) RN , value, Column1
from ##T
) x
pivot
(
max([value])
for Column1 in (' + #cols + N')
) p '
exec sp_executesql #query;
You must add a changing column to your query, I do this with ROW_NUMBER():
Just try it out:
DECLARE #tbl TABLE(Column1 VARCHAR(100),[Value] INT)
INSERT INTO #tbl VALUES
('PRODUCT_4',1)
,('PRODUCT_4',2)
,('PRODUCT_4',3)
,('PRODUCT_6',10)
,('PRODUCT_6',20)
,('PRODUCT_6',30)
,('PRODUCT_8',100)
,('PRODUCT_8',200)
,('PRODUCT_8',300)
,('PRODUCT_X',1)
,('PRODUCT_X',2)
,('PRODUCT_X',3)
SELECT p.*
FROM
(
SELECT ROW_NUMBER() OVER(PARTITION BY Column1 ORDER BY [Value]) AS ValueInx
,*
FROM #tbl AS t
) AS tbl
PIVOT
(
MAX([Value]) FOR Column1 IN(PRODUCT_4,PRODUCT_6,PRODUCT_8,PRODUCT_X)
) AS p:
The result
+----------+-----------+-----------+-----------+-----------+
| ValueInx | PRODUCT_4 | PRODUCT_6 | PRODUCT_8 | PRODUCT_X |
+----------+-----------+-----------+-----------+-----------+
| 1 | 1 | 10 | 100 | 1 |
+----------+-----------+-----------+-----------+-----------+
| 2 | 2 | 20 | 200 | 2 |
+----------+-----------+-----------+-----------+-----------+
| 3 | 3 | 30 | 300 | 3 |
+----------+-----------+-----------+-----------+-----------+
You will easily integrate this into your dyanmic approach...
Try this/ Or something along these lines.
select PRODUCT_4,PRODUCT_6,PRODUCT_8
from
(select name,value,row_number() over (partition by name order by value) as id from product) p
pivot
(
max(value)
for name
IN( PRODUCT_4,PRODUCT_6,PRODUCT_8)
)
as a
Hope this helps.
Thanks.

SQL, using rank to generate columns

I have a list of occupants of a property and need to manipulate the data so it instead shows the property as one row with each additional occupant appearing in a new column.
Here is what I've managed to do so far:
with RANKING AS
( Select
Postcode
, Number
, Occupant
, RANK() OVER
(Partition by Postcode order by Occupant) as [Rank]
from Reporting.dbo.Test --order by [Rank] desc
)
The query in RANKING outputs the following table:
Postcode | Number | Occupant | Rank
AA001AA | 12 | D | 1
AA001AA | 12 | E | 2
AA001AA | 12 | K | 3
AA001AA | 12 | M | 4
AA001AA | 12 | T | 5
BB001BB | 8 | M | 1
BB001BB | 8 | R | 2
etc.
I've then tried to use the value of ranking to create columns, like so:
Select distinct
i.Postcode
, i.Number
, case when i.[rank] = 1 then i.Occupant end as [First Tennant]
, case when i.[rank] = 2 then i.Occupant end as [Second Tennant]
, case when i.[rank] = 3 then i.Occupant end as [Third Tennant]
, case when i.[rank] = 4 then i.Occupant end as [Fourth Tennant]
, case when i.[rank] = 5 then i.Occupant end as [Fifth Tennant]
from Reporting.dbo.Test u
inner join RANKING i on i.Postcode = u.Postcode
Now, 2 questions:
1) Is there any way to automate this process so for a rank of x we have x tenant rows
2) The table this outputs is
Postcode | Number | First Tennant | Second Tennant | Third Tennant | Fourth Tennant | Fifth Tennant |
AA001AA | 12 | D | NULL | NULL | NULL | NULL |
AA001AA | 12 | NULL | E | NULL | NULL | NULL |
etc.
How do I condense this list so each postcode only has one row and all the non-null values appear on that row.
To answer your second question first (how do you condense the list), the easiest way would just to group by postcode and number. You can just take the max() of each column (i.e if there's a value, it gets chosen; all the nulls fall out).
To answer the first question (can this be automated), you probably want to look into a dynamic pivot. One such article posted in the comments is this: SQL Server dynamic PIVOT query?
The idea is basically to serialize the distinct tennants, and concantenate that into a dynamic SQL string which performs a pivot. There are several ways to skin this cat, but here's how I approached it.
use tempdb
go
if object_id('tempdb.dbo.#data') is not null drop table #data
create table #data
(
PostCode varchar(10),
Number int,
Occupant char(1),
Rnk int,
ColName as 'Tennant ' + cast(Rnk as varchar(10))
)
insert into #Data(PostCode, Number, Occupant, Rnk)
select 'AA001AA', 12, 'D',1
union all select 'AA001AA', 12, 'E',2
union all select 'AA001AA', 12, 'K',3
union all select 'AA001AA', 12, 'M',4
union all select 'AA001AA', 12, 'T',5
union all select 'BB001BB', 8, 'M',1
union all select 'BB001BB', 8, 'R',2
declare
#PivotColumns nvarchar(max),
#SelectColumns nvarchar(max),
#sql nvarchar(max)
select
#PivotColumns = stuff((select ',' + quotename(ColName)
from #data
group by ColName
order by ColName
for xml path('')), 1, 1, ''),
#SelectColumns = stuff((select ',' + quotename(ColName) + ' = max(' + quotename(ColName) + ')'
from #data
group by ColName
order by ColName
for xml path('')), 1, 1, ''),
#sql = '
select
PostCode,
Number,
' + #SelectColumns + '
from #data d
pivot (max(Occupant) for ColName in (' + #PivotColumns + ' )) p
group by PostCode, Number'
print #sql
exec sp_executesql #sql
you can use dynamic pivot to get your result. please see below code-
create table #tab (Postcode varchar(10) , Number int , Occupant char(1) , Rank int)
insert into #tab
select 'AA001AA' , 12 , 'D' , 1
union all select 'AA001AA' , 12 , 'E' , 2
union all select 'AA001AA' , 12 , 'K' , 3
union all select 'AA001AA' , 12 , 'M' , 4
union all select 'AA001AA' , 12 , 'T' , 5
union all select 'BB001BB' , 8 , 'M' , 1
union all select 'BB001BB' , 8 , 'R' , 2
union all select 'CC001CC' , 8 , 'N' , 1
union all select 'CC001CC' , 8 , 'O' , 2
union all select 'CC001CC' , 8 , 'P' , 3
union all select 'CC001CC' , 8 , 'Q' , 4
union all select 'CC001CC' , 8 , 'R' , 5
union all select 'CC001CC' , 8 , 'S' , 6
union all select 'CC001CC' , 8 , 'T' , 7
union all select 'CC001CC' , 8 , 'U' , 8
union all select 'CC001CC' , 8 , 'V' , 9
union all select 'CC001CC' , 8 , 'W' , 10
declare #mx int , #min int = 1 , #sql nvarchar(max) = '' , #select1 nvarchar(max) = '',#select2 nvarchar(max) = ''
select #mx = MAX(rank) from #tab
while #min<= #mx
begin
set #select1 = #select1 + '[' + cast(#min as varchar(10))+ '] ,'
set #select2 = #select2 + '[' + cast(#min as varchar(10))+ '] as '+ '[Tennant_' + cast(#min as varchar(10))+ '] ,'
set #min = #min + 1
end
set #select1 = SUBSTRING( #select1 , 1 , LEN(#select1)-1)
set #select2 = SUBSTRING( #select2 , 1 , LEN(#select2)-1)
set #sql = '
SELECT Postcode , Number ,'+#select2+'
FROM #tab
PIVOT
(
max(Occupant)
FOR [Rank] IN ('+#select1+')
)AS pvt '
exec sp_executesql #sql

Make query like treeview

Brothers can you help me? Thanks
Table A
Id Name IdParent
1 Operation Null
2 Developer 1
3 Android 2
4 IOS 2
Expectes result:
ID Name
1 +Operation
2 +------ Developer
3 +------------Android
4 +------------ IOS
By adding a sequence during the recursive build, you can easily create the proper presentation sequence and nesting
Declare #YourTable table (id int,IdParent int,Name varchar(50))
Insert into #YourTable values
( 1, NULL,'Operation')
,( 2, 1 ,'Developer')
,( 3, 2 ,'Android')
,( 4, 2 ,'IOS')
,( 5, 1 ,'Poet')
,( 6, 5 ,'Limerick')
,( 7, 5 ,'Haiku')
Declare #Top int = null --<< Sets top of Hier Try 2
Declare #Nest varchar(25) = '|-----' --<< Optional: Added for readability
;with cteP as (
Select Seq = cast(10000+Row_Number() over (Order by Name) as varchar(500))
,ID
,IdParent
,Lvl=1
,Name
From #YourTable
Where IsNull(#Top,-1) = case when #Top is null then isnull(IdParent ,-1) else ID end
Union All
Select Seq = cast(concat(p.Seq,'.',10000+Row_Number() over (Order by r.Name)) as varchar(500))
,r.ID
,r.IdParent
,p.Lvl+1
,r.Name
From #YourTable r
Join cteP p on r.IdParent = p.ID)
Select A.ID
,A.IdParent
,A.Lvl
,Name = Replicate(#Nest,A.Lvl-1) + A.Name
From ctep A
Order By A.Seq
Returns
ID IdParent Lvl Name
1 NULL 1 Operation
2 1 2 |-----Developer
3 2 3 |-----|-----Android
4 2 3 |-----|-----IOS
5 1 2 |-----Poet
7 5 3 |-----|-----Haiku
6 5 3 |-----|-----Limerick
Here's another version:
WITH RawData AS (
SELECT 1 AS Id, 'Operation' AS Name, CONVERT(INT, NULL) AS IdParent
UNION ALL
SELECT 2 AS Id, 'Developer' AS Name, 1 AS IdParent
UNION ALL
SELECT 3 AS Id, 'Android' AS Name, 2 AS IdParent
UNION ALL
SELECT 4 AS Id, 'IOS' AS Name, 2 AS IdParent),
Depth AS (
SELECT
Id,
1 AS depth,
IdParent
FROM
RawData
UNION ALL
SELECT
d.Id,
d.depth + 1,
r.IdParent
FROM
Depth d
INNER JOIN RawData r ON r.Id = d.IdParent),
MaxDepth AS (
SELECT
Id,
MAX(depth) AS depth
FROM
Depth
GROUP BY
Id)
SELECT
r.Id,
'+' + REPLICATE('----', m.depth - 1) + r.Name AS Name
FROM
RawData r
INNER JOIN MaxDepth m ON m.Id = r.Id;
Results:
Id Name
1 +Operation
2 +----Developer
3 +--------Android
4 +--------IOS
DECLARE #mockup TABLE(Id INT, Name VARCHAR(100), IdParent INT);
INSERT INTO #mockup VALUES
(1,'Operation',Null)
,(2,'Developer',1)
,(3,'Android',2)
,(4,'IOS',2);
--The query uses a recursive CTE and finally REPLICATE with the recursive level to add the number of hyphens...
WITH recCTE AS
(
SELECT Id, Name, 1 AS Lvl, CAST(REPLACE(STR(ROW_NUMBER() OVER (ORDER BY Id),5),' ','0') AS VARCHAR(MAX)) AS Seq
FROM #mockup
WHERE IdParent IS NULL
UNION ALL
SELECT m.Id,m.Name,r.Lvl +1,r.Seq + '.' + REPLACE(STR(ROW_NUMBER() OVER (ORDER BY m.Id),5),' ','0')
FROM #mockup AS m
INNER JOIN recCTE AS r ON m.IdParent=r.Id
)
SELECT *
,'+' + REPLICATE('-',Lvl*4) + Name
FROM recCTE
ORDER BY Seq
the result
+----+-----------+-----+----------------------+
| Id | Name | Lvl | (Kein Spaltenname) |
+----+-----------+-----+----------------------+
| 1 | Operation | 1 | +----Operation |
+----+-----------+-----+----------------------+
| 2 | Developer | 2 | +--------Developer |
+----+-----------+-----+----------------------+
| 3 | Android | 3 | +------------Android |
+----+-----------+-----+----------------------+
| 4 | IOS | 3 | +------------IOS |
+----+-----------+-----+----------------------+

Getting a minimum total number of rows either side of a specific row

I have a table of players each having an ID (indexed primary key), a name, and a score. The table is not sorted except by index. e.g.
[dbo].[PlayerScores]
ID | Name | Score
=================
1 | Bob | 17
2 | Carl | 24
3 | Ann | 31
4 | Joan | 11
5 | Lou | 17
6 | Dan | 25
7 | Erin | 33
8 | Fred | 29
I've defined a leaderboard such that all of the players are ordered by their score and assigned a rank, so I'm using the RANK() function:
SELECT RANK() OVER (ORDER BY [Score] DESC) AS [Score_Rank],
[Name],
[Score]
FROM [dbo].[PlayerScores]
So far so good. For the above data, I'll get
Rank | Name | Score
=================
1 | Erin | 33
2 | Ann | 31
3 | Fred | 29
4 | Dan | 25
5 | Carl | 24
6 | Bob | 17
6 | Lou | 17
8 | Joan | 11
However, when I present this leaderboard to the players, I don't need or want to show them everything - only the players immediately above and below them (there won't be any paged navigation - players only get to see a snapshot of their overall position).
I'm therefore trying to retrieve (n) rows of data surrounding a specific player, such that:
If there are (n) or fewer rows in the table, all rows will be returned.
Where there are at least (n) rows in the table, (n) rows of data will be returned.
There should be (n/2) rows above and below the specified player.
If there aren't (n/2) rows above the specified player, return all the rows above, and enough rows below to make up (n) rows total.
If there aren't (n/2) rows below the specified player, return all the rows below, and enough rows above to make up (n) rows total.
How can I construct my query such that I can always return the minimum number of rows? E.g. for my above dataset and n=5, Erin would see
Rank | Name | Score
=================
1 | Erin | 33
2 | Ann | 31
3 | Fred | 29
4 | Dan | 25
5 | Carl | 24
While Dan would see
Rank | Name | Score
=================
2 | Ann | 31
3 | Fred | 29
4 | Dan | 25
5 | Carl | 24
6 | Bob | 17
And Lou would see
Rank | Name | Score
=================
4 | Dan | 25
5 | Carl | 24
6 | Bob | 17
6 | Lou | 17
8 | Joan | 11
I found a partial solution for this using a UNION on two queries (one getting n/2 rows above and one getting n/2 rows below the specified player), but it falls down if the player is at (or near) the top or bottom of the table - the resulting dataset is clipped, and I always want to retrieve a full (n) rows where possible.
I think the solution might have something to do with Window functions, making use of LAG and LEAD, but I honestly can't get my head around the syntax and most of the examples I've found don't care about not returning enough rows total. Thanks!
sql rank vs row number
Two versions of the same procedure, one outputs the result set in order, the second does not.
rextester link to try it out: http://rextester.com/JLQU48329
create table dbo.PlayerScores (Id int, Name nvarchar(64), Score int)
insert into dbo.PlayerScores (Id, Name, Score) values
(1,'Bob',17) ,(2,'Carl',24) ,(3,'Ann',31) ,(4,'Joan',11)
,(5,'Lou',17) ,(6,'Dan',25) ,(7,'Erin',33) ,(8,'Fred',29);
go
/* ordered resultset */
create procedure dbo.PlayerScores_getMiddle_byId (#PlayerId int, #Results int = 5) as
begin;
with cte as (
select
Score_Order = row_number() over (order by Score desc)
, Score_Rank = rank() over (order by Score desc)
, Id
, Name
, Score
from dbo.PlayerScores
)
select c.Score_Rank, c.Name, c.Score
from (
select top (#Results) i.*
from cte i
cross apply (select Score_Order from cte where Id = #PlayerId) as x
order by abs(i.Score_Order-x.Score_Order)
) as c
order by Score_Rank;
end
go
exec dbo.PlayerScores_getMiddle_byId 7,5; -- Erin
exec dbo.PlayerScores_getMiddle_byId 6,5; --Dan
exec dbo.PlayerScores_getMiddle_byId 5,5; --Lou
go
/* unordered result set */
/*
create procedure dbo.PlayerScores_getMiddle_byId (#PlayerId int,#Results int = 5) as
begin;
with cte as (
select
Score_Order = row_number() over (order by Score desc)
, Score_Rank = rank() over (order by Score desc)
, Id
, Name
, Score
from dbo.PlayerScores
)
select top (#Results) c.Score_Rank, c.Name, c.Score
from cte as c
cross apply (select
Score_Order
from cte
where Id = #PlayerId) as x
order by abs(c.Score_Order-x.Score_Order)
end
--go
exec dbo.PlayerScores_getMiddle_byId 7,5; -- Erin
exec dbo.PlayerScores_getMiddle_byId 6,5; --Dan
exec dbo.PlayerScores_getMiddle_byId 5,5; --Lou
--*/
This will do what you want.
WITH cte AS (
SELECT RANK() OVER (ORDER BY [Score] DESC) AS [Score_Rank],
ROW_NUMBER() OVER (ORDER BY [Score] DESC) AS [RowNum],
COUNT(ID) OVER (PARTITION BY (Select NULL)) AS MaxRow,
[Name],
[Score],
[ID]
FROM #playScores
)
SELECT Score_Rank, Name, Score
FROM
cte
CROSS APPLY (SELECT RowNum AS AnchorRN FROM cte WHERE ID = #playerID) tmp
WHERE
(
RowNum <=
CASE WHEN tmp.AnchorRN < ((#n)/2) THEN #n
ELSE tmp.AnchorRN + ((#n)/2) END
)
AND
(
RowNum >=
CASE WHEN tmp.AnchorRN > (MaxRow - (#n)/2) THEN (MaxRow -#n + 1)
ELSE tmp.AnchorRN - ((#n)/2) END
);
SELECT *
, ROW_NUMBER() OVER (ORDER BY Score) AS RowNum
FROM
#playScores
ORDER BY
RowNum;
This is the whole answer and test code.
DECLARE #playScores TABLE (
ID INT
, Name NVARCHAR(50)
, Score INT
);
INSERT INTO #playScores (ID, Name, Score)
VALUES
(1 ,' Bob ', 17),
(2 ,' Carl ', 24),
(3 ,' Ann ', 31),
(4 ,' Joan ', 11),
(5 ,' Lou ', 17),
(6 ,' Dan ', 25),
(7 ,' Erin ', 33),
(8 ,' Fred ', 29);
DECLARE #n INT = 5;
DECLARE #playerID INT =5;
SELECT *
FROM
#playScores
ORDER BY
Score DESC;
WITH cte AS (
SELECT RANK() OVER (ORDER BY [Score] DESC) AS [Score_Rank],
ROW_NUMBER() OVER (ORDER BY [Score] DESC) AS [RowNum],
COUNT(ID) OVER (PARTITION BY (Select NULL)) AS MaxRow,
[Name],
[Score],
[ID]
FROM #playScores
)
SELECT Score_Rank, Name, Score
FROM
cte
CROSS APPLY (SELECT RowNum AS AnchorRN FROM cte WHERE ID = #playerID) tmp
WHERE
(
RowNum <=
CASE WHEN tmp.AnchorRN < ((#n)/2) THEN #n
ELSE tmp.AnchorRN + ((#n)/2) END
)
AND
(
RowNum >=
CASE WHEN tmp.AnchorRN > (MaxRow - (#n)/2) THEN (MaxRow -#n + 1)
ELSE tmp.AnchorRN - ((#n)/2) END
);
SELECT *
, ROW_NUMBER() OVER (ORDER BY Score) AS RowNum
FROM
#playScores
ORDER BY
RowNum;
SELECT *
, ROW_NUMBER() OVER (ORDER BY Score) AS RowNum
FROM
#playScores
ORDER BY
RowNum;
or use standard SQL:
with pRank(id, name, rank)
as (Select p.Id, p.Name nam,
(Select count(*) from players
where score <= p.score) rnk
from players p)
Select p.id, p.nam, p.score,
n.id, n.nam, n.score
from pRank p join pRank n
on n.Rnk between
case when p.Rnk < #n/2 then 0
else p.Rnk - #n / 2 end
and case when p.Rnk < #n/2 then #n
else p.Rnk + #n / 2 end
order by p.rnk, p.Id, n.rnk
Test:
declare #t table
(id integer primary key not null,
nam varchar(30) not null, score int not null)
insert #t(id, nam, score)
values
(1, 'Bob ',17),
(2, 'Carl',24),
(3, 'Ann ',31),
(4, 'Joan',11),
(5, 'Lou ',17),
(6, 'Dan ',25),
(7, 'Erin',33),
(8, 'Fred',29)
declare #n int = 4;
with pRank(id, nam, rnk)
as (Select p.Id, p.Nam,
(Select count(*) from #t
where score <= p.score) rank
from #t p)
Select p.id, p.Nam, p.rnk,
n.id, n.nam, n.rnk
from pRank p join pRank n
on n.rnk between
case when p.rnk < #n/2 then 0
else p.rnk - #n / 2 end
and case when p.rnk < #n/2 then #n
else p.rnk + #n / 2 end
order by p.rnk, p.id, n.rnk .

SQL combine two tables into one table based on the instruction of a third table

Simplified problem with minimal information is as follows:
I have 2 source tables:
Table A:
Col: 1 2 3 4 5
Data: 18 15 16 17 10
Table B:
Col: 1 2 3 4 5
Data: 81 51 61 71 99
And a third table that contains "instructions":
Table 3:
ID Source
1 A
2 A
3 B
4 A
5 B
Based on what Table 3 tells me, I need to pick values from table A and B, to form a result table:
Col: 1 2 3 4 5
Data: 18 15 61 17 99
Try this -
Schema
create table TableA (col int, data1 int);
create table TableB (col int, data1 int);
create table TableC (col int, Source1 varchar(100));
insert into TableA values (1, 18), (2, 15), (3, 16);
insert into TableB values (1, 81), (2, 51), (3, 61);
insert into TableC values (1, 'A'), (2, 'A'), (3, 'B');
Query
SELECT o.col
,CASE
WHEN o.Source1 = 'A'
THEN a.data1
ELSE b.data1
END data
FROM TableC o
LEFT JOIN TableA a ON o.col = a.col
AND o.Source1 = 'A'
LEFT JOIN TableB b ON o.col = b.col
AND o.Source1 = 'B'
Result
Col Data
---------
1 18
2 15
3 61
----------Updated---------
Okay, as per the discussion, you need to use dynamic query. You need to first construct columns from tablec and then use it dynamic query as shown below.
DECLARE #cols AS NVARCHAR(MAX)
,#query AS NVARCHAR(MAX)
SELECT #cols = STUFF((
SELECT CASE
WHEN c.Source1 = 'A'
THEN ',' + 'a.[' + cast(c.Col AS VARCHAR(4)) + ']'
ELSE ',' + 'b.[' + cast(c.Col AS VARCHAR(4)) + ']'
END
FROM TableC c
FOR XML PATH('')
,TYPE
).value('.', 'NVARCHAR(MAX)'), 1, 1, '')
SET #query = 'SELECT ' + #cols + ' FROM TableA a
FULL OUTER JOIN TableB b ON 1 = 1'
EXECUTE sp_executesql #query;
Result
1 2 3 4
--------------
18 15 61 17
There is probably a better way but this works.
SELECT
CASE WHEN (SELECT Source FROM Table3 WHERE ID = 1) = 'A' THEN a.[1] ELSE b.[1] END
, CASE WHEN (SELECT Source FROM Table3 WHERE ID = 2) = 'A' THEN a.[2] ELSE b.[2] END
, CASE WHEN (SELECT Source FROM Table3 WHERE ID = 3) = 'A' THEN a.[3] ELSE b.[3] END
, CASE WHEN (SELECT Source FROM Table3 WHERE ID = 4) = 'A' THEN a.[4] ELSE b.[4] END
, CASE WHEN (SELECT Source FROM Table3 WHERE ID = 5) = 'A' THEN a.[5] ELSE b.[5] END
FROM dbo.TableA a
JOIN TableB b ON 1=1

Resources