Django での結合と交差
-
01-07-2019 - |
質問
class Tag(models.Model):
name = models.CharField(maxlength=100)
class Blog(models.Model):
name = models.CharField(maxlength=100)
tags = models.ManyToManyField(Tag)
私の質問のためだけに単純なモデル。
2 つの異なる方法でタグを使用してブログをクエリするにはどうすればよいでしょうか。
- 「tag1」または「tag2」でタグ付けされたブログエントリ:
Blog.objects.filter(tags_in=[1,2]).distinct()
- 「tag1」および「tag2」でタグ付けされたブログ オブジェクト: ?
- 正確に「tag1」と「tag2」でタグ付けされ、それ以外は何も付けられていないブログ オブジェクト: ??
タグとブログは例として使用されているだけです。
解決
#1 には Q オブジェクトを使用できます。
# Blogs who have either hockey or django tags.
from django.db.models import Q
Blog.objects.filter(
Q(tags__name__iexact='hockey') | Q(tags__name__iexact='django')
)
ユニオンと交差は、Django ORM の範囲外に少しあると思いますが、これらを行うことは可能です。次の例は、という Django アプリケーションからのものです。 ジャンゴタグ付け 機能を提供するものです。 models.pyの346行目:
パート 2 では、基本的に 2 つのクエリの和集合を探します。
def get_union_by_model(self, queryset_or_model, tags):
"""
Create a ``QuerySet`` containing instances of the specified
model associated with *any* of the given list of tags.
"""
tags = get_tag_list(tags)
tag_count = len(tags)
queryset, model = get_queryset_and_model(queryset_or_model)
if not tag_count:
return model._default_manager.none()
model_table = qn(model._meta.db_table)
# This query selects the ids of all objects which have any of
# the given tags.
query = """
SELECT %(model_pk)s
FROM %(model)s, %(tagged_item)s
WHERE %(tagged_item)s.content_type_id = %(content_type_id)s
AND %(tagged_item)s.tag_id IN (%(tag_id_placeholders)s)
AND %(model_pk)s = %(tagged_item)s.object_id
GROUP BY %(model_pk)s""" % {
'model_pk': '%s.%s' % (model_table, qn(model._meta.pk.column)),
'model': model_table,
'tagged_item': qn(self.model._meta.db_table),
'content_type_id': ContentType.objects.get_for_model(model).pk,
'tag_id_placeholders': ','.join(['%s'] * tag_count),
}
cursor = connection.cursor()
cursor.execute(query, [tag.pk for tag in tags])
object_ids = [row[0] for row in cursor.fetchall()]
if len(object_ids) > 0:
return queryset.filter(pk__in=object_ids)
else:
return model._default_manager.none()
パート #3 では、交差点を探していると思います。見る models.pyの307行目
def get_intersection_by_model(self, queryset_or_model, tags):
"""
Create a ``QuerySet`` containing instances of the specified
model associated with *all* of the given list of tags.
"""
tags = get_tag_list(tags)
tag_count = len(tags)
queryset, model = get_queryset_and_model(queryset_or_model)
if not tag_count:
return model._default_manager.none()
model_table = qn(model._meta.db_table)
# This query selects the ids of all objects which have all the
# given tags.
query = """
SELECT %(model_pk)s
FROM %(model)s, %(tagged_item)s
WHERE %(tagged_item)s.content_type_id = %(content_type_id)s
AND %(tagged_item)s.tag_id IN (%(tag_id_placeholders)s)
AND %(model_pk)s = %(tagged_item)s.object_id
GROUP BY %(model_pk)s
HAVING COUNT(%(model_pk)s) = %(tag_count)s""" % {
'model_pk': '%s.%s' % (model_table, qn(model._meta.pk.column)),
'model': model_table,
'tagged_item': qn(self.model._meta.db_table),
'content_type_id': ContentType.objects.get_for_model(model).pk,
'tag_id_placeholders': ','.join(['%s'] * tag_count),
'tag_count': tag_count,
}
cursor = connection.cursor()
cursor.execute(query, [tag.pk for tag in tags])
object_ids = [row[0] for row in cursor.fetchall()]
if len(object_ids) > 0:
return queryset.filter(pk__in=object_ids)
else:
return model._default_manager.none()
他のヒント
Django 1.0 でこれらをテストしました。
「or」クエリ:
Blog.objects.filter(tags__name__in=['tag1', 'tag2']).distinct()
または、Q クラスを使用することもできます。
Blog.objects.filter(Q(tags__name='tag1') | Q(tags__name='tag2')).distinct()
「and」クエリ:
Blog.objects.filter(tags__name='tag1').filter(tags__name='tag2')
3 番目についてはわかりません。おそらく SQL にドロップする必要があるでしょう。
車輪の再発明をして使用しないでください django タグ付けアプリケーション これはまさにあなたのユースケースに合わせて作成されました。記述したすべてのクエリだけでなく、それ以外にもさまざまなクエリを実行できます。
タグモデルにカスタムフィールドを追加する必要がある場合は、以下も参照してください。 私の django-tagging のブランチ.
これでうまくいきます
Blog.objects.filter(tags__name__in=['tag1', 'tag2']).annotate(tag_matches=models.Count(tags)).filter(tag_matches=2)
所属していません StackOverflow