Проблема, которую нам нужно регулярно решать на моем рабочем месте, заключается в том, как создавать операторы sql на основе предоставленных пользователем имен таблиц / столбцов. Проблема, которую я пытаюсь решить, - это запятые между именами столбцов.
Один из приемов выглядит примерно так.
selectSql = "SELECT ";
for (z = 0; z < columns.size(); z++)
{
selectSql += columns[z]._name;
selectSql += ", ";
}
selectSql = selectSql(0, selectSql.len() - 2);
selectSql += "FROM some-table";
Другая техника выглядит примерно так
selectSql = "SELECT ";
for (z = 0; z < columns.size(); z++)
{
selectSql += columns[z]._name;
if (z < columns.size() - 1)
selectSql += ", ";
}
selectSql += "FROM some-table";
Меня не особо привлекает ни одна из этих реализаций.
Мне интересно услышать идеи о других способах решения этой проблемы с целью облегчения чтения / понимания / сопровождения кода.
Какие альтернативные методы доступны?





Обычно я строю утверждения:
pad = ""
stmt = "SELECT "
for (i = 0; i < number; i++)
{
stmt += pad + item[i]
pad = ", "
}
Это относительно чисто - он переназначает заполнение для каждой итерации, но это тривиально. Я использовал множество тривиальных вариантов этого, но это самый чистый механизм, который я знаю.
Конечно, будет и чей-то ответ, у которого есть чему поучиться ...
Я собирался сказать, что вы заканчиваете запятыми. Потом я увидел, что ты там делал. Я бы сказал, что этот код немного «сложен», что затрудняет его сопровождение.
Кроме того, вы получаете избыточные назначения для «пэда», что, если это высокопроизводительный цикл, будет расточительным.
@John Dibling: зависит от языка. Чаще всего для меня в C pad - это указатель const char, и присваивание тривиально. Для выполнения инструкции SQL потребуется гораздо больше времени, чем для ее создания.
Я бы предложил создать для этого универсальную функцию соединения. Вы можете использовать, например, алгоритм накопления для объединения столбцов.
Обновлено: См. реализация litb; это гораздо менее наивно.
// Untested
#include <numeric>
template<std::string separator>
struct JoinColumns {
std::string operator()(Column a, Column b) {
return a._name + separator + b._name;
}
// Too lazy to come up with a better name
std::string inArray(T array) {
stl::accumulate(array.begin(), array.end(), std::string(), *this);
}
};
selectSql += stl::accumulate(columns.begin(), columns.end(), std::string(), JoinColumns<", ">());
// or
selectSql += JoinColumns<", ">().inArray(columns);
Конечно, вы можете получить более чистый синтаксис, используя лучшие оболочки.
Это так, но его можно использовать повторно для классов Column. Лучшим подходом было бы добавить какое-то поле в шаблон.
идея хорошая, но реализация неправильная :). нет никаких параметров шаблона, не являющихся целочисленными, не указательными / не ссылочными. поэтому вы не можете просто заставить его принимать std :: string, как указано выше: p
Ха! Я этого не знал. Я не очень хорошо разбираюсь в алгоритмах STL ... Извините.
не беспокойся. Я написал в своем примере, как бы это сделать, и дал вам за это должное :)
for (z = 0; z < columns.size(); z++)
{
if ( z != 0 )
selectSql += ", ";
selectSql += columns[z]._name;
}
Похоже, это работает. Он похож на второй приведенный мною пример.
В вашем случае, вероятно, можно с уверенностью предположить, что существует хотя бы один столбец, поскольку в противном случае нет смысла делать выбор. В этом случае вы можете:
selectSql = "SELECT ";
selectSql += columns[0]._name;
for (z = 1; z < columns.size(); z++) {
selectSql += ", ";
selectSql += columns[z]._name;
}
selectSql += " FROM some-table";
Выглядит тоже чисто, без тестов.
В цикле for неявно есть тест :)
Я, конечно, имею в виду дополнительные тесты в теле цикла или дополнительную обработку вне цикла, чтобы удалить последнюю запятую :)
Мы не убираем конечную запятую. Это потому, что вы можете выбрать константу, а SQL по-прежнему действителен.
SELECT A FROM T
-- Is the same as
SELECT A,1 FROM T
-- Apart from there is an extra column named 1 where each value is 1
Итак, используя STL, чтобы сделать его компактным:
#include <sstream>
#include <iterator>
#include <algorithm>
std::stringstream select;
// Build select statement.
select << "SELECT ";
std::copy(col.begin(),col.end(),std::ostream_iterator<std::string>(select," , "));
select << " 1 FROM TABLE PLOP";
Хорошая идея. "Почему я не подумал об этом?"
Это красиво упрощает. Можно злоупотребить техникой создания столбца и значений в операторе вставки.
Теперь вы возвращаете дополнительные данные для каждой строки возвращаемых данных с сервера, что означает дополнительный трафик по сети. Используйте дополнительную миллиардную долю секунды, чтобы отсортировать ваши столбцы / запятые, вместо того, чтобы принимать удары, которые умножаются на возвращенные строки (которые могут быть большими).
(продолжение) Забыл включить ... это также может означать дополнительную память, используемую на сервере и на клиенте.
@Tom H: Да, торговля есть, как и во всех решениях CS. Вы должны измерить увеличение времени / пространства и посмотреть, стоит ли оно уменьшения сложности (а значит, облегчения сопровождения) кода.
Вместо того, чтобы каждый раз заново применять обходные пути, вы можете решить проблему раз и навсегда, написав объект функции и используя его, например, предложенный Strager (хотя его реализация скорее не на C++):
struct join {
std::string sep;
join(std::string const& sep): sep(sep) { }
template<typename Column>
std::string operator()(Column const& a, Column const& b) const {
return a._name + sep + b._name;
}
};
Так как я не знаю тип вашего столбца, я оставил его в виде шаблона. Теперь, когда вы хотите создать запрос, просто выполните
std::string query = std::accumulate(cols.begin(), cols.end(),
std::string("SELECT "), join(", ")) + " FROM some-table;";
Это не должно быть так сложно.
string sql = "SELECT " + join(cols.begin(), cols.end(), ", ") + " FROM some_table";
где
template <typename I>
string join(I begin, I end, const string& sep){
ostringstream out;
for(; begin != end; ++begin){
out << *begin;
if (begin+1 != end) out << sep;
}
return out.str();
}
Не вдаваясь в подробности, но взгляните на boost :: algorithm :: join (). Вот пример на тот случай, если вы считаете, что их документация слишком сложна для слов:
std::string
build_sql(std::vector<std::string> const& colNames,
std::string const& tableName)
{
std::ostringstream sql;
sql << "SELECT "
<< boost::algorithm::join(colNames, std::string(","))
<< " FROM " << tableName;
return sql.str();
}
В случае сомнений загляните на Boost.org. Обычно у них уже есть решение большинства подобных проблем.
Я использую аналогичный метод в своем быстром и грязном коде, за исключением того, что использую bool, чтобы проверить, нужно ли мне печатать запятую.