{"Entry":{"collection":"plan","key":"unique","name":"Unique","aliases":["T_Unique","Unique"],"metadata":{"aliases":["T_Unique","Unique"],"category":"Combination","content_hash":"7b7c186b69e0bddde136ba6b8aa1564bae380a0e2032974bc974f63554d59982","derived_content_hash":"4e3bd07279750a7954341c4078346277d1cd606739085a53830b5ca3ba2ec67d","imported_at":"2026-09-30T00:40:44.068783+08:00","name":"Unique","name_zh":"Unique","slug":"unique","summary":"Removes adjacent duplicates from a sorted input stream."}},"Definition":{"Collection":"plan","Key":"unique","SourceDatabase":"center","Version":"18","SourceTable":"plan_node","SourceKey":"unique","SourceRevision":"555610c24d53e4316da5b7d3fc25c279d96856d5e0e23ee308c328c5fa881d9f","Facts":{"comparison_data":{"initializer":"ExecInitUnique","memory_mechanism":"unclassified","node_tag":"T_Unique","parallel_callbacks":[],"partial_modes":[],"strategies":[],"text_names":["Unique"]},"comparison_hash":"8fa24b0c3a61705b0b07250c27c004e5277482cfe2af4c65cce711d7ccac63f3","description":["Removes adjacent duplicates from a sorted input stream."],"description_zh":"删除有序输入流中相邻的重复行。","description_zh_html":"\u003cp\u003e删除有序输入流中相邻的重复行。\u003c/p\u003e\u003cp\u003e核心节点标识：\u003ccode\u003eT_Unique\u003c/code\u003e；EXPLAIN 名称：\u003ccode\u003eUnique\u003c/code\u003e。\u003c/p\u003e\u003cp\u003e本版本源码中的并行执行或观测回调：该节点实现中未提取到此类回调。回调的存在不表示该节点必然支持共享并行扫描；在并行工作进程中执行也不等于具有并行感知能力。\u003c/p\u003e\u003ch3\u003e同版本中文手册节选\u003c/h3\u003e\u003cp\u003e\n    查询计划的结构是一棵由\u003cem class=\"firstterm\"\u003e计划节点\u003c/em\u003e组成的树。树的最底层是扫描节点，它们从表中返回原始行。不同的表访问方法对应不同类型的扫描节点，例如顺序扫描、索引扫描和位图索引扫描。也有一些并非表的行来源，例如 \u003ccode class=\"literal\"\u003eVALUES\u003c/code\u003e 子句和 \u003ccode class=\"literal\"\u003eFROM\u003c/code\u003e 中的返回集合函数，它们也各自有对应的扫描节点类型。如果查询需要对原始行执行连接、聚合、排序或其他操作，那么扫描节点之上还会出现额外的节点来完成这些操作。同样，这些操作通常不止一种实现方式，因此这里也会出现不同的节点类型。\u003ccode class=\"command\"\u003eEXPLAIN\u003c/code\u003e 会为计划树中的每个节点输出一行，显示基本节点类型以及规划器对该计划节点执行代价的估计值。还可能出现相对节点摘要行缩进的附加行，用来显示该节点的更多属性。第一行，也就是最顶层节点的摘要行，给出了整个计划的估计总执行代价；规划器力图最小化的正是这个数字。\n   \u003c/p\u003e\u003cp\u003e\n    下面是一个简单的示例，仅用于展示输出的样子：\n\n\u003c/p\u003e\u003cp\u003e\n    由于这个查询没有 \u003ccode class=\"literal\"\u003eWHERE\u003c/code\u003e 子句，它必须扫描表中的所有行，因此规划器选择了一个简单的顺序扫描计划。圆括号中的数字从左到右依次表示：\n\n    \u003c/p\u003e\u003cp\u003e\n    这些代价使用由规划器代价参数决定的任意单位来衡量（见\u003ca class=\"xref\" href=\"/zh/docs/18/runtime-config-query.html#RUNTIME-CONFIG-QUERY-CONSTANTS\"\u003e第 19.7.2 节\u003c/a\u003e）。传统上通常以磁盘页读取作为代价单位；也就是说，惯例上将 \u003ca class=\"xref\" href=\"/zh/docs/18/runtime-config-query.html#GUC-SEQ-PAGE-COST\"\u003eseq_page_cost\u003c/a\u003e 设为 \u003ccode class=\"literal\"\u003e1.0\u003c/code\u003e，其他代价参数都相对它来设定。本节中的示例都使用默认代价参数。\n   \u003c/p\u003e\u003cp\u003e\u003ca href=\"/zh/docs/18/using-explain.html#USING-EXPLAIN-BASICS\"\u003e查看完整中文手册与上下文\u003c/a\u003e。\u003c/p\u003e\u003cp\u003e本页开头的中文说明为 PG.CENTER 对编辑说明的翻译；手册节选保留对应版本中文文档原文。源码信息及手册示例不代表重新进行过运行测试。\u003c/p\u003e","evidence_kind":"source and documentation","explain_names":["Unique"],"explain_prefixes":["Parallel","Async"],"facts":[{"label":"Core node tag","value":"T_Unique"},{"label":"Structured EXPLAIN Node Type","value":"Unique"},{"label":"Inputs","value":"One sorted child plan"},{"label":"Output","value":"Distinct tuples"},{"label":"Executor initializer","value":"ExecInitUnique"},{"label":"Memory mechanism","value":"unclassified"}],"facts_zh":[{"label":"核心节点标签","value":"T_Unique"},{"label":"结构化 EXPLAIN 节点类型","value":"Unique"},{"label":"输入","value":"一个已排序的子计划"},{"label":"输出","value":"去重后的元组"},{"label":"执行器初始化函数","value":"ExecInitUnique"},{"label":"内存机制","value":"unclassified"}],"memory":{"description":"This extraction does not assign a universal memory limit or spill policy to this node. Inspect the same-build implementation and its expressions or provider.","evidence":[{"archive_sha256":"555610c24d53e4316da5b7d3fc25c279d96856d5e0e23ee308c328c5fa881d9f","label":"src/backend/executor/nodeUnique.c","path":"src/backend/executor/nodeUnique.c","sha256":"462de2b51c7c7501d5a83d1b5e4435ef9e752c1f1d1934c428f2d8577bd43e45","url":"https://ftp.postgresql.org/pub/source/v18.6/postgresql-18.6.tar.bz2"}],"mechanism":"unclassified","source_notes":[]},"node_tag":"T_Unique","parallel_callbacks":[],"partial_modes":[],"related":[{"label":"EXPLAIN","url":"/wiki/sql/explain/?v=18"},{"label":"Using EXPLAIN","url":"/docs/18/using-explain.html"},{"label":"Parallel plans","url":"/docs/18/parallel-plans.html"}],"release":{"channel":"stable","label":"18.6","major":"18","ref":"PostgreSQL 18.6 source archive","revision":"555610c24d53e4316da5b7d3fc25c279d96856d5e0e23ee308c328c5fa881d9f","source_snapshot_utc":"","source_url":"https://ftp.postgresql.org/pub/source/v18.6/postgresql-18.6.tar.bz2"},"runtime_verified":false,"sections":[{"paragraphs":["Structured formats use the Node Type above. Text-format spellings can also include operation, strategy, join type, scan direction or aggregation-stage attributes.","Text names recorded by this source: Unique.","Parallel-aware and parallel-safe are different plan properties. A node running inside a parallel worker is not necessarily a parallel-aware node."],"title":"EXPLAIN names and attributes"},{"paragraphs":["This extraction does not assign a universal memory limit or spill policy to this node. Inspect the same-build implementation and its expressions or provider."],"title":"Memory and temporary storage"},{"paragraphs":["The source callbacks below can coordinate execution or collect worker instrumentation. Their presence is not a blanket claim that this node supports a shared parallel scan or shared state.","Callbacks in this build: none extracted from this node implementation."],"title":"Parallel execution and instrumentation"},{"paragraphs":["The added condition stringu1 = 'xxx' reduces the output row count estimate, but not the cost because we still have to visit the same set of rows. That's because the stringu1 clause cannot be applied as an index condition, since this index is only on the unique1 column. Instead it is applied as a filter on the rows retrieved using the index. Thus the cost has actually gone up slightly to reflect this extra checking.","In this type of plan the table rows are fetched in index order, which makes them even more expensive to read, but there are so few that the extra cost of sorting the row locations is not worth it. You'll most often see this plan type for queries that fetch just a single row. It's also often used for queries that have an ORDER BY condition that matches the index order, because then no extra sorting step is needed to satisfy the ORDER BY . In this example, adding ORDER BY unique1 would use the same plan because the index already implicitly provides the requested ordering.","In this plan, we have a nested-loop join node with two table scans as inputs, or children. The indentation of the node summary lines reflects the plan tree structure. The join's first, or “ outer ” , child is a bitmap scan similar to those we saw before. Its cost and row count are the same as we'd get from SELECT ... WHERE unique1 \u003c 10 because we are applying the WHERE clause unique1 \u003c 10 at that node. The t1.unique2 = t2.unique2 clause is not relevant yet, so it doesn't affect the row count of the outer scan. The nested-loop join node will run its second, or “ inner ” child once for each row obtained from the outer child. Column values from the current outer row can be plugged into the inner scan; here, the t1.unique2 value from the outer row is available, so we get a plan and costs similar to what we saw above for a simple SELECT ... WHERE t2.unique2 = constant case. (The estimated cost is actually a bit lower than what was seen above, as a result of caching that's expected to occur during the repeated index scans on t2 .) The costs of the loop node are then set on the basis of the cost of the outer scan, plus one repetition of the inner scan for each outer row (10 * 7.90, here), plus a little CPU time for join processing.","The condition t1.hundred \u003c t2.hundred can't be tested in the tenk2_unique2 index, so it's applied at the join node. This reduces the estimated output row count of the join node, but does not change either input scan.","Here we see an Index-Only Scan node using tenk1_four_unique1_idx , a multi-column index on the tenk1 table's four and unique1 columns. The scan performs 3 searches that each read a single index leaf page: “ four = 1 AND unique1 = 42 ” , “ four = 2 AND unique1 = 42 ” , and “ four = 3 AND unique1 = 42 ” . This index is generally a good target for skip scan, since, as discussed in Section 11.3 , its leading column (the four column) contains only 4 distinct values, while its second/final column (the unique1 column) contains many distinct values."],"title":"Same-version manual discussion"},{"blocks":[{"code":"EXPLAIN SELECT * FROM tenk1 WHERE unique1 \u003c 7000;\n\n                         QUERY PLAN\n------------------------------------------------------------\n Seq Scan on tenk1  (cost=0.00..470.00 rows=7000 width=244)\n   Filter: (unique1 \u003c 7000)","paragraphs":["Example copied from the PostgreSQL 18.6 manual; it was not executed for this collection.","Now let's modify the query to add a WHERE condition:"],"source":{"label":"PostgreSQL 18.6 · using-explain","path":"using-explain.html","sha256":"60040c30180093418a0affe56dd27dff9df2504b705b38039589e458bf5c31ed","url":"/docs/18/using-explain.html#USING-EXPLAIN-BASICS"}},{"code":"EXPLAIN SELECT * FROM tenk1 WHERE unique1 \u003c 100;\n\n                                  QUERY PLAN\n------------------------------------------------------------------------------\n Bitmap Heap Scan on tenk1  (cost=5.06..224.98 rows=100 width=244)\n   Recheck Cond: (unique1 \u003c 100)\n   -\u003e  Bitmap Index Scan on tenk1_unique1  (cost=0.00..5.04 rows=100 width=0)\n         Index Cond: (unique1 \u003c 100)","paragraphs":["Example copied from the PostgreSQL 18.6 manual; it was not executed for this collection.","Now, let's make the condition more restrictive:"],"source":{"label":"PostgreSQL 18.6 · using-explain","path":"using-explain.html","sha256":"60040c30180093418a0affe56dd27dff9df2504b705b38039589e458bf5c31ed","url":"/docs/18/using-explain.html#USING-EXPLAIN-BASICS"}}],"title":"Examples from this manual build"},{"paragraphs":["nodeUnique.c Routines to handle unique'ing of queries where appropriate","Unique is a very simple node type that just filters out duplicate tuples from a stream of sorted tuples from its subplan. It's essentially a dumbed-down form of Group: the duplicate-removal functionality is identical. However, Unique doesn't do projection nor qual checking, so it's marginally more efficient for cases where neither is needed. (It's debatable whether the savings justifies carrying two plan node types, though.)","NOTES Assumes tuples returned from subplan arrive in sorted order.","now loop, returning only non-duplicate tuples. We assume that the tuples arrive in sorted order so we can detect duplicates easily. The first tuple of each group is returned.","Else test if the new tuple and the previously returned tuple match. If so then we loop back and fetch another new tuple from the subplan."],"title":"Executor implementation notes"},{"code":"case T_Unique:\n\t\t\tpname = sname = \"Unique\";\n\t\t\tbreak;","title":"EXPLAIN identity in core source"}],"sections_zh":[{"paragraphs":["结构化格式使用上述 Node Type。文本格式名称还可能包含操作、策略、连接类型、扫描方向或聚合阶段属性。","此源码记录的文本名称：Unique.","并行感知与并行安全是不同的计划属性。在并行工作进程内运行的节点不一定是并行感知节点。"],"title":"EXPLAIN 名称与属性"},{"paragraphs":["本次抽取不为此节点设定统一的内存上限或落盘策略。请查看同一构建的实现、相关表达式或提供方。"],"title":"内存与临时存储"},{"paragraphs":["以下源码回调可以协调执行或收集工作进程的测量数据。回调存在不代表该节点普遍支持共享并行扫描或共享状态。","此构建的回调：none extracted from this node implementation."],"title":"并行执行与运行信息采集"},{"paragraphs":["新增的条件 stringu1 = 'xxx' 降低了估计输出行数，但没有降低代价，因为仍然需要访问同一批行。这是因为 stringu1 子句不能作为索引条件使用，毕竟这个索引只建立在 unique1 列上。它只能作为对通过索引取回行的过滤条件。因此，为了反映这项额外检查，代价实际上略微上升了一点。","此类计划按索引顺序获取表行，这使读取代价更高；但行数很少，不值得为行位置排序付出额外代价。查询只获取一行时，最常见到这种计划。如果查询的 ORDER BY 条件与索引顺序一致，也经常采用此计划，因为无需额外排序即可满足 ORDER BY。本例即使加上 ORDER BY unique1，仍会使用同一计划，因为索引已经隐式提供了所需顺序。","在这个计划中，有一个嵌套循环连接节点，它的两个输入，也就是两个子节点，都是表扫描。节点摘要行的缩进反映了计划树结构。连接的第一个子节点，也就是 “ 外侧 ” 子节点，是一个与前面见过的位图扫描类似的节点。它的代价和行计数与 SELECT ... WHERE unique1 \u003c 10 得到的结果相同，因为 WHERE 子句 unique1 \u003c 10 正是在该节点上应用的。 t1.unique2 = t2.unique2 子句此时还无关，因此不会影响外侧扫描的行计数。嵌套循环连接节点会对从外侧子节点得到的每一行执行一次第二个，也就是 “ 内侧 ” 子节点。当前外侧行中的列值可以代入内侧扫描；这里外侧行的 t1.unique2 值可用，因此得到的计划和代价与前面看到的简单 SELECT ... WHERE t2.unique2 = constant 情形类似。（由于预期在对 t2 反复执行索引扫描期间会发生缓存命中，估计代价实际上比前面看到的略低一些。）随后，循环节点的代价建立在外侧扫描代价之上，再加上每个外侧行都要执行一次内侧扫描的代价（这里是 10 * 7.90），以及少量连接处理的 CPU 时间。","无法在 tenk2_unique2 索引中检查 t1.hundred \u003c t2.hundred 条件，因此在连接节点应用它。这会降低连接节点的预计输出行数，但不改变两侧输入扫描。","这里看到一个使用 tenk1_four_unique1_idx 的 Index-Only Scan 节点，该索引是 tenk1 表上由 four 和 unique1 两列组成的多列索引。该扫描执行了 3 次搜索，每次都只读取一个索引叶子页面： “ four = 1 AND unique1 = 42 ” 、 “ four = 2 AND unique1 = 42 ” 以及 “ four = 3 AND unique1 = 42 ” 。正如 第 11.3 节 中讨论的那样，这个索引通常很适合作为跳过扫描的目标，因为它的前导列，也就是 four 列，只包含 4 个非重复值，而它的第二列也是最后一列，即 unique1 列，则包含大量非重复值。"],"title":"同版本手册说明"},{"blocks":[{"code":"EXPLAIN SELECT * FROM tenk1 WHERE unique1 \u003c 7000;\n\n                         QUERY PLAN\n------------------------------------------------------------\n Seq Scan on tenk1  (cost=0.00..470.00 rows=7000 width=244)\n   Filter: (unique1 \u003c 7000)","paragraphs":["示例摘自 PostgreSQL 18.6 手册；本百科未实际执行此示例。","现在修改查询，添加 WHERE 条件："],"source":{"label":"PostgreSQL 18.6 · using-explain","path":"using-explain.html","sha256":"60040c30180093418a0affe56dd27dff9df2504b705b38039589e458bf5c31ed","url":"/docs/18/using-explain.html#USING-EXPLAIN-BASICS"}},{"code":"EXPLAIN SELECT * FROM tenk1 WHERE unique1 \u003c 100;\n\n                                  QUERY PLAN\n------------------------------------------------------------------------------\n Bitmap Heap Scan on tenk1  (cost=5.06..224.98 rows=100 width=244)\n   Recheck Cond: (unique1 \u003c 100)\n   -\u003e  Bitmap Index Scan on tenk1_unique1  (cost=0.00..5.04 rows=100 width=0)\n         Index Cond: (unique1 \u003c 100)","paragraphs":["示例摘自 PostgreSQL 18.6 手册；本百科未实际执行此示例。","现在进一步收紧条件："],"source":{"label":"PostgreSQL 18.6 · using-explain","path":"using-explain.html","sha256":"60040c30180093418a0affe56dd27dff9df2504b705b38039589e458bf5c31ed","url":"/docs/18/using-explain.html#USING-EXPLAIN-BASICS"}}],"title":"本版手册中的示例"},{"paragraphs":["nodeUnique.c：在适当情况下对查询去重的例程。","Unique 是一个非常简单的节点，从子计划返回的已排序元组流中过滤重复元组。它基本上是简化的 Group：去重功能相同，但不执行投影或条件检查，因此在两者都不需要时效率略高。（不过，为这点节省维护两种节点类型是否值得，仍有争议。）","说明：假定子计划返回的元组已经排序。","现在循环读取，仅返回不重复的元组。假定输入已排序，因此可以轻易检测重复，并返回每个组的第一个元组。","否则，检查新元组是否与上次返回的元组相同。如果相同，则返回循环开头，从子计划再获取一个新元组。"],"title":"执行器实现说明"},{"code":"case T_Unique:\n\t\t\tpname = sname = \"Unique\";\n\t\t\tbreak;","title":"核心源码中的 EXPLAIN 标识"}],"source_inventory":{"executor":"src/backend/executor/execProcnode.c","explain":"src/backend/commands/explain.c","implementation":"src/backend/executor/nodeUnique.c"},"sources":[{"archive_sha256":"555610c24d53e4316da5b7d3fc25c279d96856d5e0e23ee308c328c5fa881d9f","label":"src/backend/commands/explain.c:1577","line":1577,"path":"src/backend/commands/explain.c","sha256":"34c86d6070224a0e981efef51f79101d6d505e5874f1684ace183034bab14bb4","url":"https://ftp.postgresql.org/pub/source/v18.6/postgresql-18.6.tar.bz2"},{"archive_sha256":"555610c24d53e4316da5b7d3fc25c279d96856d5e0e23ee308c328c5fa881d9f","label":"src/backend/executor/execProcnode.c:350","line":350,"path":"src/backend/executor/execProcnode.c","sha256":"f8a06a3f539077249b20664b2812433db6d7bd12b2c0ca633525db43d06f112a","url":"https://ftp.postgresql.org/pub/source/v18.6/postgresql-18.6.tar.bz2"},{"archive_sha256":"555610c24d53e4316da5b7d3fc25c279d96856d5e0e23ee308c328c5fa881d9f","label":"src/backend/executor/nodeUnique.c","path":"src/backend/executor/nodeUnique.c","sha256":"462de2b51c7c7501d5a83d1b5e4435ef9e752c1f1d1934c428f2d8577bd43e45","url":"https://ftp.postgresql.org/pub/source/v18.6/postgresql-18.6.tar.bz2"},{"archive_sha256":"555610c24d53e4316da5b7d3fc25c279d96856d5e0e23ee308c328c5fa881d9f","label":"src/include/nodes/plannodes.h","path":"src/include/nodes/plannodes.h","sha256":"52422b327a8049fbbb20d8b96008a0fc0a6fafa60f7eff3c695d5b2e83830120","url":"https://ftp.postgresql.org/pub/source/v18.6/postgresql-18.6.tar.bz2"},{"label":"PostgreSQL 18.6 · using-explain","path":"using-explain.html","sha256":"60040c30180093418a0affe56dd27dff9df2504b705b38039589e458bf5c31ed","url":"/docs/18/using-explain.html#USING-EXPLAIN-BASICS"},{"label":"PostgreSQL 18.6 · using-explain","path":"using-explain.html","sha256":"60040c30180093418a0affe56dd27dff9df2504b705b38039589e458bf5c31ed","url":"/docs/18/using-explain.html#USING-EXPLAIN-ANALYZE"}],"strategies":[],"tables":[{"columns":[{"key":"label","label":"Text-format label"},{"key":"identity","label":"Structured node identity"}],"key":"explain-labels","rows":[{"identity":"Unique","label":"Unique"}],"title":"EXPLAIN labels in this source build"}],"tables_zh":[{"columns":[{"key":"label","label":"文本格式标签"},{"key":"identity","label":"结构化节点标识"}],"key":"explain-labels","rows":[{"identity":"Unique","label":"Unique"}],"title":"本构建中的 EXPLAIN 标签"}],"zh_from":{"authored_translation":"authored-translation-of-reviewed-guidance","body_sha256":"bc3566dcfc1cbba8bb960448a9ded31e04aa53f3cb4434d8b12c271ad13b7d84","kind":"same-version-chinese-manual-with-authored-translation-of-reviewed-guidance","manual":{"anchor":"USING-EXPLAIN-BASICS","build_id":107,"extraction":"concept-introduction","fragment_sha256":"bc79b6f9c70f2b56968e08ea26d2c9ec3d395e425aca0594e227203daa0ad29b","locale":"zh-Hans","page_key":"using-explain.html","source_hash":"d2f749744b3ac5aa2eb0154f295e8163903cd20fcc130c74c6172cdf2ae3a9df","source_revision":"unknown:rendered-translation-baseline","translated_from_revision":null,"translation_revision":"git:b1b500221dbe5437cfca004d6b04d3f6e422765d;html-sha256:08e987a66af27b98444a8057c256314eafa13611a3fb9e888214fc436824244b","url":"/zh/docs/18/using-explain.html#USING-EXPLAIN-BASICS","version":"18"},"runtime_rerun":false,"source_facts_revision":"555610c24d53e4316da5b7d3fc25c279d96856d5e0e23ee308c328c5fa881d9f"}},"ManualEvidence":{"release":{"channel":"stable","label":"18.6","major":"18","ref":"PostgreSQL 18.6 source archive","revision":"555610c24d53e4316da5b7d3fc25c279d96856d5e0e23ee308c328c5fa881d9f","source_snapshot_utc":"","source_url":"https://ftp.postgresql.org/pub/source/v18.6/postgresql-18.6.tar.bz2"},"sources":[{"archive_sha256":"555610c24d53e4316da5b7d3fc25c279d96856d5e0e23ee308c328c5fa881d9f","label":"src/backend/commands/explain.c:1577","line":1577,"path":"src/backend/commands/explain.c","sha256":"34c86d6070224a0e981efef51f79101d6d505e5874f1684ace183034bab14bb4","url":"https://ftp.postgresql.org/pub/source/v18.6/postgresql-18.6.tar.bz2"},{"archive_sha256":"555610c24d53e4316da5b7d3fc25c279d96856d5e0e23ee308c328c5fa881d9f","label":"src/backend/executor/execProcnode.c:350","line":350,"path":"src/backend/executor/execProcnode.c","sha256":"f8a06a3f539077249b20664b2812433db6d7bd12b2c0ca633525db43d06f112a","url":"https://ftp.postgresql.org/pub/source/v18.6/postgresql-18.6.tar.bz2"},{"archive_sha256":"555610c24d53e4316da5b7d3fc25c279d96856d5e0e23ee308c328c5fa881d9f","label":"src/backend/executor/nodeUnique.c","path":"src/backend/executor/nodeUnique.c","sha256":"462de2b51c7c7501d5a83d1b5e4435ef9e752c1f1d1934c428f2d8577bd43e45","url":"https://ftp.postgresql.org/pub/source/v18.6/postgresql-18.6.tar.bz2"},{"archive_sha256":"555610c24d53e4316da5b7d3fc25c279d96856d5e0e23ee308c328c5fa881d9f","label":"src/include/nodes/plannodes.h","path":"src/include/nodes/plannodes.h","sha256":"52422b327a8049fbbb20d8b96008a0fc0a6fafa60f7eff3c695d5b2e83830120","url":"https://ftp.postgresql.org/pub/source/v18.6/postgresql-18.6.tar.bz2"},{"label":"PostgreSQL 18.6 · using-explain","path":"using-explain.html","sha256":"60040c30180093418a0affe56dd27dff9df2504b705b38039589e458bf5c31ed","url":"/docs/18/using-explain.html#USING-EXPLAIN-BASICS"},{"label":"PostgreSQL 18.6 · using-explain","path":"using-explain.html","sha256":"60040c30180093418a0affe56dd27dff9df2504b705b38039589e458bf5c31ed","url":"/docs/18/using-explain.html#USING-EXPLAIN-ANALYZE"}]},"MeasuredEvidence":{"runtime_verified":false}},"Text":{"Collection":"plan","Key":"unique","SourceDatabase":"center","Version":"18","Locale":"en","Title":"Unique","Summary":"Removes adjacent duplicates from a sorted input stream.","BodyHTML":"\u003cp\u003eRemoves adjacent duplicates from a sorted input stream.\u003c/p\u003e","SourceRevision":"555610c24d53e4316da5b7d3fc25c279d96856d5e0e23ee308c328c5fa881d9f","ContentHash":"8c288ca025cab07dfe4f6d40d89258ae4892f87d08379da1403fed2bc271cddf","Payload":{"description":["Removes adjacent duplicates from a sorted input stream."],"related":[{"label":"EXPLAIN","url":"/wiki/sql/explain/?v=18"},{"label":"Using EXPLAIN","url":"/docs/18/using-explain.html"},{"label":"Parallel plans","url":"/docs/18/parallel-plans.html"}],"sections":[{"paragraphs":["Structured formats use the Node Type above. Text-format spellings can also include operation, strategy, join type, scan direction or aggregation-stage attributes.","Text names recorded by this source: Unique.","Parallel-aware and parallel-safe are different plan properties. A node running inside a parallel worker is not necessarily a parallel-aware node."],"title":"EXPLAIN names and attributes"},{"paragraphs":["This extraction does not assign a universal memory limit or spill policy to this node. Inspect the same-build implementation and its expressions or provider."],"title":"Memory and temporary storage"},{"paragraphs":["The source callbacks below can coordinate execution or collect worker instrumentation. Their presence is not a blanket claim that this node supports a shared parallel scan or shared state.","Callbacks in this build: none extracted from this node implementation."],"title":"Parallel execution and instrumentation"},{"paragraphs":["The added condition stringu1 = 'xxx' reduces the output row count estimate, but not the cost because we still have to visit the same set of rows. That's because the stringu1 clause cannot be applied as an index condition, since this index is only on the unique1 column. Instead it is applied as a filter on the rows retrieved using the index. Thus the cost has actually gone up slightly to reflect this extra checking.","In this type of plan the table rows are fetched in index order, which makes them even more expensive to read, but there are so few that the extra cost of sorting the row locations is not worth it. You'll most often see this plan type for queries that fetch just a single row. It's also often used for queries that have an ORDER BY condition that matches the index order, because then no extra sorting step is needed to satisfy the ORDER BY . In this example, adding ORDER BY unique1 would use the same plan because the index already implicitly provides the requested ordering.","In this plan, we have a nested-loop join node with two table scans as inputs, or children. The indentation of the node summary lines reflects the plan tree structure. The join's first, or “ outer ” , child is a bitmap scan similar to those we saw before. Its cost and row count are the same as we'd get from SELECT ... WHERE unique1 \u003c 10 because we are applying the WHERE clause unique1 \u003c 10 at that node. The t1.unique2 = t2.unique2 clause is not relevant yet, so it doesn't affect the row count of the outer scan. The nested-loop join node will run its second, or “ inner ” child once for each row obtained from the outer child. Column values from the current outer row can be plugged into the inner scan; here, the t1.unique2 value from the outer row is available, so we get a plan and costs similar to what we saw above for a simple SELECT ... WHERE t2.unique2 = constant case. (The estimated cost is actually a bit lower than what was seen above, as a result of caching that's expected to occur during the repeated index scans on t2 .) The costs of the loop node are then set on the basis of the cost of the outer scan, plus one repetition of the inner scan for each outer row (10 * 7.90, here), plus a little CPU time for join processing.","The condition t1.hundred \u003c t2.hundred can't be tested in the tenk2_unique2 index, so it's applied at the join node. This reduces the estimated output row count of the join node, but does not change either input scan.","Here we see an Index-Only Scan node using tenk1_four_unique1_idx , a multi-column index on the tenk1 table's four and unique1 columns. The scan performs 3 searches that each read a single index leaf page: “ four = 1 AND unique1 = 42 ” , “ four = 2 AND unique1 = 42 ” , and “ four = 3 AND unique1 = 42 ” . This index is generally a good target for skip scan, since, as discussed in Section 11.3 , its leading column (the four column) contains only 4 distinct values, while its second/final column (the unique1 column) contains many distinct values."],"title":"Same-version manual discussion"},{"blocks":[{"code":"EXPLAIN SELECT * FROM tenk1 WHERE unique1 \u003c 7000;\n\n                         QUERY PLAN\n------------------------------------------------------------\n Seq Scan on tenk1  (cost=0.00..470.00 rows=7000 width=244)\n   Filter: (unique1 \u003c 7000)","paragraphs":["Example copied from the PostgreSQL 18.6 manual; it was not executed for this collection.","Now let's modify the query to add a WHERE condition:"],"source":{"label":"PostgreSQL 18.6 · using-explain","path":"using-explain.html","sha256":"60040c30180093418a0affe56dd27dff9df2504b705b38039589e458bf5c31ed","url":"/docs/18/using-explain.html#USING-EXPLAIN-BASICS"}},{"code":"EXPLAIN SELECT * FROM tenk1 WHERE unique1 \u003c 100;\n\n                                  QUERY PLAN\n------------------------------------------------------------------------------\n Bitmap Heap Scan on tenk1  (cost=5.06..224.98 rows=100 width=244)\n   Recheck Cond: (unique1 \u003c 100)\n   -\u003e  Bitmap Index Scan on tenk1_unique1  (cost=0.00..5.04 rows=100 width=0)\n         Index Cond: (unique1 \u003c 100)","paragraphs":["Example copied from the PostgreSQL 18.6 manual; it was not executed for this collection.","Now, let's make the condition more restrictive:"],"source":{"label":"PostgreSQL 18.6 · using-explain","path":"using-explain.html","sha256":"60040c30180093418a0affe56dd27dff9df2504b705b38039589e458bf5c31ed","url":"/docs/18/using-explain.html#USING-EXPLAIN-BASICS"}}],"title":"Examples from this manual build"},{"paragraphs":["nodeUnique.c Routines to handle unique'ing of queries where appropriate","Unique is a very simple node type that just filters out duplicate tuples from a stream of sorted tuples from its subplan. It's essentially a dumbed-down form of Group: the duplicate-removal functionality is identical. However, Unique doesn't do projection nor qual checking, so it's marginally more efficient for cases where neither is needed. (It's debatable whether the savings justifies carrying two plan node types, though.)","NOTES Assumes tuples returned from subplan arrive in sorted order.","now loop, returning only non-duplicate tuples. We assume that the tuples arrive in sorted order so we can detect duplicates easily. The first tuple of each group is returned.","Else test if the new tuple and the previously returned tuple match. If so then we loop back and fetch another new tuple from the subplan."],"title":"Executor implementation notes"},{"code":"case T_Unique:\n\t\t\tpname = sname = \"Unique\";\n\t\t\tbreak;","title":"EXPLAIN identity in core source"}],"tables":[{"columns":[{"key":"label","label":"Text-format label"},{"key":"identity","label":"Structured node identity"}],"key":"explain-labels","rows":[{"identity":"Unique","label":"Unique"}],"title":"EXPLAIN labels in this source build"}]}},"RequestedLocale":"en","Fallback":false,"Versions":["10","11","12","13","14","15","16","17","18","19","20"],"Locales":["en","zh-Hans"],"Signatures":null,"Spellings":null,"SQLState":null,"Evidence":null}
