Какая ошибка содержится в этом SQL запросе?

Здравствуйте! 

У меня есть следующий запрос: 

<pre><code>SELECT
	co.id,
	co.title,
	co.description,
	co.created_at,
	co.updated_at,
	COALESCE(
		SUM(
			CASE
				WHEN  t.type = 'INCOME' THEN  t.amount
				ELSE 0
			END
		) - SUM(
			CASE
				WHEN  t.type = 'EXPENSE' THEN  t.amount
				ELSE 0
			END
		),
		0.00
	) AS total_amount,
	COUNT(t),
	array_to_json(ARRAY_AGG(DISTINCT to_jsonb(c))) AS categories,
	array_to_json(ARRAY_AGG(DISTINCT to_jsonb(t))) AS transactions
FROM
	companies co	
	LEFT JOIN LATERAL (
		SELECT 
		   DISTINCT t.id,
			t.description,
			t.amount,
			t.created_at,
			t.type
		FROM
			company_transactions t
		WHERE
			t.company_id = co.id
	) t ON TRUE

	LEFT JOIN LATERAL (
		SELECT 
			c.id,
			c.title,
			COALESCE(
				SUM(
					CASE
						WHEN tr.type = 'INCOME' THEN tr.amount
						ELSE 0
					END
				) - SUM(
					CASE
						WHEN tr.type = 'EXPENSE' THEN tr.amount
						ELSE 0
					END
				),
				0.00
			) AS total_amount
		FROM
			company_categories c
			LEFT JOIN company_transactions tr ON c.id = tr.category_id
		WHERE
			c.company_id = co.id
		GROUP BY
			c.id
	) c ON TRUE
WHERE
	co.id = 17
GROUP BY
	co.id</code></pre> 

Я хотел бы получить следующий выходной формат: 

<pre><code class="javascript">export interface ICompanyDetails
  extends Pick&lt;
    ICompany,
    'id' | 'title' | 'description' | 'created_at' | 'updated_at'
  &gt; {
  total_amount: number;
  categories: {
    id: number;
    title: string;
    total_amount: number;
  }[];
  transactions: Pick&lt;
    ICompanyTransaction,
    'id' | 'description' | 'amount' | 'created_at' | 'type'
  &gt;[];
}</code></pre> 

Также меня интересуют определения сущностей БД: 

<pre><code class="javascript">export interface IC</code></pre>
  • 25 декабря 2024 г. 15:13
Ответы на вопрос 2
В вашем SQL-запросе содержится несколько потенциальных ошибок и недочетов, которые могут повлиять на его корректное выполнение. Я перечислю основные моменты, на которые стоит обратить внимание:

1. **GROUP BY**: Вы используете агрегатные функции (например, `SUM`, `COUNT`, `ARRAY_AGG`), и поэтому все остальные выбранные вами поля должны быть либо агрегированы, либо перечислены в операторе `GROUP BY`. В вашем запросе использованы поля из таблицы `companies` (`co.id`, `co.title`, `co.description`, `co.created_at`, `co.updated_at`), но в `GROUP BY` указано только `co.id`. Вам необходимо добавить все остальные поля из `SELECT` в `GROUP BY`, чтобы избежать ошибки.

   Исправленный фрагмент:
   ```sql
   GROUP BY
       co.id, co.title, co.description, co.created_at, co.updated_at
   ```

2. **COUNT(t)**: В вашем запросе используется `COUNT(t)`, который будет считать количество строк, возвращенных из подзапроса `t`. Если строки из подзапроса `t` никогда не возвращаются, это может привести к неожиданным результатам. Если вы хотите подсчитать количество транзакций, вам, возможно, потребуется использовать `COUNT(DISTINCT t.id)` или другую подходящую конструкцию.

3. **COALESCE в подзапросе для категорий `c`**: Вы используете `COALESCE` для получения суммы в подзапросе категорий, но если не найдены соответствующие категории, запрос не вернет строки. Это может привести к тому, что общая сумма `total_amount` для категорий будет игнорироваться. Проверьте это поведение в контексте вашего приложения.

