
인기 상품 리스트 조회를 구현해야 했다.
상품은 지난 일주일 간 판매량을 가진 A 그룹과 그렇지 않은 B 그룹으로 나뉜다.
B 그룹은 총 (판매 가격 * 판매 수량)으로 계산한다.
ORDER BY price * count
아무렇지 않게 위처럼 썼다.
그랬더니 모든 상품에 대해 연산해야 했고, 그래서 쿼리가 너무너무 느렸다.

그렇다고 total_sale 컬럼을 따로 둬서 관리하자니
등등등 관리 포인트가 곳곳으로 늘어났다.
PostgreSQL의 generated column 기능을 활용하기로 했다.
generated column은 다른 컬럼으로부터 연산되어 값이 채워지는 컬럼을 뜻한다.
그래서 사실상 뷰 테이블의 컬럼 버전이라고 볼 수 있겠다.
generated column은 실제 데이터를 써서 저장되는 STORED와
읽을 때 계산해서 주는 VIRTUAL 총 2가지 버전으로 나뉜다.
(참고로 PostgreSQL에서는 STORED 버전만 지원한다)
ALTER TABLE products
ADD COLUMN total_sale BIGINT
GENERATED ALWAYS AS (price * count) STORED
정의하는 것도 간단하다.
GENERATED ALWAYS AS 뒤에 다른 컬럼의 연산 표현식을 써주면 된다.
기본값이 붙는 컬럼은 레코드 생성시에만 값이 채워지고 그 뒤 업데이트가 없지만,
generated column은 인자를 구성하는 컬럼의 값이 변경되면 함께 업데이트 된다.
또 기본값이 붙는 컬럼과 달리,
generated column은 현재 시각 혹은 random()과 같은 volatile function을 사용하지 못한다.
(generated column은 같은 입력값에 대해 동일한 결과를 가져야 하므로)
model Product {
...
totalSale BigInt? @ignore
...
}
우선은 스키마 파일에서 해당하는 테이블에 컬럼을 추가한다.
만약 prisma client가 사용하지 않도록 하고 싶다면 @ignore를 붙이면 된다.
prisma migrate dev --create-only --name create_column_total_sale
그리고 위 명령어를 통해 마이그레이션 파일을 생성한다.
(아니면 그냥 직접 폴더, 파일을 생성해도 된다)
ALTER TABLE products
ADD COLUMN total_sale BIGINT
GENERATED ALWAYS AS (price * count) STORED
prisma client가 자동으로 생성해준 내용은 지우고
위와 같이 generated column을 정의하는 sql을 작성하면 된다.
model Product {
...
totalSale BigInt @default(dbgenerated())
...
}
위와 같이 스키마 파일에서만 @default(dbgenerated())로 정의하면
prisma client로 하여금 해당 컬럼을 감지하고, 코드에서 사용할 수 있게 된다 (참고)
예를 들어 상품마다 총 매출을 보여줘야 한다면
굳이 상품을 들고 와서 totalSale: price * count과 같이 값을 할당할 필요성이 줄어들게 된다.

(그래서?)
약간의 우여곡절이 있었지만 생각보다 간단하게 구현할 수 있었다.
다만, 이번 주제와는 별개로 BigInt 타입을 JSON.stringify()에 넣을 때 발생하는
에러 문제를 해결해야 한다...