按字母顺序比较字符串 Django 数据库。

4

我有一个如下的模型:

class Page(Model):
      book = ForeignKey(Book, on_delete=CASCADE)
      page = IntegerField()
      start = CharField(max_length=350, db_index=True)
      end = CharField(max_length=350, db_index=True)

如何查询数据库以获取包含给定单词的页面?

page1 = Page.objects.create(start='beaver', end='brother')
page2 = Page.objects.create(start='boy', end='brother')
  • Page.objects.filter("breast" 在 start 和 end 之间) 应该返回 page1 和 page2。
  • Page.objects.filter("beast" 在 start 和 end 之间) 不应该返回任何结果。
  • Page.objects.filter("block" 在 start 和 end 之间) 应该只返回 page1,因为 block 按字母表顺序在 beaverbrother 之后。

搜索应该不区分大小写。

因此,我需要编写一个查询语句,以获取所有行,其中 start 在字母表上“更小”而end 在字母表上“更大”。


“breast”,“block” 存储在数据库中吗? - shafik
@shafik 可能。 - Paul R
3个回答

2
一种选择是在保存之前将所有startend的数据库值转换为大写或小写。然后在搜索时使用过滤器__gte__lte(同样将搜索术语转换为大写或小写)。在我看来,这个方法很有效(使用Python 3.6Django 2.2postresql 10)。"最初的回答"
# create with lowercase words
Page.objects.create(start='beaver', end='brother')
Page.objects.create(start='boy', end='brother')

# filter using lowercase as well
v = request.GET.get('search_term', '').lower()
qs = Page.objects.filter(start__lte=v, end__gte=v)

最初的回答是我是否误解了你的问题?

@AndrewFount 我认为字符串的顺序是由Postgres的COLLATE设置定义的,而不是由Django定义的;在这个类似的问题 https://dev59.com/ZKHia4cB1Zd3GeqPYcKe 中阅读更多相关信息。 - Ralf
@AndrewFount,你有一个具体的例子,在筛选时给出了错误的结果吗? - Ralf

1
您可以将endstart转换为十进制数。
在您的模型中,使用DecimalField代替CharField
然后,您可以使用ASCII表将单词转换为数字。
例如,“love”将被翻译为: 108 111 118 101
因此,在数据库中,它应该具有以下值: 0.108111118101(值应小于1,以便单词长度不会影响过滤)。
“amour”将被翻译为: 97 109 111 117 114
请注意,“a”的ASCII代码只有2个数字,并且所有字母的数字必须相同(这里是3个),因此如果是这种情况,请用0填充: 0.097109111117114
然后很容易查询一个十进制数是否在这些数之间,使用小于(lt)和大于(gt)。
注:
  • 您可以使用模型的getter和setter将单词转换为它们的ascii值,反之亦然。

  • 将单词转换为小写,否则它在ascii表中不起作用,例如'C'的值与'c'的值不同

  • 使用ascii表无法处理拉丁字母表以外的内容。例如,ç,é,à,è,ù可能会破坏搜索,您应该考虑构建自己的表,或者用基本字母替换这些字母...

现在让我们看看"django"是否在"amour"和"love"之间:

love   : 0.108111118101
django : 0.100106097110103111 
amour  : 0.097109111117114

是的,它是 :)

2
打开Python,输入0.100106097110103111 == 0.100106097110103110。答案是True,尽管我输入了两个不同的数字(最后一位不同)。换句话说,这些数字所代表的原始单词"django"和"djangn"是相同的。关键在于数字精度的限制将成为遵循此方法的人的问题。必须在数据库端和Python端都处理好精度问题,因为两边都可能出现问题。 - Louis
如果您将字符数组保留为字符串并进行比较,则此方法将起作用。对于此操作(amour < love'097109111117114' < '108111118101',结果将为True - Mauro Baraldi
@Louis 如果我说错了请指出,但在这种情况下,它只取决于数据库。 在任何时候,Django都不会尝试验证查询结果是否“有效”。 不过,我同意另一半的观点,即在数据库端定义精度非常重要。 - Loïc
@MauroBaraldi 我完全不确定所有数据库都是这样工作的。 - Loïc
2
@Loïc 你是如何将字符串转换为数字的?你是在用Python做这个吧?正如你在回答中所说:“您可以使用模型的getter和setter将单词转换为其ASCII值,反之亦然。” 这些setter和getter是用Python编写的。我可以想象在Python中规避精度问题的方法,尽管你的答案留下了足够的余地让人们自取灭亡(例如,在浮点数中工作,然后在最后一刻转换为“Decimal”)。 - Louis

0
我的回答只适用于PostgreSQL,但这里可能有一个解决方案:
Django与PostgreSQL结合使用时,可以使用`django.contrib.postgres.fields`中的`CICharField`模型字段。该字段支持对大小写不敏感的字符串进行索引。存储的字符串仍将保留其正确的大小写,但比较操作将不区分大小写。
from django.contrib.postgres.field import CICharField

class Page(Model):
      book = ForeignKey(Book, on_delete=CASCADE)
      page = IntegerField()
      start = CICharField(max_length=350, db_index=True)
      end = CICharField(max_length=350, db_index=True)

这应该就能解决你的问题了,你可以使用gtelte过滤器,并且比较是不区分大小写的。如果你的数据库设置正确,它也应该能很好地处理Unicode。

t1 = "breast"
t2 = "beast"
t3 = "block"
page1 = Page.objects.create(start='beaver', end='brother')
page2 = Page.objects.create(start='boy', end='brother')
Page.objects.filter(start__lte=t1, end__gte=t1)  # <QuerySet [<Page: Page start=beaver, end=brother>, <Page: Page start=boy, end=brother>]>
Page.objects.filter(start__lte=t2, end__gte=t2)  # <QuerySet []>
Page.objects.filter(start__lte=t3, end__gte=t3)  # <QuerySet [<Page: Page start=beaver, end=brother>]>

t4 = "Ù"  # Between Ø and Ú
t5 = "Ü"  # Not between Ø and Ú
page3 = Page.objects.create(start='Ø', end='Ú')
Page.objects.filter(start__lte=t4, end__gte=t4) # <QuerySet [<Page: Page start=Ø, end=Ú>]>
Page.objects.filter(start__lte=t5, end__gte=t5) # <QuerySet []>

这个更改将生成一个迁移,安装CITextExtension()到数据库并修改列。你可能需要将该迁移文件分成两个迁移,第一个将安装CITextExtension,第二个将修改现有的列。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接