How do I format numbers in a SQL table? - sql

I need help formatting numbers in a specific way.
If a number has three decimal places or less, I would like it to remain the same.
If a number has more than three significant figures, I would like all numbers after the third significant figure to be the fractional part of the number.
123 --> Stays the same
1234 --> 123.4
How can this be done?
EDIT:
1234567 --> 123.4567
I am on SQL 2007, wishing to UPDATE the value in the table. The value is stored as a numeric.

Here is a numeric solution:
UPDATE T SET NUM = NUM/POWER(10,FLOOR(LOG10(NUM))-2)
WHERE NUM>=1000
Or the SELECT statement:
SELECT NUM, CASE WHEN NUM<1000 THEN NUM
ELSE NUM/POWER(10,FLOOR(LOG10(NUM))-2)
END AS NewNUM
FROM T
Note that the exact results can vary depending on the data type of NUM. If it is a FLOAT field, it might round the last decimal if NUM gets too large. If it is of type NUMERIC, it will add zero's to the end. If DECIMAL, you need to be careful of the precision. Note that this applies to all the update solutions already mentioned.

This could work
SELECT
CASE WHEN Num > 999 THEN Num/10
ELSE
Num
END As Num
There could be a better way, but this is what I could think of

You could do this with strings.
CREATE TABLE T
( NUM NUMERIC(38,19) );
INSERT INTO T (NUM) VALUES ( 123456789 );
INSERT INTO T (NUM) VALUES ( 12345 );
INSERT INTO T (NUM) VALUES ( 123 );
INSERT INTO T (NUM) VALUES ( 1 );
SELECT CAST(
CASE WHEN NUM < 999 THEN CAST(FLOOR(NUM) AS VARCHAR)
ELSE SUBSTRING(CAST(NUM AS VARCHAR), 1, 3) + '.'
+ SUBSTRING(CAST(FLOOR(NUM) AS VARCHAR), 4, LEN(CAST(NUM AS VARCHAR)) - 3)
END AS NUMERIC(38, 19))
FROM T
UPDATE T
SET NUM = CAST(CASE WHEN NUM < 999 THEN CAST(FLOOR(NUM) AS VARCHAR)
ELSE SUBSTRING(CAST(NUM AS VARCHAR), 1, 3) + '.'
+ SUBSTRING(CAST(FLOOR(NUM) AS VARCHAR), 4, LEN(CAST(NUM AS VARCHAR)) - 3)
END AS NUMERIC(38, 19));
I've put a working example on SQLFiddle.

Assuming strings of only integer values:
SELECT CASE WHEN LEN(Num) <= 3 THEN Num
ELSE STUFF(Num,4,0,'.')
END
FROM (VALUES('1234567'),('123'),('1234'),('12')) t(Num) --some sample values
Result:
123.4567
123
123.4
12

I answered this on a cross-post elsewhere, but for completeness:
WITH n(r) AS (
SELECT 123 UNION ALL SELECT 1234 UNION ALL SELECT 1234567
)
SELECT LEFT(r, 3) + CASE
WHEN LEN(r) > 3 THEN '.' + SUBSTRING(RTRIM(r),4,38) ELSE '' END
FROM n;

Related

Querying varchars as decimals in SQL Server

