2

A column has a string values like "1/200", "3.5" or "6". How can I convert this String to numeric value in single SQL query?

My actual SQL is more complicated, here is a simple example:

SELECT number_value_in_string FROM table 

number_value_in_string's format will be one of:

  • ##
  • #.##
  • #/###

I need to sort by the numeric value of this column. But of course postgres doesn't agree with me that 1/200 is a proper number.

7
  • And by "a number" I assume you mean 0.005? Commented Sep 30, 2011 at 23:43
  • 1
    I know of no database that would recognize a fraction as a number. They do things in decimals. If you try to convert to a decimal beware of integer math in many dbs 1/200 = 0. Commented Sep 30, 2011 at 23:45
  • @MarkByers: yes I meant 0.005 Commented Sep 30, 2011 at 23:47
  • Why don't you just store it as a number to begin with? Or at least a pair of numbers, i.e. numerator and denominator? Otherwise you can use a regex to split it and do math on the pieces. Seems overly clever and fragile to me. Commented Oct 1, 2011 at 0:02
  • @ScottMarlowe: i'm working on a already established code base so i can't do that. i wish i could though. my life would be much easier Commented Oct 1, 2011 at 0:18

4 Answers 4

1

Seeing your name I cannot but post a simplification of your answer:

SELECT id, number_value_in_string FROM table
 ORDER BY CASE WHEN substr(number_value_in_string,1,2) = '1/'
        THEN 1/substr(number_value_in_string,3)::numeric 
        ELSE number_value_in_string::numeric END, id;

Ignoring possible divide by zero.

Sign up to request clarification or add additional context in comments.

1 Comment

nice one!! thanks for bringing down my name's reputation +1 for that :) btw, there is no such thing as divide by zero error. jk
1

I would define a stored function to convert the string to a numeric value, more or less like this:

CREATE OR REPLACE FUNCTION fraction_to_number(s CHARACTER VARYING)
RETURN DOUBLE PRECISION AS
BEGIN
   RETURN
   CASE WHEN s LIKE '%/%' THEN
       CAST(split_part(s, '/', 1) AS double_precision) 
       / CAST(split_part(s, '/', 2) AS double_precision)
   ELSE
       CAST(s AS DOUBLE PRECISION)
   END CASE
END

Then you can ORDER BY fraction_to_number(weird_column)

If possible, I would revisit the data design. Is it all this complexity really necessary?

4 Comments

the thing is i'm working with this huge code base can I can't modify the database. Only one sql is allowed to pass to the database in the function i'm modifying. But thanks though, the CASE WHEN in-line SQL is definitely legit!
I'd alter the table to have a custom type, and store this as both parts properly. I'm so sorry you have to work with this. OTOH if this will never ever change you can do it this way, but I'd at least add some error correction to prevent 200/0 etc from throwing an error or being stored or whatever. Or can you not add constraints on the db?
@ScottMarlowe: I think I will need to talk to the dev team soon about this. But for now I don't think that will change soon. They will have their reason because the 'custom' field should be string type so it can take almost anything given to it, then process the value accordingly depending on each case :(
So are they gonna store 10*4/13^8 or something in it eventually? That's a pretty crappy requirement.
1

This postgres SQL does the trick:

select (parts[1] :: decimal) / (parts[2] :: decimal) as quotient
FROM (select regexp_split_to_array(number_value_in_string, '/') as parts from table) x

Here's a test of this code:

select (parts[1] :: decimal) / (parts[2] :: decimal) as quotient
FROM (select regexp_split_to_array('1/200', '/') as parts) x

Output:

0.005

Note that you would need to wrap this in a case statement to protect against divide-by-zero errors and/or array out of bounds issues etc if the column did not contain a forward slash

Note also that you could do it without the inner select, but you would have to use regexp_split_to_array twice (once for each part) and you would probably incur a performance hit. Nevertheless, it may be easier to code in-line and just accept the small performance loss.

1 Comment

awesome idea! +1 for the regexp_split_to_array()
1

I managed to solve my problem. Thanks all. It goes something like this, in a single SQL. (I'm using POSTGRESQL)

It will sort a string coming in as either "#", "#.#" or "1/#"

SELECT id, number_value_in_string FROM table ORDER BY CASE WHEN position('1/' in number_value_in_string) = 1 
    THEN 1/substring(number_value_in_string from (position('1/' in number_value_in_string) + 2) )::numeric 
    ELSE number_value_in_string::numeric 
END ASC, id

Hope this will help someone outhere in the future.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.