YSQL currently supports only fetching rows from a cursor consecutively in the forward direction.

See the subsection Beware Issue #6514 in the generic section Cursors.


Use the DECLARE statement to create a cursor. See the generic section Cursors. The DECLARE statement is used jointly with the MOVE, FETCH, and CLOSE statements.

The term cursor is a SQL keyword that's used in the DECLARE statement (but in no other SQL statement).

There's also a PL/pgSQL API for explicit cursor management.


declare ::= DECLARE cursor_name [ BINARY ] [ INSENSITIVE ] 
            [ [ NO ] SCROLL ]  CURSOR [ { WITH | WITHOUT } HOLD ] FOR 




DECLARE creates a cursor. (There's no notion of so-called "opening" a cursor in top-level SQL.) A cursor's duration is limited to the duration of the session that declares it. Notice the critical maximum lifetime difference between a holdable, and a non-holdable, cursor. See the section Transactional behavior — holdable and non-holdable cursors. (The CLOSE statement drops a cursor so that you can shorten its lifetime if you want to—typically in order to save resources.)

The pg_cursors catalog view lists all the currently existing cursors in the current session.


A cursor is identified only by an unqualified name and is visible only in the session that declares it. This determines the uniqueness scope for its name. (The name of a cursor is like that of a prepared statement in this respect.)


Specifies that the cursor returns data in binary rather than in text format.

Usually, a cursor is specified to return data in text format, the same as a SELECT statement would produce. This binary format reduces conversion effort for both the server and client at the cost of more programmer effort to deal with platform-dependent binary data formats. For example, if a query returns a value of one from an integer column, you would get the string value 1 with the default choice. But with a binary cursor you would get a 4-byte field containing the internal representation of the value (in big-endian byte order).

Use binary cursors carefully. Many applications, including ysqlsh (and psql) are not prepared to handle binary cursors and expect data to come back in the text format.

BINARY cursors and the 'extended' query protocol

When the client application uses the “extended" query protocol to issue a FETCH statement, the bind protocol message specifies whether data is to be retrieved in text or binary format. This choice overrides the way that the cursor is defined. It therefore isn't useful to declare a cursor explicitly as BINARY because any cursor can be treated as either text or binary when you use the extended query protocol.


This indicates that data retrieved from the cursor should be unaffected by updates to the table(s) underlying the cursor that occur after the cursor has been declared. In PostgreSQL, and therefore in YSQL, this is the only behavior. This key word therefore has no effect and is accepted only for compatibility with the SQL standard.


SCROLL specifies that you can use the flavors of FETCH and MOVE to access the current row and rows in the cursor's result set that lie before the current row (i.e. including and before the row that FETCH RELATIVE 0 accesses). In simple cases, the execution plan is intrinsically reversible: it allows backwards fetching just as easily as it allows forwards fetching. But not all execution plans are reversible; and when a plan is not reversible, specifying SCROLL implies creating a cache of the rows that the cursor's subquery defines at the moment that the first MOVE or FETCH statement is executed (or on demand as new rows are accessed). This implies both a performance cost and a resource consumption cost.

NO SCROLL specifies that the cursor cannot be used to retrieve the current row or rows that lie before it.

When you specify neither SCROLL nor NO SCROLL, then allow scrolling is allowed in only some cases—and this is therefore different from specifying SCROLL explicitly.

Always specify either SCROLL or NO SCROLL explicitly

See the tip in the subsection Scrollable cursors on the dedicated Cursors page.

Choose the mode that you want explicitly to honor the requirements that you must meet. Notice that while Issue #6514 remains open, your only viable choice is NO SCROLL.


WITHOUT HOLD specifies that the cursor cannot be used after the transaction that created it ends (even if it ends with a successful commit).

WITH HOLD specifies that the cursor can continue to be used after the transaction that created it successfully commits. (Of course, it vanishes if the transaction that created it rolls back.)

Specifying neither WITHOUT HOLD nor WITH HOLD is the same as specifying WITHOUT HOLD.

Simple example

drop table if exists t cascade;
create table t(k, v) as
select g.val, g.val*100
from generate_series(1, 22) as g(val);

start transaction;
  declare cur scroll cursor without hold for
  select k, v
  from t
  where (k <> all (array[1, 3, 5, 7, 11, 13, 17, 19]))
  order by k;
  from pg_cursors where name = 'cur'
  and not is_binary
  and creation_time < (transaction_timestamp() + make_interval(secs=>0.05));

  fetch all from cur;
  close cur;

This is the result from "select... from pg_cursors...":

                       statement                        | is_holdable | is_scrollable 
 declare cur scroll cursor without hold for            +| false       | true
   select k, v                                         +|             | 
   from t                                              +|             | 
   where (k <> all (array[1, 3, 5, 7, 11, 13, 17, 19]))+|             | 
   order by k;                                          |             |

And this is the result from FETCH ALL:

 k  |  v   
  2 |  200
  4 |  400
  6 |  600
  8 |  800
  9 |  900
 10 | 1000
 12 | 1200
 14 | 1400
 15 | 1500
 16 | 1600
 18 | 1800
 20 | 2000
 21 | 2100
 22 | 2200

See also