SQL Group By 와 Aggregation 함수 적용

이한재·2023년 1월 25일
0

Group By


Group By 는 같은 값을 가진 Rows (Records) 에 대해서 그룹화 하는 키워드 이다.
주로 Aggregation Function 과 함께 쓰인다.

일반적으로 많이 쓰이는 Aggregation Function 은 MAX(), MIN(), SUM() 같은 것들인데

이 글에서는 JSON_ARRAYAGG() 라는 JSON 데이터를 배열로 Aggregation 하는 함수를 적용시켜 보겠다.

JSON_OBJECT()

JSON_OBJECT() 함수는 key ,value 를 인자로 받아 JSON 형태로 반환해주는 함수이다.

사용전

사용후

posts 에 대해서 id, title, posts 인 컬럼들이 JSON_OBJECT 를 사용했을때 posts 라는 컬럼 하나로 합쳐졌다.

JSON_ARRAYAGG()

AGG 는 Aggregate 의 약자로 합계를 구한다는 의미를 내포하고 있는 것 같다.
그리고 JSON_ARRAYAGG() 함수는 다음과 같이 mysql 공식문서에 설명 되어 있다.

Aggregates a result set as a single JSON array whose elements consist of the rows.

하나의 JSON 배열을 구한다는 의미이고 배열에는 여러개의 rows 들이 있을 수 있다고 한다.

위에서 Aggreagation 함수는 Group By 와 함께 쓰인다고 설명하였으니
users.id 를 Group 하여 JSON_ARRAYAGG() 함수를 사용해보면

다음과 같이 users.id 로 그룹화 된 Rows 를 가져올 수 있고 posting 필드 값이 배열로 되어 있는 것을 확인 할 수 있다.

위에 SQL 문에서는 users.id 가 개별적으로 출력되었고 JSON_OBJECT 값들도 각각의 Row 로 출력이 되었지만

JSON_ARRAYAGG() 함수와 GROUP BY 를 이용해서 공통된 값을 가진 필드를 GROUP 하고 JSON 값을 배열에 집어넣어 하나의 Row 로 표현 할 수 있다.

그래서 이 SQL 문을 현재 작업중인 node.js instagram API 서버에 적용해보자.

유저 한명이 작성한 모든 게시물을 가져오는 api 이고 posting 을 json 형태로 구조화하여 postings 라는 배열에 넣는 작업을 한다.

코드


수정 전


// 과제 5 유저 한명이 작성한 모든 게시물 가져오기
app.get('/posts/:userId', async (req, res) => {
  const rawQuery = `
    SELECT
      u.id AS userId,
      u.profile_image AS userProfileImage,
      p.id AS postingId,
      p.image_url AS postingImageUrl,
      p.content AS postingContent
    FROM posts p
    INNER JOIN users u
    ON p.user_id = u.id WHERE u.id = ${req.params.userId};
    `
  try {
    await database.query(rawQuery, (err, rows) => {
      user = {}
      postings = []
      for (const row of rows) {
        if (!user['userId']) {
          user['userId'] = row['userId']
        }

        if (!user['userProfileImage']) {
          user['userProfileImage'] = row['userProfileImage']
        }

        posting = {}
        posting['postingId'] = row['postingId']
        posting['postingImageUrl'] = row['postingImageUrl']
        posting['postingContent'] = row['postingContent']
        postings.push(posting)
      }
      user['postings'] = postings

      const result = {}
      result['data'] = user
      res.status(200).json(result)
    })
  } catch (error) {
    return res.status(400).json({ message: error.sqlMessage })
  }
})

수정 후


// 과제 5 유저 한명이 작성한 모든 게시물 가져오기
app.get('/posts/:userId', async (req, res) => {
  const rawQuery = `
    SELECT
      u.id AS userId,
      u.profile_image AS userProfileImage,
      JSON_ARRAYAGG(JSON_OBJECT(
                    'postingId', p.id,
                    'postingImageUrl', p.image_url,
                    'postingContent', p.content)) AS posting
    FROM posts AS p
    INNER JOIN users AS u
    ON p.user_id = u.id WHERE u.id = ${req.params.userId}
    GROUP BY u.id;
    `
  try {
    await database.query(rawQuery, (err, rows) => {
      return res.status(200).json({ data: rows })
    })
  } catch (error) {
    return res.status(400).json({ message: error.sqlMessage })
  }
})

수정을 하고 난 뒤의 코드가 훨씬 간결해진것을 확인 할 수 있다. 👍 원래 코드는 SQL raw Query 로 데이터를 가져와서 서버쪽에서 데이터를 재가공하는 작업이 필요했는데 수정 후의 코드는 수정전에서 필요했던 객체를 만들고 배열에 넣는 작업을 SQL 의 JSON_OBJECT(), JSON_ARRAYAGG() 함수를 통해 구현 할 수 있었다.

이런식으로 코드가 더 간결해 질 수 있는 방법을 항상 탐구하고
내가 작성한 코드에 항상 의문을 품어서 더 나은 코드를 작성할 수 있도록 고민을하고
다른 사람은 어떤식으로 작성했는지 다른 문제 해결방식 또한 참고해본다면
좋은 개발자가 될 수있지 않을까 하고 생각해봅니다.

profile
이한재입니다

1개의 댓글

comment-user-thumbnail
2023년 2월 9일

const [rawQuery] = await conn.query()
구조분해 할당으로 해서 윗 코드처럼 짤수 있는 방법이 있습니다. 한번 고려해보세요:)

${req.params.userId} << 이부분도 구조분해 할당으로 const {userId} = req.params 식으로 하여 해주는게 가독성이 좋습니다.

더 나아가시면 MVC 패턴으로 분리해서 리팩토링 해보세요^^

답글 달기