2012年4月6日金曜日

Django+MySQL+Cast as SIGNED INTEGERでTruncated incorrect INTEGER valueでUnicodeEncodeErrorなところから逃げ延びる

Django+MySQLで、unicodeの全角文字と数字が混じった列のdistinctな一覧を
【数字の昇順】→「文字順」で取得したい。
単純に文字順にしてしまうのは 1,10,11,2,3,4 になるのでだめなパターン。

QuerySet APIの distinct はPostgreSQLでしかサポートされていないので、
DBのViewを書いた。

create view app_view1 as
select distinct col1,
    case when length(col1) = 0 then 0
                when cast(col1 as UNSIGNED INTEGER) > 0 then cast(col1 as UNSIGNED INTEGER)
                else 2147483647 end as sort
from table1

ちなみに本題とはあまり関係ないけどModelはこんな感じ。
class View1(models.Model):
    col1 = models.CharField(max_length=10,blank=True,primary_key=True)
    sort = models.IntegerField()
    class Meta:
        app_label="app"
        ordering=["sort","col1"]
        managed=False
文字はsortを十分に大きくしておいた上でsort昇順→col1昇順にすればいいはず。

MySQL Workbenchからだとゴキゲンに動作する。

実際に実行する。タイトルのような文字列を叫んで憤死する。

原因としては、実際にはMySQL内で実行した際にcastで「Truncated incorrect INTEGER value:'【文字列】'」というwarningが出ており、
それをMySQLdbモジュールが拾おうとしてしまって、【文字列】部分がUnicodeになっているせいでUnicodeEncodeErrorになるという
余計な事すんなよというもの。

何とかする方法を考える。
・MySQLdbをいじる(UnicodeEncodeErrorを吐かないようにする)→できればやりたくない
・Djangoをいじる(warningを拾わないようにする)→できればやりたくない
・継承で何とかする→djangoのdb backendを作れば何とか出来るんだろうができればやりたくない
・MySQLでWarningを出さないようにできないか→MySQL Suppress Warningとかでググる。死屍累々。
・あきらめてPython上で並べ替えをする→今回はいいがこんな感じので大量のデータに対してやろうとしたらどうするよ

SQLに立ち戻る。
要は数字だけ以外の場合に、CASTさえしなければよい。

MySQLにはREGEXPがあった。
create view app_view1 as
select distinct col1,
    case when length(col1) = 0 then 0
                when col1 REGEXP '^[[:digit:]]+$' then cast(col1 as UNSIGNED INTEGER)
                else 2147483647 end as sort
from table1

何とか逃げ延びた。

1 件のコメント:

  1. ^と$が入っていなかったので修正。
    それがないと意味がない…。

    返信削除