add function getRandomRecords in Database class
This commit is contained in:
parent
f81ef8249c
commit
eaa86fd709
248
src/database.cpp
248
src/database.cpp
|
|
@ -35,8 +35,7 @@ json Database::getRandomRecord() {
|
|||
pqxx::work txn(*conn);
|
||||
|
||||
// Получаем общее количество записей
|
||||
auto countResult = txn.exec("SELECT COUNT(*) FROM random_data");
|
||||
int totalCount = countResult[0][0].as<int>();
|
||||
int totalCount = getTotalCount();
|
||||
|
||||
if (totalCount == 0) {
|
||||
return json{
|
||||
|
|
@ -76,3 +75,248 @@ json Database::getRandomRecord() {
|
|||
return response;
|
||||
}
|
||||
|
||||
// БЫСТРЫЙ ВАРИАНТ: Использует ORDER BY RANDOM() LIMIT
|
||||
|
||||
json Database::getRandomRecordsFast(int count) {
|
||||
pqxx::work txn(*conn);
|
||||
|
||||
try {
|
||||
// Рекурсивный CTE запрос для получения случайных записей
|
||||
std::string query =
|
||||
"WITH RECURSIVE random_selection AS ("
|
||||
" WITH bounds AS ("
|
||||
" SELECT"
|
||||
" MIN(t.id) AS min_id,"
|
||||
" MAX(t.id) AS max_id"
|
||||
" FROM random_data AS t"
|
||||
" ),"
|
||||
" first_row AS ("
|
||||
" SELECT"
|
||||
" t.id,"
|
||||
" b.min_id,"
|
||||
" b.max_id,"
|
||||
" ARRAY[t.id] AS selected_ids,"
|
||||
" 1 AS depth"
|
||||
" FROM random_data AS t, bounds AS b"
|
||||
" WHERE"
|
||||
" t.id >= b.min_id + ((b.max_id - b.min_id) * random())::INT"
|
||||
" ORDER BY t.id"
|
||||
" LIMIT 1"
|
||||
" ),"
|
||||
" recursive_rows AS ("
|
||||
" SELECT"
|
||||
" t.id,"
|
||||
" r.min_id,"
|
||||
" r.max_id,"
|
||||
" r.selected_ids || t.id AS selected_ids,"
|
||||
" r.depth + 1 AS depth"
|
||||
" FROM random_data AS t, first_row AS r"
|
||||
" WHERE"
|
||||
" t.id >= r.min_id + ((r.max_id - r.min_id) * random())::INT"
|
||||
" AND NOT (t.id = ANY(r.selected_ids))"
|
||||
" AND r.depth + 1 < " + std::to_string(count + 1) +
|
||||
" ORDER BY t.id"
|
||||
" LIMIT 1"
|
||||
" )"
|
||||
" SELECT * FROM first_row"
|
||||
" UNION ALL"
|
||||
" SELECT * FROM recursive_rows"
|
||||
")"
|
||||
"SELECT"
|
||||
" rd.id,"
|
||||
" rd.name,"
|
||||
" rd.value,"
|
||||
" rd.description,"
|
||||
" rd.created_at"
|
||||
"FROM random_selection AS rs"
|
||||
"JOIN random_data AS rd ON rd.id = rs.id"
|
||||
"ORDER BY rs.depth";
|
||||
|
||||
auto result = txn.exec(query);
|
||||
|
||||
if (result.empty()) {
|
||||
return json{
|
||||
{"error", "No records found in database"},
|
||||
{"count", 0}
|
||||
};
|
||||
}
|
||||
|
||||
json response = json::array();
|
||||
for (const auto& row : result) {
|
||||
response.push_back({
|
||||
{"id", row[0].as<int>()},
|
||||
{"name", row[1].as<std::string>()},
|
||||
{"value", row[2].as<int>()},
|
||||
{"description", row[3].as<std::string>()},
|
||||
{"created_at", row[4].as<std::string>()}
|
||||
});
|
||||
}
|
||||
|
||||
txn.commit();
|
||||
|
||||
return {
|
||||
{"data", response},
|
||||
{"count", static_cast<int>(response.size())},
|
||||
{"strategy", "fast_recursive_cte"},
|
||||
{"timestamp", std::chrono::system_clock::now().time_since_epoch().count()}
|
||||
};
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
txn.abort();
|
||||
return json{
|
||||
{"error", "Database error in fast recursive CTE strategy"},
|
||||
{"message", e.what()}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// МЕДЛЕННЫЙ ВАРИАНТ: Генерирует ID на клиенте, затем запрашивает по IN
|
||||
|
||||
json Database::getRandomRecordsSlow(int count) {
|
||||
// Шаг 1: Получаем общее количество записей с блокировкой
|
||||
int totalCount = 0;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
|
||||
pqxx::work txn(*conn);
|
||||
auto countResult = txn.exec("SELECT COUNT(*) FROM random_data");
|
||||
totalCount = countResult[0][0].as<int>();
|
||||
txn.commit();
|
||||
}
|
||||
|
||||
if (totalCount == 0) {
|
||||
return json{
|
||||
{"error", "No records found in database"},
|
||||
{"count", 0}
|
||||
};
|
||||
}
|
||||
|
||||
if (count > totalCount) {
|
||||
count = totalCount;
|
||||
}
|
||||
|
||||
// Шаг 2: Генерируем уникальные случайные ID
|
||||
std::set<int> ids;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
std::uniform_int_distribution<int> idDist(1, totalCount);
|
||||
|
||||
while (ids.size() < static_cast<size_t>(count)) {
|
||||
ids.insert(idDist(rng));
|
||||
}
|
||||
}
|
||||
|
||||
// Шаг 3: Запрашиваем записи по сгенерированным ID
|
||||
pqxx::work txn(*conn);
|
||||
|
||||
try {
|
||||
if (ids.empty()) {
|
||||
return json{
|
||||
{"data", json::array()},
|
||||
{"count", 0},
|
||||
{"strategy", "slow"}
|
||||
};
|
||||
}
|
||||
|
||||
// Формируем запрос с IN
|
||||
std::string query =
|
||||
"SELECT id, name, value, description, created_at "
|
||||
"FROM random_data WHERE id IN (";
|
||||
|
||||
// Подготавливаем параметры
|
||||
std::vector<std::string> placeholders;
|
||||
for (size_t i = 0; i < ids.size(); ++i) {
|
||||
if (i > 0) query += ",";
|
||||
query += "$" + std::to_string(i + 1);
|
||||
}
|
||||
query += ") ORDER BY id";
|
||||
|
||||
// Подготавливаем параметры для exec_params
|
||||
pqxx::params param_list;
|
||||
for (int id : ids) {
|
||||
param_list.append(id);
|
||||
}
|
||||
|
||||
auto result = txn.exec_params(query, param_list);
|
||||
|
||||
json response = json::array();
|
||||
for (const auto& row : result) {
|
||||
response.push_back({
|
||||
{"id", row[0].as<int>()},
|
||||
{"name", row[1].as<std::string>()},
|
||||
{"value", row[2].as<int>()},
|
||||
{"description", row[3].as<std::string>()},
|
||||
{"created_at", row[4].as<std::string>()}
|
||||
});
|
||||
}
|
||||
|
||||
txn.commit();
|
||||
|
||||
return {
|
||||
{"data", response},
|
||||
{"count", static_cast<int>(response.size())},
|
||||
{"strategy", "slow"},
|
||||
{"timestamp", std::chrono::system_clock::now().time_since_epoch().count()}
|
||||
};
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
txn.abort();
|
||||
return json{
|
||||
{"error", "Database error in slow strategy"},
|
||||
{"message", e.what()}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// ГЛАВНАЯ ФУНКЦИЯ: Выбирает стратегию в зависимости от count
|
||||
|
||||
json Database::getRandomRecords(int count) {
|
||||
// Валидация входных параметров
|
||||
if (count <= 0) {
|
||||
return json{
|
||||
{"error", "Count must be positive"},
|
||||
{"count", 0}
|
||||
};
|
||||
}
|
||||
|
||||
// Защита от слишком больших запросов
|
||||
const int MAX_RECORDS = 1000;
|
||||
if (count > MAX_RECORDS) {
|
||||
count = MAX_RECORDS;
|
||||
}
|
||||
|
||||
// Выбор стратегии:
|
||||
// - Для малых выборок (<= 10) используем медленный, но более точный способ
|
||||
// - Для больших выборок (> 10) используем быстрый способ с рекурсивным CTE запросом
|
||||
const int SLOW_STRATEGY_LIMIT = 10;
|
||||
|
||||
json response;
|
||||
if (count <= SLOW_STRATEGY_LIMIT) {
|
||||
response = getRandomRecordsSlow(count);
|
||||
// Добавляем информацию о стратегии в ответ
|
||||
response["strategy"] = "slow";
|
||||
response["strategy_reason"] = "count <= " + std::to_string(SLOW_STRATEGY_LIMIT) +
|
||||
" (more precise random distribution)";
|
||||
} else {
|
||||
response = getRandomRecordsFast(count);
|
||||
// Добавляем информацию о стратегии в ответ
|
||||
response["strategy"] = "fast";
|
||||
response["strategy_reason"] = "count > " + std::to_string(SLOW_STRATEGY_LIMIT) +
|
||||
" (better performance for large datasets)";
|
||||
}
|
||||
|
||||
// Добавляем общую мета-информацию
|
||||
response["requested_count"] = count;
|
||||
response["total_available"] = getTotalCount();
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
// Вспомогательный метод для получения общего количества записей
|
||||
int Database::getTotalCount() {
|
||||
pqxx::work txn(*conn);
|
||||
auto result = txn.exec("SELECT COUNT(*) FROM random_data");
|
||||
int count = result[0][0].as<int>();
|
||||
txn.commit();
|
||||
return count;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@
|
|||
#include <random>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include "config.hpp"
|
||||
#include "common.hpp"
|
||||
|
|
@ -15,6 +17,7 @@ public:
|
|||
~Database() = default;
|
||||
|
||||
json getRandomRecord();
|
||||
json getRandomRecords(int count = 5);
|
||||
|
||||
private:
|
||||
std::unique_ptr<pqxx::connection> conn;
|
||||
|
|
@ -22,5 +25,10 @@ private:
|
|||
std::mutex mtx;
|
||||
// Создание тестовой таблицы, если она не существует
|
||||
void ensureTableExists();
|
||||
|
||||
int getTotalCount();
|
||||
// Две стратегии получения случайных записей
|
||||
json getRandomRecordsFast(int count);
|
||||
json getRandomRecordsSlow(int count);
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue