T-SQL Calculate the max value from an Alpha Numeric key - sql

I have a customers table which has an Alphanumeric key consisting of 5 letters and 3 numbers.
I'm trying to calculate the next 3 digit number in sequence for each 5 letters for example:
Example Key
ALPHA001
ALPHA002
NUMBE001
NUMBE002
NUMBE003
PREST001
PREST002
PREST003
PREST004
PREST005
From the list of keys above i'd like to return the maximum of each unique 5 letter key. i.e.
Returned Values
ALPHA002
NUMBE003
PREST005

First of all: Do not store more than one value within one column. should store the key and the running number in separate columns and combine them just for display purpose...
Try this
DECLARE #mockupTable TABLE(ID INT IDENTITY,YourKey VARCHAR(100));
INSERT INTO #mockupTable VALUES
('ALPHA001')
,('ALPHA002')
,('NUMBE001')
,('NUMBE002')
,('NUMBE003')
,('PREST001')
,('PREST002')
,('PREST003')
,('PREST004')
,('PREST005');
WITH cte AS
(
SELECT *
,ROW_NUMBER() OVER(PARTITION BY LEFT(YourKey,5) ORDER BY CAST(RIGHT(YourKey,3) AS INT) DESC) AS PartitionedRowNumber
FROM #mockupTable
)
SELECT *
FROM cte
WHERE PartitionedRowNumber =1;
The result
ID Key
2 ALPHA002
5 NUMBE003
10 PREST005

You can use row_number():
select top (1) with ties t.*
from table t
order by row_number() over (partition by left(col, patindex('%[0-9]%', col)) order by col desc);
If the letters are fixed then just use left() :
order by row_number() over (partition by left(col, 5) order by col desc);

I'm trying to calculate the next 3 digit number in sequence for each 5
letters
This should do it:
SELECT CONCAT(LEFT(k, 5), FORMAT(MAX(RIGHT(k, 3)) + 1, '000'))
FROM (VALUES
('ALPHA001'),
('ALPHA002'),
('NUMBE001'),
('NUMBE002'),
('NUMBE003'),
('PREST001'),
('PREST002'),
('PREST003'),
('PREST004'),
('PREST005')
) tests(k)
GROUP BY LEFT(k, 5)

