Ошибка [HY010] [Microsoft][ODBC Driver 13 for SQL Server]Function sequence error (0) (SQLFetch) при записи в файл

Рейтинг: 1Ответов: 1Опубликовано: 02.05.2023

Делаю выгрузку из БД (диалект Microsoft SQL Server).
Программа запускается на сервере под Линуксом, ресурс оперативной памяти ограничен.
Поэтому использую функцию pd.read_sql_query() c параметром chunksize.

Порядок действий:

  1. Подключиться к БД и получить объект Iterator[DataFrame]

    @time_of_function
    def _get_chunks_iterator(url_object: URL, query: text, chunk_size: int = 10000) -> Iterator[DataFrame]:
    
        engine = create_engine(url_object)
        with engine.connect() as cnxn:
            return read_sql_query(query, cnxn, chunksize=chunk_size)
    
  2. Из полученного итератора перебрать все датафреймы и записать в файл
    одной из фукнций (в зависимости от переданного формата).

  • Excel:

    @time_of_function
    def _make_xl_out_of_iterator(chunks_iterator: Iterator[DataFrame], path: str) -> None:
        with ExcelWriter(path, engine="openpyxl", mode='w') as writer:
            chunk = next(chunks_iterator)
            chunk.to_excel(writer, index=False, engine='openpyxl')
            # первую строку займут заголовки
            # строка len(chunk) + 1 будет последней непустой
            # строка len(chunk) + 2 будет первой свободной для записи
            first_empty_line = chunk.shape[0] + 2
    
        for chunk in chunks_iterator:
            update_spreadsheet(path, chunk, starcol=1, startrow=first_empty_line)
            first_empty_line += chunk.shape[0]
    
  • CSV:

    @time_of_function
    def _make_csv_out_of_iterator(chunks_iterator: Iterator[DataFrame], path: str) -> None:
        with open(path, 'w') as f_out:
            next(chunks_iterator).to_csv(f_out, index=False)  # запись первого чанка с названием столбцов
    
            for chunk in chunks_iterator:
                chunk.to_csv(f_out, index=False, header=False)  # пропускаем запись названий столбцов
    

Где возникает ошибка?

Функция _get_chunks_iterator() отрабатывает без вызова ошибок.
Но при вызове одной одной из функций пункта (2) возникает ошибка с таким трейсбеком:

Traceback (most recent call last):
  File "C:\Users\U_M1GT7\SHELTER\AAProjects\ossktb-rcsite-reporter-api\venv\lib\site-packages\sqlalchemy\engine\cursor.py", line 1138, in fetchmany
    l = dbapi_cursor.fetchmany(size)
