【问题标题】:PostgreSQL shuffle column valuesPostgreSQL 洗牌列值
【发布时间】:2015-11-05 21:54:50
【问题描述】:

在超过 100k 行的表中,如何有效地打乱特定列的值?

表定义:

CREATE TABLE person
(
  id integer NOT NULL,
  first_name character varying,
  last_name character varying,
 CONSTRAINT person_pkey PRIMARY KEY (id)
)

为了匿名数据,我必须就地对“first_name”列的值进行洗牌(我不允许创建新表)。

我的尝试:

with
first_names as (
select row_number() over (order by random()),
       first_name as new_first_name
from person
),
ids as (
select row_number() over (order by random()), 
       id as ref_id
from person
)
update person
set first_name = new_first_name
from first_names, ids
where id = ref_id;

需要几个小时才能完成。

有没有有效的方法?

【问题讨论】:

  • 您的两个 CTE 并没有真正随机化数据。你只是生成随机的“行号”,但你不使用它们。如果您省略 row_number() 调用,它不会有任何不同。您还在两个 CTE 和 person 表之间进行交叉连接,这意味着您的语句会生成 100.000 * 100.000 * 100.000 行的中间结果。
  • stackoverflow.com/a/3100232/3574819 可能有助于加快更新速度

标签: sql performance postgresql shuffle


【解决方案1】:

postgres 的问题是每次更新均值 delete + insert

  • 您可以使用 SELECT 而不是 UPDATE 来检查分析,看看 CTE 的性能如何
  • 您可以关闭索引以加快更新速度
  • 但是当需要更新所有行时,我使用的最佳解决方案是再次创建表

.

CREATE TABLE new_table AS 
     SELECT * ....


DROP oldtable;

Rename new_table to old_table

CREATE index and constrains

对不起,这不是你的选择:(

编辑:读完a_horse_with_no_name

看起来你需要

with
first_names as (
    select row_number() over (order by random()) rn,
           first_name as new_first_name
    from person
),
ids as (
    select row_number() over (order by random()) rn, 
           id as ref_id
    from person
)
update person
set first_name = new_first_name
from first_names
join ids
  on first_names.rn = ids.rn
where id = ref_id;

如果您提供ANALYZE / EXPLAIN 结果,性能问题会更好。

【讨论】:

  • 这是对 OP 意图的正确表述。性能大概是一样的。
  • 这在我的 500.000 行测试表上实际上速度惊人:explain.depesz.com/s/Dwh
  • @a_horse_with_no_name 在我的电脑中是的。 OP 查询我在 10 分钟后停止它,您查询在 90 秒内运行 150 秒。所以我的看起来快 40%。现在必须找出为什么我的电脑这么慢。完成我的沙漠并开始检查那个内存。
  • @Juan Carlos Oropeza 非常感谢!您在 EDIT 中发布的查询在我的桌子上运行 6 秒!
  • 好还是不知道为什么我的电脑需要 100 秒,我尝试了 work_mem,但让它变得更糟。只是好奇在您的表中使用 a_horse 查询需要多长时间?
【解决方案2】:

这需要 5 秒才能在我的笔记本电脑上随机播放 500.000 行:

with names as (
  select id, first_name, last_name,
         lead(first_name) over w as first_1,
         lag(first_name) over w as first_2
  from person
  window w as (order by random())
)
update person
  set first_name = coalesce(first_1, first_2)
from names 
where person.id = names.id;

这个想法是在随机排序数据后选择“下一个”名称。这与选择一个随机名称一样好。

有可能不是所有的名字都被洗牌,但如果你运行它两到三遍,这应该就足够了。

这是 SQLFiddle 上的测试设置:http://sqlfiddle.com/#!15/15713/1

右侧的查询检查“随机化”后是否有任何名字保持不变

【讨论】:

  • 不幸的是,第一种方法可能会多次选择相同的值并跳过其他值。我不确定这是否是 OP 的意图。
  • 你能告诉我如何创建一个 500k 样本表吗?
  • @GordonLinoff:我用包含原始 id 的名称尝试了几次,只有一次运行时没有更改一个名称。我认为桌子越大,这就越有可能奏效。带有“随机偏移量”的那个实际上并不能很好地处理少量的行。
  • @a_horse_with_no_name 。 . .不是名字没有改变。就是这个每次都找一个随机的名字。因此,原始数据中的某些名称可能出现 2、3 或 4 次。其他人可能永远不会被选中。我的印象(尽管 OP 对此肯定不清楚)的意图是原始名称的排列(每个名称只使用一次)而不是独立的随机样本。
  • lead 不会为随机列表的第一个值返回 null 吗?这意味着其中一个名字会丢失
猜你喜欢
  • 1970-01-01
  • 2015-01-23
  • 1970-01-01
  • 2018-01-30
  • 1970-01-01
  • 2021-06-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多