Sorting VARCHAR column which contains integers - sql

I have this table:
IF OBJECT_ID('tempdb..#Test') IS NOT NULL
DROP TABLE #Test;
CREATE TABLE #Test (Col VARCHAR(100));
INSERT INTO #Test
VALUES ('1'), ('2'), ('10'), ('A'), ('B'), ('C1'), ('1D'), ('10HH')
SELECT * FROM #Test
I want to sort by numeric value first and then alphabetically.
Outcome of sort I want to is:
1
1D
2
10
10HH
A
B
C1
Assume structure of entries is one of those (with no dash of course)
number
number-string
string-number
string
if there is an entry like string-number-string, assume it is string-number

It's not pretty, but it works.
SELECT T.Col
FROM #Test T
CROSS APPLY (VALUES(PATINDEX('%[^0-9]%',T.Col)))PI(I)
CROSS APPLY (VALUES(TRY_CONVERT(int,NULLIF(ISNULL(LEFT(T.Col,NULLIF(PI.I,0)-1),LEN(T.Col)),''))))TC(L)
ORDER BY CASE WHEN TC.L IS NULL THEN 1 ELSE 0 END,
TC.L,
T.Col;
Honestly, I would suggest that if you want to order your data like a numerical value you actually store the numerical value in a numerical column; clearly the above should be a numerical prefix value, and then the string suffix. If you then want to then have the values you have, the use a (PERSISTED) computed column. Like this:
CREATE TABLE #Test (Prefix int NULL,
Suffix varchar(100) NULL,
Col AS CONCAT(Prefix, Suffix) PERSISTED);
INSERT INTO #Test (Prefix, Suffix)
VALUES (1,NULL), (2,NULL), (10,NULL), (NULL,'A'), (NULL,'B'), (NULL,'C1'), (1,'D'), (10,'HH');
SELECT Col
FROM #Test
ORDER BY CASE WHEN Prefix IS NULL THEN 1 ELSE 0 END,
Prefix,
Suffix;

