django-фільтр на основі довжини тексту


Відповіді:


-18

Було б набагато краще і швидше, якби ви просто додали стовпець, який попередньо обчислює (запам'ятовує) довжину тексту.

напр

class MyModel(models.Model):
    text = models.TextField()
    text_len = models.PositiveIntegerField()

     def save(self, *args, **kwargs):
         self.text_len = len(self.text)
         return super(MyModel, self).save(*args, **kwargs)

MyModel.objects.filter(text_len__gt = 10)     # Here text_len is pre-calculated by us on `save`

Це тому, що текстове поле не індексується, і довжина тексту обчислюється кожного разу, коли запит потрапляє до бази даних. Рішення, запропоноване lain, робить те саме, чи не так (хоча це рішення для мене не працює).
ashish

@ashish 1) Так, це попередньо розраховано. 2) Жодне поле не робить те саме.
rantanplan

1) Отже, якщо довжина попередньо розрахована, то чому мені потрібно мати інший стовпець 2) lain-рішення не перевіряє кожен вираз, якщо частота символів перевищує n ??
ашіш

1
@ashish Я додав коментар до останнього рядка вищезазначеного коду. Додаємо стовпець до моделі, щоб зберегти довжину text. Це оновлюється щоразу, коли текст змінюється. Отже, коли ми запитуємо модель, ми можемо відфільтрувати за довжиною тексту, яку МИ попередньо розрахували за нашим saveметодом.
rantanplan

213

Для Django> = 1,8 ви можете використовувати функцію Length , яка є @ Pratyush CHAR_LENGTH()під капотом для MySQL, або LENGTH()для деяких інших баз даних:

from django.db.models.functions import Length
qs = MyModel.objects.annotate(text_len=Length('text_field_name')).filter(
    text_len__gt=10)

1
Скажімо, я не хочу фільтрувати набір запитів, а натомість повертати себе з об’єктами text_len__gt=10в першу чергу ( order_by). Будь-який натяк?
vabada

3
@dabad, ви можете використовувати text_len анотацію таким же чином , ви будете використовувати будь-який інший бази даних поля , тому він працює в order_byабо Sumабо будь-який інший . Щоб відсортувати результати в порядку спадання тексту довжини і повертає значення довжини: MyModel.objects.annotate(text_len=Length('text_field_name')).order_by('-text_len').values_list('text_len', flat=True).
конфорки

1
@guettli Проблема з прийнятою відповіддю полягає в тому, що оригінальний плакат бачили востаннє у вересні 2015 року на SO, і ваш чудовий альтруїзм був єдиною можливістю :-) Мені довелося відредагувати цю відповідь, перш ніж я міг проголосувати. Я додав подібну відповідь для Django> = 1.9 , яка не вимагає анотації, а глобальної реєстрації LengthTransform.
hynekcer

1
Це дуже важко знайти в документах, оскільки воно не згруповане з іншими агрегаціями, наприклад Sum. Це також надзвичайно важливо для багатьох випадків. У мене був випадок, коли мені потрібно було попередньо перевірити максимальний обсяг даних, який може повернути запит, не призведе до втрати пам'яті сервера, і варіант цього працював чудово.
AlanSE

@AlanSE причиною того, що вони не задокументували це за допомогою Sum та інших агрегатів, є те, що це не агрегація . Працює над окремими записами (рядками). Отриманий набір запитів має таку ж кількість рядків, як і до запуску Lengthоператора. Тож це називається перетворенням або відображенням . Агрегації зменшують кількість записів. Відображення не роблять.
конфорки

59

Інший спосіб:

MyModel.objects.extra(where=["CHAR_LENGTH(text) > 300"])

Це можна використовувати, якщо довжина тексту також перевищує 255 символів.


4
якщо у вас є sqlite, це LENGTH(..).
Андрій-Нікулае Петре,

43

Гарне рішення для Django> = 1.9 можливо, зареєструвавши вбудовану функцію Lengthяк Transform forCharField пошуку.

Зареєструйте трансформацію в проекті один раз. (Найкраще місце - це, мабуть, models.py.)

from django.db.models import CharField
from django.db.models.functions import Length

CharField.register_lookup(Length, 'length')

Використання :

result = MyModel.objects.filter(text__length__gt=10)

Дивіться точно такий самий приклад у документах щодо Length як перетворення .


Він працює коректно для всіх серверних систем, складених LENGTH()для більшості серверів і CHAR_LENGTH()для MySQL. Потім він автоматично реєструється для всіх підкласів CharField, наприклад для EmailField. TextField реєструється індивідуально. Зареєструвати ім'я "довжина" безпечно, оскільки ім'я перетворення ніколи не може затінювати або затінювати однаково іменованим полем або пов'язаним ім'ям поля.

Єдиним недоліком могла б бути головоломка для читабельності: Звідки взялася "довжина"? (Пошук здійснюється глобально, але той самий може бути, на щастя, безпечно повторно зареєстрований у більшості модулів, якщо це зручно для читабельності, без будь-яких можливих накладних витрат під час виконання запиту.)

Іншим настільки ж цінним рішенням є наведена вище плита , яка коротша, якщо враховується реєстрація та якщо подібний запит не використовується повторно.


@guettli Несподівано, що ви написали рішення спочатку і за хвилину до того, як почали нагороду? Я зробив це також у дивному порядку: я знайшов деталі рішення з джерела Django, потім знайшов, що все є в документах про те, що нарешті ви знали рішення спочатку.
hynekcer

Я розпочав щедроту, оскільки прийняте питання, яке досі, на жаль, все ще залишається тим, що було зверху, було застарілим. Я сподівався, що відповідь із функцією Length (> = Django 1.8) отримує більше голосів. AFAIK це сталося, але, на жаль, застаріла відповідь все ще є на вершині.
guettli

29

Ви можете використовувати регулярний вираз для пошуку тексту певної довжини:

MyModel.objects.filter(text__regex = r'.{10}.*')

Застереження: для MySQL максимальне значення довжини становить 255. В іншому випадку створюється виняток:

DatabaseError: (1139, "Got error 'invalid repetition count(s)' from regexp")

3
Як свідчить документація :Using raw strings (e.g., r'foo' instead of 'foo') for passing in the regular expression syntax is recommended.
Сергій Голіній

Я отримую цей виняток після запуску коду OperationalError: (1139, "Отримав помилку" недійсні кількість повторень (ів) "із регулярного виразу"), і це через фігурні дужки.
ашіш

Насправді виняток, зазначений вище, в основному є виключенням mysql.
ашіш

Це чудово працює для будь-якого числа нижче 256. MySQL має максимальну кількість повторень 256.
Emil Stenström

2
@ emil-stenstrom насправді, це 255
glarrain

-6

Я вирішив би проблему на вашому сервері додатків, а не оподатковував вашу базу даних. Ви можете зробити це, виконавши:

models_less_than_ten = []
mymodel = MyModel.objects.all()
for m in mymodel:
    if len(m.text) > 10:
          models_less_than_ten.append(m)

2
Це не буде добре масштабуватися для багатьох рядків у MyModel. Якби у вас було 100 000 рядків, це було б меншим оподаткуванням db, щоб зробити strlen і вирішити не надсилати рядок, ніж відправлення тонн даних на сервер додатків для фільтрації. Практично завжди краще виконувати роботу на базі даних, і якщо вона занадто повільна або обкладається податком, запит можна оптимізувати.
nevelis
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.