You can do this with GROUP BY and MAX:
SELECT KeyPrefix = LEFT(ExampleKey, 5),
NextKey = CONCAT(LEFT(ExampleKey, 5),
RIGHT(CONCAT('000', MAX(CONVERT(INT, RIGHT(ExampleKey, 3))) + 1), 3))
FROM (VALUES
('ALPHA001'), ('ALPHA002'), ('NUMBE001'), ('NUMBE002'), ('NUMBE003'),
('PREST001'), ('PREST002'), ('PREST003'), ('PREST004'), ('PREST005')
) t (ExampleKey)
GROUP BY LEFT(ExampleKey, 5);
The key operations being:
Get number part of key: RIGHT(ExampleKey, 3)
Convert this to an integer: CONVERT(INT, <output from 1>)
Find the max for the key type and add 1: MAX(<output from 2>) + 1
Pad this with zeros: RIGHT(CONCAT('000', MAX(<output from 3>), 3)
Concatenate withthe original prefix: CONCAT(LEFT(ExampleKey, 5), <output from 4>)
I would however highly recommed storing this in two columns, and use a computed column to combine then:
CREATE TABLE dbo.T
(
KeyPrefix CHAR(5) NOT NULL,
KeySequence INT NOT NULL,
TKey AS CONCAT(KeyPrefix, RIGHT(CONCAT('000', KeySequence), 3))
);
Then your query becomes much simpler:
SELECT KeyPrefix,
KeySequence = MAX(KeySequence) + 1,
TKey = CONCAT(KeyPrefix, RIGHT(CONCAT('000', MAX(KeySequence) + 1), 3))
FROM (VALUES
('ALPHA', 1), ('ALPHA', 2), ('NUMBE', 1), ('NUMBE', 2), ('NUMBE', 3),
('PREST', 1), ('PREST', 2), ('PREST', 3), ('PREST', 4), ('PREST', 5)
) t (KeyPrefix, KeySequence)
GROUP BY KeyPrefix;
Although worth noting that you would never actually need to reconstruct the key as I have done above in the column TKey, you just need the max keysequence.

Use this query.
GO
;WITH cte AS
(
SELECT ROW_NUMBER() OVER(PARTITION BY LEFT(YourKey,patindex('%[0-9]%', YourKey)) ORDER BY CAST(RIGHT(YourKey,patindex('%[A-Z]%', YourKey)) AS INT) DESC) AS rr , YourKey FROM #mockupTable
)
SELECT YourKey FROM cte WHERE rr =1;
GO

Related

PostgreSQL: Refer to specific nth column of WITH created temponary table

Supposing I have a temporary table created using WITH clause as follows:
WITH temporary_table AS (
VALUES ('First', 1),
('Second', 2),
('Third', 3))
Is it possible to refer to the nth column in the SELECT clause? Something like:
WITH temporary_table AS (
VALUES ('First', 1),
('Second', 2),
('Third', 3)
)
SELECT second_column
FROM temporary_table;
If not - is there any other way to make up some temporary table only for query purposes when having read only privileges?
I haven't found anything helpful on WITH or SELECT PostgreSQL docs.
Just give them a name, e.g as part of the WITH clause:
WITH temporary_table (c1, c2) AS (
VALUES ('First', 1),
('Second', 2),
('Third', 3)
)
SELECT c2
FROM temporary_table;
A slightly more complicated way is to name the columns of the VALUES clause:
WITH temporary_table AS (
select *
from (
VALUES ('First', 1),
('Second', 2),
('Third', 3)
) as t(c1, c2)
)
SELECT c2
FROM temporary_table;
Name the columns.
For example:
WITH temporary_table (a, b) AS (
VALUES ('First', 1),
('Second', 2),
('Third', 3)
)
SELECT b
FROM temporary_table;

Precise sort order of records that were found in a IN split function in SQL Server

I need to return records that I send to a stored procedure in a comma-separated string - like this:
#PMID = 29573145,24106086,20513766,24326307
I have a stored procedure that pulls records such as
SELECT
data,
PMID
FROM
[dbo].[ADMIN_Publication_JSON]
WHERE
PMID IN (SELECT DATA FROM dbo.Split(#PMID, ','))
The problem that I am having is that the return record set is random and I need it precise because my end user could change the order and the records need to be displayed in that order which would change the order in the comma string. Is this possible or do I need to totally change the way I pull the data? Thanks
You can use a window function like
Select T1.data,
T1.PMID
FROM [dbo].[ADMIN_Publication_JSON] T1 INNER JOIN
(SELECT Data,
ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) RN
FROM dbo.Split(#PMID,',')
) T2 ON T1.PMID = T2.Data
ORDER BY T2.RN;
Here is a little sample:
CREATE TABLE T(
ID INT,
SomeValue VARCHAR(45)
);
INSERT INTO T VALUES
(1, 'One'),
(2, 'Two'),
(3, 'Three'),
(4, 'Four'),
(5, 'Five');
DECLARE #IDs VARCHAR(200) = '3,5,2';
SELECT T.*
FROM T INNER JOIN
(SELECT Value,
ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) Seq
FROM STRING_SPLIT(#Ids, ',') --instead of your function
) TT
ON T.ID = TT.Value
ORDER BY TT.Seq;
Live Demo
Split method does not sort the Data column that means simple join with its result can do the trick. You don't need ROW_NUMBER() or any sorting effort here. Have a temp table store Splits result and LEFT JOIN the two. This works for me.
CREATE TABLE #Input (PMID varchar(10))
INSERT INTO #Input SELECT Data FROM dbo.Split(#PMID, ',')
SELECT
jsn.*
FROM
#Input spl INNER JOIN ADMIN_Publication_JSON jsn on spl.PMID = jsn.PMID
Output: Returns the set in the order passed in #PMID
I'm sorry to say but the currently accepted answer (by Sami) is wrong.
The problem with this answer is that it use ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) to get the order of the items in the comma delimited string, but since the order by is done on select null, what actually happens is that the row_number will assign the numbers in an arbitrary order - that may or may not match the order of the strings in the source string.
For more information, read Conor Cunningham's No Seatbelt – Expecting Order without ORDER BY.
If your split UDF returns a table with two columns, where one contains the substring and the other contains it's index, like Jeff Moden's DelimitedSplit8K, then simply use the ItemNumber (or equivalent) column for the order by. If it only returns a single column containing the substrings, you can use this a nice trick I've learned from Aaron Bertrand's Solve old problems with SQL Server’s new STRING_AGG and STRING_SPLIT functions - it will guarantee to return the correct order of the substrings as long as they are unique.
A simple change on Sami's answer will give you correct results as long as the substrings are unique within the comma delimited string - Instead of ROW_NUMBER() OVER(ORDER BY (SELECT NULL)), use CHARINDEX(',' + Value + ',', ',' + #Ids + ','), which will return the index of each substring inside the comma delimited string:
CREATE TABLE T(
ID INT,
SomeValue VARCHAR(45)
);
INSERT INTO T VALUES
(1, 'One'),
(2, 'Two'),
(3, 'Three'),
(4, 'Four'),
(5, 'Five');
DECLARE #IDs VARCHAR(200) = '3,5,2';
SELECT T.*
FROM T
INNER JOIN
(SELECT Value,
CHARINDEX(',' + Value + ',', ',' + #Ids + ',') AS Seq
FROM dbo.Split(#Ids, ',')
) TT
ON T.ID = TT.Value
ORDER BY TT.Seq;

Find and classify sequential patterns for a distinct group T-SQL

I need help finding and classifying sequential patterns for each distinct key.
From the data I have, I need to create a new table that contains the key and a pattern identifier that belongs to that key.
From the example below the pattern is as follows:
Key #1 and #3 have the values 1, 2 and 3. The Key #3 has the values 8,
9 and 10. When a distinct pattern exists for a key I.E (1, 2, 3) I
need to create an entry on the table for the key # and that specific
pattern (1, 2, 3)
Data:
key value
1 1
1 2
1 3
2 8
2 9
2 10
3 1
3 2
3 3
Expected Output:
key pattern
1 1
2 2
3 1
Fiddle:
http://sqlfiddle.com/#!6/4fe39
Example table:
CREATE TABLE yourtable
([key] int, [value] int)
;
INSERT INTO yourtable
([key], [value])
VALUES
(1, 1),
(1, 2),
(1, 3),
(2, 8),
(2, 9),
(2, 10),
(3, 1),
(3, 2),
(3, 3)
;
You can concatenate the values together in several ways. The traditional method in SQL Server uses for xml:
select k.key,
stuff( (select ',' + cast(t.id as varchar(255))
from t
where k.key = t.key
for xml path ('')
order by t.id
), 1, 1, ''
) as ids
from (select distinct key from t) k;
You can convert this to a unique number using a CTE/subquery:
with cte as (
select k.key,
stuff( (select ',' + cast(t.id as varchar(255))
from t
where k.key = t.key
for xml path ('')
order by t.id
), 1, 1, ''
) as ids
from (select distinct key from t) k
)
select cte.*, dense_rank() over (order by ids) as ids_id
from cte;

How to Assign Numbers to a Set of Rows Partitioning Based on a Gap in Consecutive Numbers SQL

Hello Development Community,
Is there a way to assign a number to a group of rows partitioning based on a gap in a field of consecutive numbers in SQL? I've been searching/trying various things for an answer to this for a few days and have come up empty. Please consider the example:
CREATE TABLE #example
(ID int, Service_Date date, Item_Num int, Desired_Column int)
INSERT INTO #example VALUES
('1111', GetDate(), 4, 1),
('1111', GetDate(), 5, 1),
('1111', GetDate(), 7, 2),
('1111', GetDate(), 8, 2),
('1111', GetDate(), 9, 2),
('1111', GetDate(), 11, 3),
('1111', GetDate(), 12, 3),
('1111', GetDate(), 13, 3)
I am trying to assign the values in Desired_Column but am failing. A new number should be assigned each time there is a gap in consecutive Item_Num values. I've tried multiple approaches using DENSE_RANK(), PARTITION BY, NTILE(), finding the differenece between the first/next row item number, but I just can't get this working. Is this even possible?
Thanks for taking the time, it is appreciated.
This is a gaps & islands problem, a common solution applies nested Analytical Functions. First you calculate a flag based on a condition (here: there's a gap > 1 between the current and the previous row) and then you do a Cumulative Sum over that flag:
with cte as
(
select ...,
case when lag(Item_Num) over (partition by ID order by Item_Num) + 1
= Item_Num
then 0 -- gap = 1 -> part of the previous group
else 1 -- gap > 1 ->new group
end as flag
from #example
)
select ...,
sum(flag) over (partition by ID order by Item_Num)
from cte

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)