我需要一种更有效的方法来更新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_a
和id bigint
。
json_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"}
...
列中的所有键。
答案 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,演示了全部操作。