317

Shouldn't this be a pretty straightforward operation? However, I see there's neither a size() nor length() method.

Unai Vivi
  • 3,073
  • 3
  • 30
  • 46
Jake
  • 15,007
  • 22
  • 70
  • 86
  • 12
    I would love to know the reason for that omission. – Slamice Mar 04 '12 at 06:53
  • 1
    My understanding of the question was that you want to find the size of the ResultSet IN BYTES, not the number of tuples... – DejanLekic Dec 06 '12 at 17:40
  • It's very annoying to have not the right dimension before process data, but if you have to store them in an array, you can consider using a data structure like List and then convert them to an array with the toArray() method. – AndreaTaroni86 Sep 04 '19 at 09:42

15 Answers15

301

Do a SELECT COUNT(*) FROM ... query instead.

OR

int size =0;
if (rs != null) 
{
  rs.last();    // moves cursor to the last row
  size = rs.getRow(); // get row id 
}

In either of the case, you won't have to loop over the entire data.

Hamza Khan
  • 107
  • 1
  • 11
finnw
  • 47,861
  • 24
  • 143
  • 221
  • 9
    last() and getRow() aren't static methods in the ResultSet class. – JeeBee Oct 10 '08 at 16:21
  • 71
    For brevity's sake I always reference methods in this fashion when writing about them to others, regardless of whether they are static or not. Actually creating an instance of the object and calling the method is implied. – laz Oct 10 '08 at 18:23
  • 51
    I write SomeClass.staticMethod() and SomeClass#instanceMethod() for less confusion. – Jake May 13 '11 at 03:17
  • 9
    How does one fetch the value returned when executing a `select count`? – Naftuli Kay Jun 02 '11 at 09:14
  • 4
    @TK Kocheran, same way you would get the result of any one-row/one-column query, with `executeQuery()`, `next()` and `getInt(1)` – finnw Jun 05 '11 at 10:41
  • 22
    `ResultSet#last()` doesn't work on all types of `ResultSet` objects, you need to make sure you use one that is either `ResultSet.TYPE_SCROLL_INSENSITIVE` or `ResultSet.TYPE_SCROLL_SENSITIVE` – Marius Ion Jun 13 '12 at 13:47
  • 2
    What if the result set is returned from a stored procedure call? Is there no way to know its size beforehand? – Giovanni Botta May 22 '13 at 15:07
  • @Giodude, the `SELECT COUNT(*)` would need to be performed inside the stored procedure, or the stored procedure could return a temporary view and the caller could do `SELECT COUNT(*)` on that. – finnw May 22 '13 at 16:19
  • 1
    What if you can't change the stored proc because it comes from somewhere else? – Giovanni Botta May 23 '13 at 19:48
  • @Giodude, it may not be possible to efficiently count the rows in that case. But if you must manually count the rows, I would do it on the server side if possible (e.g. with a temporary procedure.) – finnw May 24 '13 at 06:42
  • 4
    Does anyone know why getting the count for a resultset is so difficult? Why didn't they just include a `ResultSet#size()` method in the API? – ryvantage Dec 13 '13 at 20:48
  • 1
    @ryvantage A bit late. They don't do that, because the count is not known in advance, only after materializing the entire result set does the database know how many rows it produced. This is inefficient as materializing all rows takes memory, I/O and processing time. When reading a forward-only result set (and sometimes also scrollable result sets), a database will only read rows when asked to do so (and it might read a bit ahead). So a client might process some rows, while the database is reading some more: efficient and less memory-intensive. – Mark Rotteveel Aug 25 '14 at 10:56
  • So does that mean `select count(*) from table` is a O(n) operation? – ryvantage Aug 25 '14 at 11:00
  • Also, other answers have indicated using `ResultSet::last()` as a way to find the count. Would the `last()` method also be a O(n) operation then? – ryvantage Aug 25 '14 at 18:59
  • 1
    @ryvantage: Yes. In MS SQL Server for example, `ResultSet::last()` is O(N) with the (default) forward-only cursor. You can get O(1) `last` (from the client's point of view) with a scrollable+static cursor. But these cursor types require the *server* to store the whole keyset in memory. The server does not know in advance that you are going to throw away the results and use only the row count. It has to build the query plan with the assumption that you will consume all the data. – finnw Aug 25 '14 at 19:24
  • 1
    This answer indirectly suggests that when you are going to use data afterwards then the `ResultSet.last()` is the optimal solution. In fact the speed measurements I did for embedded Derby and H2 databases shows that even when you are using data afterwards it is still faster to just ask database `SELECT COUNT(*)` before and then get your result in mode `ResultSet.TYPE_FORWARD_ONLY` – Vit Bernatik May 21 '15 at 21:29
102
ResultSet rs = ps.executeQuery();
int rowcount = 0;
if (rs.last()) {
  rowcount = rs.getRow();
  rs.beforeFirst(); // not rs.first() because the rs.next() below will move on, missing the first element
}
while (rs.next()) {
  // do your standard per row stuff
}
Danilo Valente
  • 11,270
  • 8
  • 53
  • 67
JeeBee
  • 17,476
  • 5
  • 50
  • 60
  • 5
    Inside the if(rs.last()) code block, wouldn't the correct method be rs.beforeFirst() instead of rs.first()? This way, you are not skipping the first record in your result set for processing in the while loop. – karlgrz Jan 26 '09 at 17:04
  • don't you forget to set the cursor back to beforeFirst outside the if block? – Gobliins Jan 13 '15 at 11:43
  • As [ResultSet docs](https://docs.oracle.com/javase/7/docs/api/java/sql/ResultSet.html) say, `getRow()` works for `TYPE_FORWARD_ONLY` ResultSets, and `beforeFirst()` throws errors for those. Isn't this answer faulty then? – CodePro_NotYet Jul 20 '16 at 00:08
  • 6
    This only works when the statement is created with the scroll insensitive option: `ps=conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);` – BullyWiiPlaza Feb 21 '17 at 13:55
20

Well, if you have a ResultSet of type ResultSet.TYPE_FORWARD_ONLY you want to keep it that way (and not to switch to a ResultSet.TYPE_SCROLL_INSENSITIVE or ResultSet.TYPE_SCROLL_INSENSITIVE in order to be able to use .last()).

I suggest a very nice and efficient hack, where you add a first bogus/phony row at the top containing the number of rows.

Example

Let's say your query is the following

select MYBOOL,MYINT,MYCHAR,MYSMALLINT,MYVARCHAR
from MYTABLE
where ...blahblah...

and your output looks like

true    65537 "Hey" -32768 "The quick brown fox"
false  123456 "Sup"    300 "The lazy dog"
false -123123 "Yo"       0 "Go ahead and jump"
false       3 "EVH"    456 "Might as well jump"
...
[1000 total rows]

Simply refactor your code to something like this:

Statement s=myConnection.createStatement(ResultSet.TYPE_FORWARD_ONLY,
                                         ResultSet.CONCUR_READ_ONLY);
String from_where="FROM myTable WHERE ...blahblah... ";
//h4x
ResultSet rs=s.executeQuery("select count(*)as RECORDCOUNT,"
                           +       "cast(null as boolean)as MYBOOL,"
                           +       "cast(null as int)as MYINT,"
                           +       "cast(null as char(1))as MYCHAR,"
                           +       "cast(null as smallint)as MYSMALLINT,"
                           +       "cast(null as varchar(1))as MYVARCHAR "
                           +from_where
                           +"UNION ALL "//the "ALL" part prevents internal re-sorting to prevent duplicates (and we do not want that)
                           +"select cast(null as int)as RECORDCOUNT,"
                           +       "MYBOOL,MYINT,MYCHAR,MYSMALLINT,MYVARCHAR "
                           +from_where);

Your query output will now be something like

1000 null     null null    null null
null true    65537 "Hey" -32768 "The quick brown fox"
null false  123456 "Sup"    300 "The lazy dog"
null false -123123 "Yo"       0 "Go ahead and jump"
null false       3 "EVH"    456 "Might as well jump"
...
[1001 total rows]

So you just have to

if(rs.next())
    System.out.println("Recordcount: "+rs.getInt("RECORDCOUNT"));//hack: first record contains the record count
while(rs.next())
    //do your stuff
Unai Vivi
  • 3,073
  • 3
  • 30
  • 46
  • Interesting, but how would you dynamically/generically generate first select statements: cast(null as boolean)as MYBOOL, ect? For that you will need metadata of the "select" statement's fields and datatypes, like boolean, char, int, ect...) that might require extra DB trip that will negate all the benefits. – user1697575 Mar 31 '16 at 14:25
  • This is useful when you do have access to all field details and speed is your main concern (and therefore need to stick with a fast `ResultSet.TYPE_FORWARD_ONLY`) – Unai Vivi May 21 '17 at 21:56
13
int i = 0;
while(rs.next()) {
    i++;
}
Garrett Hyde
  • 5,409
  • 8
  • 49
  • 55
bhaskar
  • 163
  • 1
  • 3
  • I don't understand what is the drawback of using this method to calculate ResultSet size. This is great...no use of an extra SQL parameter. Please comment on this method. – Madeyedexter Mar 13 '15 at 05:21
  • 6
    Performance is the keyword here. Imagine your resultset is 100M records then you will see the issue – Pierre May 13 '16 at 16:49
  • 9
    I want to know the result set size BEFORE processing the results because I need to make an array of the same size beforehand. And, as noted in other answers, scanning all rows twice won't always work. – Ivo Jan 10 '17 at 22:10
  • @Ivo could you not use a List instead of an array because of noticeable performance degradation? – jones-chris Sep 16 '20 at 20:27
  • 1
    @jones-chris Who knows, this is 3 years ago, I have no clue what I was doing. I hate arrays though, so I assume using a List wasn't possible. Either way, an array should be more performant than a List (unless List methods get optimized by the runtime). – Ivo Sep 17 '20 at 21:42
12

I got an exception when using rs.last()

if(rs.last()){
    rowCount = rs.getRow(); 
    rs.beforeFirst();
}

:

java.sql.SQLException: Invalid operation for forward only resultset

it's due to by default it is ResultSet.TYPE_FORWARD_ONLY, which means you can only use rs.next()

the solution is:

stmt=conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,
    ResultSet.CONCUR_READ_ONLY); 
Dan
  • 950
  • 8
  • 13
  • 14
    Switching from `ResultSet.TYPE_FORWARD_ONLY` to `ResultSet.TYPE_SCROLL_INSENSITIVE` usually incurs in a **huge** performance penalty. – Unai Vivi May 24 '13 at 07:58
  • 3
    I did test it on my table (10 columns, 187 392 rows). My test did query and load all elements to string. For TYPE_FORWARD_ONLY it took approx 1 second. For TYPE_SCROLL_INSENSITIVE it took approx 7 second. When I used rather `SELECT COUNT(*) FROM default_tbl` before the `SELECT COUNT(*) FROM default_tbl` it took altogether less than 1.5 second. I tested on embedded derby database 10.11.1.1 – Vit Bernatik May 19 '15 at 12:07
5

[Speed consideration]

Lot of ppl here suggests ResultSet.last() but for that you would need to open connection as a ResultSet.TYPE_SCROLL_INSENSITIVE which for Derby embedded database is up to 10 times SLOWER than ResultSet.TYPE_FORWARD_ONLY.

According to my micro-tests for embedded Derby and H2 databases it is significantly faster to call SELECT COUNT(*) before your SELECT.

Here is in more detail my code and my benchmarks

Community
  • 1
  • 1
Vit Bernatik
  • 3,566
  • 2
  • 34
  • 40
4

The way of getting size of ResultSet, No need of using ArrayList etc

int size =0;  
if (rs != null)   
{  
rs.beforeFirst();  
 rs.last();  
size = rs.getRow();
}

Now You will get size, And if you want print the ResultSet, before printing use following line of code too,

rs.beforeFirst();  
Anptk
  • 1,125
  • 2
  • 17
  • 28
3

It is a simple way to do rows-count.

ResultSet rs = job.getSearchedResult(stmt);
int rsCount = 0;

//but notice that you'll only get correct ResultSet size after end of the while loop
while(rs.next())
{
    //do your other per row stuff 
    rsCount = rsCount + 1;
}//end while
CounterSpell
  • 123
  • 1
  • 8
  • 5
    Yeah, that works. But I think the OP struggles with knowing the number of rows _before_ actually processing them. Real life reasons I'd have to fight this issue so far: 1.) paging of record rows 2.) showing the rows processed in long-running tasks for progress monitoring purposes... – ppeterka May 24 '13 at 07:23
  • Preallocating data structure size are another reason. I've seen plenty of libs return 10 element Lists when there is only a single value because the dev's had this same issue with ResultSet. – Joseph Lust Nov 21 '13 at 17:24
2
String sql = "select count(*) from message";
ps =  cn.prepareStatement(sql);

rs = ps.executeQuery();
int rowCount = 0;
while(rs.next()) {
    rowCount = Integer.parseInt(rs.getString("count(*)"));
    System.out.println(Integer.parseInt(rs.getString("count(*)")));
}
System.out.println("Count : " + rowCount);
Miss Chanandler Bong
  • 4,081
  • 10
  • 26
  • 36
Peter.Chu
  • 357
  • 5
  • 4
1
theStatement=theConnection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);

