3

I am using sqlalchemy on a postgres database, and I'm trying to do arithmetic in a SELECT on two JSON fields which represent floats. However, I have not figured out how to make this work.

Assume I have properly defined a table called transactions which contains a JSON column called cost_data, and assume that this JSON structure contains two attributes called cost and subtotal which represent float values.

In a SELECT statement, I generate the sum of those two fields as follows:

(cast(transactions.c.cost_data['subtotal'], sqlalchemy.Float) + cast(transactions.c.cost_data['cost'], sqlalchemy.Float)).label('total_cost')

This generates the following SQL fragment ...

CAST((transactions.cost_data -> %(cost_data_6)s) AS FLOAT) + CAST((transactions.cost_data -> %(cost_data_7)s) AS FLOAT) AS total_cost

(where cost_data_6 and cost_data_7 get set to subtotal and cost, respectively).

However, I get the following error:

sqlalchemy.exc.ProgrammingError: (psycopg2.ProgrammingError) cannot cast type json to double precision

If I remove the casting and do it as follows, it also fails ...

(transactions.c.cost_data['subtotal'] + transactions.c.cost_data['cost']).label('total_cost')

I get this error ...

sqlalchemy.exc.ProgrammingError: (psycopg2.ProgrammingError) operator does not exist: json || json
LINE 9: ... (transactions.cost_data -> 'subtotal') || (transa...
                                                   ^

Clearly, this is because the fields come in as strings, and the "+" operator gets interpreted as string concatenation.

Also, if I use the Python float operator, it also fails ...

(float(transactions.c.cost_data['subtotal']) +  float(transactions.c.cost_data['cost'])).label('total_cost')

The python interpreter doesn't even execute the code, and it gives this error:

TypeError: float() argument must be a string or a number, not 'BinaryExpression'

So how can I perform the addition of those two fields using sqlalchemy?

PS: the following is a typical cost_data column value ...

{"cost":3.99,"subtotal":12.34}
2
  • Note that money and floating point might be a bad match. You could use numeric instead. Commented Sep 20, 2017 at 9:56
  • Agreed, in general. In this case, I am not able to change the database column definitions, and other software uses floats for these fields. Commented Sep 20, 2017 at 12:58

2 Answers 2

2

The accepted answer works when only using SQLAlchemy with PostgreSQL, but it is not portable to other databases that have JSON types.

If you want to write code that is portable between PostgreSQL, MySQL, and SQLite, then you should cast a JSON column using one of the column's as_*() methods.

OP's answer would look like:

transactions.c.cost_data['subtotal'].as_float()

The methods .as_float(), .as_string(), .as_boolean(), and .as_integer() are available on SQLAlchemy JSON columns since SQLAlchemy version 1.3.11. More documentation here.

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

Comments

1

OK. I finally figured it out. I have to pass each reference through the astext operator before applying cast, as follows ...

(transactions.c.cost_data['subtotal'].astext.cast(sqlalchemy.Float) + transactions.c.cost_data['cost'].astext.cast(sqlalchemy.Float)).label('total_cost')

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.