Related
I would like to transform this string:
A1+A2+A3.B1+B2.C1
into
A1.B1.C1
A1.B2.C1
A2.B1.C1
A2.B2.C1
A3.B1.C1
A3.B2.C1
How can I do that? (note that each dimension(= a group separate by .), could have x values, I mean it can be A1+A2.B1.C1 or A1+A2.B1+B2+B3+B4+B5.C1+C2)
Thanks
If you have only 3 columns, then just use STRING_SPLIT: number your groups from first split and then do a join 3 times and select each group on corresponding join.
with a as (
select s2.value as v, dense_rank() over(order by s1.value) as rn
from STRING_SPLIT('A1+A2+A3.B1+B2.C1', '.') as s1
cross apply STRING_SPLIT(s1.value, '+') as s2
)
select
a1.v + '.' + a2.v + '.' + a3.v as val
from a as a1
cross join a as a2
cross join a as a3
where a1.rn = 1
and a2.rn = 2
and a3.rn = 3
| val |
----------
|A1.B1.C1|
|A2.B1.C1|
|A3.B1.C1|
|A1.B2.C1|
|A2.B2.C1|
|A3.B2.C1|
If you have indefinite number of groups, then it's better to use recursive CTE instead of dynamic SQL. What you should do:
Start with all the values from the first group.
On recursion step crossjoin all the values of the next group (i.e. step group number is current group number + 1).
Select the last recursion step where you'll have the result.
Code is below:
with a as (
select s2.value as v, dense_rank() over(order by s1.value) as rn
from STRING_SPLIT('A1+A2+A3.B1+B2+B3+B4.C1+C2.D1+D2+D3', '.') as s1
cross apply STRING_SPLIT(s1.value, '+') as s2
)
, b (val, lvl) as (
/*Recursion base*/
select cast(v as nvarchar(1000)) as val, rn as lvl
from a
where rn = 1
union all
/*Increase concatenation on each iteration*/
select cast(concat(b.val, '.', a.v) as nvarchar(1000)) as val, b.lvl + 1 as lvl
from b
join a
on b.lvl + 1 = a.rn /*Recursion step*/
)
select *
from b
where lvl = (select max(rn) from a) /*You need the last step*/
order by val
I won't add a tabular result since it is quite big. But try it by yourself.
Here is SQL server version and fiddle:
with lst(s) as (select * from STRING_SPLIT('A1+A2.B1+B2+B3+B4+B5.C1+C2','.'))
select t1+'.'+t2+'.'+t3 as res from
(select * from STRING_SPLIT((select s from lst where s like 'A%'), '+')) s1(t1) cross join
(select * from STRING_SPLIT((select s from lst where s like 'B%'), '+')) s2(t2) cross join
(select * from STRING_SPLIT((select s from lst where s like 'C%'), '+')) s3(t3);
Of course you can grow it in a regular fashion if the number of dimensions grows.
Here is a Postgresql solution:
with x(s) as (select string_to_array('A1+A2.B1+B2+B3+B4+B5.C1+C2','.'))
select t1||'.'||t2||'.'||t3 as res from
unnest((select string_to_array(s[1],'+') from x)) t1 cross join
unnest((select string_to_array(s[2],'+') from x)) t2 cross join
unnest((select string_to_array(s[3],'+') from x)) t3;
result:
res |
--------|
A1.B1.C1|
A1.B2.C1|
A1.B3.C1|
A1.B4.C1|
A1.B5.C1|
A2.B1.C1|
A2.B2.C1|
A2.B3.C1|
A2.B4.C1|
A2.B5.C1|
A1.B1.C2|
A1.B2.C2|
A1.B3.C2|
A1.B4.C2|
A1.B5.C2|
A2.B1.C2|
A2.B2.C2|
A2.B3.C2|
A2.B4.C2|
A2.B5.C2|
Here my code with your help. I didn't mention, but I can also have more or less than 3 parts, so I'm using a dynamic SQL for this:
declare #FILTER varchar(max)='B+C+D.A+G.T+Y+R.E'
-- Works also with A.B.C
-- Works also with A+B+C.D.E+F
-- Works also with A+B+C.D+E+F+G+H
declare #NB int
declare #SQL varchar(max)=''
select #NB=count(*) from STRING_SPLIT(#FILTER,'.')
set #SQL='
;with T(A,B) as
(select *, row_number() over (order by (select NULL))
from STRING_SPLIT(''' + #FILTER + ''',''.'')
)
select '
;with T(V,N) as (
select *, row_number() over (order by (select NULL))
from STRING_SPLIT(#FILTER,'.')
)
select #SQL=#SQL + 'T' + cast(N as varchar(max)) + ' + ''.'' + ' from T
set #SQL=left(#SQL,len(#SQL)-1) + ' as res from'
;with T(V,N) as (
select *, row_number() over (order by (select NULL))
from STRING_SPLIT(#FILTER,'.')
)
select #SQL=#SQL + '
(select * from STRING_SPLIT((select A from T where B=' + cast(N as varchar(max)) + '), ''+'')) s' + cast(N as varchar(max)) + '(t' + cast(N as varchar(max)) + ') cross join'
from T
set #SQL=left(#SQL,len(#SQL)-len('cross join'))
exec(#SQL)
**N** is a prositive number
Need list of scenarios which have sum equal N
For example if N=4
ScenarioId Value
---------- -----
1 1
1 1
1 1
1 1
2 2
2 1
2 1
3 2
3 2
4 3
4 1
5 4
above list is required. If you sum by ScenarioId all sum must equal to N
UPDATE
Here is my own solution. however, I am not sure about the multiplication of two different number sets would not be equal at any time.
My current question is
Is there any possibilities a + b + c = d + e + f and a * b * c = d * e * f
Test link is here
DECLARE #N int = 4;
SELECT
[Value] = CAST(number + 1 as tinyint)
INTO #Values
FROM master.dbo.spt_values
WHERE number < #N
AND [Type] = 'p'
;WITH COMBINATIONS AS(
SELECT ScenarioKey = CAST(NULL AS nvarchar(MAX)), [Value], Total = 0, Multipication = 1, MemeberCount = 0
FROM #Values
UNION ALL
SELECT ScenarioKey = ISNULL(S.ScenarioKey, '') + IIF(S.ScenarioKey IS NULL, '', N'-') + CAST(P.[Value] AS nvarchar(10)), S.[Value], Total = S.Total + P.[Value], Multipication = S.Multipication * P.[Value], MemeberCount = MemeberCount + 1
FROM #Values P
JOIN COMBINATIONS AS S ON S.Total < S.[Value]
),
SCENARIOS AS(
SELECT
ScenarioKey
,ScenarioId = ROW_NUMBER() OVER(ORDER BY ScenarioKey)
,[Value]
FROM
(
SELECT
ScenarioKey
,[Value]
,Multipication
,MemeberCount
-- this will prevent dublications. because 1 * 2 * 3 = 3 * 2 * 1
-- however, I am not sure about multipication of two different number sets would not be equal any time
,RowNo = ROW_NUMBER() OVER(PARTITION BY [Value],Multipication,MemeberCount ORDER BY [Value],ScenarioKey)
FROM COMBINATIONS
WHERE Total = #N
) X
WHERE RowNo = 1 AND [Value] = #N
)
SELECT
R.ScenarioId
,[Value] = S.[value]
FROM SCENARIOS R
CROSS APPLY (SELECT [value] FROM STRING_SPLIT(R.ScenarioKey, '-')) S
DROP TABLE #Values
It's too long for comment, so I post this as an answer. I want to note, that this is a static example, but I hope it can be easily translated as a dynamic statement.
Steps are written as comments in the statement:
WITH rcte AS
(
-- Recursive query to generate all numbers from 1 to 4
SELECT 0 AS Number
UNION ALL
SELECT Number + 1
FROM rcte
WHERE Number < 4
), permutations AS (
-- All possible permutations with sum equal to 4
-- There is additional column DuplicateMarker.
-- It will be used later, because 0,0,0,4 and 0,4,0,0 are the same
SELECT
t1.Number AS Number1,
t2.Number AS Number2,
t3.Number AS Number3,
t4.Number AS Number4,
CONCAT(LTRIM(STR(t1.Number)), '.', LTRIM(STR(t2.Number)), '.', LTRIM(STR(t3.Number)), '.', LTRIM(STR(t4.Number))) AS DuplicateMarker
FROM rcte t1, rcte t2, rcte t3, rcte t4
WHERE (t1.Number + t2.Number + t3.Number + t4.Number) = 4
), duplicates AS (
-- Get data with splitted DuplicateMarker column
SELECT *
FROM permutations
CROSS APPLY (SELECT [value] FROM STRING_SPLIT(DuplicateMarker, '.')) t
), results AS (
-- Get unique combinations
-- WITHIN GROUP (ORDER BY) will order strings and 0.0.0.4 and 0.4.0.0 will be the same
SELECT DISTINCT STRING_AGG([value], '.') WITHIN GROUP (ORDER BY [value]) AS ScenarioValue
FROM duplicates
GROUP BY Number1, Number2, Number3, Number4
)
SELECT
DENSE_RANK() OVER (ORDER BY r.ScenarioValue) AS ScenarioID,
s.[value]
FROM results r
CROSS APPLY (SELECT [value] FROM STRING_SPLIT(r.ScenarioValue, '.')) s
WHERE [value] <> '0'
Output:
ScenarioID value
1 4
2 1
2 3
3 2
3 2
4 1
4 1
4 2
5 1
5 1
5 1
5 1
Update:
Thanks to #AndriyM's comment, I've made some changes and now you can eliminate string manipulations:
WITH rcte AS
(
-- Recursive query to generate all numbers from 0 to 4
SELECT 0 AS Number
UNION ALL
SELECT Number + 1
FROM rcte
WHERE Number < 4
), combinations AS (
-- All different combinations with sum equal to 4
SELECT
t1.Number AS Number1,
t2.Number AS Number2,
t3.Number AS Number3,
t4.Number AS Number4,
ROW_NUMBER() OVER (ORDER BY t1.Number, t2.Number, t3.Number, t4.NUmber) AS ScenarioID
FROM rcte t1, rcte t2, rcte t3, rcte t4
WHERE
((t1.Number + t2.Number + t3.Number + t4.Number) = 4) AND
(t1.Number <= t2.Number) AND
(t2.Number <= t3.Number) AND
(t3.Number <= t4.Number)
)
SELECT c.ScenarioID, v.[value]
FROM combinations c
CROSS APPLY (VALUES (c.NUmber1), (c.Number2), (c.Number3), (c.Number4)) AS v ([value])
WHERE v.[value] > 0
Update 2:
Approach using dynamic statement - probably not the best approach, but is based on statement from first update:
-- Set your #n value
DECLARE #n int
SET #n = 4
-- Declarations
DECLARE #combinationsSelect nvarchar(max)
DECLARE #combinationsRowNumber nvarchar(max)
DECLARE #combinationsFrom nvarchar(max)
DECLARE #combinationsWhere1 nvarchar(max)
DECLARE #combinationsWhere2 nvarchar(max)
DECLARE #combinationsValues nvarchar(max)
SET #combinationsSelect = N''
SET #combinationsRowNumber = N''
SET #combinationsFrom = N''
SET #combinationsValues = N''
SET #combinationsWhere1 = N''
SET #combinationsWhere2 = N''
-- Generate dynamic parts of the statement
;WITH numbers AS
(
SELECT 1 AS Number
UNION ALL
SELECT Number + 1
FROM Numbers
WHERE Number < #n
)
SELECT
#combinationsSelect = #combinationsSelect + N', t' + LTRIM(STR(Number)) + N'.Number AS Number' + LTRIM(STR(Number)),
#combinationsRowNumber = #combinationsRowNumber + N', t' + LTRIM(STR(Number)) + N'.Number',
#combinationsValues = #combinationsValues + N', (c.Number' + LTRIM(STR(Number)) + N')',
#combinationsFrom = #combinationsFrom + N', rcte t' + LTRIM(STR(Number)),
#combinationsWhere1 = #combinationsWhere1 + N'+ t' + LTRIM(STR(Number)) + N'.Number ',
#combinationsWhere2 = #combinationsWhere2 +
CASE
WHEN Number = 1 THEN N''
ELSE N'AND (t' + LTRIM(STR(Number-1)) + N'.Number <= t' + + LTRIM(STR(Number)) + N'.Number) '
END
FROM
numbers
SET #combinationsSelect = STUFF(#combinationsSelect, 1, 2, N'')
SET #combinationsRowNumber = STUFF(#combinationsRowNumber, 1, 2, N'')
SET #combinationsValues = STUFF(#combinationsValues, 1, 2, N'')
SET #combinationsFrom = STUFF(#combinationsFrom, 1, 2, N'')
SET #combinationsWhere1 = STUFF(#combinationsWhere1, 1, 2, N'')
SET #combinationsWhere2 = STUFF(#combinationsWhere2, 1, 4, N'')
-- Dynamic statement
DECLARE #stm nvarchar(max)
SET #stm =
N'WITH rcte AS (
SELECT 0 AS Number
UNION ALL
SELECT Number + 1
FROM rcte
WHERE Number < ' + LTRIM(STR(#n)) +
N'), combinations AS (
SELECT ' +
#combinationsSelect +
N', ROW_NUMBER() OVER (ORDER BY ' + #combinationsRowNumber + N') AS ScenarioID
FROM ' + #combinationsFrom +
N' WHERE ((' + #combinationsWhere1 + N') = ' + LTRIM(STR(#n)) + ') AND ' + #combinationsWhere2 +
N')
SELECT c.ScenarioID, v.[value]
FROM combinations c
CROSS APPLY (VALUES ' + #combinationsValues + N') AS v ([value])
WHERE v.[value] > 0'
-- Execute dynamic statement
EXEC (#stm)
If you have sample data like below
You can write query like below
Declare #N int =4
Select T.*
From #T T
cross apply (
select S, SUM(V) Total
From #T
Group By S) Totals
Where Totals.Total=#N and T.S = Totals.S
How might I select the following data in an existing table and order by a mix of letters and numbers. Here is the sample...
A-1
A-10
A-2
A-3
A-4
A-5
A-6
A-7
A-8
A-9
A-3a
A-3b
A-3c
B-1
B-10
B-11
B-12
B-12a
B-12b
B-13
B-2
B-3
B-4
B-5
B-6
B-7
B-8
B-9
I place this as a new answer, as it is not really an answer but rather a comparison of different approaches:
The conclusio:
all approaches scale fairly linear, except XML
XML is fastest with small row count but gets worse with high row count
Create a test scenario
CREATE TABLE #tbl (ID INT IDENTITY,sortColumn VARCHAR(100));
INSERT INTO #tbl VALUES
('A-1')
,('A-10')
,('A-2')
,('A-3')
,('A-4')
,('A-5')
,('A-6')
,('A-7')
,('A-8')
,('A-9')
,('A-3a')
,('A-3b')
,('A-3c')
,('B-1')
,('B-10')
,('B-11')
,('B-12')
,('B-12a')
,('B-12b')
,('B-13')
,('B-2')
,('B-3')
,('B-4')
,('B-5')
,('B-6')
,('B-7')
,('B-8')
,('A-8a')
,('B-8')
,('B-9'); --30 rows
GO 1000 -- x 1.000 = 30.000 rows
Matt's approach (cleaned to the necessary)
46 seconds on 3 mio rows
4.5 seconds on 300.000 rows
1.3 seconds on 30.000 rows
0.7 seconds on 3.000 rows
The code
SELECT ID,sortColumn
FROM
#tbl
ORDER BY
LEFT(sortColumn,CHARINDEX('-',sortColumn) -1)
,CAST((CASE
WHEN ISNUMERIC(SUBSTRING(sortColumn,CHARINDEX('-',sortColumn) + 1,3)) = 1 THEN SUBSTRING(sortColumn,CHARINDEX('-',sortColumn) + 1,3)
WHEN ISNUMERIC(SUBSTRING(sortColumn,CHARINDEX('-',sortColumn) + 1,2)) = 1 THEN SUBSTRING(sortColumn,CHARINDEX('-',sortColumn) + 1,2)
WHEN ISNUMERIC(SUBSTRING(sortColumn,CHARINDEX('-',sortColumn) + 1,1)) = 1 THEN SUBSTRING(sortColumn,CHARINDEX('-',sortColumn) + 1,1)
ELSE NULL
END) AS INT)
,RIGHT(sortColumn,
LEN(sortColumn) -
LEN(LEFT(sortColumn,CHARINDEX('-',sortColumn) -1))
- LEN(CASE
WHEN ISNUMERIC(SUBSTRING(sortColumn,CHARINDEX('-',sortColumn) + 1,3)) = 1 THEN SUBSTRING(sortColumn,CHARINDEX('-',sortColumn) + 1,3)
WHEN ISNUMERIC(SUBSTRING(sortColumn,CHARINDEX('-',sortColumn) + 1,2)) = 1 THEN SUBSTRING(sortColumn,CHARINDEX('-',sortColumn) + 1,2)
WHEN ISNUMERIC(SUBSTRING(sortColumn,CHARINDEX('-',sortColumn) + 1,1)) = 1 THEN SUBSTRING(sortColumn,CHARINDEX('-',sortColumn) + 1,1)
ELSE NULL
END)
- 1 --the '-'
),ID;
Stepwise calculation in CROSS APPLYs, sorting on calculated columns
44 seconds on 3 mio rows
4.4 seconds on 300.000 rows
0.9 seconds on 30.000 rows
0.3 seconds on 3.000 rows
The code
SELECT ID,sortColumn
FROM #tbl
CROSS APPLY(SELECT CHARINDEX('-',sortColumn) AS posMinus) AS pos
CROSS APPLY(SELECT SUBSTRING(sortColumn,1,posMinus-1) AS part1
,SUBSTRING(sortColumn,posMinus+1,1000) AS part2
) AS parts
CROSS APPLY(SELECT ISNUMERIC(part2) AS p2isnum) AS checknum
CROSS APPLY(SELECT CASE WHEN p2isnum=1 THEN '' ELSE RIGHT(part2,1) END AS part3
,CASE WHEN p2isnum=1 THEN part2 ELSE SUBSTRING(part2,1,LEN(part2)-1) END AS part2New
) AS partsNew
ORDER BY part1,part2new,part3,ID;
Stepwise calculation in CROSS APPLYs, sorting on concatenated padded string
42 seconds on 3 mio rows
4.2 seconds on 300.000 rows
0.7 seconds on 30.000 rows
0.4 seconds on 3.000 rows
The code
SELECT ID,sortColumn
FROM #tbl
CROSS APPLY(SELECT CHARINDEX('-',sortColumn) AS posMinus) AS pos
CROSS APPLY(SELECT SUBSTRING(sortColumn,1,posMinus-1) AS part1
,SUBSTRING(sortColumn,posMinus+1,1000) AS part2
) AS parts
ORDER BY RIGHT('.....' + part1,5) + RIGHT('.....' + part2,5 - ISNUMERIC(RIGHT(part2,1)))
,ID;
Splitting with XML, sorting on concatenated padded string
67 seconds on 3 mio rows
6.2 seconds on 300.000 rows
0.7 seconds on 30.000 rows
0.3 seconds on 3.000 rows
The code
SELECT ID,sortColumn
FROM
(
SELECT CAST('<r>' + REPLACE(sortColumn,'-','</r><r>') + '</r>' AS XML) AS SortColumnSplitted
,*
FROM #tbl
) AS tbl
ORDER BY RIGHT('.....' + SortColumnSplitted.value('r[1]','varchar(max)'),5) + RIGHT('.....' + SortColumnSplitted.value('r[2]','varchar(max)'),5 - ISNUMERIC(RIGHT(SortColumnSplitted.value('r[2]','varchar(max)'),1)))
,ID;
I agree with thomas, but I also have a lot of the .Net Regex and String functions exposed via CLR. Other techniques we use a little are user defined functions that recursively go character by character to strip out non desired characters (e.g. no alpha when looking for a number, no number when looking for alpha). But in the particular case you presented if you know the format will be pretty standard you can use a combination of ISNUMERIC, SUBSTRINGS, etc. to reach your goal pretty easily. For example. If you know it is always:
Alpha + "-" + Numeric (1-3 digits) + alpha
you could do the following and it will sort The alpha as alpha, numeric as numeric, and alpha as alpha.
DECLARE #Values AS TABLE (Value VARCHAR(5))
INSERT INTO #Values (Value)
VALUES ('A-1')
,('A-10')
,('A-2')
,('A-3')
,('A-4')
,('A-5')
,('A-6')
,('A-7')
,('A-8')
,('A-9')
,('A-3a')
,('A-3b')
,('A-3c')
,('B-1')
,('B-10')
,('B-11')
,('B-12')
,('B-12a')
,('B-12b')
,('B-13')
,('B-2')
,('B-3')
,('B-4')
,('B-5')
,('B-6')
,('B-7')
,('B-8')
,('B-9')
SELECT
*
,FirstAlphaSection = LEFT(Value,CHARINDEX('-',Value) -1)
,SecondNumericSection = CASE
WHEN ISNUMERIC(SUBSTRING(Value,CHARINDEX('-',Value) + 1,3)) = 1 THEN SUBSTRING(Value,CHARINDEX('-',Value) + 1,3)
WHEN ISNUMERIC(SUBSTRING(Value,CHARINDEX('-',Value) + 1,2)) = 1 THEN SUBSTRING(Value,CHARINDEX('-',Value) + 1,2)
WHEN ISNUMERIC(SUBSTRING(Value,CHARINDEX('-',Value) + 1,1)) = 1 THEN SUBSTRING(Value,CHARINDEX('-',Value) + 1,1)
ELSE NULL
END
,ThirdAlphaSection =
RIGHT(Value,
LEN(Value) -
LEN(LEFT(Value,CHARINDEX('-',Value) -1))
- LEN(CASE
WHEN ISNUMERIC(SUBSTRING(Value,CHARINDEX('-',Value) + 1,3)) = 1 THEN SUBSTRING(Value,CHARINDEX('-',Value) + 1,3)
WHEN ISNUMERIC(SUBSTRING(Value,CHARINDEX('-',Value) + 1,2)) = 1 THEN SUBSTRING(Value,CHARINDEX('-',Value) + 1,2)
WHEN ISNUMERIC(SUBSTRING(Value,CHARINDEX('-',Value) + 1,1)) = 1 THEN SUBSTRING(Value,CHARINDEX('-',Value) + 1,1)
ELSE NULL
END)
- 1 --the '-'
)
FROM
#Values
ORDER BY
LEFT(Value,CHARINDEX('-',Value) -1)
,CAST((CASE
WHEN ISNUMERIC(SUBSTRING(Value,CHARINDEX('-',Value) + 1,3)) = 1 THEN SUBSTRING(Value,CHARINDEX('-',Value) + 1,3)
WHEN ISNUMERIC(SUBSTRING(Value,CHARINDEX('-',Value) + 1,2)) = 1 THEN SUBSTRING(Value,CHARINDEX('-',Value) + 1,2)
WHEN ISNUMERIC(SUBSTRING(Value,CHARINDEX('-',Value) + 1,1)) = 1 THEN SUBSTRING(Value,CHARINDEX('-',Value) + 1,1)
ELSE NULL
END) AS INT)
,RIGHT(Value,
LEN(Value) -
LEN(LEFT(Value,CHARINDEX('-',Value) -1))
- LEN(CASE
WHEN ISNUMERIC(SUBSTRING(Value,CHARINDEX('-',Value) + 1,3)) = 1 THEN SUBSTRING(Value,CHARINDEX('-',Value) + 1,3)
WHEN ISNUMERIC(SUBSTRING(Value,CHARINDEX('-',Value) + 1,2)) = 1 THEN SUBSTRING(Value,CHARINDEX('-',Value) + 1,2)
WHEN ISNUMERIC(SUBSTRING(Value,CHARINDEX('-',Value) + 1,1)) = 1 THEN SUBSTRING(Value,CHARINDEX('-',Value) + 1,1)
ELSE NULL
END)
- 1 --the '-'
)
The most powerful solution is to create an SQL CLR function. That's a bit tough, though.
Another approach is writing an insert/update trigger that splits the value in the mixed column with TSQL and stores the three parts (character, number, character) in specific helper columns (that you can use to sort).
Based on your examples, you can experiment with the splitting along the lines of this code:
declare #value nvarchar(10) = 'B-12b';
-- first part
select substring(#value, 1, 1)
-- second part
select case when isnumeric(right(#value, 1)) = 1
then substring(#value, 3, len(#value) - 2)
else substring(#value, 3, len(#value) - 3)
end
-- third part
select case when isnumeric(right(#value, 1)) = 1
then '_'
else right(#value, 1)
end
I have a table with two string columns, ID and LANGUAGE. Language can be one of CYM, ENG, GAE.
When selecting data from the table, I want to order it by the language column, putting a specified language at the start of the results, with the other languages later, in any order.
Say I have the data
+===+=====+
+ 1 + CYM +
+ 2 + GAE +
+ 3 + ENG +
+ 4 + CYM +
+===+=====+
and I want the output to be
+===+=====+
+ 3 + ENG +
+ 1 + CYM +
+ 2 + GAE +
+ 4 + CYM +
+===+=====+
How do I do the equivalent of
SELECT ID, LANGUAGE
FROM TABLE
ORDER BY (LANGUAGE = 'ENG'), ID
You can use CASE WHEN to ensure ENG will be first:
SELECT ID, LANGUAGE
FROM your_table
ORDER BY
CASE LANGUAGE WHEN 'ENG' THEN 0
ELSE 1
END ASC;
-- ,ID ASC -- if needed
LiveDemo
==> Referring to this Thread!
Referring to the output shown as best solution there, how can I get the boundary cells? That is min(StartX), min(StartY) and max(EndX) and max(EndY) OR in certain cases max(EndX+1) or max(EndY+1) if the column or row be missed out as in the case of 3,10 in the image below (green bordered are my bounding cells)
X Y PieceCells Boundary
1 1 (1,1)(2,1)(2,2)(3,2) (1,1)(3,2)
8 1 (10,1)(8,1)(8,2)(9,1)(9,2)(9,3) (8,1)(10,1)
Well I want like this:
BoundaryStartX, BoundaryStartY, BoundaryEndX, BoundaryEndY
1 1
3 2
8
1
10 3
I was able to do this pretty simply with the geometry data type.
declare #g geometry;
set #g = geometry::STGeomFromText(
'POLYGON( (1 -1, 1 -2, 2 -2, 2 -3, 4 -3, 4 -2, 3 -2, 3 -1, 1 -1) )'
, 0);
select #g, #g.STEnvelope();
Geometry is available starting in SQL2008. Also note that I converted your coordinate system to standard Cartesian (positive x axis to the right of the origin, negative y axis below); you'd do well to consider doing the same.
use tempdb;
if exists (select 1 from sys.tables where name = 'grid')
drop table grid;
if not exists (select 1 from sys.tables where name = 'tally')
begin
create table tally (i int not null);
with
a as (select 1 as [i] union select 0),
b as (select 1 as [i] from a as [a1] cross join a as [a2]),
c as (select 1 as [i] from b as [a1] cross join b as [a2]),
d as (select 1 as [i] from c as [a1] cross join c as [a2]),
e as (select 1 as [i] from d as [a1] cross join d as [a2])
insert into tally
select row_number() over (order by i) from e
create unique clustered index [CI_Tally] on tally (i)
end
create table grid (
x tinyint,
y tinyint,
cell as geometry::STGeomFromText(
'POLYGON( (' +
cast(x as varchar) + ' ' + cast(-1*y as varchar) + ', ' +
cast(x+1 as varchar) + ' ' + cast(-1*y as varchar) + ', ' +
cast(x+1 as varchar) + ' ' + cast(-1*(y+1) as varchar) + ', ' +
cast(x as varchar) + ' ' + cast(-1*(y+1) as varchar) + ', ' +
cast(x as varchar) + ' ' + cast(-1*y as varchar) +
') )'
, 0)
);
insert into grid (x, y)
values
(1,1),
(2,1),
(2,2),
(3,2),
(8,1),
(9,1),
(8,2),
(9,2),
(9,3),
(10,1);
with cte as (
select cell, row_number() over (order by x, y) as [rn]
from grid
),
cte2 as (
select cell, [rn]
from cte
where [rn] = 1
union all
select a.cell.STUnion(b.cell) as [cell], b.rn
from cte2 as a
inner join cte as b
on a.rn + 1 = b.[rn]
), cte3 as (
select cell
from cte2
where [rn] = (select count(*) from grid)
), clusters as (
select i, cell.STGeometryN(t.i) as c
from cte3 as [a]
cross join tally as [t]
where t.i <= cell.STNumGeometries()
)
select *, c.STEnvelope() from clusters
This solution solves both your original problem and this one. I like this because you can still use whatever weird coordinate system you want and it'll do what you want. All you'd have to do is modify the computed column on the grid table accordingly. I'm going to leave the computation of the corners of the envelope as an exercise to the reader. :)
By way of explanation, the computed column makes a 1x1 geometry instance out of the given x and y coordinates. From there, I essentially union all of those together which will yield a multipolygon. From there, I iterate through the individual polygons in the multipolygon to get the individual clusters. The envelope comes along for free. From here, you should be able to wrap that final select (or something very like it) in a view if you so choose.