我在使用随机数和临时表时遇到问题。我有一个表,我想从中选择一些随机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
,但是,它会导致不完整的集合,但会重复。你知道我怎么能解决这个问题吗?
答案 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