4. **Оптимизация LEFT JOIN LATERAL**: В случае, если не требуется DISTINCT по `t.id`, использование `DISTINCT` в подзапросах может быть избыточным и может негативно сказаться на производительности.

Вот исправленный пример вашего SQL-запроса с учетом вышеупомянутых моменты:

```sql
SELECT
    co.id,
    co.title,
    co.description,
    co.created_at,
    co.updated_at,
    COALESCE(
        SUM(
            CASE
                WHEN  t.type = 'INCOME' THEN  t.amount
                ELSE 0
            END
        ) - SUM(
            CASE
                WHEN  t.type = 'EXPENSE' THEN  t.amount
                ELSE 0
            END
        ),
        0.00
    ) AS total_amount,
    COUNT(DISTINCT t.id) AS transaction_count, -- пример явного подсчета
    array_to_json(ARRAY_AGG(DISTINCT to_jsonb(c))) AS categories,
    array_to_json(ARRAY_AGG(DISTINCT to_jsonb(t))) AS transactions
FROM
    companies co    
    LEFT JOIN LATERAL (
        SELECT 
            t.id,
            t.description,
            t.amount,
            t.created_at,
            t.type
        FROM
            company_transactions t
        WHERE
            t.company_id = co.id
    ) t ON TRUE
    LEFT JOIN LATERAL (
        SELECT 
            c.id,
            c.title,
            COALESCE(
                SUM(
                    CASE
                        WHEN tr.type = 'INCOME' THEN tr.amount
                        ELSE 0
                    END
                ) - SUM(
                    CASE
                        WHEN tr.type = 'EXPENSE' THEN tr.amount
                        ELSE 0
                    END
                ),
                0.00
            ) AS total_amount
        FROM
            company_categories c
            LEFT JOIN company_transactions tr ON c.id = tr.category_id
        WHERE
            c.company_id = co.id
        GROUP BY
            c.id
    ) c ON TRUE
WHERE
    co.id = 17
GROUP BY
    co.id, co.title, co.description, co.created_at, co.updated_at;
```

Проверьте, пожалуйста, указанные моменты, и вносите необходимые изменения в соответствии с вашими требованиями.
Решил задачу так, работает, но можно лучше. 
Пока не знаю как.

SELECT
	co.id,
	co.title,
	co.description,
	co.created_at,
	co.updated_at,
	COALESCE(total_amount.total, 0.00) AS total_amount,
	array_to_json(ARRAY_AGG(DISTINCT to_jsonb(c))) AS categories,
	array_to_json(ARRAY_AGG(DISTINCT to_jsonb(t))) AS transactions
FROM
	companies co
	LEFT JOIN (
		SELECT
			company_id,
			SUM(
				CASE
					WHEN type = 'INCOME' THEN amount
					ELSE - amount
				END
			) AS total
		FROM
			company_transactions
		GROUP BY
			company_id
	) total_amount ON co.id = total_amount.company_id
	LEFT JOIN LATERAL (
		SELECT
			t.id,
			t.description,
			t.amount,
			t.created_at,
			t.type
		FROM
			company_transactions t
		WHERE
			t.company_id = co.id
	) t ON TRUE
	LEFT JOIN LATERAL (
		SELECT
			c.id,
			c.title,
			COALESCE(
				SUM(
					CASE
						WHEN tr.type = 'INCOME' THEN tr.amount
						ELSE - tr.amount
					END
				),
				0.00
			) AS total_amount
		FROM
			company_categories c
			LEFT JOIN company_transactions tr ON c.id = tr.category_id
		WHERE
			c.company_id = co.id
		GROUP BY
			c.id
	) c ON TRUE
WHERE
	co.id = 17
GROUP BY
	co.id,
	total_amount.total


Если убрать эти DISTINCT, то дубли присутствуют.
array_to_json(ARRAY_AGG(DISTINCT to_jsonb(c))) AS categories,
array_to_json(ARRAY_AGG(DISTINCT to_jsonb(t))) AS transactions
Похожие вопросы