Запросы и операторы

Продвинутые техники работы с базой данных в MNRFY Framework

Сложные WHERE условия

Комбинированные условия

<!-- Сложные AND условия -->
{% set premiumUsers = db('1752665380840')->getItems('users', {
    'balance': '>1000',
    'verified': 1,
    'ban': 0,
    'last_seen': '>' ~ (time() - 86400 * 7),  # Активен за неделю
    'created_at': '>' ~ (time() - 86400 * 30)  # Регистрация меньше месяца назад
}) %}

<!-- OR условия с implode -->
{% set moderators = db('1752665380840')->getItems('users', {
    'role': 'admin',
    'role ': 'moderator',
    'role  ': 'supervisor',
    'implode': 'OR'
}) %}

Диапазоны и интервалы

<!-- Числовые диапазоны -->
{% set middleClassUsers = db('1752665380840')->getItems('users', {
    'balance': '>=100',
    'balance ': '<=10000'
}) %}

<!-- Временные интервалы -->
{% set thisMonthUsers = db('1752665380840')->getItems('users', {
    'created_at': '>=' ~ strtotime('first day of this month'),
    'created_at ': '<' ~ strtotime('first day of next month')
}) %}

<!-- Возрастные группы -->
{% set adultsUsers = db('1752665380840')->getItems('users', {
    'age': '>=18',
    'age ': '<65'
}) %}

NULL и пустые значения

<!-- Поиск NULL значений -->
{% set usersWithoutAvatar = db('1752665380840')->getItems('users', {
    'avatar': 'IS NULL'
}) %}

<!-- Поиск НЕ NULL значений -->
{% set usersWithAvatar = db('1752665380840')->getItems('users', {
    'avatar': 'IS NOT NULL'
}) %}

<!-- Поиск пустых строк -->
{% set usersWithoutBio = db('1752665380840')->getItems('users', {
    'bio': ''
}) %}

Прямые SQL запросы

Простые SELECT запросы

<!-- Базовый SELECT -->
{% set users = db('1752665380840')->query('SELECT * FROM users WHERE active = 1') %}

