Ответ
Использование нативного SQL оправдано, когда ORM (Hibernate, JPA) генерирует неэффективные запросы или не поддерживает специфичные функции СУБД.
Ключевые сценарии:
-
Сложные аналитические запросы (OLAP):
- Оконные функции (
ROW_NUMBER(),LAG(),LEAD(), агрегаты сOVER). - Рекурсивные запросы (
WITH RECURSIVE). - Группировки и агрегация с
CUBE,ROLLUP,GROUPING SETS.-- Пример: ранжирование с помощью оконной функции SELECT user_id, order_date, amount, SUM(amount) OVER (PARTITION BY user_id ORDER BY order_date) AS running_total FROM orders WHERE order_date >= '2024-01-01';
- Оконные функции (
-
Массовые операции (Bulk Updates/Deletes):
- ORM может загружать сущности в память, что неэффективно. Нативный SQL выполнит одну операцию на стороне БД.
-- Эффективное обновление UPDATE orders SET status = 'ARCHIVED' WHERE created_at < '2023-01-01';
- ORM может загружать сущности в память, что неэффективно. Нативный SQL выполнит одну операцию на стороне БД.
-
Использование специфичных функций СУБД:
- Геопространственные функции (PostGIS в PostgreSQL).
- Полнотекстовый поиск (
tsvector/tsqueryв PostgreSQL,MATCH ... AGAINSTв MySQL). - JSON/XML функции для нереляционных данных внутри БД.
-
Оптимизация производительности критичных запросов:
- Когда DBA или разработчик должен явно контролировать план выполнения, использовать хинты или определенные индексы.
-
Создание и модификация схемы БД (DDL):
- Создание сложных индексов (частичных, составных), триггеров, материализованных представлений.
Как использовать в Java (Spring JPA):
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
// Использование @Query с nativeQuery = true
@Query(value =
"SELECT * FROM orders o " +
"WHERE o.region_id = :regionId " +
"AND earth_distance(ll_to_earth(o.lat, o.lng), ll_to_earth(:lat, :lng)) < :radius",
nativeQuery = true)
List<Order> findOrdersInRadius(@Param("regionId") Long regionId,
@Param("lat") double latitude,
@Param("lng") double longitude,
@Param("radius") double radiusMeters);
// Массовое обновление через @Modifying
@Modifying
@Query(value = "UPDATE orders SET priority = :newPriority WHERE status = 'PENDING'",
nativeQuery = true)
@Transactional
int bulkUpdatePriority(@Param("newPriority") int priority);
}
Риски: Нативный SQL теряет переносимость между СУБД и может быть подвержен SQL-инъекциям, если не использовать параметризованные запросы.
Ответ 18+ 🔞
А, ну вот, опять про этот ваш нативный SQL! Слушай, а ты знаешь, что ORM — это как тёща: вроде удобно, но иногда такую хуйню сморозит, что волосы дыбом встают. Ну, типа, Hibernate там, JPA... Они же иногда запросы такие строят, что хочется глаза выколоть и бежать в монастырь.
Так вот, когда надо брать в руки этот самый нативный SQL и ебашить как мужик:
-
Сложные штуки, где мозг ORM плавится. Ну, там, оконные функции эти,
ROW_NUMBER(),LAG()— вся эта аналитическая магия. ORM на это смотрит как баран на новые ворота. Или рекурсивные запросы,WITH RECURSIVE— это вообще за гранью его понимания, там у него предохранители выбивает.-- Смотри, как красиво: бегущая сумма по пользователю. ORM будет полчаса кряхтеть, а тут — раз, и готово. SELECT user_id, order_date, amount, SUM(amount) OVER (PARTITION BY user_id ORDER BY order_date) AS running_total FROM orders WHERE order_date >= '2024-01-01'; -
Когда надо обновить или удалить ОВЕРДОХУИЩА записей. А ORM что делает? Он их все в память грузит, сущности создаёт, потом по одной штучке обновляет и плачет. Это ж пиздец, а не подход! Нативный SQL приходит и делает одним махом: "Так, всё, что старше 2023 года — в архив!". И БД сама, быстро, без соплей.
UPDATE orders SET status = 'ARCHIVED' WHERE created_at < '2023-01-01'; -
Когда нужны фишки конкретной базы. Ну, типа, в PostgreSQL есть PostGIS для работы с координатами. Или полнотекстовый поиск. ORM про это нихуя не знает, он же для всех один. А тут ты можешь выжать из базы все соки, как лимон.
-
Когда запрос тормозит, а ты его должен вручную отшлифовать. DBA посмотрел на план запроса от Hibernate, перекрестился и сказал: "Чувак, тут только нативный SQL и хинты спасут". Иногда надо взять управление на себя, а не надеяться на автосборку.
-
Когда надо наколдовать что-то на уровне схемы. Создать хитрый индекс, триггер, материализованное представление — ORM для этого не предназначен, это как молотком гвозди забивать. Бери SQL и делай как надо.
А вот как это в Java (Spring JPA) выглядит, чтоб ты не думал, что это шаманство:
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
// Всё просто: говоришь, что запрос нативный, и пишешь его как есть.
@Query(value =
"SELECT * FROM orders o " +
"WHERE o.region_id = :regionId " +
"AND earth_distance(ll_to_earth(o.lat, o.lng), ll_to_earth(:lat, :lng)) < :radius",
nativeQuery = true) // <-- Вот этот волшебный флажок!
List<Order> findOrdersInRadius(@Param("regionId") Long regionId,
@Param("lat") double latitude,
@Param("lng") double longitude,
@Param("radius") double radiusMeters);
// А для массового обновления ещё и @Modifying нужен.
@Modifying
@Query(value = "UPDATE orders SET priority = :newPriority WHERE status = 'PENDING'",
nativeQuery = true)
@Transactional // Не забудь про транзакцию, а то будешь потом искать, почему ничего не обновилось.
int bulkUpdatePriority(@Param("newPriority") int priority);
}
НО! Предупреждаю сразу, как мать родная: с великой силой приходит и великая ответственность, ёпта.
- Переносимость — нахуй улетела. Запустишь этот запрос под другой БД — получишь ошибку размером с КамАЗ.
- SQL-инъекции — если будешь строки склеивать как дешёвые колготки, типа
"WHERE name = '" + userName + "'", то тебя взломают через неделю, и ты будешь объяснять, почему в твоей базе появились таблицыbitcoin_walletsиhackers_rulez. Всегда используй параметры, как в примере выше (:regionId).
Короче, нативный SQL — это как острый нож. В умелых руках — инструмент, в кривых — член себе отрежешь. Используй с умом, и будет тебе счастье.