在Postgres中描述SELECT TOP ...
查询的大多数资源都说您应该使用LIMIT
,如果需要通过某种排序选择顶部元素,可能使用ORDER BY
子句。 / p>
如果你需要从递归查询中选择前N个元素,那么你做什么,没有排序,并且有可能在没有递归的情况下查询返回少于N行(以便{{1} }必须确保结果集至少 N行,而TOP
可以允许更少的行)?
我的具体用例是对dynamic SQL pattern for selecting a random subsample of a table的修改。
以下是我修改的link to the sql source。最简单的方法是查看那里定义的最终函数LIMIT
。它紧跟上述链接,但在输入表和输出结果集中已经被修改为多态,并且正确地说明了只返回输入表中已存在的列的需要(还有另一个动态SQL) hack从最终结果集中排除临时_random_select
结果。)
这是一个眼睛,但这是我对可重复的例子最接近的事情。如果您使用row_number
并尝试从大于4500行的表中获取大约4500行的内容,则会开始看到较小概率的较小结果集,并且只会随着您增加所需样本的大小而变得更糟(因为当你想要的样本变大时,重复的出现会变得更糟。)
请注意,在我的修改中,我没有使用此链接中的_random_select
技巧,如果某个索引列中存在间隙,则意味着过度采样以抵消采样效率低下。该部分与此问题无关,在我的情况下,我使用_gaps
来确保存在没有可能间隙的整数列。
CTE是递归的,以确保如果CTE的第一个非递归部分没有给你足够的行(因为row_number
删除了重复项),那么它将返回另一个对CTE进行一轮递归调用,并继续研究更多结果,直到你得到足够的结果。
在链接示例中,使用UNION
,但我发现这不起作用。该方法返回的结果较少,因为LIMIT
只是最多N行保证。
如何获得最少 N行保证?选择LIMIT
N行似乎是这样做的自然方式(因此递归CTE必须保持一直持续直到它获得足够的行以满足TOP
条件),但这不可用于Postgres的。
答案 0 :(得分:3)
这对于评论来说太长了,但是对我现有查询的内容有所了解。从documentation on recursive query evaluation开始,递归查询将采用的步骤为:
评估非递归术语。对于UNION(但不是UNION ALL),丢弃重复的行。在递归查询的结果中包括所有剩余行,并将它们放在临时工作表中。
只要工作台不为空,请重复以下步骤:
一个。评估递归项,用工作表的当前内容替换递归自引用。对于UNION(但不是UNION ALL),丢弃重复任何先前结果行的重复行和行。包括递归查询结果中的所有剩余行,并将它们放在临时中间表中。
湾用中间表的内容替换工作表的内容,然后清空中间表。
所以我在评论中的预感(在尝试使用UNION ALL
之后)主要是在正确的轨道上。
正如文档所述,这实际上只是一种迭代,它重用了以前的非递归结果部分来代替在其中使用的递归名称。递归部分。
所以它更像是一个不断缩小的过程,其中的工作表"用来代替递归名称的只包括最近一次递归回合中特定的结果子集,其中不是以前结果的重复。
这是一个例子。假设我们在表中有5000行,我们想要对3000个唯一行进行采样,一次执行1000个(可能不是唯一的)样本的递归样本。
我们做第一批1000,删除重复,所以我们最终得到大约818个非欺骗based on the binomial distribution这些大数字(N = 5000,m = 1000,k = 1,重新排列条款以避免溢出)。
这些818成为工作表,这个结果集作为我们下一遍的递归术语。我们绘制另一组约818个唯一行,但在与工作表中的原始818进行比较时,必须删除重复项(UNION
)。 818的两个不同随机抽取将具有显着重叠(平均约150个),因此所有这些都被丢弃,并且新唯一行仍然成为 new 工作表。
所以你在第一次抽奖时添加大约818个独特样本,然后工作台缩小,你在第二次抽奖时大约650,工作台收缩,......你继续这样做直到您达到所需的总样本数量(在这种情况下为3000)或,工作台最终为空。
一旦工作表足够小,其中的所有内容都很有可能在下一次1000次抽取中被复制,此时工作表变空并且递归终止。
对于绘制3000个样本,您可以在足够的时间内更新此工作表。但是当你从3000更接近桌子的总大小5000时,概率很快就会缩小到接近零。
因此,与使用较小结果集进行短路的优化器问题不同,它实际上是特定方式的问题"递归"在Postgres中实现 - 它实际上并不是递归,而是对当前工作表和最新递归查询结果集之间的集合差异进行操作的简单迭代。对于这样的随机抽样,每次迭代时该工作表会变得非常快,直到由于选择重复的可能性很高,它很可能是空的。
答案 1 :(得分:1)
您的评估非常重要。 my referenced answer中的递归查询仅比原始简单查询更灵活。它仍然需要ID空间中相对较少的间隙,并且样本大小远小于表格大小,以便可靠。
虽然我们在简单查询中需要一个舒适的剩余(“限制+缓冲”)来覆盖最糟糕的错失和重复的情况,但我们可以使用通常足够的较小盈余 - 因为我们有一个安全网递归查询,如果我们在第一遍中达不到限制,将填写。
无论哪种方式,该技术都旨在快速从大表中获得一个小的随机选择。
毫无意义 对于太多间隙或(您的焦点)样本大小太接近总表的情况 size - 这样递归项可能会在达到限制之前运行干。对于这种情况,一个普通的老人:
SELECT * -- or DISTINCT * to fold duplicates like UNION does
FROM TABLE
ORDER BY random()
LIMIT n;
..效率更高:无论如何你都要阅读大部分的表格。
答案 2 :(得分:0)
可以使用Set Returning Functions(SRF)生成已知行数。此外,OUTER
加入保证,联接的一方将完全返回。
在这种情况下,我假设generate_series(1, 100)
之间的FULL JOIN
(如果您的目标是至少 100行)应该这样做。事实上,LEFT
联接也可以做到这一点,但它也可以过滤掉实际需要的额外行,因此我会选择FULL
。
P.S。如果您可以展示您的代码,那么提供一些示例会更容易。