Reformatting data in column - sql

have something kinda weird here. I have a database that's called FLDOC. In it has a column called SENTENCE that contains 7 numbers that represent a length of time.
example:
0050000
0750000
0000600
0040615
0000110
In those 7 digits is a length of type since the digits represent YYYMMDD
So what I'd like is a script that can convert it to something like this:
5Y 00M 00D
75Y 00M 00D
6M (or 000Y 6M 00D is fine as well)
4Y 6M 15D etc etc
thanks in advance...

CONCAT is new to SQL Server 2012. If you have previous version of SQL Server, you could do something like this instead to achieve your desired output:
SELECT sentence
,(
CASE
WHEN cast(left(sentence, 3) AS INT) > 0
THEN cast(cast(left(sentence, 3) AS INT) AS VARCHAR(3)) + 'Y '
ELSE cast(left(sentence, 3) AS VARCHAR(3)) + 'Y '
END +
CASE
WHEN cast(substring(sentence, 4, 2) AS INT) > 0
THEN cast(cast(substring(sentence, 4, 2) AS INT) AS VARCHAR(2)) + 'M '
ELSE cast(substring(sentence, 4, 2) AS VARCHAR(2)) + 'M '
END +
CASE
WHEN cast(right(sentence, 2) AS INT) > 0
THEN cast(cast(right(sentence, 2) AS INT) AS VARCHAR(3)) + 'D'
ELSE cast(right(sentence, 2) AS VARCHAR(3)) + 'D'
END
) AS new_sentence
FROM FLDOC;
SQL Fiddle Demo
UPDATE
To answer your question below in the comments, you could maybe just write a update statement like this:
update FLDOC
set sentence = (
CASE
WHEN cast(left(sentence, 3) AS INT) > 0
THEN cast(cast(left(sentence, 3) AS INT) AS VARCHAR(3)) + 'Y '
ELSE cast(left(sentence, 3) AS VARCHAR(3)) + 'Y '
END +
CASE
WHEN cast(substring(sentence, 4, 2) AS INT) > 0
THEN cast(cast(substring(sentence, 4, 2) AS INT) AS VARCHAR(2)) + 'M '
ELSE cast(substring(sentence, 4, 2) AS VARCHAR(2)) + 'M '
END +
CASE
WHEN cast(right(sentence, 2) AS INT) > 0
THEN cast(cast(right(sentence, 2) AS INT) AS VARCHAR(3)) + 'D'
ELSE cast(right(sentence, 2) AS VARCHAR(3)) + 'D'
END
)

Try this query
select convert(varchar(10),left(example,3))+'Y '+
convert(varchar(10),Substring(example,4,3))+'M '+
convert(varchar(10),Right(example,3))+'D'+ from tablename

You can do this with Concat as well:
Select Concat
(
Left(SENTENCE, 3), 'Y ',
SubString(SENTENCE, 4, 2), 'M ',
Right(SENTENCE, 2), 'D'
)
From Table
To condense the expression as in your example, this can be used as well:
Select Concat
(
Case When (IsNumeric(Left(SENTENCE, 3)) = 1 And Left(SENTENCE, 3) <> '000')
Then Convert(Varchar (3), Convert(Int, Left(SENTENCE, 3))) + 'Y ' End,
Case When (IsNumeric(SubString(SENTENCE, 4, 2)) = 1 And SubString(SENTENCE, 4, 2) <> '00')
Then Convert(Varchar (2), Convert(Int, SubString(SENTENCE, 4, 2))) + 'M ' End,
Case When (IsNumeric(Right(SENTENCE, 2)) = 1 And Right(SENTENCE, 2) <> '00')
Then Convert(Varchar (2), Convert(Int, Right(SENTENCE, 2))) + 'D' End
)
From Table

Related

Select record between two IP ranges

