6

I have an existing SQLAlchemy model that is supposed to have a relation to another model, however the connecting point there is sadly a (sub)field of a JSON PostgreSQL column.

So let's say one table "category" is:

id(bigint) | name(string)
-------------------------
         5 | Comics

... and second table "hero" is:

id(bigint) | name(string) | info(JSON)
-------------------------------------------------------------
         2 | Tranquility  | {"category_id":5, "genre_id": 17}

How would I, in python SQLAlchemy&PostgreSQL9.5, join category on the hero through the hero's info["category_id"] to get the following results?

id(bigint) | name(string) | category_name | info(JSON)
-------------------------------------------------------------
         2 | Tranquility  | Comics        | {"category_id":5, "genre_id": 17}

I know such things are possible in the PostgreSQL itself according to this answer

https://dba.stackexchange.com/a/83935 ( http://sqlfiddle.com/#!15/226c33/1 )

And that there probably needs to be some trickstery of casting or bypassing default ways
as described in sqlalchemy filter by json field

Note: Please note that I can't change the DB structure nor the existing parts of the model and I am also not looking for a double query bypass which I already know.

jave.web
  • 13,880
  • 12
  • 91
  • 125
  • Building on the linked question and the jsonb access+cast pattern there, adding that `Query.join()` accepts an explicit ON clause as the second argument should get you forward. – Ilja Everilä May 14 '20 at 15:46
  • @IljaEverilä do you mean raw SQL? (I am looking for more sql-alchemy way) – jave.web May 15 '20 at 21:22

1 Answers1

6

You can use json_to_record to create an in-memory table and then join on that.

https://www.postgresql.org/docs/9.4/functions-json.html

Another question which already answered this, but that was using json_to_recordset as the data was a list of objects https://stackoverflow.com/a/31333794/3358570

Here is the SQL you can transform to sqlalchemy according to your need,

SELECT hero.id as hero_id, hero.name as name, d.name as category_name, info
FROM hero,
     json_to_record(hero.info) AS x(category_id int)
         JOIN category d on d.id = category_id

Here db fiddle for you https://www.db-fiddle.com/f/7ops4KXVDF6KpY5JZau7vG/1

Another, a simpler approach would be to access JSON value in join and type caste category_id

SELECT hero.id       AS hero_id,
       hero.name     AS hero_name,
       category.name AS category_name,
       category.id   AS category_id,
       hero.info     AS hero_info
FROM hero
     JOIN category ON category.id = CAST(hero.info ->> 'category_id' AS INTEGER)

equivalent sqalchemy would become,

category_id = cast(Hero.info.op("->>")("category_id"), Integer)
query = (db.session.query(Hero.id, Hero.name, Category.name, Category.id, Hero.info)
         .join(Category, Category.id == category_id))
data = query.all()
Talha Junaid
  • 2,351
  • 20
  • 29
  • Although this is a nice feature, this does not answer my question in any way and is not applicable in my scenario anyway. – jave.web May 15 '20 at 21:22
  • 1
    How? Little more details will be very helpful – Talha Junaid May 16 '20 at 10:26
  • My question was directed at python - sqlalchemy - how to write such query in python in the "sqlalchemy-way" - I already know how to do the JSON joining in SQL - I've referenced the stackexchange answer & sqlfiddle describing that... I've only mentioned postgre, because it is the underlying engine which could result to need of a more dialect-oriented sqlalchemy library. Overall **I've really provided all the details and quite thoroughly in my question**. – jave.web May 17 '20 at 04:55
  • Updated answer, you did not explicitly say that you need sqlalchemy, how would one know that you need help with SQL or sqlalchemy. My assumption was you need help with the approach. If you just needed to translate the SQL to sqlalchemy then you should have posted raw SQL as well. – Talha Junaid May 17 '20 at 07:56
  • And instead of vaguely saying this does not answer my question in any way and is not applicable in my scenario anyway. you could have just said you need sqlalchemy too. – Talha Junaid May 17 '20 at 08:10
  • Yes, I did explicitly state that "How would I, in python SQLAlchemy&...", also the second referenced link is targeted at a sqlalchemy solution, on top of all that, the question title itself starts with "SQLAlchemy" on top of that it is also tagged with python & sqlalchemy - I understand you've missed all that, I just don't understand HOW? x) . Applicability was referencing the temporary table creation you've described, sorry if that came out vauge for you, it was rather very specific. – jave.web May 17 '20 at 19:50
  • Anyways... thank you for the effort, I think we finally understand each other ! :) The answer looks very promising, will test and accept if it's the one :) – jave.web May 17 '20 at 20:25
  • 1
    So I've finally tested it and the python/SQLAlchemy code really produces the same query - so thank you very much :) Sidenote for **MySQL/Percona people** - this is PostgreSQL query only! (it doesn't seem to work on MySQL/Percona, but it does work on Postgre) – jave.web Jun 10 '20 at 10:25
  • Congratulations! – Talha Junaid Jun 10 '20 at 11:38
  • @TalhaJunaid thanks for the explaination it works. But can you add: from sqlalchemy.sql.operators import op – user3761555 Aug 27 '22 at 06:25
  • As we are accessing op from Hero columns, it's not required. may be in your case you need it – Talha Junaid Aug 28 '22 at 08:52