This awful and unintuitive solution, that would be unnecessary if you stored the two pieces of data separately, brought to you by bad idea designs™:
;WITH cte AS
(
SELECT Col, rest = SUBSTRING(Col, pos, 100),
possible_int = TRY_CONVERT(bigint, CASE WHEN pos <> 1 THEN
LEFT(Col, COALESCE(NULLIF(pos,0),100)-1) END)
FROM (SELECT Col, pos = PATINDEX('%[^0-9]%', Col) FROM #Test) AS src
)
SELECT Col FROM cte
ORDER BY CASE
WHEN possible_int IS NULL THEN 2 ELSE 1 END,
possible_int,
rest;
Result:
Col
1
1D
2
10
10HH
A
B
C1
Example db<>fiddle

Related

Can I return all columns as rows and their values in the second column?

I'm trying to create some reports for auditing, but I have a very specific question.
There's about 120 columns, each with a specific numeric answer. I'd like to return the column name and the value of the rows of that column. I'm aware I'll get a lot of results, but it's not a problem.
For example I have:
KEY |ColumnA | ColumnB
1 |Value A | ValueB
2 |ValueA2 | ValueB2
But want I want is:
1 |ColumnA | Value A
2 |ColumnA | Value A2
1 |ColumnB | Value B
2 |ColumnB | Value B2
I've tried returning all rows and then joining on itself, but it didn't provide me with the output I needed.
Simple unpivot will do the work :)
declare #tbl table ([Key] int, ColumnA varchar(15), ColumnB varchar(15));
insert into #tbl values
(1, 'Value A', 'ValueB'),
(2, 'ValueA2', 'ValueB2');
select [key], [column], [value] from
(select * from #tbl) p
unpivot
([value] for [column] in (ColumnA, ColumnB)) u
order by [column]
it's so simple...If you know the column names, you could use a simple UNION
SELECT * FROM tblAuditing
SELECT 'ColumnA' AS ColumnA,'ColumnB' AS ColumnA UNION
SELECT ColumnA AS ColumnA,ColumnB AS ColumnA FROM tblAuditing
The following query should do what you want - you need to do a customized sorting for the columns names:
CREATE TABLE #temp (ColumnA VARCHAR(20), ColumnB VARCHAR(20))
INSERT INTO #temp VALUES ('Value A','Value B'),('Value A2','Value B2')
SELECT t.Col, t.Val
FROM (SELECT *,ROW_NUMBER() OVER (ORDER BY (SELECT 1)) RNO FROM #temp t) tmp
CROSS APPLY (VALUES (tmp.ColumnA,'ColumnA',tmp.RNO),(tmp.ColumnB,'ColumnB',tmp.RNO)) AS T(Val,Col,sort)
ORDER BY T.Col, Sort
The result is as below,
Col Val
ColumnA Value A
ColumnA Value A2
ColumnB Value B
ColumnB Value B2

Find rows that contain same value inside comma separated values

I have a varchar column, populated by another process where I have no control over, that is filled with comma separated values.
Now I need to find all rows where part of this column exists in that same column, in another row
example
declare #table table (value varchar(50))
insert into #table values ('NB,BD,FR'), ('BD,GK'), ('SL,SR')
select * from #table
so the table contains
value
-----
NB,BD,FR
BD,GK
SL,SR
from the example above I would like to get
value
-----
NB,BD,FR
BD,GK
Because there is a value (in this case BD but can be anything) present in both rows
Can this be done in sql?
You could use clunky XML manipulation to convert comma separated values to rows:
DECLARE #table TABLE (value VARCHAR(50));
INSERT INTO #table VALUES
('NB,BD,FR'),
('BD,GK'),
('SL,SR');
WITH cte AS (
SELECT value, node.value('.', 'varchar(10)') AS substr
FROM #table
CROSS APPLY (SELECT CAST('<x>' + REPLACE(value, ',', '</x>,<x>') + '</x>' AS XML)) AS x(doc)
CROSS APPLY doc.nodes('/x') AS n(node)
)
-- use your favorite technique to find the duplicate
SELECT value
FROM cte AS m
WHERE EXISTS (
SELECT 1
FROM cte AS x
WHERE value <> m.value AND substr = m.substr
)
The CAST(... AS XML) part assumes that your data does not contain characters that have special meaning in XML. The nodes method will convert one row to many, rest is straight forward.
This is the wrong data structure. Don't store values in strings!
declare #table table (id int, value varchar(50));
insert into #table
values (1, 'NB'), (1, 'BD'), (1, 'FR'),
(2, 'BD'), (2, 'GK'),
(3, 'SL'), (3, 'SR');
Then you can get what you want using window functions:
select id, value
from (select t.*, max(cnt) over (partition by id) as max_cnt
from (select t.*, count(*) over (partition by value) as cnt
from #table t
) t
) t
where max_cnt >= 2

Splitting of string by fixed keyword

Hi I currently have a tables with a column that I would like to split.
ID Serial
1 AAA"-A01-AU-234-U_xyz(CY)(REV-002)
2 AAA"-A01-AU-234-U(CY)(REV-1)
3 AAA"-A01-AU-234-U(CY)(REV-101)
4 VVV"-01-AU-234-Z_ww(REV-001)
5 VVV"-01-AU-234-Z(REV-001)_xyz(CY)
6 V-VV"-01-AU-234-Z(REV-03)_xyz(CY)
7 V-VV"-01-AU-234-Z-ZZZ(REV-004)_xyz(CY)
I would like to split this column into 2 field via a select statement
The first field would consist of the text from the start and end when this scenario is satisfied
After the first "-
take all text till the next 3 hypen (-)
Take the first letter after the last hypen(-)
The second field would want to store the Value(Int) inside the (REV) bracket. Rev is always stored inside a compassing bracket (Rev-xxx) the number may stretch from 0-999 and have different form of representation
Example of output
Field 1 Field 2
AAA"-A01-AU-234-U 2
AAA"-A01-AU-234-U 1
AAA"-A01-AU-234-U 101
VVV"-01-AU-234-Z 1
VVV"-01-AU-234-Z 1
V-VV"-01-AU-234-Z 3
V-VV"-01-AU-234-Z 4
Maybe it is possible to make it better and faster, but at least it does work. If i will have some time more i will look at this again to think of better solution, but it do the job.
create table #t
(
id int,
serial nvarchar(255)
)
go
insert into #t values (1, 'AAA"-A01-AU-234-U_xyz(CY)(REV-002)')
insert into #t values (2, 'AAA"-A01-AU-234-U(CY)(REV-1)')
insert into #t values (3, 'AAA"-A01-AU-234-U(CY)(REV-101)')
insert into #t values (4, 'VVV"-01-AU-234-Z_ww(REV-001)')
insert into #t values (5, 'VVV"-01-AU-234-Z(REV-001)_xyz(CY)')
insert into #t values (6, 'VVV"-01-AU-234-Z(REV-03)_xyz(CY)')
insert into #t values (7, 'VVV"-01-AU-234-Z(REV-004)_xyz(CY)')
go
select id, serial,
left(serial,charindex('-', serial, charindex('-', serial, charindex('-', serial, charindex('"',serial) + 2) +1) + 1) + 1) as 'Field2'
,cast( replace(left(right(serial, len(serial) - charindex('REV',serial) +1 ), CHARINDEX(')',right(serial, len(serial) - charindex('REV',serial) +1 )) - 1), 'REV-', '')as int) as 'Field1'
from #t
go
gives me:
id serial Field2 Field1
1 AAA"-A01-AU-234-U_xyz(CY)(REV-002) AAA"-A01-AU-234-U 2
2 AAA"-A01-AU-234-U(CY)(REV-1) AAA"-A01-AU-234-U 1
3 AAA"-A01-AU-234-U(CY)(REV-101) AAA"-A01-AU-234-U 101
4 VVV"-01-AU-234-Z_ww(REV-001) VVV"-01-AU-234-Z 1
5 VVV"-01-AU-234-Z(REV-001)_xyz(CY) VVV"-01-AU-234-Z 1
6 VVV"-01-AU-234-Z(REV-03)_xyz(CY) VVV"-01-AU-234-Z 3
7 VVV"-01-AU-234-Z(REV-004)_xyz(CY) VVV"-01-AU-234-Z 4
I came up with a solution in php using regular expressions.I am trying to convert it into posix standards supported by mysql.Anyways in the meanwhile you can have a look at this and it works perfect.
/The first script select the values for fields 1 namely AAA"-A01-AU-234-U/
<?php
$txt='VVV"-01-AU-234-Z(REV-001)_xyz(CY)';
$re1='((?:[a-z][a-z0-9_]*))';
$re2='.*?';
$re3='(\\d+)';
$re4='.*?';
$re5='((?:[a-z][a-z0-9_]*))';
$re6='.*?';
$re7='(\\d+)';
$re8='.*?';
$re9='([a-z])';
echo $re1.$re2.$re3.$re4.$re5.$re6.$re7.$re8.$re9;
if ($c=preg_match_all ("/".$re1.$re2.$re3.$re4.$re5.$re6.$re7.$re8.$re9."/is", $txt, $matches))
{
$var1=$matches[1][0];
$int1=$matches[2][0];
$var2=$matches[3][0];
$int2=$matches[4][0];
$w1=$matches[5][0];
print "($var1) ($int1) ($var2) ($int2) ($w1) \n";
}
?>
/*The second script selects values for field 2 namely the last integer*/
<?php
$txt='VVV"-01-AU-234-Z_ww(REV-001)';
$re1='.*?';
$re2='\\d';
$re3='.*?';
$re4='\\d';
$re5='.*?';
$re6='\\d';
$re7='.*?';
$re8='\\d';
$re9='.*?';
$re10='\\d';
$re11='.*?';
$re12='\\d';
$re13='.*?';
$re14='\\d';
$re15='(\\d)';
if ($c=preg_match_all ("/".$re1.$re2.$re3.$re4.$re5.$re6.$re7.$re8.$re9.$re10.$re11.$re12.$re13.$re14.$re15."/is", $txt, $matches))
{
$d1=$matches[1][0];
print "($d1) \n";
}
?>
OUTPUT:
(VVV) (01) (AU) (234) (Z) //script 1
(1) //script 2
You can add database connection to the script and store the results in a new table.You can aslo iterate each row as input to the script and store corresponding results in the table.
Note:
The regular expression used for selecting field 1:
((?:[a-z][a-z0-9_]*)).*?(\d+).*?((?:[a-z][a-z0-9_]*)).*?(\d+).*?([a-z])
The regular expression used for selecting field 2:
.*?\d.*?\d.*?\d.*?\d.*?\d.*?\d.*?\d(\d)
If anybody can convert the above expressions to posix standards then the user can write a simple query like
select t.serial as field 1 from table t
where t.serial regexp 'converted exp' join
(select t1.serial as field 2 from table t1
where t1.serial regexp 'converted exp')q
on q.id=t.id;
I tried to convert it but the matching constraints were lost.You should actually change ?: to ^ and ? to [^>] and //d to [0-9] or digit.Hope it helps.
Try this solution. It uses a combination of charindex and the substring function.
DECLARE #TempTable table
(
id int,
serial nvarchar(255)
)
insert into #TempTable values (1, 'AAA"-A01-AU-234-U_xyz(CY)(REV-002)')
insert into #TempTable values (2, 'AAA"-A01-AU-234-U(CY)(REV-1)')
insert into #TempTable values (3, 'AAA"-A01-AU-234-U(CY)(REV-101)')
insert into #TempTable values (4, 'VVV"-01-AU-234-Z_ww(REV-001)')
insert into #TempTable values (5, 'VVV"-01-AU-234-Z(REV-001)_xyz(CY)')
insert into #TempTable values (6, 'VVV"-01-AU-234-Z(REV-03)_xyz(CY)')
insert into #TempTable values (7, 'VVV"-01-AU-234-Z(REV-004)_xyz(CY)')
select
id,
serial,
substring(serial, 1, P4.Pos+1) as field1,
convert(int, substring(Serial, P6.Pos , P7.Pos - P6.Pos)) as field2
from #TempTable
cross apply (select (charindex('-', Serial))) as P1(Pos)
cross apply (select (charindex('-', Serial, P1.Pos+1))) as P2(Pos)
cross apply (select (charindex('-', Serial, P2.Pos+1))) as P3(Pos)
cross apply (select (charindex('-', Serial, P3.Pos+1))) as P4(Pos)
cross apply (select (charindex('REV-', Serial,P1.Pos+1)+4)) as P6(Pos)
--+4 because 'REV-' is 4 chars long
cross apply (select (charindex(')', Serial,P6.Pos+1))) as P7(Pos);
I have updated my answer. Is this better now?
DECLARE #Table table(ID int, SERIAL nvarchar(100));
INSERT INTO #Table(ID, SERIAL)
VALUES ('1', 'AAA"-A01-AU-234-U_xyz(CY)(REV-002)'),
('2', 'AAA"-A01-AU-234-U(CY)(REV-1)'),
('3', 'AAA"-A01-AU-234-U(CY)(REV-101)'),
('4', 'VVV"-01-AU-234-Z_ww(REV-001)'),
('5', 'VVV"-01-AU-234-Z(REV-001)_xyz(CY)'),
('6', 'VVV"-01-AU-234-Z(REV-03)_xyz(CY)'),
('7', 'VVV"-01-AU-234-Z(REV-004)_xyz(CY)'),
('8', 'AAA"-A01-AU-234-U-1111-(REV-111)'),
('9', 'AAA"-A01-AU-234-U-111111-5555(CY)(REV-101)'),
('10', 'V-VV"-01-AU-234-Z-ZZZ(REV-004)_xyz(CY)')
SELECT
ID,
SERIAL,
LEFT(SERIAL, P5.Pos + 1) AS Field1,
CONVERT(int, SUBSTRING(SERIAL, P6.Pos, CHARINDEX(')', RIGHT(SERIAL, LEN(SERIAL) - P6.Pos)))) AS Field2
FROM #Table
CROSS APPLY (SELECT CHARINDEX('"-', SERIAL)) AS P1(Pos)
CROSS APPLY (SELECT CHARINDEX('-', SERIAL, P1.Pos + 1)) AS P2(Pos)
CROSS APPLY (SELECT CHARINDEX('-', SERIAL, P2.Pos + 1)) AS P3(Pos)
CROSS APPLY (SELECT CHARINDEX('-', SERIAL, P3.Pos + 1)) AS P4(Pos)
CROSS APPLY (SELECT CHARINDEX('-', SERIAL, P4.Pos + 1)) AS P5(Pos)
CROSS APPLY (SELECT CHARINDEX('REV-', SERIAL, P5.Pos + 1) + 4) AS P6(Pos)

Find missing numbers in a column

I have this column in T-SQL:
1
2
3
7
10
have SQl a function for detect the missing numbers in the sequence 4,5,6 and 8,9
I have try
something like
if ( a-b >1 ) then we have a missing number
with coalesce but i dont understand .
Thanks by any orientation
You can try this:
DELCARE #a
SET #a = SELECT MIN(number) FROM table
WHILE (SELECT MAX(number) FROM table ) > #a
BEGIN
IF #a NOT IN ( SELECT number FROM table )
PRINT #a
SET #a=#a+1
END
The following query will identify where each sequence starts and the number that are missing:
select t.col + 1 as MissingStart, (nextval - col - 1) as MissingSequenceLength
from (select t.col,
(select min(t.col) from t t2 where t2.col > t.col) as nextval
from t
) t
where nextval - col > 1
This is using a correlated subquery to get the next value in the table.
I know this is a late answer, but here is a query that uses recursive table expressions to get the missing values between the minimum and maximum values in a table:
WITH CTE AS
(
--This is called once to get the minimum and maximum values
SELECT nMin = MIN(t.ID), MAX(t.ID) as 'nMax'
FROM Test t
UNION ALL
--This is called multiple times until the condition is met
SELECT nMin + 1, nMax
FROM CTE
WHERE nMin < nMax
)
--Retrieves all the missing values in the table.
SELECT c.nMin
FROM CTE c
WHERE NOT EXISTS
(
SELECT ID
FROM Test
WHERE c.nMin = ID
)
This was tested with the following schema:
CREATE TABLE Test
(
ID int NOT NULL
)
INSERT INTO Test
Values(1)
INSERT INTO Test
Values(2)
INSERT INTO Test
Values(3)
INSERT INTO Test
Values(7)
INSERT INTO Test
Values(10)

MySQL: Is it possible to return a "mixed" dataset?

I'm wondering if there's some clever way in MySQL to return a "mixed/balanced" dataset according to a specific criterion?
To illustrate, let's say that there are potential results in a table that can be of Type 1 or Type 2 (i.e. a column has a value 1 or 2 for each record). Is there a clever query that would be able to directly return results alternating between 1 and 2 in sequence:
1st record is of type 1,
2nd record is of type 2,
3rd record is of type 1,
4th record is of type 2,
etc...
Apologies if the question is silly, just looking for some options. Of course, I could return any data and do this in PHP, but it does add some code.
Thanks.
Something like this query should do:
Select some_value, x, c
From
(
Select
some_value, x,
Case When x=1 Then #c1 Else #c2 End As c,
#c1 := Case When x=1 Then #c1+2 Else #c1 End As c1,
#c2 := Case When x=2 Then #c2+2 Else #c2 End As c2
From test_data, (Select #c1:=0, #c2:=1) v
Order By some_value
) sub
Order By c
It assigns unique even numbers to x=0, and odd numbers to x=1, and uses these values as sort criteria.
It returns
some_value x c
A 1 0
X 2 1
B 1 2
Y 2 3
C 1 4
Z 2 5
for the following test-data:
Create Table test_data (
some_value VARCHAR(10),
x INT
);
Insert Into test_data Values('A', 1);
Insert Into test_data Values('B', 1);
Insert Into test_data Values('C', 1);
Insert Into test_data Values('Z', 2);
Insert Into test_data Values('Y', 2);
Insert Into test_data Values('X', 2);
Within the alternating rule values are sorted by some_value, you can change this in the inner select, or add your conditions there.
If there are more values of a certain type (1 or 2), you get them after the rest (1 2 1 2 2 2).
You can use IF function as a part of your SELECT statement to change columns, but I'm not sure how to make is alternate automatically between two columns. If you however find proper condition this will work for you
SELECT IF(condition, first_column, second_column) FROM your_table
first_column and second_column can be of different type, for example:
SELECT IF(id > 10, name, status_id) FROM clients
works well when name is a VARCHAR and status_id is an INT