【问题标题】:Django db error: could not identify an equality operator for type json when trying to annotate a model with jsonfieldDjango db 错误:尝试使用 jsonfield 注释模型时无法识别 json 类型的相等运算符
【发布时间】:2013-10-07 17:11:06
【问题描述】:

我在 Django 1.5.4 和 PostgreSQL 9.3 中工作,将 django-jsonfield 用于 JSONField。

以下查询引发 db 错误(无法识别 json 类型的相等运算符):

ModelWithJsonField.objects.annotate(count=Count('field_to_count_by'))

field_to_count_by 不是 JSONField,普通的 int 字段。

有什么想法可以解决问题并仍然使用注释吗?

引擎盖后面有什么注释?

【问题讨论】:

    标签: python django orm django-models django-jsonfield


    【解决方案1】:

    我遇到了同样的问题,最后(今天)通过在 psql 控制台中以管理员身份运行它来实现一个假运算符:

    -- This creates a function named hashjson that transforms the
    -- json to texts and generates a hash
    CREATE OR REPLACE FUNCTION hashjson(            
        json          
    ) RETURNS INTEGER LANGUAGE SQL STRICT IMMUTABLE AS $$ 
        SELECT hashtext($1::text); 
    $$; 
    
    -- This creates a function named json_eq that checks equality (as text)
    CREATE OR REPLACE FUNCTION json_eq(            
        json, 
        json              
    ) RETURNS BOOLEAN LANGUAGE SQL STRICT IMMUTABLE AS $$ 
        SELECT bttextcmp($1::text, $2::text) = 0; 
    $$; 
    
    -- This creates an operator from the equality function
    CREATE OPERATOR = (            
        LEFTARG   = json, 
        RIGHTARG  = json, 
        PROCEDURE = json_eq 
    ); 
    
    -- Finaly, this defines a new default JSON operator family with the
    -- operators and functions we just defined.
    CREATE OPERATOR CLASS json_ops
       DEFAULT FOR TYPE json USING hash AS
       OPERATOR 1  =,
       FUNCTION 1  hashjson(json);
    

    (大量灵感来自 this 线程)

    我还在 django-jsonfield GitHub issue 中引用了您的问题。

    请注意:

    • 我对这将产生的影响的想法非常有限。也许这不是一个好主意。这些实现是幼稚的,但它们应该足够了。也可能不是。
    • 特别是,相等运算符检查文本相等而不是语义 json 相等。但就 django-jsonField 而言,我认为我们真的需要结果正确的可能性很小(可能是 SELECT FALSE 甚至可以解决问题)。

    【讨论】:

    • 我在我的 postgres 控制台中创建了上述函数和运算符类并重新连接了我的 django 应用程序,但我仍然在任何使用 Count() 进行 django 查询的地方看到相同的错误,例如:return dict(self.filter(**kwargs).annotate(terminated_count=Count('results')) .values_list('uuid', 'terminated_count')) 其他我需要照顾吗?创建这些函数和运算符后,是否需要更改 django 查询?
    • 嗯,以上步骤就是我避免问题所需要的。我不知道其他任何事情。你确定你在 righe cluster/db 上创建了这个函数吗?您确定该函数已创建并且没有错误吗?我不知道...
    • 我的错。出于某种原因,我对创建它的数据库视而不见。奇迹般有效。谢谢。
    【解决方案2】:

    我遇到了同样的问题,然后我尝试了 Joachim Jablon 的代码,虽然它看起来运行良好,但仍然存在问题。我就说到点子上了,最长的版本is on my blog

    • SELECT '{"a":1,"b":2}'::json = '{"b":2,"a":1}'::json 返回了false,因为它是基于字符串表示的。​​
    • 不允许对字段进行排序,因为运算符类是hash 而不是btree

    然后,我在 PL/V8 中创建了一个 json_cmp() 函数,可用于为 btree 所需的运算符提供动力。

    这是完整的 SQL 脚本

    CREATE OR REPLACE FUNCTION json_cmp(left json, right json)
    RETURNS integer AS $$
        function cleverType(obj) {
            var type = typeof obj;
    
            if (type === 'object') {
                if (obj === null) {
                    type = 'null';
                } else if (obj instanceof Array) {
                    type = 'array';
                }
            }
    
            return type;
        }
    
        function cmp(left, right) {
            var leftType = cleverType(left),
                rightType = cleverType(right),
                i,
                buf,
                leftKeys,
                rightKeys,
                output = 0;
    
            if (leftType !== rightType) {
                output = leftType.localeCompare(rightType);
            } else if (leftType === 'number'
                    || leftType === 'boolean'
                    || leftType === 'string') {
                if (left < right) {
                    output = -1;
                } else if (left > right) {
                    output = 1;
                } else {
                    output = 0;
                }
            } else if (leftType === 'array') {
                if (left.length !== right.length) {
                    output = cmp(left.length, right.length);
                } else {
                    for (i = 0; i < left.length; i += 1) {
                        buf = cmp(left[i], right[i]);
    
                        if (buf !== 0) {
                            output = buf;
                            break;
                        }
                    }
                }
            } else if (leftType === 'object') {
                leftKeys = Object.keys(left);
                rightKeys = Object.keys(right);
    
                if (leftKeys.length !== rightKeys.length) {
                    leftKeys.sort();
                    rightKeys.sort();
                    buf = cmp(leftKeys, rightKeys);
                } else {
                    buf = cmp(leftKeys.length, rightKeys.length);
                }
    
                if (buf !== 0) {
                    output = buf;
                } else {
                    for (i = 0; i < leftKeys.length; i += 1) {
                        buf = cmp(left[leftKeys[i]], right[leftKeys[i]]);
    
                        if (buf !== 0) {
                            output = buf;
                            break;
                        }
                    }
                }
            }
    
            return output;
        }
    
        return cmp(left, right);
    $$ LANGUAGE plv8 IMMUTABLE STRICT;
    
    CREATE OR REPLACE FUNCTION json_eq(json, json)
    RETURNS BOOLEAN LANGUAGE SQL STRICT IMMUTABLE AS $$
        SELECT json_cmp($1, $2) = 0;
    $$;
    
    CREATE OR REPLACE FUNCTION json_lt(json, json)
    RETURNS BOOLEAN LANGUAGE SQL STRICT IMMUTABLE AS $$
        SELECT json_cmp($1, $2) < 0;
    $$;
    
    CREATE OR REPLACE FUNCTION json_lte(json, json)
    RETURNS BOOLEAN LANGUAGE SQL STRICT IMMUTABLE AS $$
        SELECT json_cmp($1, $2) <= 0;
    $$;
    
    CREATE OR REPLACE FUNCTION json_gt(json, json)
    RETURNS BOOLEAN LANGUAGE SQL STRICT IMMUTABLE AS $$
        SELECT json_cmp($1, $2) > 0;
    $$;
    
    CREATE OR REPLACE FUNCTION json_gte(json, json)
    RETURNS BOOLEAN LANGUAGE SQL STRICT IMMUTABLE AS $$
        SELECT json_cmp($1, $2) >= 0;
    $$;
    
    CREATE OPERATOR =  (LEFTARG = json, RIGHTARG = json, PROCEDURE = json_eq);
    CREATE OPERATOR <  (LEFTARG = json, RIGHTARG = json, PROCEDURE = json_lt);
    CREATE OPERATOR <= (LEFTARG = json, RIGHTARG = json, PROCEDURE = json_lte);
    CREATE OPERATOR >  (LEFTARG = json, RIGHTARG = json, PROCEDURE = json_gt);
    CREATE OPERATOR >= (LEFTARG = json, RIGHTARG = json, PROCEDURE = json_gte);
    
    CREATE OPERATOR CLASS json_ops
       DEFAULT FOR TYPE json USING btree AS
       OPERATOR 1 <,
       OPERATOR 2 <=,
       OPERATOR 3 =,
       OPERATOR 4 >=,
       OPERATOR 5 >,
       FUNCTION 1 json_cmp(json, json);
    

    当然,这往往比简单的字符串比较慢很多,但具有产生更可靠结果的优势。

    请注意,如果您使用 South 进行迁移,您可以创建一个空迁移并从 forwards() 方法执行 SQL。这将在您迁移应用时自动安装这些功能。

    【讨论】:

      【解决方案3】:

      我的解决方案使用 PL/Python,它解析并重新转储排序键的 json,然后生成结果的 FNV1a 哈希:https://github.com/ifad/chronomodel/blob/master/sql/json_ops.sql

      我没有使用 hashtext(),因为它仅供内部使用:http://www.postgresql.org/message-id/24463.1329854466@sss.pgh.pa.us

      这不是灵丹妙药,只是粗略的破解。真正的解决方案是等待 Postgres 的全面支持。

      【讨论】:

        猜你喜欢
        • 2017-09-28
        • 2014-04-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-08-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多