diferencia entre el filtro con múltiples argumentos y el filtro de cadena en django

Respuestas:

60

Como puede ver en las declaraciones SQL generadas, la diferencia no es el "O" como algunos sospechan. Así es como se colocan WHERE y JOIN.

Example1 (misma tabla unida): de https://docs.djangoproject.com/en/dev/topics/db/queries/#spanning-multi-valued-relationships

Blog.objects.filter(
       entry__headline__contains='Lennon', 
       entry__pub_date__year=2008)

Esto le dará todos los blogs que tienen una entrada con ambos (entry__headline__contains='Lennon') AND (entry__pub_date__year=2008), que es lo que esperaría de esta consulta.

Resultado:

Blog with {entry.headline: 'Life of Lennon', entry.pub_date: '2008'}

Ejemplo 2 (encadenado)

Blog.objects.filter(
       entry__headline__contains='Lennon'
           ).filter(
       entry__pub_date__year=2008)

Esto cubrirá todos los resultados del Ejemplo 1, pero generará un poco más de resultado. Porque primero filtra todos los blogs con (entry__headline__contains='Lennon')y luego desde los filtros de resultados (entry__pub_date__year=2008).

La diferencia es que también te dará resultados como:

Un solo Blog con múltiples entradas

{entry.headline: '**Lennon**', entry.pub_date: 2000}, 
{entry.headline: 'Bill', entry.pub_date: **2008**}

Cuando se evaluó el primer filtro, el libro se incluye debido a la primera entrada (aunque tiene otras entradas que no coinciden). Cuando se evalúa el segundo filtro, el libro se incluye debido a la segunda entrada.

Una tabla: pero si la consulta no implica tablas unidas como el ejemplo de Yuji y DTing. El resultado es el mismo.

Johnny Tsang
fuente
20
Supongo que estoy denso esta mañana, pero esta oración me confunde: "Porque primero filtra todos los blogs con (entry__headline__contains='Lennon')y luego desde los filtros de resultados (entry__pub_date__year=2008)" Si "luego del resultado" es correcto, ¿por qué incluirá algo con entry.headline == 'Bill'.. .¿no entry__headline__contains='Lennon'filtraría la Billinstancia?
Dustin Wyatt
7
También estoy confundido. Parece que esta respuesta es simplemente incorrecta, pero tiene 37 votos a favor ...
Personman
1
Esta respuesta es engañosa y confusa, tenga en cuenta que lo anterior solo es correcto cuando se filtra utilizando relaciones M2M como se indica en la respuesta de Yuji. El punto clave es que el ejemplo es filtrar los elementos del Blog con cada declaración de filtro, no los elementos de Entrada.
theannouncer
1
Porque posiblemente haya varias entradas por blog. El idioma es correcto. El concepto puede resultar confuso si no se tienen en cuenta todas las piezas móviles.
DylanYoung
@DustinWyatt Yo también tenía las mismas preguntas que tú, ¡pero finalmente las entendí! Consulte el ejemplo de empleado y dependiente escrito por Grijesh Chauhan a continuación en esta página y también lo obtendrá.
theQuestionMan
33

El caso en el que los resultados de "consulta de filtro de argumentos múltiples" es diferente de "consulta de filtro encadenado", siguiente:

La selección de objetos referenciados sobre la base de los objetos de referencia y la relación es de uno a muchos (o de muchos a muchos).

Varios filtros:

    Referenced.filter(referencing1_a=x, referencing1_b=y)
    #  same referencing model   ^^                ^^

Filtros encadenados:

    Referenced.filter(referencing1_a=x).filter(referencing1_b=y)

Ambas consultas pueden generar resultados diferentes:
si más de una fila en el modelo de Referencing1referencia puede hacer referencia a la misma fila en el modelo de referencia Referenced. Este puede ser el caso en Referenced: Referencing1tener una relación 1: N (uno a muchos) o N: M (muchos a muchos).

Ejemplo:

Considere que mi aplicación my_companytiene dos modelos Employeey Dependent. Un empleado my_companypuede tener más de dependientes (en otras palabras, un dependiente puede ser hijo / a de un solo empleado, mientras que un empleado puede tener más de un hijo / hija).
Ehh, asumiendo que, como marido-mujer, ambos no pueden trabajar en a my_company. Tomé el ejemplo 1: m

