В проекте мне не разрешено использовать сторонний код (например, GRDB, Swift.sqlite), поэтому мне нужно напрямую использовать функции sqlite3 c-wrapper Swift.
Мой оператор SQL является переменным, основанным на определенных флагах.
var sql = "SELECT * FROM myTable WHERE "
sql += " colA = ? "
sql += " AND (colB > ? OR colB < ?) "
if flagC == true {
sql += " AND colC = ? "
}
sql += " AND colD = ? "
var stmt = OpaquePointer?
guard sqlite3_prepare_v2(dbPointer, sql, -1, &stmt, nil) == SQLITE_OK
else { print("error") }
guard sqlite3_bind_text(stmt, 1, (myVarA as NSString).utf8String, nil) == SQLITE_OK &&
sqlite3_bind_double(stmt, 2, myVarB, nil) == SQLITE_OK &&
sqlite3_bind_double(stmt, 3, myVarB1, nil) == SQLITE_OK
else { print("error") }
var nextPosition = 4
if flagC == true {
guard sqlite3_bind_int(stmt, nextPosition, myVarC, nil) == SQLITE_OK
else { print("error") }
nextPosition += 1
}
guard sqlite3_bind_double(stmt, nextPosition, myVarD, nil) == SQLITE_OK
else { print("error") }
while sqlite3_step(stmt) == SQLITE_ROW {
// deal with result
}
Это работает, однако кажется довольно неуклюжим из-за прерывания операторов guard для размещения потенциальных параметров.
Единственный способ сделать привязки позиций параметров «динамическими» — это использовать вспомогательную переменную nextPosition.
Кажется неправильным «жестко закодировать» положение привязок. Есть ли способ решить эту проблему без жесткого кодирования этих позиций?
Жестко закодированные числовые индексы так же жестко закодированы, как и сама необработанная строка SQL - не лучше, но и не хуже. Я бы не слишком беспокоился о них. 1, 2, 3? ОК: это первый, второй и третий параметр - это понятно.
Я согласен с тем, что мы могли бы улучшить ваш пример кода, если бы потребность в nextPosition исчезла. Я перечисляю несколько вариантов ниже.
Одним из возможных улучшений является перемещение теста flagC в конец при построении SQL-запроса:
var sql = "SELECT * FROM myTable WHERE "
sql += " colA = ? "
sql += " AND (colB > ? OR colB < ?) "
sql += " AND colD = ? "
if flagC == true {
sql += " AND colC = ? "
}
Вы по-прежнему будете использовать жестко заданные индексы (1, 2, 3, 4), но вам больше не понадобится nextPosition.
Еще один метод заключается в использовании именованных параметров:
var sql = "SELECT * FROM myTable WHERE "
sql += " colA = :a "
sql += " AND (colB > :minB OR colB < :maxB) "
if flagC == true {
sql += " AND colC = :c "
}
sql += " AND colD = :d "
Вместо того, чтобы указывать числовые индексы для ваших параметров (1, 2, 3, nextPosition), вы должны использовать sqlite3_bind_parameter_index, чтобы превратить имена параметров (:a, :minB и т. д.) в индексы.
Наконец, еще один подход полностью состоит в том, чтобы заранее протестировать flagC и полностью независимо обрабатывать оба SQL-запроса. Это устранит вашу потребность в nextPosition.
Спасибо, Джозеф :-) Счастливый SQLite - это многословно, но все же ваш друг!
У меня довольно много случаев, подобных примеру flagC, поэтому перемещение в конец не очень помогает. Но использование sqlite3_bind_parameter_index отлично решило бы мою проблему. Спасибо - и я обычно использую GRDB - и мне это нравится. 🤣 Выполнение этого проекта "вручную" действительно дало мне дополнительную оценку! Спасибо за создание этой замечательной библиотеки.