SQL 中的随机数和临时表

Random numbers and temp tables in SQL

我在将随机数与临时 tables 一起使用时遇到问题。我有一个 table,我想从中 select 一些随机的 user_id,然后在第二个 table select 中,但没有重复第一个。下面是一个简单的例子(它是简化的,所以请不要首先简单地写 select user_ids):

drop table if exists test; 

create table test (
user_id int,
g int);

insert into test values (1, 1);
insert into test values (2, 1);
insert into test values (3, 1);
insert into test values (4, 1);
insert into test values (5, 1);
insert into test values (6, 1);

with temp as (
select t.user_id
from (select tt.*, row_number() over (partition by tt.g order by randomint(100)) as seqnum
      from test tt) t 
where t.seqnum <= 2
)
select user_id from temp union all 
select user_id from test where user_id not in (select user_id from temp)

理想情况下,查询的结果应该 return 所有 user_id,但是,它会导致不完整的集合但有重复。你知道我怎么解决这个问题吗?

有两种不同的方法来处理 CTE。一种方法将 CTE 存储在临时 table 中,因此无论引用数量如何,代码只执行一次。

第二种方法是将CTE代码合并到查询中,所以代码可能运行不止一次。

据我所知,ANSI 标准没有指定使用哪种方法,不同的数据库使用不同的方法——有些甚至使用优化器在两者之​​间进行选择。

Vertica 似乎使用第二种方法(这是根据您的结果推测的,不是我对 Vertica 肯定知道的东西)。因此,将重新计算对 temp 的第二个引用——生成您看到的结果。

你能做什么?

一件事是使用 "random" 数字生成器,它将 return 相同的值。老实说,由于并行化和计时,这可能不适用于 Vertica 中更大的 table。但是,提供种子可能会有所帮助。

类似的替代方法是使用类似 "row_number * 17 - 39 mod 101" 的方法。如果 row_number() 值使用 stable 排序,那么每次都会 return 相同的行。

另一种方法是将结果存储在临时 table。

最后,可能有一个编译器选项指示 Vertica 实现 CTE。

就我个人而言,我会使用第二种方法,因为我知道它适用于多个数据库,但其他方法可能适用于您的特定情况。

编辑:

with temp as (
      select t.*
      from (select t.user_id
                   row_number() over (partition by tt.g order by  mod(71 * seqnum - 31, 101), user_id) as psuedo
            from (select tt.*,
                         row_number() over (partition by tt.g order by user_id) as seqnum
                  from test tt
                 ) t 
           ) t
      where t.pseudo <= 2
     )

row_number() 是 stable -- 它应该在每次 运行 时产生相同的值。算术将其转换为伪随机数,然后将其转换为另一个序列。这种方法通常就足够了。您可以调整质数以更改值。

这是某些后端 CTE 的问题(例如 MS SQL 服务器也有同样的问题)。我不使用 Vertica,如果它支持 temp tables,那么一个简单且不神秘的解决方案是 select 将初始值 table 转换为 temp table 而不是 CTE。使用 MS SQL 服务器的示例如下所示:

CREATE TABLE #test (user_id INT, g INT);

INSERT INTO #test VALUES(1, 1);
INSERT INTO #test VALUES(2, 1);
INSERT INTO #test VALUES(3, 1);
INSERT INTO #test VALUES(4, 1);
INSERT INTO #test VALUES(5, 1);
INSERT INTO #test VALUES(6, 1);

SELECT TOP(2) * INTO #temp FROM #test ORDER BY NEWID();

SELECT user_id FROM #temp
UNION ALL
SELECT user_id FROM #test WHERE user_id NOT IN(SELECT user_id FROM #temp);

DROP TABLE #test;
DROP TABLE #temp;

您可以使用提示强制 Vertica 实现 WITH 子句。
WITH /*+ENABLE_WITH_CLAUSE_MATERIALIZATION*/ with-query...

有关更多信息,包括如何在会话级别而不是每个带有提示的查询进行设置,请参阅 https://my.vertica.com/docs/9.0.x/HTML/index.htm#Authoring/AnalyzingData/Queries/Subqueries/WithClauseMaterialization.htm