views:

160

answers:

3

I make use of generic views and I am attempting to query my MySQL db (utf8_bin collation) in a case insensitive manor to try to find all my song titles that start with a particular letter.

view.py

def tracks_by_title(request, starts_with):
    return object_list(
        request,
        queryset = Track.objects.filter(title__istartswith=starts_with),
        template_name = 'tlkmusic_base/titles_list.html',
        template_object_name = 'tracks',
        paginate_by = 25,
    )

and my

urls.py

urlpatterns = patterns('tlkmusic.apps.tlkmusic_base.views',
    (r'^titles/(?P<starts_with>\w)/$', tracks_by_title),
)

the query it produces according to the django debug toolbar is:

SELECT `tracks`.`id`, `tracks`.`url`, `tracks`.`artist`, `tracks`.`album`, `tracks`.`genre`, `tracks`.`year`, `tracks`.`title`, `tracks`.`comment`, `tracks`.`tracknumber`, `tracks`.`discnumber`, `tracks`.`bitrate`, `tracks`.`length`, `tracks`.`samplerate`, `tracks`.`filesize`, `tracks`.`createdate`, `tracks`.`modifydate` FROM `tracks` WHERE `tracks`.`title` LIKE a% LIMIT 1

specifically this line:

WHERE `tracks`.`title` LIKE a% LIMIT 1

Why is it not case-insensitive which is what I was expecting by using __istartswith?

I am using Django 1.1.1 on Ubuntu.

EDIT

Running SELECT * FROM tracks WHERE title LIKE 'a%' LIMIT 0 , 30 in phpmyadmin still returns case-sensitive results, changing my collation is something I want to avoid mostly because the database is maintained by Amarok and I don't know the results of changing the collation on it's end.

+2  A: 

MySQL does not support ILIKE.

By default MySQL's LIKE compares strings case-insensitively.

Edit:
Thanks to the OP for providing additional information about the collation.
The current collation, utf8_bin is case-sensitive.
In contrast, utf8_general_ci is case-insensitive.

It's probably easiest to modify collation.
Something like this:

ALTER TABLE `mydb`.`mytable` 
MODIFY COLUMN `song_title` VARCHAR(254) 
CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL;
Adam Bernier
Hmm, sure enough it doesn't support ILIKE but why does the above query only return a track title with a lowercase a?
TheLizardKing
I tested a raw query (not Django), which returns case-insensitive results. I'll look around for more info on what Django may or may not be doing behind the scenes.
Adam Bernier
I appreciate it. http://code.djangoproject.com/ticket/2170 is the only reference to my problem and I might be off target here.
TheLizardKing
The database is created by Amarok and gives the tables a collation of utf8_binary, not sure if that helps.
TheLizardKing
http://www.mail-archive.com/[email protected]/msg30704.html is also an interesting read.
TheLizardKing
Sigh, I know your above solution would probably fix my issue but I just want to see if I can do something from Django's end.
TheLizardKing
@TheLizardKing: does Amarok support exporting into different collations? That would seem to be where you should tackle the issue as downstream changes might likely be more difficult to implement and / or maintain.
Adam Bernier
I wish, I'd kill to use PostgreSQL but MySQL is the only DB it chooses to allow which is better than using it's own db concoction I should say.
TheLizardKing
+1  A: 

A solution, while not what I was hoping/expecting but still works is:

SELECT * FROM tracks WHERE title REGEXP BINARY '^(a|A)';

To use a REGEXP.

Which means changing my queryset string.

queryset = Track.objects.filter(title__regex=r'^(a|A)'),

Not optimal I am going to have to upper and lower the query string and then write an entirely new queryset for numbers and non-alphanumeric characters.

TheLizardKing
+1 for finding your own workaround. Best of luck :-)
Adam Bernier
Haha, I would of much rather of been made a fool of with a simple "oh, just use X"
TheLizardKing
A: 

God damnit me, I found another awkward way of doing it.

from django.db.models import Q

queryset = Track.objects.filter(Q(title__startswith=starts_with.upper) | Q(title__startswith=starts_with.lower)),
TheLizardKing