有条件地替换jsonb列中每行的单个值

时间:2018-12-16 21:38:50

标签: sql postgresql replace sql-update jsonb

我需要一种更有效的方法来更新Postgres 9.5中单个表的行。 我目前正在使用pg_dump进行此操作,并在Linux OS环境中搜索和替换操作后使用更新后的值重新导入。

public class TestClass : IEquatable<TestClass> { public int TestValue01 { get; private set; } public int TestValue02 { get; private set; } public TestClass(int testValue01, int testValue02) { TestValue01 = testValue01; TestValue02 = testValue02; } public override bool Equals(object obj) { return Equals(obj as TestClass); } public bool Equals(TestClass other) { return other != null && TestValue01 == other.TestValue01 && TestValue02 == other.TestValue02; } public static bool operator ==(TestClass test1, TestClass test2) { return EqualityComparer<TestClass>.Default.Equals(test1, test2); } public static bool operator !=(TestClass test1, TestClass test2) { return !(test1 == test2); } } 具有300000行和2列:table_aid bigintjson_col jsonb大约有30个键:“ C1”至“ C30”,如本例所示:

json_col
  

要求是从C1到C30的所有键进行大规模搜索,然后查找   仅将其替换为“柏林”值,然后替换为“马德里”,并且仅当   马德里不再重复。即ID:1(带有键C3)和ID:2(带有C2)。 id:3   将被跳过,因为C30已经存在该值

它必须在PostgreSQL 9.5中的一个SQL命令中一次,并考虑Table_A id,json_col 1 {"C1":"Paris","C2":"London","C3":"Berlin","C4":"Tokyo", ... "C30":"Dallas"} 2 {"C1":"Dublin","C2":"Berlin","C3":"Kiev","C4":"Tokyo", ... "C30":"Phoenix"} 3 {"C1":"Paris","C2":"London","C3":"Berlin","C4":"Ankara", ... "C30":"Madrid"} ... 列中的所有键。

3 个答案:

答案 0 :(得分:1)

最快,最简单的方法是将列修改为文本:

c1 = Cat()
c1.clone_pop  # 0
c2 = c1.clone()
c1.clone_pop, c2.clone_pop  #  (1, 1)
c3 = c2.clone()
c1.clone_pop, c2.clone_pop, c3.clone_pop  # (2, 2, 2)

这是一个实际的选择。上面的查询不是对象属性的修改,而是查找和替换操作(如在文本编辑器中)。第二种选择更为复杂,而且肯定要昂贵得多。即使使用快速的Javascript引擎(下面的示例),更正式的解决方案也会慢很多倍。

您可以尝试Postgres Javascript

update table_a
set json_col = replace(json_col::text, '"Berlin"', '"Madrid"')::jsonb
where json_col::text like '%"Berlin"%'
and json_col::text not like '%"Madrid"%'

答案 1 :(得分:1)

好吧,我已经测试了所有方法,可以说你做得很好 这对我很有帮助。让我与您分享我的反馈。

方法1 被克林认为。完美工作,并且完全可以,除非 键像值一样命名,然后键和值都将被替换。 即:“柏林”:“柏林”变为“马德里”:“马德里”

具有plv8扩展名的

方法2 无效,因为我缺少controll文件 我必须安装它,而我只是跳过了这种方法,所以我没有 有关此方法的反馈。 我得到的错误是这样的: 错误:无法打开扩展控制文件 “ /usr/pgsql-9.5/share/extension/plv8.control”:没有这样的文件或目录

方法3 与具有 jsonb_replace_value函数方法 2相似 完美工作,替换包含特定值的行,无论 的关键。并添加条件

WHERE json_col <> jsonb_replace_value(json_col, '"Berlin"', '"Madrid"')

将避免空更新,并且将跳过不需要更新的行 像这样的

{“柏林”:“柏林”}变成{“柏林”:“马德里”},即键没有被触摸,仅是值

方法4 稍微复杂一点,它使用方法3和索引 它的工作原理非常棒,而且速度超快。
并且NOT EXISTS半反连接确实被迫再次使用Index。
我对它的执行速度感到震惊!

但是我发现,如果json字符串看起来像这样,所有这些方法都可以使用: {“核心价值”} 例如,如果我要更新一个json对象的值,它将不会更新 像这样的东西:
{"C30":{"id":10044,"value":"Berlin","created_by":"John Doe"}}

