diff --git a/doc/source/whatsnew/v1.3.0.rst b/doc/source/whatsnew/v1.3.0.rst index 196d2f2d968a7..0cb46a5164674 100644 --- a/doc/source/whatsnew/v1.3.0.rst +++ b/doc/source/whatsnew/v1.3.0.rst @@ -281,6 +281,7 @@ I/O - :func:`read_excel` now respects :func:`set_option` (:issue:`34252`) - Bug in :func:`read_csv` not switching ``true_values`` and ``false_values`` for nullable ``boolean`` dtype (:issue:`34655`) - Bug in :func:`read_json` when ``orient="split"`` does not maintain numeric string index (:issue:`28556`) +- :meth:`read_sql` returned an empty generator if ``chunksize`` was no-zero and the query returned no results. Now returns a generator with a single empty dataframe (:issue:`34411`) Period ^^^^^^ diff --git a/pandas/io/sql.py b/pandas/io/sql.py index bbc5e6ad82493..e1af3169420fc 100644 --- a/pandas/io/sql.py +++ b/pandas/io/sql.py @@ -926,11 +926,17 @@ def _query_iterator( parse_dates=None, ): """Return generator through chunked result set.""" + has_read_data = False while True: data = result.fetchmany(chunksize) if not data: + if not has_read_data: + yield DataFrame.from_records( + [], columns=columns, coerce_float=coerce_float + ) break else: + has_read_data = True self.frame = DataFrame.from_records( data, columns=columns, coerce_float=coerce_float ) @@ -1343,11 +1349,21 @@ def _query_iterator( dtype: Optional[DtypeArg] = None, ): """Return generator through chunked result set""" + has_read_data = False while True: data = result.fetchmany(chunksize) if not data: + if not has_read_data: + yield _wrap_result( + [], + columns, + index_col=index_col, + coerce_float=coerce_float, + parse_dates=parse_dates, + ) break else: + has_read_data = True yield _wrap_result( data, columns, @@ -1849,14 +1865,20 @@ def _query_iterator( dtype: Optional[DtypeArg] = None, ): """Return generator through chunked result set""" + has_read_data = False while True: data = cursor.fetchmany(chunksize) if type(data) == tuple: data = list(data) if not data: cursor.close() + if not has_read_data: + yield DataFrame.from_records( + [], columns=columns, coerce_float=coerce_float + ) break else: + has_read_data = True yield _wrap_result( data, columns, diff --git a/pandas/tests/io/test_sql.py b/pandas/tests/io/test_sql.py index 6fb120faa6db2..b70bc3c598702 100644 --- a/pandas/tests/io/test_sql.py +++ b/pandas/tests/io/test_sql.py @@ -661,6 +661,12 @@ def test_read_sql_view(self): iris_frame = sql.read_sql_query("SELECT * FROM iris_view", self.conn) self._check_iris_loaded_frame(iris_frame) + def test_read_sql_with_chunksize_no_result(self): + query = "SELECT * FROM iris_view WHERE SepalLength < 0.0" + with_batch = sql.read_sql_query(query, self.conn, chunksize=5) + without_batch = sql.read_sql_query(query, self.conn) + tm.assert_frame_equal(pd.concat(with_batch), without_batch) + def test_to_sql(self): sql.to_sql(self.test_frame1, "test_frame1", self.conn) assert sql.has_table("test_frame1", self.conn)