Typeorm Virtual Column 설정 (1)
Table of contents
- <ul><li><a href="https://seongsu.me/typeorm-virtual-column-2" target="_blank">Typeorm Virtual Column 설정 (2)</a></li></ul>
- 🎓 가상 컬럼이란?
- 🤔 데이터 조회
- 📚 참고 자료
Typeorm을 사용하며 대부분 마음에 들었는데 한가지 딱 아쉬운 부분이 있다...
Mysql에서는 테이블에 특정 컬럼이 없어도 SELECT시에 새로 만들어 출력 할 수 있다.
현재 Typeorm에 가상 컬럼 기능이 있긴 한데 원하는 목적과 좀 달라서 원하는 데이터를 가공 후 새로운 컬럼을 추가 해 return 받을 수 있게 가상 컬럼에 관해 포스팅 하려 한다.
Typeorm 버전 0.2.37에 가상컬럼 업데이트 될거라는 얘기가 있었지만 현재 0.2.41 버전까지 지원이 안되고 있다....
🎓 가상 컬럼이란?
Mysql 5.7 부터 지원되는 가상 컬럼은 가상의 컬럼을 둬서 수식과 조건문을 사용해 데이터의 가공 결과를 저장하는 것을 말한다. 사용 방법은 PERSISTENT(stored)와 VIRTUAL(generated-only)이라는 두 가지 타입이 존재하고 디폴트는 PERSISTENT 이다
PERSISTENT virtual columns은 실제 데이터가 데이터베이스에 저장되는 특성을 갖게 되며, VIRTUAL virtual columns은 실제 데이터가 데이터베이스에 저장되지 않고 그때 그때 계산돼 보여주는 역할을 한다.
이 포스팅에서 사용하는 타입은 VIRTUAL 이다.
🤔 데이터 조회
예를들어 데이터베이스 각 행에 버스 정류소의 위도, 경도 값이 저장이 돼 있다.
현재 위치 : 37.57418192 127.015664
(롯데캐슬 천지인 정류소)
목표 정류소1 : 37.57198805 127.0117451
(동대문역 2번출구 정류소)
목표 정류소2 : 37.57578617
126.998124 (원남동 사거리 정류소)
목표 정류소3 : 37.58630021 126.9853527
(감사원 정류소)
여기서 현재 위치 좌표 기준 직선거리 2km 이내의 데이터만 조회하려 하며
각 데이터마다 거리가 얼마나 차이 나는지 알 수 있게 distance
라는 key를 가진 컬럼을 추가해 조회하려 한다고 가정해 보자.
@entities/stations.ts
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm";
@Entity("stations")
export class Stations {
@PrimaryGeneratedColumn()
id: number;
@Column("varchar")
station: string;
@Column("double")
latitude: number;
@Column("double")
longitude: number;
}
Data Mapper 형식으로 Entity를 생성한다.
우선 위도, 경도 값을 double
형식으로 갖는 Entity를 설정 해 준다.
그 뒤 getMany()
함수로 호출 할 때, getRawMany()
함수로 호출 할 때를 나눠 예를 들어 보겠다.
float형식으로 정할 시 좌표가 소숫점 5자리부터 잘리는 현상이 발생하니 double로 설정해주자
getMany() 사용 시
import { getRepository } from "typeorm";
import { Stations } from "@entities/stations";
/**
lat: 37.57418192,
lon: 127.015664,
radius(기준 km): 2
*/
const findAroundLocation = async (lat: number, lon: number, radius: number) => {
return await getRepository(Stations)
.createQueryBuilder("stations")
.addSelect(
`6371 * acos(cos(radians(${lat})) * cos(radians(latitude)) * cos(radians(longitude) - radians(${lon})) + sin(radians(${lat})) * sin(radians(latitude)))`,
"distance"
)
.having(`distance <= ${radius}`)
.orderBy("distance", "ASC")
.getMany();
}
console.log(findAroundLocation());
Return
[
Stations {
id: 1,
station: "동대문역 2번출구 정류소",
latitude: 37.57198805,
longitude: 127.0117451,
},
Stations {
id: 2,
station: "원남동 사거리 정류소",
latitude: 37.57578617,
longitude: 126.998124,
}
]
현재 좌표와 목표 정류소3의 좌표간 거리가 2km가 넘기 때문에 조회되지 않는다.
실 거리가 약 3km (2.991km) 이기 때문이다.
분명 addSelect()
함수에 두 좌표간 거리 구하는 공식과 컬럼명을 지정해 줬는데 결과에는 distance
가 나오지 않는다.
왜냐하면 DB에서 단일한 결과를 가져오는 getMany()
함수의 특성상 쿼리문에서 적용한 데이터는 출력이 안되고 계산에만 사용이 된다.
getRawMany() 사용 시
import { getRepository } from "typeorm";
import { Stations } from "@entities/stations";
/**
lat: 37.57418192,
lon: 127.015664,
radius(기준 km): 2
*/
const aroundStation = async (lat: number, lon: number, radius: number) => {
return await getRepository(Stations)
.createQueryBuilder("stations")
.select()
.addSelect(
`6371 * acos(cos(radians(${lat})) * cos(radians(latitude)) * cos(radians(longitude) - radians(${lon})) + sin(radians(${lat})) * sin(radians(latitude)))`,
"distance"
)
.having(`distance <= ${radius}`)
.orderBy("distance", "ASC")
.getRawMany();
}
console.log(aroundStation());
Return
[
Stations {
stations_id: 1,
stations_station: "동대문역 2번출구 정류소",
stations_latitude: 37.57198805,
stations_longitude: 127.0117451,
distance: 0.4228400907307967
},
Stations {
stations_id: 2,
stations_station: "원남동 사거리 정류소",
stations_latitude: 37.57578617,
stations_longitude: 126.998124,
distance: 1.5482886513847316
}
]
distance는 킬로미터 기준으로 계산한 결과이다.
getRawMany()
함수로 조회 시 원시 결과를 얻어오기 때문에 addSelect()
함수에서 지정한 distance
를 갖고올 수 있다.
이 경우 Mysql 가상 컬럼과 매우 비슷한 결과를 도출할 수 있게 된다.
하지만 객체 key에 stations_ 가 붙어 나오게 된다.
이 경우는 addSelect()
함수 호출 이전에 select()
함수를 호출해 alias
설정을 해주면 손쉽게 해결 가능하다.
이상 Mysql의 가상 컬럼을 비슷하게 구현할 수 있는 Typeorm의 기능을 포스팅 했다.
다음 포스팅에서 왜 Custom Virtual Column을 사용해야 하는지 이유와 함께 포스팅 해야겠다.
📚 참고 자료
Typeorm 공식 레퍼런스