2025, Nov 26 00:01

Тихий баг в SQLite и Python из‑за двойных кавычек: как получить ошибку no such column

Почему SQLite с Python молча возвращает 0 из‑за двойных кавычек и как получить no such column: используйте квадратные скобки или обратные апострофы в SQL.

При работе с SQLite3 из Python неочевидное правило кавычек способно незаметно превратить ошибку в схеме в тихий логический баг. Неправильно набранный или отсутствующий столбец, заключённый в двойные кавычки, не обязательно вызовет ошибку. В определённых условиях SQLite трактует его как строковый литерал, и запрос продолжает выполняться, возвращая на вид корректный результат, например 0. Ниже показано, как это проявляется и как заставить базу данных выдавать реальную ошибку, если столбца нет.

Как воспроизвести проблему

import sqlite3

DB = sqlite3.connect(':memory:')
cur = DB.cursor()

cur.execute('''
CREATE TABLE schools (
    county TEXT,
    statustype TEXT
)
''')

cur.execute('''
INSERT INTO schools VALUES ('some_county', 'some_status')
''')

cur.execute('''
SELECT COUNT(*) FROM schools 
WHERE county = 'Alpine' 
  AND statustype IN ('Active', 'Closed') 
  AND "School Type" = 'District Community Day Schools'
''')

rows_found = cur.fetchone()[0]
print(f"Count result: {rows_found}")

DB.close()

В таблице намеренно нет столбца с именем School Type. Тем не менее запрос отрабатывает, и результат — 0, а не ошибка.

Что происходит на самом деле

В стандартном SQL двойные кавычки используются для выделения идентификаторов. Однако в SQLite есть режим совместимости: если токен в двойных кавычках не распознаётся как корректный идентификатор, он интерпретируется как строковый литерал. В результате выражение "School Type" = 'District Community Day Schools' превращается в сравнение строки со строкой, а не столбца со значением. Поскольку литерал "School Type" не равен строке 'District Community Day Schools', предикат всегда ложен, и COUNT(*) просто возвращает 0.

Любопытное следствие этого же правила: строка в двойных кавычках, сравниваемая с такой же строкой в двойных кавычках, будет истинной. Например, сравнение "District Community Day Schools" = "District Community Day Schools" даст true, что ещё сильнее маскирует тот факт, что на самом деле столбец не был задействован.

Как принудительно получить ошибку для несуществующих столбцов

Чтобы заставить SQLite трактовать такой токен именно как идентификатор и сообщать об ошибке при отсутствии столбца, используйте квадратные скобки или обратные апострофы для его выделения. С такими ограничителями движок пытается разрешить идентификатор и корректно падает с сообщением вроде «no such column».

import sqlite3

dbh = sqlite3.connect(':memory:')
q = dbh.cursor()

q.execute('''
CREATE TABLE schools (
    county TEXT,
    statustype TEXT
)
''')

q.execute('''
INSERT INTO schools VALUES ('some_county', 'some_status')
''')

q.execute('''
SELECT COUNT(*) FROM schools 
WHERE county = 'Alpine' 
  AND statustype IN ('Active', 'Closed') 
  AND [School Type] = 'District Community Day Schools'
''')

count_total = q.fetchone()[0]
print(f"Count result: {count_total}")

dbh.close()

Запуск запроса с [School Type] (или `School Type`) приведёт к ошибке, потому что такого столбца нет — именно этого поведения и стоит добиваться, когда вы хотите, чтобы база данных ловила проблемы со схемой.

Почему это важно

Тихие сбои опасны. Когда опечатка или отсутствующий столбец превращаются в сравнение строк, запрос всё равно возвращает формально корректный результат, но логически неверный. Такая проблема легко ускользнёт в продакшен, стабильно выдавая нули, неверно отфильтровывая строки или даже иногда «находя совпадения», если случайно сравнить одну и ту же строку в двойных кавычках с самой собой. Ради качества данных, экономии времени на отладку и безопасности при рефакторинге лучше сразу получать понятную ошибку.

Практические рекомендации

Если нужно обращаться к идентификаторам с пробелами, используйте квадратные скобки или обратные апострофы — так SQLite будет обрабатывать их как идентификаторы и сообщать об отсутствии столбцов. Ещё надёжнее — вовсе избегать пробелов в названиях и использовать, например, school_type. Согласно документации SQLite, в новых версиях возможность трактовать строки в двойных кавычках как строковые литералы может быть отключена во время выполнения, что помогает убрать эту категорию путаницы.

Итоги

Если запрос к SQLite тихо возвращает 0 вместо ошибки из-за отсутствующего столбца, в первую очередь проверьте двойные кавычки вокруг идентификаторов. Когда нужного идентификатора нет, двойные кавычки могут превратить его в строковый литерал — и ошибка схемы станет логической ошибкой. Используйте квадратные скобки или обратные апострофы, когда нужно добиться строгого разрешения столбцов, или выбирайте имена без пробелов — так запросы будут предсказуемыми и устойчивыми.