<!-- С параметрами (безопасно) -->
{% set users = db('1752665380840')->query('
    SELECT id, login, balance 
    FROM users 
    WHERE balance > ? AND role = ?
', [1000, 'user']) %}

<!-- Подсчет с группировкой -->
{% set roleStats = db('1752665380840')->query('
    SELECT role, COUNT(*) as count, AVG(balance) as avg_balance
    FROM users 
    GROUP BY role 
    ORDER BY count DESC
') %}

Сложные аналитические запросы

<!-- Топ пользователей по балансу -->
{% set topUsers = db('1752665380840')->query('
    SELECT 
        id,
        login,
        balance,
        RANK() OVER (ORDER BY balance DESC) as rank
    FROM users 
    WHERE ban = 0 
    ORDER BY balance DESC 
    LIMIT 10
') %}

<!-- Статистика по месяцам -->
{% set monthlyStats = db('1752665380840')->query('
    SELECT 
        DATE_FORMAT(FROM_UNIXTIME(created_at), "%Y-%m") as month,
        COUNT(*) as registrations,
        AVG(balance) as avg_balance,
        MAX(balance) as max_balance
    FROM users 
    WHERE created_at >= ?
    GROUP BY month
    ORDER BY month DESC
', [strtotime('-12 months')]) %}

<!-- Активность пользователей -->
{% set activityStats = db('1752665380840')->query('
    SELECT 
        CASE 
            WHEN last_seen > ? THEN "Онлайн"
            WHEN last_seen > ? THEN "Сегодня" 
            WHEN last_seen > ? THEN "На этой неделе"
            ELSE "Давно"
        END as activity_group,
        COUNT(*) as count
    FROM users 
    WHERE ban = 0
    GROUP BY activity_group
    ORDER BY 
        CASE activity_group
            WHEN "Онлайн" THEN 1
            WHEN "Сегодня" THEN 2  
            WHEN "На этой неделе" THEN 3
            ELSE 4
        END
', [
    time() - 300,      # 5 минут назад
    time() - 86400,    # Сегодня
    time() - 604800    # Неделя назад
]) %}

JOIN запросы

<!-- Простой JOIN -->
{% set usersWithProfiles = db('1752665380840')->query('
    SELECT 
        u.id,
        u.login,
        u.balance,
        p.avatar,
        p.bio,
        p.website
    FROM users u
    LEFT JOIN user_profiles p ON u.id = p.user_id
    WHERE u.ban = 0
    ORDER BY u.created_at DESC
    LIMIT 50
') %}

<!-- Множественные JOIN -->
{% set ordersWithDetails = db('1752665380840')->query('
    SELECT 
        o.id,
        o.total,
        o.status,
        o.created_at,
        u.login as customer_login,
        u.email as customer_email,
        COUNT(oi.id) as items_count,
        SUM(oi.quantity) as total_quantity
    FROM orders o
    LEFT JOIN users u ON o.user_id = u.id
    LEFT JOIN order_items oi ON o.id = oi.order_id
    WHERE o.created_at >= ?
    GROUP BY o.id
    ORDER BY o.created_at DESC
', [strtotime('-30 days')]) %}

Подзапросы

Коррелированные подзапросы

<!-- Пользователи с количеством заказов -->
{% set usersWithOrderCount = db('1752665380840')->query('
    SELECT 
        u.id,
        u.login,
        u.balance,
        (
            SELECT COUNT(*) 
            FROM orders o 
            WHERE o.user_id = u.id AND o.status = "completed"
        ) as completed_orders,
        (
            SELECT COALESCE(SUM(total), 0) 
            FROM orders o 
            WHERE o.user_id = u.id AND o.status = "completed"
        ) as total_spent
    FROM users u
    WHERE u.ban = 0
    HAVING completed_orders > 0
    ORDER BY total_spent DESC
') %}

<!-- Товары с последним заказом -->
{% set productsWithLastOrder = db('1752665380840')->query('
    SELECT 
        p.id,
        p.name,
        p.price,
        (
            SELECT MAX(o.created_at) 
            FROM orders o
            JOIN order_items oi ON o.id = oi.order_id
            WHERE oi.product_id = p.id
        ) as last_ordered
    FROM products p
    WHERE p.active = 1
    ORDER BY last_ordered DESC
') %}

EXISTS и NOT EXISTS

<!-- Пользователи с заказами -->
{% set usersWithOrders = db('1752665380840')->query('
    SELECT u.id, u.login, u.email
    FROM users u
    WHERE EXISTS (
        SELECT 1 
        FROM orders o 
        WHERE o.user_id = u.id AND o.status = "completed"
    )
    ORDER BY u.login
') %}

<!-- Пользователи без заказов -->
{% set usersWithoutOrders = db('1752665380840')->query('
    SELECT u.id, u.login, u.created_at
    FROM users u
    WHERE NOT EXISTS (
        SELECT 1 
        FROM orders o 
        WHERE o.user_id = u.id
    )
    AND u.created_at < ?
    ORDER BY u.created_at
', [time() - 86400 * 30]) %}  # Регистрация больше месяца назад

Агрегационные функции

Группировка и статистика

<!-- Статистика по ролям -->
{% set roleStats = db('1752665380840')->query('
    SELECT 
        role,
        COUNT(*) as count,
        AVG(balance) as avg_balance,
        MIN(balance) as min_balance,
        MAX(balance) as max_balance,
        SUM(balance) as total_balance,
        STDDEV(balance) as balance_stddev
    FROM users 
    WHERE ban = 0
    GROUP BY role
    HAVING count >= 5
    ORDER BY avg_balance DESC
') %}

<!-- Активность по дням недели -->
{% set weeklyActivity = db('1752665380840')->query('
    SELECT 
        DAYOFWEEK(FROM_UNIXTIME(last_seen)) as day_of_week,
        CASE DAYOFWEEK(FROM_UNIXTIME(last_seen))
            WHEN 1 THEN "Воскресенье"
            WHEN 2 THEN "Понедельник" 
            WHEN 3 THEN "Вторник"
            WHEN 4 THEN "Среда"
            WHEN 5 THEN "Четверг"
            WHEN 6 THEN "Пятница"
            WHEN 7 THEN "Суббота"
        END as day_name,
        COUNT(*) as active_users
    FROM users 
    WHERE last_seen >= ? AND ban = 0
    GROUP BY day_of_week, day_name
    ORDER BY day_of_week
', [time() - 604800]) %}  # За последнюю неделю

Оконные функции (для MySQL 8.0+)

<!-- Ранжирование пользователей -->
{% set rankedUsers = db('1752665380840')->query('
    SELECT 
        id,
        login,
        balance,
        ROW_NUMBER() OVER (ORDER BY balance DESC) as row_num,
        RANK() OVER (ORDER BY balance DESC) as rank,
        DENSE_RANK() OVER (ORDER BY balance DESC) as dense_rank,
        PERCENT_RANK() OVER (ORDER BY balance DESC) as percentile
    FROM users 
    WHERE ban = 0 AND balance > 0
    ORDER BY balance DESC
    LIMIT 20
') %}

<!-- Прирост по месяцам -->
{% set monthlyGrowth = db('1752665380840')->query('
    SELECT 
        month,
        registrations,
        LAG(registrations) OVER (ORDER BY month) as prev_month,
        registrations - LAG(registrations) OVER (ORDER BY month) as growth,
        ROUND(
            (registrations - LAG(registrations) OVER (ORDER BY month)) * 100.0 
            / LAG(registrations) OVER (ORDER BY month), 2
        ) as growth_percent
    FROM (
        SELECT 
            DATE_FORMAT(FROM_UNIXTIME(created_at), "%Y-%m") as month,
            COUNT(*) as registrations
        FROM users 
        WHERE created_at >= ?
        GROUP BY month
    ) monthly_data
    ORDER BY month
', [strtotime('-12 months')]) %}

Полнотекстовый поиск

FULLTEXT поиск (MySQL)

<!-- Поиск по контенту -->
{% set searchResults = db('1752665380840')->query('
    SELECT 
        id,
        title,
        content,
        MATCH(title, content) AGAINST(? IN BOOLEAN MODE) as relevance
    FROM articles 
    WHERE MATCH(title, content) AGAINST(? IN BOOLEAN MODE)
    AND published = 1
    ORDER BY relevance DESC
    LIMIT 20
', [searchQuery, searchQuery]) %}

<!-- Поиск с модификаторами -->
{% set advancedSearch = db('1752665380840')->query('
    SELECT 
        id,
        title,
        content,
        MATCH(title, content) AGAINST(? IN BOOLEAN MODE) as score
    FROM articles 
    WHERE MATCH(title, content) AGAINST(? IN BOOLEAN MODE)
    ORDER BY score DESC
', [
    '+' ~ searchTerm ~ '* -excluded',  # Обязательно содержит, исключая слово
    '+' ~ searchTerm ~ '* -excluded'
]) %}

Поиск LIKE для простых случаев

<!-- Множественный поиск -->
{% set userSearch = db('1752665380840')->query('
    SELECT id, login, email, first_name, last_name
    FROM users 
    WHERE (
        login LIKE ? 
        OR email LIKE ? 
        OR CONCAT(first_name, " ", last_name) LIKE ?
        OR first_name LIKE ?
        OR last_name LIKE ?
    )
    AND ban = 0
    ORDER BY 
        CASE 
            WHEN login = ? THEN 1
            WHEN login LIKE ? THEN 2
            WHEN email LIKE ? THEN 3
            ELSE 4
        END,
        login
    LIMIT 50
', [
    '%' ~ searchTerm ~ '%',  # login LIKE
    '%' ~ searchTerm ~ '%',  # email LIKE  
    '%' ~ searchTerm ~ '%',  # full name LIKE
    '%' ~ searchTerm ~ '%',  # first_name LIKE
    '%' ~ searchTerm ~ '%',  # last_name LIKE
    searchTerm,              # exact login match
    searchTerm ~ '%',        # login starts with
    '%' ~ searchTerm ~ '%'   # email contains
]) %}

Операции изменения данных

INSERT запросы

<!-- Простая вставка -->
{% php %}
    $newUserId = db('1752665380840')->query('
        INSERT INTO users (login, email, password, created_at) 
        VALUES (?, ?, ?, ?)
    ', [
        $username, 
        $email, 
        password_hash($password, PASSWORD_DEFAULT), 
        time()
    ]);
{% endphp %}

<!-- Множественная вставка -->
{% php %}
    db('1752665380840')->query('
        INSERT INTO user_sessions (user_id, token, ip_address, created_at) 
        VALUES 
            (?, ?, ?, ?),
            (?, ?, ?, ?),
            (?, ?, ?, ?)
    ', [
        $userId1, $token1, $ip1, time(),
        $userId2, $token2, $ip2, time(), 
        $userId3, $token3, $ip3, time()
    ]);
{% endphp %}

<!-- INSERT ... SELECT -->
{% php %}
    db('1752665380840')->query('
        INSERT INTO user_backup (user_id, login, email, backup_date)
        SELECT id, login, email, ?
        FROM users 
        WHERE last_seen < ? AND ban = 0
    ', [time(), time() - 86400 * 365]);  # Год назад
{% endphp %}

UPDATE запросы

<!-- Простое обновление -->
{% php %}
    db('1752665380840')->query('
        UPDATE users 
        SET last_seen = ?, ip_address = ?
        WHERE id = ?
    ', [time(), $userIP, $userId]);
{% endphp %}

<!-- Условное обновление -->
{% php %}
    db('1752665380840')->query('
        UPDATE users 
        SET 
            balance = balance + ?,
            updated_at = ?
        WHERE id = ? AND ban = 0
    ', [$bonusAmount, time(), $userId]);
{% endphp %}

<!-- Обновление с подзапросом -->
{% php %}
    db('1752665380840')->query('
        UPDATE users 
        SET orders_count = (
            SELECT COUNT(*) 
            FROM orders 
            WHERE user_id = users.id AND status = "completed"
        )
        WHERE id IN (?)
    ', [implode(',', $userIds)]);
{% endphp %}

Практические примеры запросов

Отчет по продажам

<!-- Детальный отчет продаж -->
{% set salesReport = db('1752665380840')->query('
    SELECT 
        DATE(FROM_UNIXTIME(o.created_at)) as sale_date,
        COUNT(DISTINCT o.id) as orders_count,
        COUNT(oi.id) as items_sold,
        SUM(oi.quantity) as total_quantity,
        SUM(oi.price * oi.quantity) as gross_revenue,
        AVG(o.total) as avg_order_value,
        COUNT(DISTINCT o.user_id) as unique_customers
    FROM orders o
    JOIN order_items oi ON o.id = oi.order_id
    WHERE o.status = "completed" 
        AND o.created_at BETWEEN ? AND ?
    GROUP BY sale_date
    ORDER BY sale_date DESC
', [
    strtotime($startDate),
    strtotime($endDate . ' 23:59:59')
]) %}

<!-- Топ продуктов -->
{% set topProducts = db('1752665380840')->query('
    SELECT 
        p.id,
        p.name,
        p.price,
        SUM(oi.quantity) as total_sold,
        SUM(oi.price * oi.quantity) as revenue,
        COUNT(DISTINCT oi.order_id) as orders_count,
        AVG(oi.price) as avg_sell_price,
        ROUND(SUM(oi.quantity * oi.price) / SUM(oi.quantity), 2) as weighted_avg_price
    FROM products p
    JOIN order_items oi ON p.id = oi.product_id
    JOIN orders o ON oi.order_id = o.id
    WHERE o.status = "completed" 
        AND o.created_at >= ?
    GROUP BY p.id
    HAVING total_sold >= 10
    ORDER BY revenue DESC
    LIMIT 20
', [strtotime('-30 days')]) %}

Анализ пользователей

<!-- Сегментация пользователей -->
{% set userSegments = db('1752665380840')->query('
    SELECT 
        segment,
        COUNT(*) as users_count,
        AVG(total_spent) as avg_spent,
        AVG(orders_count) as avg_orders
    FROM (
        SELECT 
            u.id,
            u.login,
            COALESCE(user_stats.total_spent, 0) as total_spent,
            COALESCE(user_stats.orders_count, 0) as orders_count,
            CASE 
                WHEN COALESCE(user_stats.total_spent, 0) = 0 THEN "Не покупал"
                WHEN user_stats.total_spent < 1000 THEN "Новичок"
                WHEN user_stats.total_spent < 5000 THEN "Постоянный"
                WHEN user_stats.total_spent < 20000 THEN "VIP"
                ELSE "Премиум"
            END as segment
        FROM users u
        LEFT JOIN (
            SELECT 
                user_id,
                SUM(total) as total_spent,
                COUNT(*) as orders_count
            FROM orders 
            WHERE status = "completed"
            GROUP BY user_id
        ) user_stats ON u.id = user_stats.user_id
        WHERE u.ban = 0
    ) segmented_users
    GROUP BY segment
    ORDER BY 
        CASE segment
            WHEN "Премиум" THEN 1
            WHEN "VIP" THEN 2
            WHEN "Постоянный" THEN 3  
            WHEN "Новичок" THEN 4
            ELSE 5
        END
') %}
Следующий шаг: Изучите транзакции и кеширование для продвинутой работы с БД.