jon333
jon333

Reputation: 831

How to create an index on a nested key of a JSON PostgreSQL column in SQLAlchemy?

This has been a tricky one, hope someone out there can help us all out by posting a/their method of creating an index on a nested key of a JSON (or JSONB) column in PostgreSQL using SQLAlchemy (i'm specifically using Flask-SQLAlchemy, but I do not think that will matter much for the answer).

I've tried all sorts of permutations of the index creations below and get everything from key errors, to 'c' is not an attribute, to that the operator 'getitem' is not supported on this expression.

Any help would be greatly appreciated.

# Example JSON, the nested property is "level2_A"
{
    'level1': { 
        'level2_A': 'test value', 
    } 
}

class TestThing(db.Model):
    __tablename__ = 'test_thing'

    id = db.Column(db.BigInteger(), primary_key=True)
    data = db.Column(JSONB)

    __table_args__ = (db.Index('ix_1', TestThing.data['level1']['level2_A']), 
            db.Index('ix_2', data['level1']['level2_A'].astext), 
            db.Index('ix_3', "TestThing.c.data['level1']['level2_A'].astext"), 
            db.Index('ix_4', TestThing.c.data['level1']['level2_A'].astext), 
            db.Index('ix_5', "test_thing.c.data['level1']['level2_A']"), 
                      )

# db.Index('ix_1', TestThing.data['level1']['level2_A'])
# db.Index('ix_2_t', "test_thing.data['level1']['level2_A']")
# db.Index('ix_3', "TestThing.c.data['level1']['level2_A'].astext")
# db.Index('ix_4', TestThing.c.data['level1']['level2_A'].astext)
# db.Index('ix_5', "test_thing.c.data['level1']['level2_A']")

Upvotes: 7

Views: 1912

Answers (2)

kartikey rajvaidya
kartikey rajvaidya

Reputation: 91


class TestThing(db.Model):
    __tablename__ = 'test_thing'

    id = db.Column(db.BigInteger(), primary_key=True)
    data = db.Column(JSONB)

    __table_args__ = (
        Index("ix_7", "(data->'level1'->>'level2_A')"),
    )

This should ideally work without the text() because -> returns json(b) and ->> returns text:

The query which will be generated will be

CREATE INDEX ix_7 ON test_thing(((data->'level1')->>'level2_A') text_ops);

Upvotes: 0

Tom Viner
Tom Viner

Reputation: 6783

The solution I've found is using text to create a functional index.

Two example indexes here, depending on whether you want to cast the result to text or not:

from sqlalchemy.sql.expression import text
from sqlalchemy.schema import Index

class TestThing(db.Model):
    __tablename__ = 'test_thing'

    id = db.Column(db.BigInteger(), primary_key=True)
    data = db.Column(JSONB)

    __table_args__ = (
        Index("ix_6", text("(data->'level1'->'level2_A')")),
        Index("ix_7", text("(data->'level1'->>'level2_A')")),
    )

Which results in the following SQL to create the indexes:

CREATE INDEX ix_6 ON test_thing(((data -> 'level1'::text) -> 'level2_A'::text) jsonb_ops);
CREATE INDEX ix_7 ON test_thing(((data -> 'level1'::text) ->> 'level2_A'::text) text_ops);

Upvotes: 3

Related Questions