I have a problem in SQL Server, I am trying to query
where x.numbers >= '9'
where x.numbers was stored as varchar and some of the values I have in that field are
8.9
9.3
6.7
>10
8.3
>= 9
If I try isnumeric(x.numbers), then it is excluding those values that start with > or >=. and I tried cast(x.numbers as decimal) but it is not working as well. Please advise
This is my attempt. It's not pretty, but might get you what you're after. It increases/decreases the values of values that have either < or >:
CREATE TABLE #Sample (N varchar(5));
INSERT INTO #Sample
VALUES ('7.9'),('4.5'),('9'),('>10'),('6.7'),('11.7'),('>12'),('<=10'),('<9');
GO
SELECT *
FROM #Sample;
GO
WITH CTE AS (
SELECT *,
CASE WHEN N LIKE '>=%' OR N LIKE '<=%' THEN TRY_CONVERT(decimal(5,1),REPLACE(REPLACE(REPLACE(N,'>',''),'<',''),'=',''))
WHEN N LIKE '>%' THEN TRY_CONVERT(decimal(5,1),REPLACE(REPLACE(REPLACE(N,'>',''),'<',''),'=','')) + 0.1 --Adding .1 as it needs to be more than it's value
WHEN N LIKE '<%' THEN TRY_CONVERT(decimal(5,1),REPLACE(REPLACE(REPLACE(N,'>',''),'<',''),'=','')) - 0.1
ELSE TRY_CONVERT(decimal(5,1),N)
END AS Nr
FROM #Sample S)
SELECT N
FROM CTE
WHERE Nr >= 9;
GO
DROP TABLE #Sample;
--SQL 2008, just CONVERT
WITH CTE AS (
SELECT *,
CASE WHEN N LIKE '>=%' OR N LIKE '<=%' THEN CONVERT(decimal(5,1),REPLACE(REPLACE(REPLACE(N,'>',''),'<',''),'=',''))
WHEN N LIKE '>%' THEN CONVERT(decimal(5,1),REPLACE(REPLACE(REPLACE(N,'>',''),'<',''),'=','')) + 0.1 --Adding .1 as it needs to be more than it's value
WHEN N LIKE '<%' THEN CONVERT(decimal(5,1),REPLACE(REPLACE(REPLACE(N,'>',''),'<',''),'=','')) - 0.1
ELSE CONVERT(decimal(5,1),N)
END AS Nr
FROM #Sample S)
SELECT N
FROM CTE
WHERE Nr >= 9;
I would use try_convert():
where try_convert(decimal(38, 6), field) > 9
Now, this works for many circumstances and assumes that you want to ignore non-numeric values.
You can modify this to get rid of various "other" characters:
where try_convert(decimal(38, 6), replace(replace(replace(replace(field, ' ', ''), '=', ''), '>', ''), '<', '')) > 9
This ignores the "operator" characters.
However, your problem is incompletely specified. If the field were '< 12' or '> 7' what do you want it to return?
try cast(n as float) as shown below:
create table #tmp(numbers varchar(10))
insert into #tmp values('8.9')
insert into #tmp values('9.3')
insert into #tmp values('6.7')
insert into #tmp values('11')
insert into #tmp values('8.3')
insert into #tmp values('9')
insert into #tmp values('10')
Select * from #tmp where cast(numbers as float)> = cast('9' as float)
Drop table #tmp

Cast substring to int only for numeric values in SQL

I have this query :
SUBSTRING (
dbo.Table.RNumber,
1,
CHARINDEX(
'+',
dbo.Table.RNumber
) - 1
) AS RoomNumber,
SUBSTRING (
dbo.Table.R.Number,
CHARINDEX(
'+',
dbo.Table.R.Number
) + 1,
LEN(
dbo.Table.R.Number
)
) AS HallNumber,
My Table RNumber is mostly like 2+3 or 3+5, but sometimes it is like x+5 or y+0. I want to convert fields to int, but I want to convert strings like "x" or "y" to 0. I googled it but I couldn't find a solution. How can I do that? Thanks.
You can use case statement try this
Edited to use isnumeric() method
CASE
WHEN isnumeric(SUBSTRING(dbo.Table.RNumber,1,CHARINDEX('+',dbo.Table.RNumber) - 1)) = 1
THEN SUBSTRING(dbo.Table.RNumber,1,CHARINDEX('+',dbo.Table.RNumber) - 1)
else 0
end AS RoomNumber,
CASE
WHEN isnumeric(SUBSTRING(dbo.Table.R.Number,CHARINDEX('+',dbo.Table.RNumber) + 1,LEN(dbo.Table.R.Number))) = 1
THEN SUBSTRING(dbo.Table.R.Number,CHARINDEX('+',dbo.Table.RNumber) + 1,LEN(dbo.Table.R.Number))
else 0
end AS HallNumber,
Hope this should solve your problem
Perhaps you can use ParseName() and Try_Convert()
Declare #YourTable table (SomeField varchar(50))
Insert Into #YourTable values
('2+3'),('3+5'),('x+5'),('y+0')
Select *
,RoomNumber = IsNull(Try_Convert(int,ParseName(Replace(SomeField,'+','.'),2)),0)
,HallNumber = IsNull(Try_Convert(int,ParseName(Replace(SomeField,'+','.'),1)),0)
From #YourTable
Returns
SomeField RoomNumber HallNumber
2+3 2 3
3+5 3 5
x+5 0 5
y+0 0 0
For versions prior to 2012, you can do it like this:
CASE
WHEN NOT columnName like '%[^0-9]%' -- Contains no non-digits
AND columnName like '%[0-9]%' -- contains at least one digit
THEN CAST(columnName as INT) ELSE NULL
END
(Note that this will reject negative numbers, but you can easily adapt it if you need to support them)
Alternatively using IsNumeric, you must first cast to float because Isnumeric accepts some strings that Cast(EXPRESSION as INT) does not accept:
CASE WHEN ISNUMERIC(columnName)=1
THEN CAST(CAST(columnName as float) as int) END

sql: How to find the max length of consecutive sequence of given char in a string using sql

let the char be s and string be mississsipisssss . The sql should return 5.
I was thinking to makeup something using
regexp_count(string,'s+') would return the number of substrings whith consecutive sequence of s .
REGEXP_INSTR(string,'s',1,m) would return the position of the m'th occurence of char s(starting position of mth sequence)
REGEXP_INSTR(string,'s[^s]',1,m) would return the position of the m'th occurence of char s (ending position of mth sequence)
I havent still come to any solution.But I dont think this is a correct way,as the strings could be of any length, and there could be any number of such consecutive sequences. Could someone give any idea for solution (I am beginner)
Here is a standard solution using a hierarchical query. The CASE expression in the outer query is needed to give the answer "null" when the input string is "null" (otherwise the answer would be 0, and it shouldn't be). I didn't add an ORDER BY clause - you can do so if needed. Good luck!
with
inputs ( str ) as (
select 'mississsipisssss' from dual union all
select null from dual union all
select 'stress' from dual union all
select 'alphabeta' from dual
),
prep ( str, lvl ) as (
select str, level
from inputs
connect by prior str = str
and prior sys_guid() is not null
and regexp_instr(str, 's{' || to_char(level-1) || '}') != 0
)
select str, case when str is not null then max(lvl) - 1 end as max_len
from prep
group by str
;
STR MAX_LEN
---------------- ----------
(null) (null)
alphabeta 0
stress 2
mississsipisssss 5
4 rows selected.
This will work in sql-server 2008 +. The same concept will work in Oracle you just need to add a couple of syntax differences such as SUBSTR instead of SUBSTRING. Here is a quick attempt of converting to Oracle syntax:
CREATE GLOBAL TEMPORARY TABLE TempTable
(String VARCHAR(100)) ON COMMIT PRESERVE ROWS;
INSERT INTO TempTable (String) VALUES ('mississsipisssss');
INSERT INTO #TempTable (String) VALUES ('ssasdfs');
WITH cteTokens (String, IndexNum, Token, CharCount) AS (
SELECT
String
,1 as IndexNum
,SUBSTR(t.String,1,1) as Token
,CASE WHEN SUBSTR(t.String,1,1) = 's' THEN 1 ELSE 0 END As CharCount
FROM
#TempTable t
UNION ALL
SELECT
t.String
IndexNum + 1 as IndexNum
,SUBSTR(t.String,IndexNum + 1,1) as Token
,CASE WHEN SUBSTR(t.String,IndexNum + 1,1) = 's' THEN t.CharCount + 1 ELSE 0 END AS CharCount
FROM
#TempTable s
INNER JOIN cteTokens t
ON s.String = t.String
AND LENGTH(s.String) >= t.IndexNum + 1
)
SELECT
String
,MAX(CharCount)
FROM
cteTokens
GROUP BY
String
;
And Here is a SQL-Server Version
CREATE TABLE #TempTable (String VARCHAR(100))
INSERT INTO #TempTable (String) VALUES ('mississsipisssss')
INSERT INTO #TempTable (String) VALUES ('ssasdfs')
;WITH cteTokens (String, IndexNum, Token, CharCount) AS (
SELECT
t.String
,1 as IndexNum
,SUBSTRING(t.String,1,1) as Token
,CASE WHEN SUBSTRING(t.String,1,1) = 's' THEN 1 ELSE 0 END As CharCount
FROM
#TempTable t
UNION ALL
SELECT
t.String
,IndexNum + 1 as IndexNum
,SUBSTRING(t.String,IndexNum + 1,1) as Token
,CASE WHEN SUBSTRING(t.String,IndexNum + 1,1) = 's' THEN t.CharCount + 1 ELSE 0 END As CharCount
FROM
#TempTable s
INNER JOIN cteTokens t
ON s.String = t.String
AND LEN(s.String) >= t.IndexNum + 1
)
SELECT
String
,MAX(CharCount)
FROM
cteTokens
GROUP BY
String
It is a Recursive Common Table Expression [CTE] that splits the string into character tokens in order of Index Position and tests to see if they are the character you desire. If the token is the character then it builds on the count from the previous token if is character so all you have to do is take the MAX() of the result and you have your answer.
This is not an operation well supported by databases, although Postres definitely has functionality in this area.
If you know there is some limit on the number of substrings of "s"s, then you could do something like this:
select greatest(coalesce(length(regexp(substr(string, '[s]+', 1, 1)), 0),
coalesce(length(regexp(substr(string, '[s]+', 1, 2)), 0),
coalesce(length(regexp(substr(string, '[s]+', 1, 3)), 0),
coalesce(length(regexp(substr(string, '[s]+', 1, 4)), 0),
coalesce(length(regexp(substr(string, '[s]+', 1, 5)), 0),
coalesce(length(regexp(substr(string, '[s]+', 1, 6)), 0)
)
This would find the first 6 substrings and then calculate the maximum length.
The alternative is essentially to split the string yourself using connect by or a recursive CTE. However, the above might be sufficient.

Formatting a number as a monetary value including separators

I need some help with a sql transformation. This part of query that I have been provided with:
'$' + replace(cast((CAST(p.Price1 AS decimal(10,2)) * cast(isnull(p.Multiplier,1) as decimal(10,2))) as varchar), '.0000', '')
Basically, it ends up being a varchar that looks like this: $26980
I need to insert a comma at the thousand and million mark (if applicable). So in this instance, $26,980
What's the easiest way to do that without having to rewrite the whole thing?
Do it on the client side. Having said that, this example should show you the way.
with p(price1, multiplier) as (select 1234.5, 10)
select '$' + replace(cast((CAST(p.Price1 AS decimal(10,2)) * cast(isnull(p.Multiplier,1) as decimal(10,2))) as varchar), '.0000', ''),
'$' + parsename(convert(varchar,cast(p.price1*isnull(p.Multiplier,1) as money),1),2)
from p
The key is in the last expression
'$' + parsename(convert(varchar,cast(p.price1*isnull(p.Multiplier,1) as money),1),2)
Note: if p.price1 is of a higher precision than decimal(10,2), then you may have to cast it in the expression as well to produce a faithful translation since the original CAST(p.Priced1 as decimal(10,2)) will be performing rounding.
If you really must do it in TSQL you can use CONVERT(), but this sort of thing really doesn't belong in the database:
declare #m money = 12345678
-- with decimal places
select '$' + convert(varchar, #m, 1)
-- without decimal places
select '$' + replace(convert(varchar, #m, 1), '.00', '')
You could turn this into a function, it only goes 50 characters back.
DECLARE #input VARCHAR(50)
SELECT #input = '123123123.00'
SELECT #input = CASE WHEN CHARINDEX('.', #input) > offset +1
THEN STUFF(#input, CHARINDEX('.', #input) - offset, 0, ',')
ELSE #input END
FROM (SELECT 3 offset UNION SELECT 7 UNION SELECT 12 UNION SELECT 18 UNION SELECT 25 UNION SELECT 33 UNION SELECT 42) b
PRINT #input
The offset grows by +1 for each position, because it's assuming you've already inserted the commas for the previous positions.

t-sql: return range in which a given number falls

I am trying to figure out a good way to return a string 'name' of a range in which a given number falls. Ranges are spans of 1000, so the first range is '0000-0999', the second is '1000-1999' etc. For example, given the number 1234, I want to return the literal string '1000-1999'.
It seems to me that I could maintain a reference table with these ranges, like this
--create & populate temp table with ranges
create table #ranges (st int,en int)
go
insert into #ranges values(0,999)
insert into #ranges values(1000,1999)
insert into #ranges values(2000,2999)
go
--example query
select replace(str(st,4),' ','0') + '-' + replace(str(en,4),' ','0') as TheStringIWant
from #ranges
where 1234 between st and en
...but it seems to me that the ranges should be able to be determined from the given number itself, and that I shouldn't need the (redundant) reference table (or, for that matter, a function) just for this.
It also seems to me that I should be able to figure this out with just a bit of brain power, except that I've just had 2 beers in quick succession this evening...
Another way;
select case when value / 1000 < 1
then '0000-0999'
else cast(value / 1000 * 1000 as varchar(16)) + '-' + cast(value / 1000 * 1000 + 999 as varchar(16))
end
You can use mathematical functions to avoid using the temp table:
SELECT 1234,
RIGHT('0000' + CAST(FLOOR(1234/1000.0) * 1000 AS VARCHAR(11)),4)
+ '-'
+ RIGHT('0000' + CAST( (FLOOR(1234/1000.0) * 1000) + 999 AS VARCHAR(11)),4)
In the shell, I can use integer-arithmetic to cut off the 234, and calculate the string with a simple formular, however it wouldn't produce 0000-0999 but 0-999 for the first 1000.
v=1234
echo $(((v/1000)*1000))-$(((v/1000)*1000+999))
1000-1999
I don't know how to adapt it to tsql - whether possible at all.
declare #range int;
set #range = 1000;
select replace(str(st,4),' ','0') + '-' +
replace(str(st + #range,4),' ','0') as TheStringIWant
from (
select st = v / #range * #range
from (select v = 1234) s
) s