ResultSet theResult=theStatement.executeQuery(query); 

//Get the size of the data returned
theResult.last();     
int size = theResult.getRow() * theResult.getMetaData().getColumnCount();       
theResult.beforeFirst();
Ben
  • 19
  • 1
1

I checked the runtime value of the ResultSet interface and found out it was pretty much a ResultSetImpl all the time. ResultSetImpl has a method called getUpdateCount() which returns the value you are looking for.

This code sample should suffice:
ResultSet resultSet = executeQuery(sqlQuery);
double rowCount = ((ResultSetImpl)resultSet).getUpdateCount()

I realize that downcasting is generally an unsafe procedure but this method hasn't yet failed me.

clausavram
  • 546
  • 8
  • 14
  • 3
    Not working with Tomcat/MySQL: `java.lang.ClassCastException: org.apache.tomcat.dbcp.dbcp.DelegatingResultSet cannot be cast to com.mysql.jdbc.ResultSetImpl` – Panu Haaramo Jun 18 '14 at 10:31
  • from the name `getupdatecount`, presumably this would only return a number of rows that gets updated, which wouldn't work if the statement is just reading data – jbu Dec 17 '21 at 10:08
1

Today, I used this logic why I don't know getting the count of RS.

int chkSize = 0;
if (rs.next()) {
    do {  ..... blah blah
        enter code here for each rs.
        chkSize++;
    } while (rs.next());
} else {
    enter code here for rs size = 0 
}
// good luck to u.
0

