앱을 개발하면서 이런 기능이 필요했다.
일정 시간마다 평점을 취합하여 평균을 계산하고 업데이트한다.
이 기능을 구현하기 위해서 총 2가지의 시도를 했다.
처음에는 1번으로 시도 했으나 너무 어려워서 MariaDB의 기능들을 찾아봤다. 그리고 GROUP BY
를 알았고 적극 활용하여 비교적 쉽게 기능을 구현할 수 있었다.
일단 테이블을 살펴보자
내가 처음 설계한 로직은 이렇다.
store_vote_grade
의 question1
, question2
, question3
을 포함한다.)store_id
와 member_id
가 함께 중복 될 수 없다. 각각 하나씩은 중복가능)question1
, question2
, question3
을 모두 더한다.question1_average
, question2_average
, question3_average
에 업데이트한다.question_average
에 업데이트한다. 여기서 2번이 너무 어려웠다. 그래서 알아본 Group By를 활용하여 설계한 로직은 이렇다.
이게 끝이다. 젠장.. 아는 것이 힘이다.. 이제 새로 설계한 로직을 어떻게 구현 했는지 살펴보자
다짜고짜 쿼리를 살펴보자
SELECT
si.store_id,
FORMAT(avg(so.question1), 1) AS question1,
FORMAT(avg(so.question2), 1) AS question2,
FORMAT(avg(so.question3), 1) AS question3,
FORMAT((so.question1 + so.question2 + so.question3)/3, 1) AS average
FROM store_information si
LEFT JOIN store_vote_grade so ON si.store_id = so.store_id AND is_available = 1
WHERE so.question1 IS NOT NULL
GROUP BY si.store_id
맨 아래 GROUP BY를 살펴보면 기준이 될 컬럼을 지정한다. 나는 store_id
를 기준으로 question
들의 점수를 취합하고 싶었으니 당연히 GROUP BY si.store_id
로 작성했다. 그리고 avg(soquestion1)
함수를 사용해서 평균을 구했다. 이외에도 sum()
, count()
등 내장 함수가 굉장히 많다.
이후 해당 결과값들을 매핑하여 업데이트 쿼리만 날려주면 끝이다. 여러 행을 업데이트 하는 것은 생각보다는 쉬운 일이 아니였다. INSERT INTO
처럼 ,
를 기준으로 쭉 나열하면 되는 줄 알았는데 그게 아니였다.
검색을 해보니 방법은 여러가지가 있었다. 언제나 그렇듯이
NOT NULL
로 설정되어 있는 Column들의 값을 추가로 매핑해줘야만 했다. 또 다짜고짜 코드를 보자
let array = []
let i = 0;
let sql = "UPDATE store_information si JOIN ("
for (let row in score) {
let id = score[row]["store_id"]
let question1 = score[row]["question1"]
let question2 = score[row]["question2"]
let question3 = score[row]["question3"]
let average = score[row]["average"]
if (i === 0) {
array.push("SELECT " + id + " as store_id, "
+ question1 + " as question1_average, "
+ question2 + " as question2_average, "
+ question3 + " as question3_average, "
+ average + " as average")
} else {
array.push("SELECT " + id + ", "
+ question1 + ", "
+ question2 + ", "
+ question3 + ", "
+ average)
}
++i;
}
sql += array.join(" UNION ALL ")
sql += ") vals ON si.store_id = vals.store_id \
SET si.question1_average = vals.question1_average, \
si.question2_average = vals.question2_average, \
si.question3_average = vals.question3_average, \
si.vote_grade_average = vals.average"
let [result] = await pool.execute(sql)
위에서 살펴본 GROUP BY를 활용한 SELECT문의 결과값이 score
에 담겨있다. 미천한 코딩 실력으로 결과값을 토대로 쿼리를 생성하는 코드이다. 생성되는 쿼리는 아래와 같다.
UPDATE store_information si
JOIN (
SELECT
1 as store_id,
1.3 as question1_average,
2.3 as question2_average,
3.3 as question3_average,
2.2 as average
UNION ALL
SELECT 2, 3.6, 2.8, 2.5, 3.5
UNION ALL
SELECT 3, 2.5, 1.0, 4.5, 2.7
UNION ALL
SELECT 4, 2.8, 2.2, 4.0, 3.3
UNION ALL
...
) vals ON si.store_id = vals.store_id
SET
si.question1_average = vals.question1_average,
si.question2_average = vals.question2_average,
si.question3_average = vals.question3_average,
si.vote_grade_average = vals.average
현기증이 나지만 순서대로 살펴보자
UNION
연산자를 활용해 하나의 결과로 합친다store_information
쿼리와 JOIN을 하여 원하는 store_id
를 일치시킨다.vals ON si.store_id = vals.store_id
부분)아래 링크의 방법을 그대로 배꼈다. 그래도 경험치가 늘었으니 만족한다.