如何优化这个多对多的Django ORM查询和模型集?

3 投票
3 回答
617 浏览
提问于 2025-04-17 07:36

我有一个Django查询和一些Python代码,我想优化一下,因为1)它看起来很丑,而且性能不如我可以用SQL写的那样好,2)因为数据的层次重组对我来说看起来很乱。

所以,

  1. 有没有可能把这个优化成一个查询?
  2. 我该如何改进我的Python代码,让它更符合Python的风格?

背景

这是一个照片库系统。这个特定的视图是想展示图库中所有照片的缩略图。每张照片的大小都是固定的,以避免动态调整大小,我还想获取每种大小的URL和“大小类型”(例如,缩略图、中等、大号),这样我就可以在不再次访问数据库的情况下显示其他尺寸的照片。

相关实体

我有5个相关的模型:

class Gallery(models.Model):
    Photos = models.ManyToManyField('Photo', through = 'GalleryPhoto', blank = True, null = True)

class GalleryPhoto(models.Model):
    Gallery = models.ForeignKey('Gallery')
    Photo = models.ForeignKey('Photo')
    Order = models.PositiveIntegerField(default = 1)

class Photo(models.Model):
    GUID = models.CharField(max_length = 32)

class PhotoSize(models.Model):
    Photo = models.ForeignKey('Photo')
    PhotoSizing = models.ForeignKey('PhotoSizing')
    PhotoURL = models.CharField(max_length = 1000)

class PhotoSizing(models.Model):
    SizeName = models.CharField(max_length = 20)
    Width = models.IntegerField(default = 0, null = True, blank = True)
    Height = models.IntegerField(default = 0, null = True, blank = True)
    Type = models.CharField(max_length = 10, null = True, blank = True)

大致的想法是,我想通过GalleryPhoto获取图库中的所有照片,并且对于每张照片,我想获取所有的PhotoSizes,我希望能够通过字典遍历并访问所有这些数据。

SQL的大致草图可能是这样的:

Select PhotoSize.PhotoURL
From PhotoSize
Inner Join Photo On Photo.id = PhotoSize.Photo_id
Inner Join GalleryPhoto On GalleryPhoto.Photo_id = Photo.id
Inner Join Gallery On Gallery.id = GalleryPhoto.Gallery_id
Where Gallery.id = 5
Order By GalleryPhoto.Order Asc

我想把它变成一个这样的列表:

(
    photo: {
        'guid': 'abcdefg',
        'sizes': {
            'Thumbnail': 'http://mysite/image1_thumb.jpg',
            'Large': 'http://mysite/image1_full.jpg',
            more sizes...
        }
    },
    more photos...
)

我目前有以下Python代码(虽然它并不完全符合上面的结构,但可以作为一个例子)。

gallery_photos = [(photo.Photo_id, photo.Order) for photo in GalleryPhoto.objects.filter(Gallery = gallery)]
photo_list = list(PhotoSize.objects.select_related('Photo', 'PhotoSizing').filter(Photo__id__in=[gallery_photo[0] for gallery_photo in gallery_photos]))

photos = {}
for photo in photo_list:
    order = 1
    for gallery_photo in gallery_photos:
        if gallery_photo[0] == photo.Photo.id:
            order = gallery_photo[1] //this gets the order column value

            guid = photo.Photo.GUID
            if not guid in photos:
                photos[guid] = { 'Photo': photo.Photo, 'Thumbnail': None, 'Sizes': [], 'Order': order }

            photos[guid]['Sizes'].append(photo)

    sorted_photos = sorted(photos.values(), key=operator.itemgetter('Order'))

实际问题,第一部分

所以,我的问题首先是,是否可以更好地处理我的多对多查询,这样我就不需要对gallery_photos和photo_list进行两次查询。

实际问题,第二部分

我看着这段代码,觉得它的样子不太好。我希望有更好的方法通过列名将层次查询结果分组到字典中。有没有这样的办法?

3 个回答

1

你可以通过一次查询获取所有数据,并得到一个数据字典的列表。然后你可以管理这个字典,或者创建一个新的字典来形成你最终想要的字典……你可以在过滤和选择表中特定行时使用反向关系……所以:

假设x是你选择的画廊……

GalleryPhoto.objexts.filter(Galery=x).values('Order', 'Photo__GUID', 'Photo__Photo__PhotoURL', 'Photo__Photo__PhotoSizing__SizeName', 'Photo__Photo__PhotoSizing__Width', 'Photo__Photo__PhotoSizing__Height', 'Photo__Photo__PhotoSizing__Type')

使用Photo__会创建一个与Photo表的内连接,而Photo__Photo__会通过反向关系创建一个与PhotoSize的内连接,Photo__Photo__PhotoSizing__则会与PhotoSizing进行内连接……

这样你就得到了一个字典列表:

[{'Order':....,'GUID': ..., 'PhotoURL':....., 'SizeName':...., 'Width':...., 'Height':..., 'Type':...}, {'Order':....,'GUID': ..., 'PhotoURL':....., 'SizeName':...., 'Width':...., 'Height':..., 'Type':...},....]

你可以选择你需要的行,并将所有值作为字典列表获取……然后你可以写一个循环函数或者迭代器,遍历这个列表,创建一个新的字典来对你的数据进行分组……

1

Django有一些内置的功能,可以让你的代码看起来更整洁。这会导致一些子查询,所以性能可能会受到影响。你可以查看这个链接了解更多信息:https://docs.djangoproject.com/en/dev/ref/models/querysets/#django.db.models.query.QuerySet.values

gallery_photos = GalleryPhoto.objects.filter(Gallery=gallery).values('Photo_id', 'Order')
photo_queryset = PhotoSize.objects.selected_related('Photo', 'PhotoSizing').filter(
                 Photo__id__in=gallery_photos.values_list('Photo_id', flat=True))

调用list()会立即计算出查询结果,如果你的数据量很大,这可能会影响性能。

另外,有一种比较简单的方法可以去掉if gallery_photo[0] == photo.Photo.id:这段代码。看起来可以通过另一个查询来轻松解决,获取所有照片的画廊照片。

3

当你有一个SQL查询,使用ORM(对象关系映射)写起来很困难时,可以考虑使用PostgreSQL的视图。不太确定MySQL是否也能这样做。在这种情况下,你会看到:

原始的SQL代码,比如:

CREATE VIEW photo_urls AS
Select
photo.id, --pseudo primary key for django mapper
Gallery.id as gallery_id, 
PhotoSize.PhotoURL as photo_url
From PhotoSize
Inner Join Photo On Photo.id = PhotoSize.Photo_id
Inner Join GalleryPhoto On GalleryPhoto.Photo_id = Photo.id
Inner Join Gallery On Gallery.id = GalleryPhoto.Gallery_id
Order By GalleryPhoto.Order Asc

Django模型的样子:

class PhotoUrls(models.Model):
    class Meta: 
         managed = False 
         db_table = 'photo_urls'
    gallery_id = models.IntegerField()
    photo_url = models.CharField()

ORM查询集的样子:

PhotoUrls.objects.filter(gallery_id=5)

希望这些对你有帮助。

撰写回答