SQL中的随机数和临时表

时间:2018-02-08 13:07:44

标签: sql random vertica

我在使用随机数和临时表时遇到问题。我有一个表,我想从中选择一些随机user_id,然后在第二个表中选择更多,但没有从第一个重复。下面是一个简单的例子(它是简化的,所以请不要写,只是首先选择user_id):

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)

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

3 个答案:

答案 0 :(得分:3)

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

第二种方法是将CTE代码合并到查询中,因此代码可能会运行多次。

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

Vertica似乎使用第二种方法(这是基于您的结果的推测,而不是我对Vertica肯定知道的事情)。结果,重新计算了对temp的第二次引用 - 导致您看到的结果。

你能做什么?

有一件事是使用“随机”数字生成器,它将返回相同的值。说实话,由于并行化和计时,这可能不适用于Vertica中较大的表。但是,提供种子可能有所帮助。

类似的替代方案是使用类似“row_number * 17 - 39 mod 101”的内容。如果row_number()值使用稳定排序,则每次都会返回相同的行。

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

最后,可能有一个编译器选项指示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()是稳定的 - 它应该在每次运行时生成相同的值。算术将其转换为伪随机数,然后将其转换为另一个序列。这种方法通常足够好。您可以调整素数以更改值。

答案 1 :(得分:1)

某些后端的CTE存在问题(例如,MS SQL服务器也有同样的问题)。我不使用Vertica,如果它支持临时表,那么一个简单且非神秘的解决方案是将初始值选择到临时表而不是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;

答案 2 :(得分:0)

您可以使用提示强制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