How to index my rows in sql? - sql

I have the following function:
CREATE FUNCTION dbo.SplitStrings_XML
(
#List NVARCHAR(MAX),
#Delimiter NVARCHAR(255)
)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
(
SELECT Item = y.i.value('(./text())[1]', 'nvarchar(4000)')
FROM
(
SELECT x = CONVERT(XML, '<i>'
+ REPLACE(#List, #Delimiter, '</i><i>')
+ '</i>').query('.')
) AS a CROSS APPLY x.nodes('i') AS y(i)
);
GO
and the following code:
declare #string nvarchar(max) = 'aaa,1.3,1,bbb,1.5,ccc,2.0,1'
;WITH AllItems as
(
SELECT Item, ROW_NUMBER() OVER(ORDER BY (select null)) as rn
FROM dbo.SplitStrings_XML(#string, ',')
)
, Strings as
(
SELECT Item as Name, ROW_NUMBER() OVER(ORDER BY (select null)) as rn
FROM dbo.SplitStrings_XML(#string, ',')
WHERE ISNUMERIC(Item) = 0
), Doubles as
(
SELECT Item as Measure, ROW_NUMBER() OVER(ORDER BY (select null)) as rn
FROM dbo.SplitStrings_XML(#string, ',')
WHERE ISNUMERIC(Item) = 1 AND CHARINDEX('.', Item) > 0
), Integers as
(
SELECT Item as Value, ROW_NUMBER() OVER(ORDER BY (select null)) as rn
FROM dbo.SplitStrings_XML(#string, ',')
WHERE ISNUMERIC(Item) = 1 AND CHARINDEX('.', Item) = 0
)
SELECT Name, Measure, Value
FROM AllItems A
LEFT JOIN Strings S ON A.rn = S.rn
LEFT JOIN Doubles D ON A.rn = D.rn
LEFT JOIN Integers I ON A.rn = I.rn
WHERE COALESCE(Name, Measure, Value) IS NOT NULL
In this code we got a #string = 'aaa,1.3,1,bbb,1.5,ccc,2.0,1' that returns the chars in a row named Name ,returns the double values in a row named Measure and the int values in a row named Value,the problem is that in my string i have always a Name and Measure but sometimes the Value is missing and I would like to place a NULL value in that space.
So in my example I shouldhave something like
Name Measure Value
---------+--------+-------
aaa 1.3 1
bbb 1.5 NULL
ccc 2.0 1
Instead I have :
Name Measure Value
---------+--------+-------
aaa 1.3 1
bbb 1.5 1
ccc 2.0 NULL

First, I would suggest that you modify the function to return the item number. However, that is not necessary because your row_number() does that.
Then, I assume that a "missing value" means ",,".
If so, I would suggest defining the CTEs as:
WITH AllItems as (
SELECT Item, ROW_NUMBER() OVER (ORDER BY (select null)) as rn
FROM dbo.SplitStrings_XML(#string, ',')
),
Strings as (
SELECT Item as Name, ROW_NUMBER() OVER (ORDER BY (select null)) as rn
FROM AllItems ai
WHERE ai.rn % 3 = 1
),
Doubles as (
SELECT Item as Measure, ROW_NUMBER() OVER (ORDER BY (select null)) as rn
FROM AllItems ai
WHERE ai.rn % 3 = 2
),
Integers as (
SELECT Item as Value, ROW_NUMBER() OVER(ORDER BY (select null)) as rn
FROM AllItems ai
WHERE ai.rn % 3 = 0
)
. . .

Related

How to split the result row by comma

I have a question I have a simple table that looks like this when i do select all on it (one column with some rows)
| a, b, c | - 1st row
| b, d, d | - 2nd row
| d, e, f | - 3rd row
Now in trying to split those values by comma so each value would be in separate row something like
|a| - 1st row
|b| - 2nd row
|c| - 3rd row
|d| - 4th row
|e| - 5th row
|f| - 6th row
I was trying with something like:
select id,
case when CHARINDEX(', ', [value])>0
then SUBSTRING([value] , 1, CHARINDEX(', ',[value])-1) else [value] end firstname,
CASE WHEN CHARINDEX(', ', [value])>0
THEN SUBSTRING([value],CHARINDEX(', ',[value])+1,len([value])) ELSE NULL END as lastname from table
But it is not the way.
Without a UDF Parse/Split function
You didn't specify a Table or Column name so replace YourTable and YourList with your actual table and column names.
Select Distinct RetVal
,RowNr = Dense_Rank() over (Order by RetVal)
From YourTable A
Cross Apply (
Select RetSeq = Row_Number() over (Order By (Select null))
,RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>'+ replace((Select A.YourList as [*] For XML Path('')),',','</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
) B
Returns
RetVal RowNr
a 1
b 2
c 3
d 4
e 5
f 6
Using a Split/Parse function (everyone should have a good one)
Select Distinct RetVal
,RowNr = Dense_Rank() over (Order by RetVal)
From YourTable A
Cross Apply (Select * from [dbo].[udf-Str-Parse-8K](A.YourList,',') ) B
The UDF -- if interested
CREATE FUNCTION [dbo].[udf-Str-Parse-8K] (#String varchar(max),#Delimiter varchar(10))
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
);
--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','||')
A recursive cte solution which finds all the ,s in the string and gets the substring between 2 ,s. (Assuming you are on a sql server version 2012+ for lead to work)
with cte as (
select val,charindex(',',','+val+',') as location from t
union all
select val,charindex(',',','+val+',',location+1) from cte
where charindex(',',','+val+',',location+1) > 0
)
,substrings as (select *,
substring(val,location,
lead(location,1) over(partition by val order by location)-location-1) as sub
from cte)
select distinct sub
from substrings
where sub is not null and sub <> ''
order by 1;
Sample Demo
1) The first cte gets all the , locations in the string recursively. , is appended at the beginning and end of the string to avoid missing the first substring before , and the last substring after ,.
2) For each string, the location of the next , is found using lead ordered by the location of ,.
3) Finally get all those substrings which are not null and are not empty strings.
You can do this by using cross apply and XML
select distinct
p.a.value('.','varchar(10)') col
from (
select
cast('<x>' + replace(col,', ','</x><x>') + '</x>' as XML) as x
from your_table) t
cross apply x.nodes ('/x') as p(a)
) t

SQL - generate alphanumeric string with specific format

I need to find out how to generate an alphanumeric string that follows the format like in the answer for this question which I'm currently using, except it has to be in the following format:
Vowel + consonant + vowel + consonant + 4-digit number
For example ABAB1111 or IJUZ9236.
Thanks for any suggestion.
You can follow this steps:
Generate a vowels(A,E...) table , consonants (B,C..) table and numbers (1,2,..) table .
Then use this query:
SELECT (SELECT TOP 1 * FROM vowels ORDER BY newid()) +
(SELECT TOP 1 * FROM consonants ORDER BY newid()) +
(SELECT TOP 1 * FROM vowels ORDER BY newid()) +
(SELECT TOP 1 * FROM consonants ORDER BY newid()) +
(SELECT TOP 1 * FROM numbers ORDER BY newid()) +
(SELECT TOP 1 * FROM numbers ORDER BY newid()) +
(SELECT TOP 1 * FROM numbers ORDER BY newid()) +
(SELECT TOP 1 * FROM numbers ORDER BY newid())
I assume you want a random string. Something like this should work:
with v as (
select 'A' as c union all select 'E' union all . . .
),
c as (
select 'B' as c union all select 'C' union all . . .
),
d as (
select '0' as c union all select '1' union all . . .
)
select ((select top 1 c from v order by newid()) +
(select top 1 c from c order by newid()) +
(select top 1 c from v order by newid()) +
(select top 1 c from c order by newid()) +
(select top 1 c from d order by newid()) +
(select top 1 c from d order by newid()) +
(select top 1 c from d order by newid()) +
(select top 1 c from d order by newid())
);
Using temp tables as example data i'd do it like this;
CREATE TABLE #Vowels (Vowel varchar(1))
INSERT INTO #Vowels VALUES ('A'),('E'),('I'),('O'),('U')
CREATE TABLE #Consonants (Consonant varchar(1))
INSERT INTO #Consonants VALUES ('B'),('C'),('D'),('F'),('G'),('H'),('J'),('K'),('L'),('M'),('N'),('P'),('Q'),('R'),('S'),('T'),('V'),('W'),('X'),('Y'),('Z')
CREATE TABLE #Numbers (Numbers varchar(1))
INSERT INTO #Numbers VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)
SELECT
v1.Vowel + c1.Consonant + v2.Vowel + c2.Consonant + n1.Numbers + n2.Numbers + n3.Numbers + n4.Numbers AS Result
FROM (SELECT TOP 1 Vowel FROM #Vowels ORDER BY NEWID()) v1
CROSS JOIN (SELECT TOP 1 Consonant FROM #Consonants ORDER BY NEWID()) c1
CROSS JOIN (SELECT TOP 1 Vowel FROM #Vowels ORDER BY NEWID()) v2
CROSS JOIN (SELECT TOP 1 Consonant FROM #Consonants ORDER BY NEWID()) c2
CROSS JOIN (SELECT TOP 1 Numbers FROM #Numbers ORDER BY NEWID()) n1
CROSS JOIN (SELECT TOP 1 Numbers FROM #Numbers ORDER BY NEWID()) n2
CROSS JOIN (SELECT TOP 1 Numbers FROM #Numbers ORDER BY NEWID()) n3
CROSS JOIN (SELECT TOP 1 Numbers FROM #Numbers ORDER BY NEWID()) n4
DROP TABLE #Consonants
DROP TABLE #Numbers
DROP TABLE #Vowels
The result comes out like this but with different values each time you run it.
Result
AQOF7641
If you are running this a number of times, it would make sense to make proper tables containing your vowels, consonants and number. It would reduce the (admittedly small) cost of this query.
This should do the trick:
WITH letters as
(
SELECT 'bcdfghjklmnpqrstvwxyz' c, 'aeiou' v
)
,CTE as
(
SELECT
SUBSTRING(v, CAST(rand()*5 as int)+1, 1)+
SUBSTRING(c, CAST(rand()*21 as int)+1, 1)+
SUBSTRING(v, CAST(rand()*5 as int)+1, 1)+
SUBSTRING(c, CAST(rand()*21 as int)+1, 1)+
right(10000+ CAST(rand()*10000 as int),4) x
FROM letters
)
SELECT x
FROM CTE
DECLARE #AlphaString VARCHAR(200) = NULL;
WITH
CTE_Digits AS (
SELECT TOP 255
ROW_NUMBER() OVER (ORDER BY a.object_id) AS RowNum
FROM
sys.all_columns a
CROSS JOIN
sys.all_columns b),
CTE_Types AS (
SELECT
CHAR(RowNum) AS Digit,
CASE
WHEN RowNum < 58 THEN 'D'
WHEN CHAR(RowNum) IN ('A','E','I','O','U') THEN 'V'
ELSE 'C'
END AS CharType
FROM
CTE_Digits
WHERE
RowNum BETWEEN 48 AND 57
OR RowNum BETWEEN 65 AND 90),
CTE_List AS (
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY CharType ORDER BY NEWID()) AS NewRow
FROM
CTE_Types),
CTE_Ordered AS (
SELECT
*,
CASE CharType
WHEN 'V' THEN 2
WHEN 'C' THEN 3
WHEN 'D' THEN 7
END * NewRow AS DigitOrder
FROM
CTE_List
WHERE
(NewRow < 5
AND CharType = 'D')
OR NewRow < 3)
SELECT #AlphaString =
(SELECT
CAST(Digit AS VARCHAR(MAX))
FROM
CTE_Ordered
ORDER BY
DigitOrder
FOR XML PATH(''));
SELECT #AlphaString;

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.

Pull out first index record in each REPEATING group ordered by index

I have this table with this data
DECLARE #tbl TABLE
(
IDX INTEGER,
VAL VARCHAR(50)
)
--Inserted values for testing
INSERT INTO #tbl(IDX, VAL) VALUES(1,'A')
INSERT INTO #tbl(IDX, VAL) VALUES(2,'A')
INSERT INTO #tbl(IDX, VAL) VALUES(3,'A')
INSERT INTO #tbl(IDX, VAL) VALUES(4,'B')
INSERT INTO #tbl(IDX, VAL) VALUES(5,'B')
INSERT INTO #tbl(IDX, VAL) VALUES(6,'B')
INSERT INTO #tbl(IDX, VAL) VALUES(7,'A')
INSERT INTO #tbl(IDX, VAL) VALUES(8,'A')
INSERT INTO #tbl(IDX, VAL) VALUES(9,'A')
INSERT INTO #tbl(IDX, VAL) VALUES(10,'C')
INSERT INTO #tbl(IDX, VAL) VALUES(11,'C')
INSERT INTO #tbl(IDX, VAL) VALUES(12,'A')
INSERT INTO #tbl(IDX, VAL) VALUES(13,'A')
--INSERT INTO #tbl(IDX, VAL) VALUES(14,'A') -- this line has bad binary code
INSERT INTO #tbl(IDX, VAL) VALUES(14,'A') -- replace with this line and it works
INSERT INTO #tbl(IDX, VAL) VALUES(15,'D')
INSERT INTO #tbl(IDX, VAL) VALUES(16,'D')
Select * From #tbl -- to see what you have inserted...
And the Output I'm looking for is the FIRST and LAST Idx and Val in each group of Val's prior ordering over Idx. Noting that Val's may repeat !!! also Idx may not be in ascending order in the table as they are in the imsert statments. No cursors please !
i.e
Val First Last
=================
A 1 3
B 4 6
A 7 9
C 10 11
A 12 14
D 15 16
If the idx values are guaranteed to be sequential, then try this:
Select f.val, f.idx first, l.idx last
From #tbl f
join #tbl l
on l.val = f.val
and l.idx > f.idx
and not exists
(Select * from #tbl
Where val = f.val
and idx = l.idx + 1)
and not exists
(Select * from #tbl
Where val = f.val
and idx = f.idx - 1)
and not exists
(Select * from #tbl
Where val <> f.val
and idx Between f.idx and l.idx)
order by f.idx
if the idx values are not sequential, then it needs to be a bit more complex...
Select f.val, f.idx first, l.idx last
From #tbl f
join #tbl l
on l.val = f.val
and l.idx > f.idx
and not exists
(Select * from #tbl
Where val = f.val
and idx = (select Min(idx)
from #tbl
where idx > l.idx))
and not exists
(Select * from #tbl
Where val = f.val
and idx = (select Max(idx)
from #tbl
where idx < f.idx))
and not exists
(Select * from #tbl
Where val <> f.val
and idx Between f.idx and l.idx)
order by f.idx
SQL Server 2012
In SQL Server 2012, you can use cte sequence with lag/lead analytical functions like below (fiddle here). The code does not assume any type or sequence about idx, and queries first and last occurrence of val within each window.
;with cte as
(
select val, idx,
ROW_NUMBER() over(order by (select 0)) as urn --row_number without ordering
from #tbl),
cte1 as
(
select urn, val, idx,
lag(val, 1) over(order by urn) as prevval,
lead(val, 1) over(order by urn) as nextval
from cte
),
cte2 as
(
select val, idx, ROW_NUMBER() over(order by (select 0)) as orn,
(ROW_NUMBER() over(order by (select 0))+1)/2 as prn from cte1
where (prevval <> nextval or prevval is null or nextval is null)
),
cte3 as
(
select val, FIRST_VALUE(idx) over(partition by prn order by prn) as firstidx,
LAST_VALUE(idx) over(partition by prn order by prn) as lastidx, orn
from cte2
),
cte4 as
(
select val, firstidx, lastidx, min(orn) as rn
from cte3
group by val, firstidx, lastidx
)
select val, firstidx, lastidx
from cte4
order by rn;
SQL Server 2008
In SQL Server 2008, it is bit more tortured code due to the lack of lag/lead analytical functions. (fiddle here). Here also, the code does not assume any type or sequence about idx, and queries first and last occurrence of val within each window.
;with cte as
(
select val, idx, ROW_NUMBER() over(order by (select 0)) as urn
from #tbl),
cte1 as
(
select m.urn, m.val, m.idx,
_lag.val as prevval, _lead.val as nextval
from cte as m
left join cte as _lag
on _lag.urn = m.urn-1
left join cte AS _lead
on _lead.urn = m.urn+1),
cte2 as
(
select val, idx, ROW_NUMBER() over(order by (select 0)) as orn,
(ROW_NUMBER() over(order by (select 0))+1)/2 as prn from cte1
where (prevval <> nextval or prevval is null or nextval is null)),
cte3 as
( select *, ROW_NUMBER() over(partition by prn order by orn) as rownum
from cte2),
cte4 as
(select o.val, (select i.idx from cte3 as i where i.rownum = 1 and i.prn = o.prn)
as firstidx,
(select i.idx from cte3 as i where i.rownum = 2 and i.prn = o.prn) as lastidx,
o.orn from cte3 as o),
cte5 as (
select val, firstidx, lastidx, min(orn) as rn
from cte4
group by val, firstidx, lastidx
)
select val, firstidx, lastidx
from cte5
order by rn;
Note:
Both of the solutions are based on the assumption that the database engine preserves the order of insertion, though relational database does not guaranteed the order in theory.
A way to do it - at least for SQL Server 2008 without using special functionality would be to introduce a helper table and helper variable.
Now whether that's actually possible for you as is (due to many other requirements) I don't know - but it might lead you on a solution path, but it does look to solve your current set up requirements of no cursor and nor lead/lag:
So basically what I do is make a helper table and a helper grouping variable:
(sorry about the naming)
DECLARE #grp TABLE
(
idx INTEGER ,
val VARCHAR(50) ,
gidx INT
)
DECLARE #gidx INT = 1
INSERT INTO #grp
( idx, val, gidx )
SELECT idx ,
val ,
0
FROM #tbl AS t
I populate this with the values from your source table #tbl.
Then I do an update trick to assign a value to gidx based on when VAL changes value:
UPDATE g
SET #gidx = gidx = CASE WHEN val <> ISNULL(( SELECT val
FROM #grp AS g2
WHERE g2.idx = g.idx - 1
), val) THEN #gidx + 1
ELSE #gidx
END
FROM #grp AS g
What this does is assign a value of 1 to gidx until VAL changes, then it assigns gidx + 1 which is also assigned to #gixd variable. And so on.
This gives you the following usable result:
idx val gidx
1 A 1
2 A 1
3 A 1
4 B 2
5 B 2
6 B 2
7 A 3
8 A 3
9 A 3
10 C 4
11 C 4
12 A 5
13 A 5
14 A 5
15 D 6
16 D 6
Notice that gidx now is a grouping factor.
Then it's a simple matter of extracting the data with a sub select:
SELECT ( SELECT TOP 1
VAL
FROM #GRP g3
WHERE g2.gidx = g3.gidx
) AS Val ,
MIN(idx) AS First ,
MAX(idx) AS Last
FROM #grp AS g2
GROUP BY gidx
This yields the result:
A 1 3
B 4 6
A 7 9
C 10 11
A 12 14
D 15 16
Fiddler link
I'm assuming that IDX values are unique. If they can also be assumed to start from 1 and have no gaps, as in your example, you could try the following SQL Server 2005+ solution:
WITH partitioned AS (
SELECT
IDX, Val,
grp = IDX - ROW_NUMBER() OVER (PARTITION BY Val ORDER BY IDX ASC)
FROM #tbl
)
SELECT
Val,
FirstIDX = MIN(IDX),
LastIDX = MAX(IDX)
FROM partitioned
GROUP BY
Val, grp
ORDER BY
FirstIDX
;
If IDX values may have gaps and/or may start from a value other than 1, you could use the following modification of the above instead:
WITH partitioned AS (
SELECT
IDX, Val,
grp = ROW_NUMBER() OVER ( ORDER BY IDX ASC)
- ROW_NUMBER() OVER (PARTITION BY Val ORDER BY IDX ASC)
FROM #tbl
)
SELECT
Val,
FirstIDX = MIN(IDX),
LastIDX = MAX(IDX)
FROM partitioned
GROUP BY
Val, grp
ORDER BY
FirstIDX
;
Note: If you end up using either of these queries, please make sure the statement preceding the query is delimited with a semicolon, particularly if you are using SQL Server 2008 or later version.

SQL- pad results with extra rows

I have a group of records that have a "Store" column. I need to basically split the result sets into groups of 13 records, creating blank rows to pad out each store to have 13 rows.
For simplicity, lets say I need groups of 4 records.
Example, given the below table:
-----------------
Store Name
-----------------
A John
A Bill
B Sam
C James
C Tim
C Chris
D Simon
D Phil
I need the results to look like:
-----------------
Store Name
-----------------
A John
A Bill
A
B Sam
B
B
C James
C Tim
C Chris
D Simon
D Phil
D
Is this at all possible with pure SQL? There are never going to be more than 3 rows for each store.
SQL Fiddle
Try this one -
DDL:
SET STATISTICS IO ON;
IF OBJECT_ID (N'tempdb.dbo.#temp') IS NOT NULL
DROP TABLE #temp
CREATE TABLE #temp
(
Store CHAR(1)
, Name VARCHAR(10)
)
INSERT INTO #temp (Store, Name)
VALUES
('A', 'John'), ('A', 'Bill'),
('B', 'Sam'), ('C', 'James'),
('C', 'Tim'), ('C', 'Chris'),
('D', 'Simon'), ('D', 'Phil')
Queries:
DevArt #1:
;WITH cte AS
(
SELECT
Store
, Name
, rn = ROW_NUMBER() OVER (PARTITION BY Store ORDER BY (SELECT 1))
FROM #temp
)
SELECT t.Store, Name = ISNULL(t3.Name, '')
FROM (
SELECT DISTINCT Store
FROM cte
) t
CROSS JOIN (SELECT rn = 1 UNION ALL SELECT 2 UNION ALL SELECT 3) t2
LEFT JOIN cte t3 ON t2.rn = t3.rn AND t.Store = t3.Store
DevArt #2:
SELECT t2.Store, Name = ISNULL(t3.Name, '')
FROM (
SELECT *
FROM (
SELECT Store, r = COUNT(1)
FROM #temp
GROUP BY Store
) t
CROSS APPLY (
VALUES (r), (r+1), (r+2)
) t2 (x)
) t2
LEFT JOIN #temp t3 ON t2.Store = t3.Store AND t2.x = t2.r
WHERE t2.x < 4
Alexander Fedorenko:
;WITH cte AS
(
SELECT DISTINCT Store
FROM #temp
)
SELECT o.Store, o.name
FROM cte s
CROSS APPLY (
SELECT TOP 3 x.Store, x.name
FROM (
SELECT s2.Store, s2.name
FROM #temp s2
WHERE s.Store = s2.Store
UNION ALL
SELECT s.Store, ''
UNION ALL
SELECT s.Store, ''
) x
) o
ErikE:
SELECT Store, Name
FROM (
SELECT
x.Store
, x.Name
, s = ROW_NUMBER() OVER (PARTITION BY x.Store ORDER BY x.s)
FROM #temp t
CROSS APPLY (
VALUES
(Store, Name, 0),
(Store, '', 1),
(Store, '', 1)
) x (Store, Name, S)
) z
WHERE s <= 3
ORDER BY Store
AmitSingh:
SELECT t.Store, Name = COALESCE(
(
SELECT name
FROM (
SELECT
row1 = ROW_NUMBER() OVER (PARTITION BY Store ORDER BY Store)
, *
FROM #temp
) c
WHERE t.[row] = c.row1
AND t.Store = c.Store
)
, '')
FROM
(
SELECT
[Row] = ROW_NUMBER() OVER (PARTITION BY a.Store ORDER BY a.Store)
, a.Store
FROM (
SELECT Store
FROM #temp
GROUP BY Store
) a
, (
SELECT TOP 3 Store
FROM #temp
) b
) t
Andriy M #1:
;WITH ranked AS
(
SELECT
Store
, Name
, rnk = ROW_NUMBER() OVER (PARTITION BY Store ORDER BY 1/0)
FROM #temp
)
, pivoted AS
(
SELECT
Store
, [1] = ISNULL([1], '')
, [2] = ISNULL([2], '')
, [3] = ISNULL([3], '')
FROM ranked
PIVOT (
MAX(Name)
FOR rnk IN ([1], [2], [3])
) p
)
, unpivoted AS
(
SELECT
Store
, Name
FROM pivoted
UNPIVOT (
Name FOR rnk IN ([1], [2], [3])
) u
)
SELECT *
FROM unpivoted
Andriy M #2:
;WITH ranked AS
(
SELECT
Store
, Name
, rnk = ROW_NUMBER() OVER (PARTITION BY Store ORDER BY 1/0)
FROM #temp
)
, padded AS
(
SELECT
Store
, Name
FROM ranked
PIVOT (
MAX(Name)
FOR rnk IN ([1], [2], [3])
) p
CROSS APPLY (
VALUES
(ISNULL([1], '')),
(ISNULL([2], '')),
(ISNULL([3], ''))
) x (Name)
)
SELECT *
FROM padded
Output:
Store Name
----- ----------
A John
A Bill
A
B Sam
B
B
C James
C Tim
C Chris
D Simon
D Phil
D
Statistics:
Query Presenter Scans Logical Reads
------------------- ----- -------------
DevArt #1 3 41
DevArt #2 2 9
Alexander Fedorenko 4 5
ErikE 1 1
AmitSingh 22 25
Andriy M #1 1 1
Andriy M #2 1 1
Query cost:
Extended statistics:
Execution statistics:
Query plan (from dbForge Studio for MS SQL):
Select t.store_id,Coalesce((Select Name from (
Select row_Number() Over(Partition by store_id order by store_id) as row1, * from stores)c
where t.row=c.row1 and t.store_id=c.store_id),'') as cfgg
from
(Select row_Number() Over(Partition by a.store_id order by a.store_id) as row,
a.store_id from
(Select store_id from stores group by store_id) a ,(Select top 3 store_id from stores)b
) t
SQL Fiddle Demo
One more option with APPLY operator
;WITH cte AS
(
SELECT store_id
FROM stores
GROUP BY store_id
)
SELECT o.store_id, o.name
FROM cte s CROSS APPLY (
SELECT TOP 3 x.store_id, x.name
FROM (
SELECT s2.store_id, s2.name
FROM stores s2
WHERE s.store_id = s2.store_id
UNION ALL
SELECT s.store_id, ''
UNION ALL
SELECT s.store_id, ''
) x
) o
Demo on SQLFiddle
Here's a query that works (SQL Server 2008 and up, can be fixed to work in 2005):
SELECT Store, Name
FROM (
SELECT
X.Store, X.Name, R = Row_Number() OVER (PARTITION BY X.Store ORDER BY X.S)
FROM
#temp T
CROSS APPLY (VALUES
(Store, Name, 0), (Store, '', 1), (Store, '', 1)
) X (Store, Name, S)
) Z
WHERE R <= 3
ORDER BY Store
;
According to SET STATISTICS IO ON;, here are performance statistics (all have negligible CPU at this low number of rows, perhaps more rows would help determine the best performer):
Query Presenter Scans Logical Reads
------------------- ----- -------------
ErikE 1 1
Alexander Fedorenko 4 5
Devart 3 41
AmitSingh 22 25
My query does not preserve the "original" ordering of the names for each store, however that is not a flaw because there is no concept of ordering in a relational database table. You must supply a column to order by if you want to preserve a particular sequence.
Yet another option, this time using PIVOT and UNPIVOT:
WITH ranked AS (
SELECT
store_id,
name,
rnk = ROW_NUMBER() OVER (PARTITION BY store_id ORDER BY 1/0)
FROM stores
),
pivoted AS (
SELECT
store_id,
[1] = ISNULL([1], ''),
[2] = ISNULL([2], ''),
[3] = ISNULL([3], '')
FROM ranked
PIVOT (
MAX(name) FOR rnk IN ([1], [2], [3])
) p
),
unpivoted AS (
SELECT
store_id,
name
FROM pivoted
UNPIVOT (
name FOR rnk IN ([1], [2], [3])
) u
)
SELECT *
FROM unpivoted
;
A SQL Fiddle demo to play with: http://sqlfiddle.com/#!3/354df/39.
Note that the UNPIVOT step in the above query has to be done in a separate SELECT from the PIVOT action. That is because UNPIVOT does not generate rows where columns listed in the IN column list contain NULLs. However, you could replace UNPIVOT with an equivalent technique (like CROSS APPLY) and thus move the unpivoting to the same subquery that does the pivoting:
WITH ranked AS (
SELECT
store_id,
name,
rnk = ROW_NUMBER() OVER (PARTITION BY store_id ORDER BY 1/0)
FROM stores
),
padded AS (
SELECT
store_id,
name
FROM ranked
PIVOT (
MAX(name) FOR rnk IN ([1], [2], [3])
) p
CROSS APPLY (
VALUES
(ISNULL([1], '')),
(ISNULL([2], '')),
(ISNULL([3], ''))
) x (name)
)
SELECT *
FROM padded
;
A SQL Fiddle demo for the modified version: http://sqlfiddle.com/#!3/354df/40.