在PostgreSQL中选择N个匹配条件的随机行

Posted

技术标签:

【中文标题】在PostgreSQL中选择N个匹配条件的随机行【英文标题】:Select N random rows with matching conditions in PostgreSQL 【发布时间】:2021-11-12 13:35:53 【问题描述】:

我在 PostgreSQL 中有一个个人小表(10K 记录),我想从一个大(100M 记录)表中随机选择一个年龄+性别匹配项,并获取有关这些人的几个额外列。

有几个注意事项:

    我想要一个高效的解决方案,因为桌子有点大 虽然不太可能,但我不想从大表的记录中意外选择小表中的任何人。虽然没有替换的完整是理想的,但我可以将小桌子中的所有人从大桌子上移走。 大表可以为每个人提供多条记录,因此需要一个 DISTINCT。 获得 N 个随机匹配后,我必须将结果重新加入 mybigtable 以获得所需的其他列 在此数据库中,我有权创建 TEMP 表,但我无法将 CSV 中的数据加载到其中,也无法创建常规表。

我已经弄清楚(如下)如何低效地为一个人随机选择 N(在本例中为 3)条记录。

我真正想做的是能够概括这一点,因此它会为 mymatch 表中的所有人随机选择 10 条记录,匹配​​年龄+性别的值。我不太明白如何移动到这里。

DROP TABLE IF EXISTS mybigtable;  -- this is 100M
CREATE TEMPORARY TABLE mybigtable (ID varchar, eID varchar, age INT, gender VARCHAR);

INSERT INTO mybigtable VALUES 
    ('1', 'aaa', 84, 'F'),('2', 'aaa', 16, 'M'),('3', 'aaa', 23, 'F'),('4', 'aaa', 16, 'F'),('5', 'aaa', 94, 'F'),('6', 'aaa', 91, 'F'),('7', 'aaa', 18, 'M'),('8', 'aaa', 57, 'F'),('9', 'aaa', 84, 'F'),('10', 'aaa', 80, 'M'),('11', 'aaa', 16, 'M'),('12', 'aaa', 46, 'M'),('13', 'aaa', 84, 'F'),('14', 'aaa', 16, 'M'),('15', 'aaa', 23, 'F'),('16', 'aaa', 84, 'F'),('17', 'aaa', 30, 'M'),('18', 'aaa', 15, 'M'),('19', 'aaa', 16, 'M'),('20', 'aaa', 23, 'F'),('21', 'aaa', 84, 'F'),('22', 'aaa', 14, 'M'),('23', 'aaa', 84, 'F'),('24', 'aaa', 57, 'M'),('25', 'aaa', 89, 'M'),('1', 'bbb', 83, 'F'),('2', 'bbb', 19, 'M'),('3', 'bbb', 64, 'F'),('4', 'bbb', 92, 'M'),('5', 'bbb', 23, 'F'),('6', 'bbb', 62, 'M'),('7', 'bbb', 43, 'M'),('8', 'bbb', 16, 'M'),('9', 'bbb', 93, 'M'),('10', 'bbb', 45, 'M'),('11', 'bbb', 96, 'M'),('12', 'bbb', 68, 'M'),('13', 'bbb', 16, 'M'),('14', 'bbb', 97, 'F'),('15', 'bbb', 31, 'M'),('16', 'bbb', 23, 'F'),('17', 'bbb', 32, 'F'),('18', 'bbb', 18, 'F'),
    ('19', 'bbb', 23, 'F'),('20', 'bbb', 16, 'M'),('21', 'bbb', 35, 'M'),('22', 'bbb', 84, 'F'),('23', 'bbb', 48, 'F'),('24', 'bbb', 73, 'F'),('25', 'bbb', 46, 'F'),('26', 'bbb', 16, 'M'),('27', 'bbb', 39, 'M'),('28', 'bbb', 86, 'M'),('29', 'bbb', 78, 'F'),('30', 'bbb', 28, 'M'),('31', 'bbb', 32, 'F'),('32', 'bbb', 43, 'M'),('33', 'bbb', 64, 'F'),('34', 'bbb', 26, 'M'),('35', 'bbb', 81, 'M'),('36', 'bbb', 84, 'F'),('37', 'bbb', 23, 'F'),('38', 'bbb', 49, 'F'),('39', 'bbb', 66, 'F'),('40', 'bbb', 23, 'F'),('41', 'bbb', 23, 'F'),('42', 'bbb', 16, 'M'),('43', 'bbb', 92, 'M'),
    ('44', 'bbb', 16, 'M'),('45', 'bbb', 62, 'M'),('46', 'bbb', 16, 'M'),('47', 'bbb', 24, 'M'),('48', 'bbb', 16, 'M'),('49', 'bbb', 94, 'F'),('50', 'bbb', 58, 'F'),('1', 'ccc', 69, 'F'),('2', 'ccc', 97, 'M'),('3', 'ccc', 84, 'F'),('4', 'ccc', 78, 'M'),('5', 'ccc', 84, 'F'),('6', 'ccc', 54, 'M'),('7', 'ccc', 21, 'M'),('8', 'ccc', 23, 'F'),('9', 'ccc', 26, 'M'),('10', 'ccc', 84, 'M'),('11', 'ccc', 84, 'F'),('12', 'ccc', 69, 'M'),('13', 'ccc', 74, 'M'),('14', 'ccc', 83, 'F'),('15', 'ccc', 97, 'M'),('16', 'ccc', 55, 'M'),('17', 'ccc', 23, 'F'),('18', 'ccc', 59, 'F'),('19', 'ccc', 23, 'F'),('20', 'ccc', 68, 'F'),('21', 'ccc', 23, 'F'),('22', 'ccc', 84, 'F'),('23', 'ccc', 63, 'M'),('24', 'ccc', 88, 'M'),('25', 'ccc', 70, 'M');

