2

I am using SQLAlchemy and PostgreSQL and I would like to enforce a unique constraint on two columns in one table in sqlalchemy. One column is numeric (Integer), and one is alphanumeric string. The alphanumeric column must be stored 'as is' (i.e. a mix of upper and lower case chars), but the unique constraint being enforced needs to be case insensitive.

It is a list of word 'tags' each with a foreign key to 'topics'. Consider three columns in the tags table: id, topic_id, and name. There cannot be more than one of the same tag name (on a case insensitive basis) for each topic. In other words, if the topic 'fish' (tags.topic_id = 7) already has a tags.name of 'goldfish', you would not be able to add another row with tags.topic_id=7 and with a tag.name of 'GolDFiSH'. The two tags are identical and the topic_id is the same on a case-insensitive basis.

What is the right way to do this?

I have come across two relevant references for thought starters:

(i) Defining a new variable type: https://bitbucket.org/zzzeek/sqlalchemy/wiki/UsageRecipes/StringComparisonFilter

(ii) Raw SQL: Create Unqiue case-insensitive constraint on two varchar fields

Option (i) and (ii) both seem ok, but which of these two is better, or is there a third better way?

The simplest solution is what I am after.

My current idea is to create another 'phantom' column called, say, tags.lower_case.name, which gets populated with a lowercase version of tags.name as a consequence of a custom init method and a custom setter for tags.name. Then enforce the unique constraint on this phantom column.

Another idea I am considering is to to see if I can create an index across the two columns, and enforce some unique constraint on that index (which ignores case on one column), see here.

Community
  • 1
  • 1
Soferio
  • 483
  • 6
  • 14
  • In PostgreSQL proper (with no ORM) you'd use the `citext` extension data type as part of the `PRIMARY KEY`. With your ORM, you might instead have to live with a synthetic primary key and create a unique index like `CREATE UNIQUE INDEX blahindexname ON thetable(topic_id, lower("name"))`. Not posted as an answer because I have no idea how that interacts with SQLAlchemy. – Craig Ringer Sep 29 '14 at 05:16
  • Interesting, I can see a github repo in which someone experimented with citextand SQLAlchemy [here](https://github.com/mahmoudimus/sqlalchemy-citext), but I am not sure whether this is the simplest solution yet. I also just noticed an old question along the same lines [here](https://groups.google.com/forum/#!topic/sqlalchemy/65t1HmNxIYQ). – Soferio Sep 29 '14 at 05:29
  • @Soferio did you ever get to the bottom of this? – Evan Hammer Aug 04 '15 at 22:11
  • I did not find any wonderful solution, I just created a custom initialiser which takes care of things. It is a classmethod and it first loads all tags for a topic. If those tags (on a case insensitive basis) include a tag with the name being sought to be created, it will just return that existing tag, if not, it will create a new tag and return that. It is not as good as the database itself controlling things with a uniqueness constraints, but so be it. – Soferio Aug 15 '15 at 10:07

0 Answers0