非常感谢你们。 @klin和@ erwin-brandstetter。 这帮助我学习了一些新知识!

答案 2 :(得分:0)

造成这种困难的原因是,您正在寻找具有感兴趣的的未知。对Postgres基础结构进行了优化,以查找(或数组值)。

可能是由于表设计欠佳所致。 jsonb列中的许多顶级对象可能会被 array 替换,从而完全放弃无关的键名。 (或者可能是另一个用于键名的数组。)或者,理想情况下,首先要使用完整的标准化DB模式。

尽管如此,这是一个概念证明,如何使用 stock Postgres 9.5或更高版本快速而干净无论如何。

附加困难1:未知是否可以重复值。
附加困难2:价值频率也未知。
额外的困难3:只有找到的 first 值将被替换,并且仅当目标值不存在时才替换。可以通过基于集合的操作来实现这一点,但是很麻烦。我改写了一个plpgsql函数:

CREATE OR REPLACE FUNCTION jsonb_replace_value(_j jsonb, _old jsonb, _new jsonb)
   RETURNS jsonb AS
$func$
DECLARE
   _key text;
   _val jsonb;
BEGIN
   FOR _key, _val IN
      SELECT * FROM jsonb_each(_j)
   LOOP
      IF _val = _old THEN
         RETURN jsonb_set(_j, ARRAY[_key], _new);  -- update 1st key
      END IF;
   END LOOP;

   RETURN _j;  -- nothing found, return original
END
$func$ LANGUAGE plpgsql IMMUTABLE;

COMMENT ON FUNCTION jsonb_replace_value(jsonb, jsonb, jsonb) IS '
Replace the first occurrence of _old value with _new.
Call:
    SELECT jsonb_replace_value('{"C1":"Paris","C3":"Berlin","C4":"Berlin"}', '"Berlin"', '"Madrid"')';

可以增强以选择性地替换所有出现的事件,等等,但这不在此问题的范围之内。

现在这很简单:

UPDATE table_a
SET    json_col = jsonb_replace_value(json_col, '"Berlin"', '"Madrid"'); -- note jsonb literal syntax!

如果 所有 行需要更新,我们可以在此处停止。不会更快。 (除非可能有demonstrated by @klin之类的替代方法。)
如果所有行中的 很大百分比 需要更新,请添加一个WHERE条件以避免空更新:

...
WHERE  json_col <> jsonb_replace_value(json_col, '"Berlin"', '"Madrid"');

请参阅:

通常,实际上只有 几行 需要更新。然后使用上述查询遍历所有行是昂贵的。我们需要索引支持以使其快速运行。这种情况并不容易。我建议基于IMMUTABLE函数的表达式索引,该函数提取值数组:

CREATE OR REPLACE FUNCTION jsonb_object_val_arr(jsonb)
   RETURNS text[] LANGUAGE sql IMMUTABLE AS
'SELECT ARRAY (SELECT value FROM jsonb_each_text($1))';

COMMENT ON FUNCTION jsonb_object_val_arr(jsonb) IS '
   Generates text array of values in outermost jsonb object.
   Of limited use if there can be nested objects.';

CREATE INDEX table_a_val_arr_idx ON table_a USING gin (jsonb_object_val_arr(json_col));

相关,带有更多说明:

使用此索引进行查询:

UPDATE table_a a
SET    json_col = jsonb_replace_value(a.json_col, '"Berlin"', '"Madrid"')
WHERE  jsonb_object_val_arr(json_col) @> '{Berlin}' -- has Berlin, possibly > 1x ..
-- AND    NOT jsonb_object_val_arr(json_col) @> '{Madrid}'
AND    NOT EXISTS (                                         -- .. but not Madrid
   SELECT FROM table_a b
   WHERE  jsonb_object_val_arr(json_col) @> '{Madrid}'  -- note array literal syntax
   AND    b.id = a.id
   );

NOT EXISTS半反联接是经过精心设计的,以便第二次使用索引。

如果行中带有“柏林” “马德里”的行很少,则注释更简单的替代方法会更快-这样查询计划中的过滤步骤将更便宜。

应该非常快

db <>小提琴here 用于Postgres 9.5,演示了全部操作。