I was having the same problem. Using ResultSet.first() in this way just after the execution solved it:

if(rs.first()){
    // Do your job
} else {
    // No rows take some actions
}

Documentation (link):

boolean first()
    throws SQLException

Moves the cursor to the first row in this ResultSet object.

Returns:

true if the cursor is on a valid row; false if there are no rows in the result set

Throws:

SQLException - if a database access error occurs; this method is called on a closed result set or the result set type is TYPE_FORWARD_ONLY

SQLFeatureNotSupportedException - if the JDBC driver does not support this method

Since:

1.2

Bas Peeters
  • 3,269
  • 4
  • 33
  • 49
0

Easiest approach, Run Count(*) query, do resultSet.next() to point to the first row and then just do resultSet.getString(1) to get the count. Code :

ResultSet rs = statement.executeQuery("Select Count(*) from your_db");
if(rs.next()) {
   int count = rs.getString(1).toInt()
}
-1

Give column a name..

String query = "SELECT COUNT(*) as count FROM

Reference that column from the ResultSet object into an int and do your logic from there..

PreparedStatement statement = connection.prepareStatement(query);
statement.setString(1, item.getProductId());
ResultSet resultSet = statement.executeQuery();
while (resultSet.next()) {
    int count = resultSet.getInt("count");
    if (count >= 1) {
        System.out.println("Product ID already exists.");
    } else {
        System.out.println("New Product ID.");
    }
}
ReMaX
  • 1
  • 1