Por lo tanto, Employeees un modelo de referencia que puede ser referenciado por más de Dependentese modelo de referencia. Ahora considere el estado de relación de la siguiente manera:

Employee:        Dependent:
+------+        +------+--------+-------------+--------------+
| name |        | name | E-name | school_mark | college_mark |
+------+        +------+--------+-------------+--------------+
| A    |        | a1   |   A    |          79 |           81 |
| B    |        | b1   |   B    |          80 |           60 |
+------+        | b2   |   B    |          68 |           86 |
                +------+--------+-------------+--------------+  

Dependiente se a1refiere al empleado Ay dependiente b1, b2al empleado B.

Ahora mi consulta es:

¿Encuentra todos los empleados que tienen un hijo / hija con marcas de distinción (digamos> = 75%) tanto en la universidad como en la escuela?

>>> Employee.objects.filter(dependent__school_mark__gte=75,
...                         dependent__college_mark__gte=75)

[<Employee: A>]

La salida es 'A' dependiente 'a1' tiene marcas de distinción tanto en la universidad como en la escuela depende del empleado 'A'. Tenga en cuenta que 'B' no se selecciona porque ninguno de los hijos de 'B' tiene marcas de distinción tanto en la universidad como en la escuela. Álgebra relacional:

Empleado (school_mark> = 75 AND college_mark> = 75) Dependiente

En segundo lugar, caso necesito una consulta:

¿Encontrar todos los empleados cuyos dependientes tengan marcas de distinción en la universidad y la escuela?

>>> Employee.objects.filter(
...             dependent__school_mark__gte=75
...                ).filter(
...             dependent__college_mark__gte=75)

[<Employee: A>, <Employee: B>]

Esta vez 'B' también se seleccionó porque 'B' tiene dos hijos (¡más de uno!), Uno tiene una marca de distinción en la escuela 'b1' y el otro tiene una marca de distinción en la universidad 'b2'.
El orden del filtro no importa, también podemos escribir la consulta anterior como:

>>> Employee.objects.filter(
...             dependent__college_mark__gte=75
...                ).filter(
...             dependent__school_mark__gte=75)

[<Employee: A>, <Employee: B>]

¡el resultado es el mismo! El álgebra relacional puede ser:

(Empleado (marca_escuela> = 75) Dependiente) (marca_universidad> = 75) Dependiente

Nota siguiente:

dq1 = Dependent.objects.filter(college_mark__gte=75, school_mark__gte=75)
dq2 = Dependent.objects.filter(college_mark__gte=75).filter(school_mark__gte=75)

Produce el mismo resultado: [<Dependent: a1>]

Verifico la consulta SQL de destino generada por Django usando print qd1.queryy print qd2.queryambos son iguales (Django 1.6).

Pero semánticamente ambos son diferentes para . primero se ve como una sección simple σ [marca_escuela> = 75 Y marca_universidad> = 75] (Dependiente) y la segunda como una consulta anidada lenta: σ [marca_escuela> = 75][marca_universidad> = 75] (Dependiente)).

Si uno necesita Code @codepad

por cierto, se da en la documentación @ Abarcando relaciones de múltiples valores . Acabo de agregar un ejemplo, creo que será útil para alguien nuevo.

Grijesh Chauhan
fuente
4
Gracias por esta útil explicación, es mejor que la de la documentación que no es clara en absoluto.
wim
1
La última nota sobre filtrar a los dependientes directamente es muy útil. Muestra que el cambio en los resultados definitivamente solo ocurre cuando atraviesas una relación de muchos a muchos. Si consulta una tabla directamente, encadenar filtros es como combinar dos veces.
Chris
20

La mayoría de las veces, solo hay un conjunto posible de resultados para una consulta.

El uso para encadenar filtros se produce cuando se trata de m2m:

Considera esto:

# will return all Model with m2m field 1
Model.objects.filter(m2m_field=1) 

# will return Model with both 1 AND 2    
Model.objects.filter(m2m_field=1).filter(m2m_field=2) 

