10

Is there a way in Django to write a query using the ORM, not raw SQL that allows you to JOIN on another table without there being a foreign key? Looking through the documentation it appears in order for the One to One relationship to work there must be a foreign key present?

In the models below I want to run a query with a JOIN on UserActivity.request_url to UserActivityLink.url.

class UserActivity(models.Model):
    id = models.IntegerField(primary_key=True)
    last_activity_ip = models.CharField(max_length=45L, blank=True)
    last_activity_browser = models.CharField(max_length=255L, blank=True)
    last_activity_date = models.DateTimeField(auto_now_add=True)
    request_url = models.CharField(max_length=255L, blank=True)
    session_id = models.CharField(max_length=255L)
    users_id = models.IntegerField()
    class Meta:
        db_table = 'user_activity'

class UserActivityLink(models.Model):
    id = models.IntegerField(primary_key=True)
    url = models.CharField(max_length=255L, blank=True)
    url_description = models.CharField(max_length=255L, blank=True)
    type = models.CharField(max_length=45L, blank=True)
    class Meta:
        db_table = 'user_activity_link'

The link table has a more descriptive translation of given URLs in the system, this is needed for some reporting the system will generate.

I've tried creating the foreign key from UserActivity.request_url to UserActivityLink.url but it fails with the following error: ERROR 1452: Cannot add or update a child row: a foreign key constraint fails

xXPhenom22Xx
  • 1,265
  • 5
  • 29
  • 63
  • I'm wondering what would happen if you defined your UserActivity.request_url field as a ForeignKey with `to_field='url'`. – Daniel Roseman May 21 '13 at 12:32
  • I tried that same thing via Workbench but if barfed an error. I tried to create a foreign key relationship from UserActivity.request_url TO UserActivityLink.url but got the following error, not sure why or what it really means: ERROR 1452: Cannot add or update a child row: a foreign key constraint fails – xXPhenom22Xx May 21 '13 at 13:38

3 Answers3

16

No, there isn't an effective way unfortunately.

The .raw() is there for this exact thing. Even if it could it probably would be a lot slower than raw SQL.

There is a blogpost here detailing how to do it with query.join() but as they themselves point out. It's not best practice.

Henrik Andersson
  • 45,354
  • 16
  • 98
  • 92
11

Just reposting some related answer, so everyone could see it. Taken from here: Most efficient way to use the django ORM when comparing elements from two lists

First problem: joining unrelated models

I'm assuming that your Model1 and Model2 are not related, otherwise you'd be able to use Django's related objects interface. Here are two approaches you could take:

  1. Use extra and a SQL subquery:

    Model1.objects.extra(where = ['field in (SELECT field from myapp_model2 WHERE ...)'])
    

    Subqueries are not handled very efficiently in some databases (notably MySQL) so this is probably not as good as #2 below.

  2. Use a raw SQL query:

    Model1.objects.raw('''SELECT * from myapp_model1
                       INNER JOIN myapp_model2
                       ON myapp_model1.field = myapp_model2.field
                       AND ...''')
    

Second problem: enumerating the result

Two approaches:

  1. You can enumerate a query set in Python using the built-in enumerate function:

    enumerate(Model1.objects.all())
    
  2. You can use the technique described in this answer to do the enumeration in MySQL. Something like this:

    Model1.objects.raw('''SELECT *, @row := @row + 1 AS row
                       FROM myapp_model1
                       JOIN (SELECT @row := 0) rowtable
                       INNER JOIN myapp_model2
                       ON myapp_model1.field = myapp_model2.field
                       AND ...''')
    
Community
  • 1
  • 1
Vasilly.Prokopyev
  • 856
  • 1
  • 10
  • 24
10

The Django ForeignKey is different from SQL ForeignKey. Django ForeignKey just represent a relation, it can specify whether to use database constraints.

Try this:

request_url = models.ForeignKey(UserActivityLink, to_field='url_description', null=True, on_delete=models.SET_NULL, db_constraint=False)

Note that the db_constraint=False is required, without it Django will build a SQL like:

ALTER TABLE `user_activity` ADD CONSTRAINT `xxx` FOREIGN KEY (`request_url`) REFERENCES `user_activity_link` (`url_description`);" 

I met the same problem, after a lot of research, I found the above method.

Hope it helps.

XiaoShengfeng
  • 109
  • 1
  • 4