I have a table which stores a ID, Name, Code, IPLow, IPHigh such as:
1, Lucas, 804645, 192.130.1.1, 192.130.1.254
2, Maria, 222255, 192.168.2.1, 192.168.2.254
3, Julia, 123456, 192.150.3.1, 192.150.3.254
Now, if I have an IP address 192.168.2.50, how can I retrieve the matching record?
Edit
Based on Gordon's answer (which I'm getting compilation errors) this is what I have:
select PersonnelPC.*
from (select PersonnelPC.*,
(
cast(parsename(iplow, 4)*1000000000 as decimal(12, 0)) +
cast(parsename(iplow, 3)*1000000 as decimal(12, 0)) +
cast(parsename(iplow, 2)*1000 as decimal(12, 0)) +
(parsename(iplow, 1))
) as iplow_decimal,
(
cast(parsename(iphigh, 4)*1000000000 as decimal(12, 0)) +
cast(parsename(iphigh, 3)*1000000 as decimal(12, 0)) +
cast(parsename(iphigh, 2)*1000 as decimal(12, 0)) +
(parsename(iphigh, 1))
) as iphigh_decimal
from PersonnelPC
) PersonnelPC
where 192168002050 between iplow_decimal and iphigh_decimal;
but this gives me an error:
Msg 8115, Level 16, State 2, Line 1
Arithmetic overflow error converting expression to data type int.
Any ideas?
Painfully. SQL Server has lousy string manipulation functions. It does, however, offer parsename(). This approach converts the IP address to a large decimal value for the comparison:
select t.*
from (select t.*,
(cast(parsename(iplow, 4)*1000000000.0 as decimal(12, 0)) +
cast(parsename(iplow, 3)*1000000.0 as decimal(12, 0)) +
cast(parsename(iplow, 2)*1000.0 as decimal(12, 0)) +
cast(parsename(iplow, 1) as decimal(12, 0))
) as iplow_decimal,
(cast(parsename(iphigh, 4)*1000000000.0 as decimal(12, 0)) +
cast(parsename(iphigh, 3)*1000000.0 as decimal(12, 0)) +
cast(parsename(iphigh, 2)*1000.0 as decimal(12, 0)) +
cast(parsename(iphigh, 1) as decimal(12, 0))
) as iphigh_decimal
from t
) t
where 192168002050 between iplow_decimal and iphigh_decimal;
I should note that IP addresses are often stored in the database as the 4-byte unsigned integers. This makes comparisons much easier . . . although you need complicated logic (usually wrapped in a function) to convert the values to a readable format.
Try this simple way checking range
DECLARE #IP NVARCHAR(30)='192.168.500.1'
SELECT * FROM
Branches
WHERE
CAST (PARSENAME(#IP,4) AS INT)>=CAST(PARSENAME(IPLow,4) AS INT) AND CAST(PARSENAME(#IP,3) AS INT)>=CAST(PARSENAME(IPLow,3) AS INT) AND CAST(PARSENAME(#IP,2) AS INT)>=CAST(PARSENAME(IPLow,2) AS INT) AND CAST(PARSENAME(#IP,1) AS INT)>=CAST(PARSENAME(IPLow,1) AS INT)
AND
CAST(PARSENAME( #IP,4) AS INT) <= CAST(PARSENAME(IPHigh ,4) AS INT) AND CAST(PARSENAME(#IP ,3) AS INT) <=CAST(PARSENAME(IPHigh ,3) AS INT) AND CAST(PARSENAME(#IP ,2) AS INT) <=CAST(PARSENAME(IPHigh ,2) AS INT) AND CAST(PARSENAME(#IP ,1) AS INT)<=CAST(PARSENAME(IPHigh ,1) AS INT)
AS Per #Ed Haper Comment Cast is needed.
With this function you can transform any IP address to a form where each part has 3 digits. With this you could do a normal alphanumeric compare. if you want you could return BIGINT too...
CREATE FUNCTION dbo.IPWidth3(#IP VARCHAR(100))
RETURNS VARCHAR(15)
BEGIN
DECLARE #RetVal VARCHAR(15);
WITH Splitted AS
(
SELECT CAST('<x>' + REPLACE(#IP,'.','</x><x>') + '</x>' AS XML) AS IPSplitted
)
SELECT #RetVal = STUFF(
(
SELECT '.' + REPLACE(STR(Part.value('.','int'),3),' ','0')
FROM Splitted.IPSplitted.nodes('/x') AS One(Part)
FOR XML PATH('')
),1,1,'')
FROM Splitted;
RETURN #RetVal;
END
GO
DECLARE #IP VARCHAR(100)='192.43.2.50';
SELECT dbo.IPWidth3(#IP);
The result
192.043.002.050
To reflect Ed Harper's comment here the same function returning a DECIMAL(12,0):
CREATE FUNCTION dbo.IP_as_Number(#IP VARCHAR(100))
RETURNS DECIMAL(12,0)
BEGIN
DECLARE #RetVal DECIMAL(12,0);
WITH Splitted AS
(
SELECT CAST('<x>' + REPLACE(#IP,'.','</x><x>') + '</x>' AS XML) AS IPSplitted
)
SELECT #RetVal =
CAST((
SELECT REPLACE(STR(Part.value('.','int'),3),' ','0')
FROM Splitted.IPSplitted.nodes('/x') AS One(Part)
FOR XML PATH('')
) AS DECIMAL(12,0))
FROM Splitted;
RETURN #RetVal;
END
GO
DECLARE #IP VARCHAR(100)='192.43.2.50';
SELECT dbo.IP_as_Number(#IP);
Use below to fetch the ipLow / IPHigh in 4 columns. You can use those columns to compare Ips.
DECLARE#ip VARCHAR(50)='192.168.0.81'
SELECT (SUBSTRING((#ip), 0,
patindex('%.%',
(#ip))))
,
substring((REPLACE(#ip, (SUBSTRING((#ip), 0,
patindex('%.%',
(#ip)) + 1)),
'')),
0,
patindex('%.%',
((REPLACE(#ip, (SUBSTRING((#ip), 0,
patindex('%.%',
(#ip)) + 1)),
''))))),
SUBSTRING((SUBSTRING(#ip, LEN((SUBSTRING((#ip), 0,
patindex('%.%',
(#ip))))) + 2 + LEN(substring((REPLACE(#ip, (SUBSTRING((#ip), 0,
patindex('%.%',
(#ip)) + 1)),
'')),
0,
patindex('%.%',
((REPLACE(#ip, (SUBSTRING((#ip), 0,
patindex('%.%',
(#ip)) + 1)),
'')))))) + 1,
LEN(#IP) - 1 - LEN(reverse(SUBSTRING(reverse(#ip), 0,
patindex('%.%',
reverse(#ip))))))), 0,
PATINDEX('%.%',
(SUBSTRING(#ip, LEN((SUBSTRING((#ip), 0,
patindex('%.%',
(#ip))))) + 2 + LEN(substring((REPLACE(#ip, (SUBSTRING((#ip), 0,
patindex('%.%',
(#ip)) + 1)),
'')),
0,
patindex('%.%',
((REPLACE(#ip, (SUBSTRING((#ip), 0,
patindex('%.%',
(#ip)) + 1)),
'')))))) + 1,
LEN(#IP) - 1 - LEN(reverse(SUBSTRING(reverse(#ip), 0,
patindex('%.%',
reverse(#ip))))))
))),
reverse(SUBSTRING(reverse(#ip), 0,
patindex('%.%',
reverse(#ip))))
Consider something like this example to convert the address into a number.
CREATE FUNCTION dbo.IPAddressAsNumber (#IPAddress AS varchar(15))
RETURNS bigint
BEGIN
RETURN
CONVERT (bigint,
CONVERT(varbinary(1), CONVERT(int, PARSENAME(#IPAddress, 4))) +
CONVERT(varbinary(1), CONVERT(int, PARSENAME(#IPAddress, 3))) +
CONVERT(varbinary(1), CONVERT(int, PARSENAME(#IPAddress, 2))) +
CONVERT(varbinary(1), CONVERT(int, PARSENAME(#IPAddress, 1))) )
END
and with that you could use standard operators like BETWEEN to find rows within the range you have in the table
DECLARE #t table (ID int, Name varchar(50), Code int, IPLow varchar(15), IPHigh varchar(15))
INSERT INTO #t VALUES
(1, 'Lucas', 804645, '192.130.1.1', '192.130.1.254'),
(2, 'Maria', 222255, '192.168.2.1', '192.168.2.254'),
(3, 'Julia', 123456, '192.150.3.1', '192.150.3.254')
SELECT * FROM #t
WHERE dbo.IPAddressAsNumber('192.168.2.50')
BETWEEN dbo.IPAddressAsNumber(IPLow) AND dbo.IPAddressAsNumber(IPHigh)
The scheme essentially uses PARSENAME to isolate each part of the address, converts each part into a SQL binary string, concatenating the strings together to get a single SQL binary string representing the address, and shows the result as a bigint.
In a textual representation of hexadecimal values think of this as smashing the 4 parts together 192(0xC0) + 168(0xA8) + 2(0x02) + 50(0x32) into 0xC0A80232. When you turn that combined string into its binary digits (0s and 1s) you would end up with something that could be thought of as the address in a binary form used by the network stack in address routing and subnet masking tables. When you turn that into a number in the form of an unsigned integer (or in this case a bigint) you get 3232236082.
Interestingly this scheme gives you a "number" that can be used in place of the original address in lots of ways. You can for example ping the number 2130706433 instead of the address 127.0.0.1 -- the name resolver in Windows will convert it similarly to how DNS is used to find the address of a hostname.
For the sake of completeness, here is another function that can be used to convert the number form back into the standard string form
CREATE FUNCTION dbo.IPAddressFromNumber (#IPNumber AS bigint)
RETURNS varchar(15)
BEGIN
RETURN
CONVERT (varchar(15),
CONVERT(varchar(3), CONVERT(int, SUBSTRING(CONVERT(varbinary(4), #IPNumber), 1,1))) + '.' +
CONVERT(varchar(3), CONVERT(int, SUBSTRING(CONVERT(varbinary(4), #IPNumber), 2,1))) + '.' +
CONVERT(varchar(3), CONVERT(int, SUBSTRING(CONVERT(varbinary(4), #IPNumber), 3,1))) + '.' +
CONVERT(varchar(3), CONVERT(int, SUBSTRING(CONVERT(varbinary(4), #IPNumber), 4,1))) )
END
select *
from ip a
join ip_details b
on a.ip_address >= b.ip_start
and a.ip_address <= b.ip_end;
In this, table "a" contains list of IP address and table "b" contains the IP ranges.
Instead of converting the ip address to numeric we can directly compare the string, it will do a byte by byte comparison.
This is working for me(PostgreSQL).
I was thinking along the lines of Gordon's answer, then realized you don't actually need to mess with numbers. If you zero-pad each part of the address, a string comparison works:
DECLARE #search varchar(50) = '192.168.2.50';
WITH DATA AS (
SELECT * FROM ( values
(1, 'Lucas', '192.130.1.1', '192.130.1.254'),
(2, 'Maria', '192.168.2.1', '192.168.2.254'),
(3, 'Julia', '192.150.3.1', '192.150.3.254')
) AS tbl (ID,Name,IPLow,IPHigh)
)
SELECT *
FROM DATA
WHERE REPLACE(STR(PARSENAME( #search, 4 ), 3, 0), ' ', '0')
+ REPLACE(STR(PARSENAME( #search, 3 ), 3, 0), ' ', '0')
+ REPLACE(STR(PARSENAME( #search, 2 ), 3, 0), ' ', '0')
+ REPLACE(STR(PARSENAME( #search, 1 ), 3, 0), ' ', '0')
BETWEEN
REPLACE(STR(PARSENAME( IPLow, 4 ), 3, 0), ' ', '0')
+ REPLACE(STR(PARSENAME( IPLow, 3 ), 3, 0), ' ', '0')
+ REPLACE(STR(PARSENAME( IPLow, 2 ), 3, 0), ' ', '0')
+ REPLACE(STR(PARSENAME( IPLow, 1 ), 3, 0), ' ', '0')
AND
REPLACE(STR(PARSENAME( IPHigh, 4 ), 3, 0), ' ', '0')
+ REPLACE(STR(PARSENAME( IPHigh, 3 ), 3, 0), ' ', '0')
+ REPLACE(STR(PARSENAME( IPHigh, 2 ), 3, 0), ' ', '0')
+ REPLACE(STR(PARSENAME( IPHigh, 1 ), 3, 0), ' ', '0')
You can, of course, put this inside a UDF for simplicity, though watch out for the performance hit on large queries.
CREATE FUNCTION dbo.IP_Comparable(#IP varchar(50))
RETURNS varchar(50)
WITH SCHEMABINDING
BEGIN
RETURN REPLACE(STR(PARSENAME( #IP, 4 ), 3, 0), ' ', '0')
+ REPLACE(STR(PARSENAME( #IP, 3 ), 3, 0), ' ', '0')
+ REPLACE(STR(PARSENAME( #IP, 2 ), 3, 0), ' ', '0')
+ REPLACE(STR(PARSENAME( #IP, 1 ), 3, 0), ' ', '0')
END
GO
DECLARE #search varchar(50) = '192.168.2.50';
WITH DATA AS (
SELECT * FROM ( values
(1, 'Lucas', '192.130.1.1', '192.130.1.254'),
(2, 'Maria', '192.168.2.1', '192.168.2.254'),
(3, 'Julia', '192.150.3.1', '192.150.3.254')
) AS tbl (ID,Name,IPLow,IPHigh)
)
SELECT *
FROM DATA
WHERE dbo.IP_Comparable(#search) BETWEEN dbo.IP_Comparable(IPLow) AND dbo.IP_Comparable(IPHigh)
This will avoid the issue you're having with integer overflows.
Depends on which record you are looking for the high or the low.
select * from table where IPlow like '192.168.2.50' or IPHigh like '192.168.2.50'

Extract last number out of existing one

I have to extract the next number out of given numbers. My table contains numbers like below. The main product is always with .1 at the end and could or not contains his subproducts e.g:
07.0001.1 (main product)
07.0001.2 (his sub)
07.0001.3 (his sub)
etc..
01.1453.1
01.1453.2
03.3456.1
03.3456.2
03.3456.3
03.5436.1
03.5436.2
03.5436.3
03.5436.4
12.7839.1
12.7839.2
12.3232.1
12.4444.1
12.4444.2
13.7676.1
i want to pass first to digits of a number to the query and based on that get all which starts with that and then get the highest number out of next four and return this number + 1.
So if we would take above example inputs if i say 12 then it should find this product: 12.7839.x and return 12.7839 + 1 so 12.7840
Another example if i say 03 then should find 03.5436 so 03.5436 + 1 so should return 03.5437
Hope you know what i mean.
I am not so familiar with SQL but this is how far i am:
select * from tbArtikel where Nummer LIKE '12.%'
This is another alternate for achieving the desired results. Providing the option to pass number to be queried. Consider following SQL statements
CREATE TABLE tblDummyExample
(
Number VARCHAR(64)
)
INSERT INTO tblDummyExample
VALUES ('07.0001.1')
, ('07.0001.2')
, ('07.0001.3')
, ('01.1453.1')
, ('01.1453.2')
, ('03.3456.1')
, ('03.3456.2')
, ('03.3456.3')
, ('03.5436.1')
, ('03.5436.2')
, ('03.5436.3')
, ('03.5436.4')
, ('12.7839.1')
, ('12.7839.2')
, ('12.3232.1')
, ('12.4444.1')
, ('12.4444.2')
, ('13.7676.1')
DECLARE #startWith VARCHAR(2) = '12' -- provide any number as input
SELECT #startWith + '.'+ CAST((MAX(CAST(SUBSTRING(ex.Number, (CHARINDEX('.', ex.Number, 1) + 1), (CHARINDEX('.', ex.Number, (CHARINDEX('.', ex.Number, 1) + 1)) - (CHARINDEX('.', ex.Number, 1) + 1))) AS INT)) + 1) AS VARCHAR(16))
FROM tblDummyExample ex
WHERE ex.Number LIKE #startWith+'%'
I'm sure, this solution is not restricted to any specific SQL Server version.
Try this, extract the first two parts, convert the 2nd to a numeric value, add one and convert back to a string again:
select
parsename(max(nummer), 3) + '.' -- 03
+ ltrim(max(cast(parsename(nummer, 2) as int) +1)) -- 5436 -> 5437
+ '.1'
from tbArtikel
where Nummer LIKE '03.%'
Try like this,
DECLARE #table TABLE (col VARCHAR(10))
INSERT INTO #table
VALUES ('01.1453.1')
,('01.1453.2')
,('03.3456.1')
,('03.3456.2')
,('03.3456.3')
,('03.5436.1')
,('03.5436.2')
,('03.5436.3')
,('03.5436.4')
,('12.7839.1')
,('12.7839.2')
,('12.3232.1')
,('12.4444.1')
,('12.4444.2')
,('13.7676.1')
SELECT TOP 1 left(col, charindex('.', col, 1) - 1) + '.' + convert(VARCHAR(10), convert(INT, substring(col, charindex('.', col, 1) + 1, charindex('.', col, charindex('.', col, 1) + 1) - (charindex('.', col, 1) + 1))) + 1)
FROM #table
WHERE col LIKE '03.%'
ORDER BY 1 DESC

SQL Group By Query

I have the following table with one column ArbPlWPos:
+------------+
+ IH-MKE +
+ IH-MKEEA +
+ IH-MKEEB +
+ IH-MKEPE +
+ IH-MKEPM +
+ IH-MVKE1 +
+ IH-MVKM1 +
+------------+
I can run a statement in MS Access which groups by the first 6 letters:
SELECT left(ArbPlWPos, 6), count(left(ArbPlWPos, 6))
FROM my_table
GROUP BY left(ArbPlWPos, 6)
+------------+------+
+ IH-MKE + 10 +
+ IH-MKM + 20 +
+ IH-MVK + 30 +
+------------+------+
How to include the IH-MVK into the IH-MKE, so the result should be:
+------------+------+
+ IH-MKE + 40 +
+ IH-MKM + 20 +
+------------+------+
Is this somehow possible with SQL/Access?
In MS Access, you can do this using a conditional expression, iif():
SELECT iif(ArbPlWPos like "IH-MVK*", "IH-MKE", left(ArbPlWPos, 6)),
count(*)
FROM TABLE
GROUP BY iif(ArbPlWPos like "IH-MVK*", "IH-MKE", left(ArbPlWPos, 6));
You can group by any expression, but you are to repeat it after SELECT (is you need) and after GROUP BY as you did with an usual column. For example:
SELECT my_function_or_expression(column_A, column_B), count(1)
FROM my_table
GROUP BY my_function_or_expression(column_A, column_B);
In your case it will be:
SELECT
CASE WHEN left(ArbPlWPos, 6) = 'IH-MVK'
THEN 'IH-MKE'
ELSE left(ArbPlWPos, 6) END AS cutArbPlWPos,
count(1) AS amount
FROM my_table
GROUP BY CASE WHEN left(ArbPlWPos, 6) = 'IH-MVK'
THEN 'IH-MKE'
ELSE left(ArbPlWPos, 6) END;
You can also simplify it using subquery.
SELECT cutArbPlWPos, count(1)
FROM (SELECT
CASE WHEN left(ArbPlWPos, 6) = 'IH-MVK'
THEN 'IH-MKE'
ELSE left(ArbPlWPos, 6) END AS cutArbPlWPos
FROM my_table
)
GROUP BY cutArbPlWPos;

How to create ordinal numbers (i.e. "1st" "2nd", etc.) in SQL Server

I recently responded to this question in the SSRS-2008 tag that required changing the day number in a date to the ordinal number (i.e. "1st", "2nd" instead of "1", "2"). The solution involved a VB.Net function. I'm curious how one would go about performing this task in SQL (t-sql and SQL Server in particular), or if there is some built in support.
So here is a scenario: say you have organized a footrace for 1000 runners and have the results in a table with the columns Name and Place (in normal numbers). You want to create a query that will display a user's name and their place in ordinal numbers.
Here's a scalable solution that should work for any number. I thought other's used % 100 for 11,12,13 but I was mistaken.
WITH CTE_Numbers
AS
(
SELECT 1 num
UNION ALL
SELECT num + 1
FROM CTE_Numbers
WHERE num < 1000
)
SELECT CAST(num AS VARCHAR(10))
+
CASE
WHEN num % 100 IN (11,12,13) THEN 'th' --first checks for exception
WHEN num % 10 = 1 THEN 'st'
WHEN num % 10 = 2 THEN 'nd'
WHEN num % 10 = 3 THEN 'rd'
ELSE 'th' --works for num % 10 IN (4,5,6,7,8,9,0)
END
FROM CTE_Numbers
OPTION (MAXRECURSION 0)
You can do that just as easily in SQL as in the app layer:
DECLARE #myDate DATETIME = '2015-05-21';
DECLARE #day INT;
SELECT #day = DAY(#myDate);
SELECT CASE WHEN #day IN ( 11, 12, 13 ) THEN CAST(#day AS VARCHAR(10)) + 'th'
WHEN #day % 10 = 1 THEN CAST(#day AS VARCHAR(10)) + 'st'
WHEN #day % 10 = 2 THEN CAST(#day AS VARCHAR(10)) + 'nd'
WHEN #day % 10 = 3 THEN CAST(#day AS VARCHAR(10)) + 'rd'
ELSE CAST(#day AS VARCHAR(10)) + 'th'
END
You could also put this in a scalar function if necessary.
EDIT
For your example, it would be:
SELECT Name ,
CASE WHEN Place IN ( 11, 12, 13 )
THEN CAST(Place AS VARCHAR(10)) + 'th'
WHEN Place % 10 = 1 THEN CAST(Place AS VARCHAR(10)) + 'st'
WHEN Place % 10 = 2 THEN CAST(Place AS VARCHAR(10)) + 'nd'
WHEN Place % 10 = 3 THEN CAST(Place AS VARCHAR(10)) + 'rd'
ELSE CAST(Place AS VARCHAR(10)) + 'th'
END AS Place
FROM FootRaceResults;
Be very afraid:
with
ArabicRomanConversions as (
select *
from ( values
( 0, '', '', '', '' ), ( 1, 'I', 'X', 'C', 'M' ), ( 2, 'II', 'XX', 'CC', 'MM' ), ( 3, 'III', 'XXX', 'CCC', 'MMM' ), ( 4, 'IV', 'XL', 'CD', '?' ),
( 5, 'V', 'L', 'D', '?' ), ( 6, 'VI', 'LX', 'DC', '?' ), ( 7, 'VII', 'LXX', 'DCC', '?' ), ( 8, 'VIII', 'LXXX', 'DCCC', '?' ), ( 9, 'IX', 'XC', 'CM', '?' )
) as Placeholder ( Arabic, Ones, Tens, Hundreds, Thousands )
),
OrdinalConversions as (
select *
from ( values
( 1, 'st' ), ( 2, 'nd' ), ( 3, 'rd' ), ( 11, 'th' ), ( 12, 'th' ), ( 13, 'th' )
) as Placeholder2 ( Number, Suffix )
),
Numbers as (
select 1 as Number
union all
select Number + 1
from Numbers
where Number < 3999 )
select Number as Arabic,
( select Thousands from ArabicRomanConversions where Arabic = Number / 1000 ) +
( select Hundreds from ArabicRomanConversions where Arabic = Number / 100 % 10 ) +
( select Tens from ArabicRomanConversions where Arabic = Number / 10 % 10 ) +
( select Ones from ArabicRomanConversions where Arabic = Number % 10 ) as Roman,
Cast( Number as VarChar(4) ) + Coalesce( (
select top 1 Suffix from OrdinalConversions where Number = Numbers.Number % 100 or Number = Numbers.Number % 10 order by Number desc ), 'th' ) as Ordinal
from Numbers option ( MaxRecursion 3998 );
You could use a case statement, I.e.,
UPDATE: Taking into account the teens, as mentioned by TPhe and refactored slightly.
SELECT
Name,
CASE
WHEN Place in(11, 12, 13) then CAST(Place as VARCHAR(20)) + 'th'
WHEN RIGHT(CAST(Place as VARCHAR(20)), 1) = '1' then CAST(Place as VARCHAR(20)) + 'st'
WHEN RIGHT(CAST(Place as VARCHAR(20)), 1) = '2' then CAST(Place as VARCHAR(20)) + 'nd'
WHEN RIGHT(CAST(Place as VARCHAR(20)), 1) = '3' then CAST(Place as VARCHAR(20)) + 'rd'
ELSE CAST(Place as VARCHAR(20)) + 'th'
END as Place
FROM
RunnerTable
DECLARE #Number int = 94
SELECT
CONVERT(VARCHAR(10),#NUMBER) + CASE WHEN #Number % 100 IN (11, 12, 13) THEN 'th'
ELSE
CASE #Number % 10
WHEN 1 THEN 'st'
WHEN 2 THEN 'nd'
WHEN 3 THEN 'rd'
ELSE 'th'
END
END
This Would be much better for any number
create Function dbo.fn_Numbers_Ordinal (#N as bigint) returns varchar(50)
as Begin
Declare #a as varchar(50)= CAST(#N AS VARCHAR(50))
return(
SELECT CAST(#N AS VARCHAR(50))
+
CASE
WHEN Right(#a,2)='11' or Right(#a,2)='12' or Right(#a,2)='13' Then 'th'
WHEN #N % 10 = 1 THEN 'st'
WHEN #N % 10 = 2 THEN 'nd'
WHEN #N % 10 = 3 THEN 'rd'
ELSE 'th' --for #N % 10 IN (4,5,6,7,8,9,0)
END
)
end
Just figured I would add onto the various options. Here's a one-liner. I left this as a comment about a year ago. But someone suggested I submit it as an answer. So here ya go:
SELECT OrdinalRank = CONCAT(num, IIF(num % 100 IN (11,12,13),'th',COALESCE(CHOOSE(num % 10,'st','nd','rd'),'th')))
FROM (
VALUES (1),(2),(3),(4),(5),(10),(11),(20),(21),(22),(23),(24),(101),(102),(103)
) x(num)
--Result:
--1st
--2nd
--3rd
--4th
--5th
--10th
--11th
--20th
--21st
--22nd
--23rd
--24th
--101st
--102nd
--103rd
This takes advantage of the IIF and CHOOSE functions, which are only available in SQL 2012+.
DECLARE #Number int = 113,
#Superscript int
IF #Number IS NOT NULL
BEGIN
IF LEN(#Number) >= 2
SELECT #Superscript = RIGHT(#Number, 2)
ELSE
SELECT #Superscript = RIGHT(#Number, 1)
SELECT #Number as Number,
CASE WHEN #Superscript in (11,12,13) THEN 'th'
ELSE CASE WHEN #Superscript = 1 THEN 'st'
WHEN #Superscript = 2 THEN 'nd'
WHEN #Superscript = 3 THEN 'rd'
ELSE 'th'
END
END as Superscript
END ELSE
SELECT 0 as Number, 'th' as Superscript
Another option to solve this with the FORMAT function (also you can display month names in other languages):
;WITH cte AS (
SELECT 1 AS dayordinal ,'st' AS suffix
UNION
SELECT 2 AS dayordinal ,'nd' AS suffix
UNION
SELECT 3 AS dayordinal ,'rd' AS suffix
)
, YourTable AS --this is just for example
(SELECT CAST('1/1/2022' AS DATE) DateColumn
UNION
SELECT CAST('1/14/2022' AS DATE) DateColumn
UNION
SELECT CAST('4/4/2022' AS DATE) DateColumn
UNION
SELECT CAST('2/2/2022' AS DATE) DateColumn
UNION
SELECT CAST('3/13/2022' AS DATE) DateColumn
)
SELECT CAST(DATEPART(DAY, DateColumn) AS NVARCHAR(2))+ISNULL(c.suffix, 'th')+' '+ FORMAT(DateColumn, 'MMMM yyyy', 'fr-FR')
FROM YourTable t
LEFT JOIN cte c ON c.dayordinal=RIGHT(DATEPART(DAY, DateColumn),1)
Use the SSRS expression below:
= DAY(Globals!ExecutionTime) &
SWITCH(
DAY(Globals!ExecutionTime)= 1 OR DAY(Globals!ExecutionTime) = 21 OR DAY(Globals!ExecutionTime)=31, "st",
DAY(Globals!ExecutionTime)= 2 OR DAY(Globals!ExecutionTime) = 22 , "nd",
DAY(Globals!ExecutionTime)= 3 OR DAY(Globals!ExecutionTime) = 23 , "rd",
true, "th"
)
This is easy to implement and works well.
Public Function OrdinalNumberSuffix(ByVal InNumber As Integer) As String
Dim StrNumber As String, _
Digit As Byte, _
Suffix As String
StrNumber = Trim(Str(InNumber))
If Val(StrNumber) > 3 And Val(StrNumber) < 14 Then
Digit = Val(Right(StrNumber, 2))
Else
Digit = Val(Right(StrNumber, 1))
End If
Select Case Digit
Case 1: Suffix = "st"
Case 2: Suffix = "nd"
Case 3: Suffix = "rd"
Case Else: Suffix = "th"
End Select
OrdinalNumberSuffix = " " & StrNumber & Suffix & " "
End Function

Update SQL Data using mask and rules

I have about 3000 entries in a column in SQL 2012 which are unstructured at the moment ie
1.1.01.10, 1.1.1.11
I want to get the data into a format which includes a leading 0 for all single numbers i.e.
01.01.01.10 and so on.
is there any way of doing this with an update query? I can do this by exporting to excel and manipulating there but I want to avoid this if possible.
Alter function Pad
(
#str varchar(max)
)
returns varchar(max)
as
begin
Declare #nstr varchar(max)
while(PATINDEX('%.%',#str)<>0)
begin
Set #nstr = isnull(#nstr,'')+case when PATINDEX('%.%',#str) = 2 then '0'+substring(#str,PATINDEX('%.%',#str)-1,1) else SUBSTRING(#str,1,PATINDEX('%.%',#str)-1) end+'.'
Set #str = case when PATINDEX('%.%',#str) = 2 then stuff(#str,PATINDEX('%.%',#str)-1,2,'') else stuff(#str,1,PATINDEX('%.%',#str),'') end
end
Set #nstr = isnull(#nstr,'')+case when len(#str) <> 1 then #str when len(#str) = 1 then '0'+#str else '' end
return #nstr
end
update t
set num = [dbo].pad(num)
from table t
If the data have always 4 block it's possible to break them in single unit one at a time.
With F AS (
SELECT data
, rem = substring(data, patindex('%.%', data) + 1, len(data))
, value1 = substring(data, 1, patindex('%.%', data) - 1)
FROM Table1
), S AS (
SELECT data
, rem = substring(rem, patindex('%.%', rem) + 1, len(rem))
, value1
, value2 = substring(rem, 1, patindex('%.%', rem) - 1)
FROM F
), T AS (
SELECT data
, value1
, value2
, value3 = substring(rem, 1, patindex('%.%', rem) - 1)
, value4 = substring(rem, patindex('%.%', rem) + 1, len(rem))
FROM S
)
UPDATE T SET
Data = CONCAT(RIGHT('00' + value1, 2), '.'
, RIGHT('00' + value2, 2), '.'
, RIGHT('00' + value3, 2), '.'
, RIGHT('00' + value4, 2));
SQLFiddle Demo
the query can be made smaller, but losing readability.
If the number of block are unknown and/or can change between rows, the query is more complex and involve a recursive CTE
With Splitter AS (
-- anchor
SELECT data
, rem = substring(data, patindex('%.%', data) + 1, len(data))
, pos = len(data) - len(replace(data, '.', '')) + 1
, value = substring(data, 1, patindex('%.%', data) - 1)
, res = CAST('' as nvarchar(50))
FROM Table1
UNION ALL
-- runner
SELECT data
, rem = substring(rem, patindex('%.%', rem) + 1, len(rem))
, pos = pos - 1
, value = substring(rem, 1, patindex('%.%', rem) - 1)
, res = CAST(res + RIGHT('00' + value, 2) + '.' as nvarchar(50))
FROM Splitter
WHERE patindex('%.%', rem) > 1
UNION ALL
-- stop
SELECT data
, rem = ''
, pos = pos - 1
, value = rem
, res = CAST(res + RIGHT('00' + value, 2)
+ '.' + RIGHT('00' + rem, 2) as nvarchar(50))
FROM Splitter
WHERE patindex('%.%', rem) = 0
AND rem <> ''
)
UPDATE table1 Set
Data = res
FROM table1 t
INNER JOIN Splitter s ON t.Data = s.Data and s.Pos = 1
SQLFiddle demo
The anchor query of the CTE get the first block in value, set pos with the number of block and prepare the result (res).
The runner query works for the following block, but not the last one, searching the nth block and adding blocks to the result.
The stop query get the last block without searching for another dot, that will not find, and complete the constrution of the result. Having set the pos initially to the number of blocks, now it'll be 1.