# this will NOT work
Model.objects.filter(Q(m2m_field=1) & Q(m2m_field=2))

Otros ejemplos son bienvenidos.

Yuji 'Tomita' Tomita
fuente
4
Otro ejemplo: no solo se limita a m2m, esto también puede suceder con uno a muchos, con la búsqueda inversa, por ejemplo, usando el nombre relacionado en una ForeignKey
wim
¡Gracias por tu explicación! Antes de eso, pensé que el último y el segundo ejemplo son iguales, por lo que el último ejemplo no funcionó para mí (resultados de consulta incorrectos) y pasé mucho tiempo en búsquedas. Segundo ejemplo muy útil para mí. Además, como dijo Wim, esto se puede usar con relaciones inversas de uno a muchos como en mi caso.
zen11625
12

La diferencia de rendimiento es enorme. Pruébalo y verás.

Model.objects.filter(condition_a).filter(condition_b).filter(condition_c)

es sorprendentemente lento en comparación con

Model.objects.filter(condition_a, condition_b, condition_c)

Como se menciona en Effective Django ORM ,

  • QuerySets mantiene el estado en la memoria
  • El encadenamiento desencadena la clonación, duplicando ese estado
  • Desafortunadamente, QuerySets mantienen mucho estado
  • Si es posible, no encadene más de un filtro
alquitrán
fuente
8

Puede usar el módulo de conexión para ver las consultas SQL sin procesar para comparar. Como explica Yuji, en su mayor parte son equivalentes como se muestra aquí:

>>> from django.db import connection
>>> samples1 = Unit.objects.filter(color="orange", volume=None)
>>> samples2 = Unit.objects.filter(color="orange").filter(volume=None)
>>> list(samples1)
[]
>>> list(samples2)
[]
>>> for q in connection.queries:
...     print q['sql']
... 
SELECT `samples_unit`.`id`, `samples_unit`.`color`, `samples_unit`.`volume` FROM `samples_unit` WHERE (`samples_unit`.`color` = orange  AND `samples_unit`.`volume` IS NULL)
SELECT `samples_unit`.`id`, `samples_unit`.`color`, `samples_unit`.`volume` FROM `samples_unit` WHERE (`samples_unit`.`color` = orange  AND `samples_unit`.`volume` IS NULL)
>>> 
dting
fuente
2

Si al final de esta página buscando la manera de construir dinámicamente un conjunto de consultas Django con varios filtros de encadenamiento, pero necesita los filtros para ser del ANDtipo en lugar de OR, considere el uso de objetos Q .

Un ejemplo:

# First filter by type.
filters = None
if param in CARS:
  objects = app.models.Car.objects
  filters = Q(tire=param)
elif param in PLANES:
  objects = app.models.Plane.objects
  filters = Q(wing=param)

# Now filter by location.
if location == 'France':
  filters = filters & Q(quay=location)
elif location == 'England':
  filters = filters & Q(harbor=location)

# Finally, generate the actual queryset
queryset = objects.filter(filters)
Mate
fuente
En caso de que no se pase if o elif, la variable de filtros será None y luego recibirá un TypeError: tipo (s) de operando no admitido para &: 'NoneType' y 'Q'.
Inicié
-4

Hay una diferencia cuando tiene una solicitud a su objeto relacionado, por ejemplo

class Book(models.Model):
    author = models.ForeignKey(Author)
    name = models.ForeignKey(Region)

class Author(models.Model):
    name = models.ForeignKey(Region)

solicitud

Author.objects.filter(book_name='name1',book_name='name2')

devuelve el conjunto vacío

y solicitud

Author.objects.filter(book_name='name1').filter(book_name='name2')

devuelve autores que tienen libros con 'nombre1' y 'nombre2'

para obtener más detalles, consulte https://docs.djangoproject.com/en/dev/topics/db/queries/#s-spanning-multi-valued-relationships

Marta
fuente
5
Author.objects.filter(book_name='name1',book_name='name2')ni siquiera es python válido, lo seríaSyntaxError: keyword argument repeated
wim
1
¿Dónde se define exactamente book_name? ¿Te refieres a book_set__name?
DylanYoung