# Composite Primary Key

**URL:** <https://forum.djangoproject.com/t/composite-primary-key/1767>\
**Category:** Using Django\
**Created:** [April 1, 2020, 11:05am UTC](https://forum.djangoproject.com/t/composite-primary-key/1767 "2020-04-01T11:05:12Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![karlffreire](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/karlffreire/32/946_2.png) [@karlffreire](https://forum.djangoproject.com/u/karlffreire)\
**Post date:** [April 1, 2020, 11:05am UTC](https://forum.djangoproject.com/t/composite-primary-key/1767/1 "2020-04-01T11:05:12Z")

</div>

Hi  
  
I am new to Django and working on an pre-existing database that has 22 tables with compound primary keys. I have seen the existing ticket on this subject, talking about faking primary keys by using a primary key and a unique constraint. But that does not allow (obviously) the inclusion of duplicates in the primary key. I have tried to remove the primary key constraint, but then it does not work either because it is expecting an id field (my classes are not managed, since I started from an existing database).  
  
I would like to maintain my database structure as much as possible, but I am afraid that I will have to add new ids to all my tables. Is there another possible way to work this out? or maybe is it about to be solved in a next release…?

Thank you

---

<div class="post-metadata">

**Author:** ![adamchainz](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/adamchainz/32/26_2.png) [@adamchainz](https://forum.djangoproject.com/u/adamchainz)\
**Post date:** [April 1, 2020, 11:57am UTC](https://forum.djangoproject.com/t/composite-primary-key/1767/2 "2020-04-01T11:57:01Z")

</div>

Hi

This was literally asked on the django-developers mailing list at about the same time: [Redirecting to Google Groups](https://groups.google.com/forum/#!msg/django-developers/OeA7PcK87to/oAY7epAgBwAJ)

Copying some bits of my reply here:

> Multiple column primary keys is a long-open feature request. There’s a wiki page: [MultipleColumnPrimaryKeys – Django](https://code.djangoproject.com/wiki/MultipleColumnPrimaryKeys) and the ticket, still open, is #373: [https://code.djangoproject.com/ticket/373](https://code.djangoproject.com/ticket/373) .
> 
> I’ve thought of a technique before for making composite PK’s work, though haven’t tested it. It should be possible to put a database view in front of your table that combines your primary key columns into one string. For example:
> 
> `CREATE VIEW book_view AS SELECT CONCAT(catalogue_id, '-', book_id) AS primary_key, author_id, ... FROM book;`
> 
> Then define your model as having a `CharField` as a primary key. PostgreSQL and MariaDB/MySQL will both automatically make single-table views map inserts and updates to the underlying table. Though you might need a trigger to allow inserts (new object saves) to work, splitting the parts back out.

If you try this techinque, let me know.

> [@karlffreire](#):
>
> I am afraid that I will have to add new ids to all my tables.

If you are at liberty to move your data around to add ID columns, it’s probably not _that much_ work - depending on the size of the data. But changing primary key is often not possible without downtime.

> [@karlffreire](#):
>
> or maybe is it about to be solved in a next release…?

The ticket is 15 years old and I don’t believe anyone is working on it currently, so I wouldn’t expect any changes there in the next release.

---

<div class="post-metadata">

**Author:** ![karlffreire](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/karlffreire/32/946_2.png) [@karlffreire](https://forum.djangoproject.com/u/karlffreire)\
**Post date:** [April 1, 2020, 12:09pm UTC](https://forum.djangoproject.com/t/composite-primary-key/1767/3 "2020-04-01T12:09:41Z")

</div>

Thank you for your quick response.  
  
I am working with postgres, so I will try to implement your solution and post the result.

---

<div class="post-metadata">

**Author:** ![KenWhitesell](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/kenwhitesell/32/280_2.png) [@KenWhitesell](https://forum.djangoproject.com/u/KenWhitesell)\
**Post date:** [April 1, 2020, 3:00pm UTC](https://forum.djangoproject.com/t/composite-primary-key/1767/4 "2020-04-01T15:00:59Z")

</div>

I was lucky enough to listen in on a conversation about this back at DjangoCon among a couple of very knowledgeable people. Apparently there have been multiple attempts to do this (at least 3), and they have all run across specific edge cases that create really serious problems, either difficulties in the ORM itself, or by creating breaking changes to existing code. (Sorry, I can’t speak to any of these issues specifically, I’m just parroting what I heard. I’m also quite interested in the topic myself.)

---

<div class="post-metadata">

**Author:** ![karlffreire](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/karlffreire/32/946_2.png) [@karlffreire](https://forum.djangoproject.com/u/karlffreire)\
**Post date:** [April 1, 2020, 3:05pm UTC](https://forum.djangoproject.com/t/composite-primary-key/1767/5 "2020-04-01T15:05:59Z")

</div>

Your solution seems to work OK with a little example:  
Database:

```
CREATE OR REPLACE FUNCTION public.manage_foo()
  RETURNS trigger AS
$BODY$

DECLARE

new_id character varying[];
old_id character varying[];

BEGIN

IF (TG_OP='INSERT') THEN
	new_id := string_to_array(NEW.id_foo,'-');
	INSERT INTO foo (id1,id2,txt) VALUES (new_id[1]::integer,new_id[2]::integer,NEW.txt);
	RETURN NULL;
ELSEIF (TG_OP='UPDATE') THEN
	new_id := string_to_array(NEW.id_foo,'-');
	old_id := string_to_array(OLD.id_foo,'-');
	UPDATE foo SET txt = NEW.txt WHERE id1 = old_id[1]::integer AND id2 = old_id[2]::integer;
	RETURN NULL;
ELSEIF (TG_OP='DELETE') THEN
	old_id := string_to_array(OLD.id_foo,'-');
	DELETE FROM foo WHERE id1 = old_id[1]::integer AND id2 = old_id[2]::integer;
	RETURN NULL;
END IF;
	RETURN NULL;
END;

$BODY$
  LANGUAGE plpgsql VOLATILE
  COST 100;

--Create trigger
CREATE TRIGGER manage_foo_view INSTEAD OF INSERT OR UPDATE OR DELETE
   ON public.foo_view FOR EACH ROW
   EXECUTE PROCEDURE public.manage_foo();

```

On Django models  
  
class Foo(models.Model):  
id\_foo = models.CharField(max\_length=100, primary\_key=True)  
txt = models.CharField(max\_length=100)

```
    class Meta:
        managed = False
        db_table = 'foo_view'

```

Get, save, update and delete seem to work fine  
Next step will be related tables…

Thanks for the idea!

---

<div class="post-metadata">

**Author:** ![karlffreire](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/karlffreire/32/946_2.png) [@karlffreire](https://forum.djangoproject.com/u/karlffreire)\
**Post date:** [April 1, 2020, 3:08pm UTC](https://forum.djangoproject.com/t/composite-primary-key/1767/6 "2020-04-01T15:08:55Z")

</div>

It would be nice to know what they said to save big troubles in the future…

---

<div class="post-metadata">

**Author:** ![KenWhitesell](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/kenwhitesell/32/280_2.png) [@KenWhitesell](https://forum.djangoproject.com/u/KenWhitesell)\
**Post date:** [April 1, 2020, 7:44pm UTC](https://forum.djangoproject.com/t/composite-primary-key/1767/7 "2020-04-01T19:44:31Z")

</div>

Well, in the context of that (very casual) conversation (outside, Monday evening, during the group “social”), there weren’t any details mentioned - at least none I recall. (Or if there were, they went over my head.) It’s something that has been worked on multiple times in the past, and all attempts have fallen short for one reason or another.  
I know it’s not much help, but the only suggestion I might have would be to read all of ticket 373 and follow that through to some of the older conversations on the mailing list that it references. You might be able to chase down some of the people involved to find out what they encountered.