pyodbc.Error: ('HY010', '[HY010] [Microsoft][ODBC Driver 13 for SQL Server]Function sequence error (0) (SQLFetch)')

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "C:\Users\U_M1GT7\SHELTER\AAProjects\ossktb-rcsite-reporter-api\auxiliary\df_itarator_to_excel.py", line 324, in <module>
    prepare_report_file(sql3, absolute_path=CSV_REPORT_PATH, file_format='csv')
  File "C:\Users\U_M1GT7\SHELTER\AAProjects\ossktb-rcsite-reporter-api\auxiliary\df_itarator_to_excel.py", line 28, in wrapper
    res = function(*args, **kwargs)
  File "C:\Users\U_M1GT7\SHELTER\AAProjects\ossktb-rcsite-reporter-api\auxiliary\df_itarator_to_excel.py", line 188, in prepare_report_file
    _make_csv_out_of_iterator(chunks_iter, absolute_path)
  File "C:\Users\U_M1GT7\SHELTER\AAProjects\ossktb-rcsite-reporter-api\auxiliary\df_itarator_to_excel.py", line 28, in wrapper
    res = function(*args, **kwargs)
  File "C:\Users\U_M1GT7\SHELTER\AAProjects\ossktb-rcsite-reporter-api\auxiliary\df_itarator_to_excel.py", line 173, in _make_csv_out_of_iterator
    next(chunks_iterator).to_csv(f_out, index=False)  # запись первого чанка с названием столбцов
  File "C:\Users\U_M1GT7\SHELTER\AAProjects\ossktb-rcsite-reporter-api\venv\lib\site-packages\pandas\io\sql.py", line 1480, in _query_iterator
    data = result.fetchmany(chunksize)
  File "C:\Users\U_M1GT7\SHELTER\AAProjects\ossktb-rcsite-reporter-api\venv\lib\site-packages\sqlalchemy\engine\result.py", line 1346, in fetchmany
    return self._manyrow_getter(self, size)
  File "C:\Users\U_M1GT7\SHELTER\AAProjects\ossktb-rcsite-reporter-api\venv\lib\site-packages\sqlalchemy\engine\result.py", line 697, in manyrows
    rows: List[_InterimRowType[Any]] = self._fetchmany_impl(num)
  File "C:\Users\U_M1GT7\SHELTER\AAProjects\ossktb-rcsite-reporter-api\venv\lib\site-packages\sqlalchemy\engine\cursor.py", line 2107, in _fetchmany_impl
    return self.cursor_strategy.fetchmany(self, self.cursor, size)
  File "C:\Users\U_M1GT7\SHELTER\AAProjects\ossktb-rcsite-reporter-api\venv\lib\site-packages\sqlalchemy\engine\cursor.py", line 1144, in fetchmany
    self.handle_exception(result, dbapi_cursor, e)
  File "C:\Users\U_M1GT7\SHELTER\AAProjects\ossktb-rcsite-reporter-api\venv\lib\site-packages\sqlalchemy\engine\cursor.py", line 1097, in handle_exception
    result.connection._handle_dbapi_exception(
  File "C:\Users\U_M1GT7\SHELTER\AAProjects\ossktb-rcsite-reporter-api\venv\lib\site-packages\sqlalchemy\engine\base.py", line 2326, in _handle_dbapi_exception
    raise sqlalchemy_exception.with_traceback(exc_info[2]) from e
  File "C:\Users\U_M1GT7\SHELTER\AAProjects\ossktb-rcsite-reporter-api\venv\lib\site-packages\sqlalchemy\engine\cursor.py", line 1138, in fetchmany
    l = dbapi_cursor.fetchmany(size)
sqlalchemy.exc.DBAPIError: (pyodbc.Error) ('HY010', '[HY010] [Microsoft][ODBC Driver 13 for SQL Server]Function sequence error (0) (SQLFetch)')
(Background on this error at: https://sqlalche.me/e/20/dbapi)

Process finished with exit code 1

Всё ломается, когда пытаюсь применить функцию next() к полученному итератору.

Вопросы:

  1. Корректно ли я получаю данные из БД, разбивая на чанки?
  2. Что может приводить к ошибке Function sequence error (0) (SQLFetch)?
  3. Как её исправить?

P.S.

Я нашёл такое решение: https://issuehint.com/issue/groveco/django-sql-explorer/423
Но я не понимаю, подходит ли оно мне и как его использовать (где менять эти значения на False) Ответ другого пользователя

Прошу подсказать, спасибо за любую помощь.

Ответы

▲ 0

Проблема оказалась простой. Её корни в этой функции:

@time_of_function
def _get_chunks_iterator(url_object: URL, query: text, chunk_size: int = 10000) -> Iterator[DataFrame]:

    engine = create_engine(url_object)
    with engine.connect() as cnxn:
        return read_sql_query(query, cnxn, chunksize=chunk_size)

Нарушена логика работы с БД. Я открыл соединение cnxn внутри конструкции with.
Пока существует это соединение, я могу итерироваться по результату, который возвращает БД.
Но, как только функция _get_chunks_iterator() завершается и возвращает ссылку на итератор, эта ссылка становится бесполезной, так как конструкция with всё за собой подчищает (закрывает созданный процесс или что-то подобное, тут не до конца разобрался).

Решение: получить итератор от функции read_sql_query(..., chunksize=chunk_size) и обработать его внутри конструкции with.

В результате написал такую функцию:

def prepare_report_file(server: str,
                        sql_query: str,
                        absolute_path: str,
                        file_format: str = 'xlsx',
                        ) -> None:
    """
    Создать и сохранить файл в примонтированную папку.

    :param server: название сервера с БД
    :param sql_query: текст запроса, полученный из формы на сайте
    :param absolute_path: полный адрес, по которому должен быть сохранен файл
    :param file_format: формат файла ('xlsx' или 'csv')
    :return: None
    """
    sql_query = text(sql_query)

    conn_url = prepare_connection_url(host=server)
    engine = create_engine(conn_url)

    with engine.connect().execution_options(stream_results=True) as conn:
        chunks_iter = pd.read_sql(sql_query, conn, chunksize=OPTIMAL_CHUNKSIZE)

        if file_format == 'xlsx':
            _make_xl_out_of_iterator(chunks_iter, absolute_path)
        elif file_format == 'csv':
            _make_csv_out_of_iterator(chunks_iter, absolute_path)

С ней такой ошики больше не возникает.