DROP TABLE IF EXISTS mymatch;  -- this will be about 10000
CREATE TEMPORARY TABLE mymatch (ID varchar, eID varchar, age INT, gender VARCHAR);

INSERT INTO mymatch VALUES
    ('16', 'aaa', 84, 'F'),('8', 'bbb', 16, 'M'),('15', 'aaa', 23, 'F');

DROP TABLE IF EXISTS mynotin;
CREATE TEMPORARY TABLE mynotin (ID varchar, eID varchar, age INT, gender VARCHAR);

--Create a table that does not have the people of interest
INSERT INTO mynotin
    SELECT DISTINCT ID, eID, age, gender 
    FROM   mybigtable mbt 
    WHERE  NOT EXISTS 
        (SELECT  
        FROM   mymatch
        WHERE  mymatch.ID = mbt.ID AND mymatch.eID = mbt.eID);


--This is the SELECT statement to get 3 random rows.  Eventually this has to go to a table so I can join it to mybigtable and get additional columns of interest for the matched people.
SELECT id, eid, age, gender  
    FROM (
        SELECT 
            t.*, 
            row_number() OVER(partition by age, gender ORDER BY RANDOM()) rn  -- is there a more efficient method 
        FROM mynotin t
        WHERE age=84 AND gender='F') t  -- These are the conditions I want to change to the table mymatch
    WHERE rn <= 3;  --three for the example this will change to 10

DROP TABLE IF EXISTS mybigtable, mymatch, mynotin;

【问题讨论】:

不会:Select id, eid, age, gender from mynotin WHERE age=84 AND gender='F' order by random() limit 3 也能工作得更好吗? 我不确定它如何能够匹配 mymatch 中其余的 10K 值——这正是我所需要的 【参考方案1】:

这实际上比我想象的要容易......

这一点

FROM mynotin t
        WHERE age=84 AND gender='F') t

需要改成

FROM mynotin t
        JOIN mymatch mm ON t.age=mm.age AND t.gender=mm.gender) t

【讨论】:

以上是关于在PostgreSQL中选择N个匹配条件的随机行的主要内容,如果未能解决你的问题,请参考以下文章

选择 1 个具有复杂过滤的随机行

从Hive表中选择每列的随机行数

Phpmyadmin 显示数据库中的随机行数

读取大型 csv 文件、python、pandas 的随机行

shellshuf命令提取文件的随机行

在 spark scala 中为数据帧中的每个组采样不同数量的随机行