반응형

PostgreSQL에서 시퀀스(sequence)는 주로 자동 증가(primary key) 필드를 위한 값을 생성하는 데 사용됩니다. 시퀀스를 초기화하는 것은 데이터베이스 테이블의 ID 값 등을 재설정하거나, 특정 값부터 시작하게 하고 싶을 때 유용합니다. 이번 포스트에서는 PostgreSQL에서 시퀀스를 초기화하는 방법을 단계별로 알아보겠습니다.

1. PostgreSQL 시퀀스란?

시퀀스는 PostgreSQL에서 자동 증가 컬럼에 주로 사용되는 객체로, 테이블의 특정 컬럼이 새 레코드를 추가할 때마다 고유한 값을 자동으로 할당해줍니다. 일반적으로 시퀀스는 SERIAL이나 BIGSERIAL 타입을 통해 생성되며, 시퀀스가 생성되면 테이블이 해당 값을 참조하게 됩니다.

2. 시퀀스 초기화가 필요한 상황

시퀀스를 초기화하는 상황은 다음과 같습니다:

  • 테이블의 ID 값 등을 재설정해야 할 때
  • 데이터 삭제 후 특정 값부터 시작하도록 설정할 때
  • 잘못된 시퀀스 값을 바로잡아야 할 때

예를 들어, 데이터를 삭제한 후 시퀀스가 다음에 삽입될 레코드에서 1부터 다시 시작하도록 설정하고 싶다면 시퀀스 초기화가 필요합니다.

3. PostgreSQL에서 시퀀스 초기화하기

시퀀스를 초기화하는 방법에는 여러 가지가 있지만, 가장 일반적인 방법은 RESTART 명령어를 사용하는 것입니다. ALTER SEQUENCE 구문을 사용하여 특정 값부터 시퀀스를 다시 시작하도록 설정할 수 있습니다.

-- 기본 시퀀스 초기화 (1부터 시작)
ALTER SEQUENCE 시퀀스이름 RESTART WITH 1;

예제

user_id_seq라는 시퀀스를 1부터 다시 시작하고 싶다면 다음과 같이 설정할 수 있습니다.

ALTER SEQUENCE user_id_seq RESTART WITH 1;

위의 구문은 user_id_seq 시퀀스가 다음에 사용할 값을 1로 재설정합니다.

시퀀스를 현재 최대값으로 동기화하기

테이블의 최대 ID값과 시퀀스를 일치시키고 싶을 때는 setval 함수를 사용할 수 있습니다. 다음과 같이 setval을 사용하여 시퀀스를 초기화할 수 있습니다.

-- 특정 시퀀스를 테이블의 최대값으로 초기화하기
SELECT setval('시퀀스이름', (SELECT MAX(컬럼명) FROM 테이블이름));

 

예를 들어, user_id_seq 시퀀스를 users 테이블의 id 컬럼의 최대값으로 설정하려면 다음과 같습니다.

SELECT setval('user_id_seq', (SELECT MAX(id) FROM users));

 

4. 시퀀스 초기화 주의사항

  • 트랜잭션 관리: 시퀀스 초기화는 트랜잭션으로 처리되지 않기 때문에, 변경 후 복구가 필요할 수 있습니다.
  • 동시성: 시퀀스 초기화 작업이 다른 트랜잭션과 충돌하지 않도록 주의해야 합니다.
  • 자동 증가 컬럼 유지보수: 만약 여러 테이블에 적용되는 시퀀스가 있다면, 다른 테이블의 시퀀스도 확인이 필요합니다.

결론

PostgreSQL에서 시퀀스 초기화는 데이터베이스의 ID와 같은 고유 값을 재설정하거나 특정 값부터 시작하도록 설정할 때 유용합니다. ALTER SEQUENCE 구문과 setval 함수를 활용하여 시퀀스를 초기화하고, 시퀀스를 설정할 때 데이터의 동기화와 성능을 고려하면 더욱 효과적입니다.

728x90
반응형
반응형

데이터베이스 설계에서 테이블의 컬럼 수는 성능과 관리 효율성에 큰 영향을 미칩니다. 특히 PostgreSQL과 같은 관계형 데이터베이스에서는 컬럼 수를 적절하게 제한하는 것이 중요합니다. 오늘은 PostgreSQL에서 권장되는 컬럼 수와 성능을 고려한 데이터베이스 설계 팁을 알아보겠습니다.

1. PostgreSQL의 컬럼 수 한계

PostgreSQL은 테이블 당 최대 1,600개의 컬럼을 허용합니다. 이론적으로는 많은 수의 컬럼을 포함할 수 있지만, 실무에서 이렇게 많은 컬럼을 가진 테이블을 설계하는 것은 좋지 않습니다. 너무 많은 컬럼은 성능 저하, 데이터 관리의 어려움 등을 초래할 수 있기 때문입니다.

2. 실무에서 권장되는 적정 컬럼 수

PostgreSQL에서 50개 이하의 컬럼이 가장 이상적인 수치입니다. 이는 유지보수성, 인덱스 관리, 쿼리 성능을 종합적으로 고려한 결과입니다. 실무에서는 테이블의 컬럼 수가 많아지면 관리가 어려워지고, 불필요한 데이터 전송이 많아져 쿼리 성능이 저하될 수 있습니다.

50개 이상 200개 이하의 컬럼은 특정 요구 사항에 따라 허용될 수 있지만, 200개 이상의 컬럼을 초과할 경우 성능 저하가 본격적으로 나타나므로 가능한 피하는 것이 좋습니다. 예를 들어, 너무 많은 컬럼이 포함된 테이블에서 SELECT * 쿼리를 실행하면 불필요한 데이터를 전송하게 되어 응답 속도가 느려질 수 있습니다.

3. 성능 최적화 및 설계 팁

테이블의 컬럼 수를 적정 수준으로 유지하면서 성능을 극대화하는 몇 가지 팁을 소개합니다.

  • 정규화: 데이터 중복을 줄이고 필수적인 정보만 남겨두는 정규화를 통해 불필요한 컬럼을 줄일 수 있습니다. 이렇게 하면 테이블이 더 간결해지고 관리가 쉬워집니다.
  • 데이터 타입 최적화: 컬럼의 데이터 타입을 최소한의 크기로 지정하여 데이터 저장 공간을 효율적으로 관리하세요. 예를 들어, 정수형 데이터에는 INT 대신 SMALLINT와 같은 작은 크기의 데이터 타입을 사용할 수 있습니다.
  • 인덱스 관리: 컬럼이 많을수록 인덱스 관리가 복잡해집니다. 필수적인 컬럼에만 인덱스를 추가하고, 자주 사용하지 않는 컬럼에는 인덱스를 추가하지 않는 것이 좋습니다.
  • 컬럼 그룹화: 비슷한 역할을 하는 컬럼들을 하나의 테이블에 모두 포함시키는 대신, 1:1 관계의 별도 테이블로 분리하는 방법을 고려할 수 있습니다. 예를 들어, 고객 정보를 저장하는 테이블에서 연락처 관련 컬럼을 별도의 테이블로 분리하면 쿼리 성능과 관리 효율이 개선될 수 있습니다.
  • JSONB 사용: 만약 컬럼이 1,600개에 가까워야 한다면, JSONB 데이터 타입을 활용해 비정형 데이터를 JSON 형태로 저장하는 것도 하나의 방법입니다. 이렇게 하면 유연한 구조로 다양한 데이터를 담을 수 있습니다.

4. 주의할 사항

테이블을 너무 많이 분리하면 JOIN 연산이 많이 발생하여 쿼리 성능이 저하될 수 있습니다. 데이터베이스 설계 초기 단계에서부터 컬럼 수를 최적화하는 것이 필요하며, 미래 확장성도 고려하여 테이블 설계를 진행해야 합니다.

결론

PostgreSQL에서 적절한 컬럼 수는 약 50~100개 수준입니다. 가능하다면 200개 이상의 컬럼을 포함하는 것을 피하는 것이 성능과 관리 측면에서 좋습니다. 데이터베이스 설계는 현재뿐만 아니라 향후 데이터 확장성과 성능을 고려한 장기적인 관점에서 접근해야 합니다.

728x90
반응형
반응형

아래와 같이 해당 BOOLEAN 타입의 컬럼 값을 반전시켜 저장하려고 하는 쿼리가 있습니다.

update table 
set column = !column 
where seq = 1

 

실행을 하면 오류가 발생하는데요 이유는 PostgreSQL에서 ! 연산자는 지원되지 않기때문입니다.

대신 NOT 키워드를 사용해야 합니다.

해결 방법

컬럼의 값을 반전시키기 위해 아래와 같이 쿼리를 수정합니다.

UPDATE table
SET column = NOT column
WHERE seq = 1;

 

설명

  • NOT column은  컬럼의 값을 True에서 False로, 또는 False에서 True로 반전시킵니다.
  • 컬럼이 BOOLEAN 타입일 때만 NOT 연산을 사용할 수 있습니다.
728x90
반응형
반응형

Step1: 프로젝트 초기 설정

패키지 관리자는 yarn을 사용였습니다. 

먼저 프로젝트에 대한 새 디렉토리를 만들고 해당 디렉토리로 이동 후  Yarn 프로젝트를 초기화합니다.

mkdir my-project
cd my-project
yarn init -y

 

Step2: 프로젝트 초기 설정

Node.js, TypeScript, Express, TypeORM 및 PostgreSQL에 대한 패키지를 설치합니다.

yarn add express typeorm reflect-metadata pg
yarn add -D typescript ts-node @types/node @types/express

 

1. 'yarn add express typeorm reflect-metadata pg'

이 명령어는 express, typeorm, reflect-metadata, pg 패키지를 설치합니다. 이 패키지들은 프로젝트의 런타임 의존성으로 설치됩니다.

  • express: Node.js를 위한 빠르고 간단한 웹 프레임워크입니다. HTTP 서버를 쉽게 구축할 수 있게 해줍니다.
  • typeorm: TypeScript와 JavaScript(ES7, ES6, ES5)를 위한 ORM(Object Relational Mapper)입니다. 데이터베이스와 상호작용을 쉽게 해줍니다.
  • reflect-metadata: 메타데이터를 사용하여 객체지향 프로그래밍을 지원하는 데 필요한 라이브러리입니다. TypeORM에서 데코레이터 기능을 사용할 때 필요합니다.
  • pg: PostgreSQL 데이터베이스와 상호작용하기 위한 PostgreSQL 클라이언트입니다.

2. 'yarn add -D typescript ts-node @types/node @types/express'

이 명령어는 typescript, ts-node, @types/node, @types/express 패키지를 개발 의존성으로 설치합니다. 즉, 이 패키지들은 개발 중에만 필요하며, 프로덕션 빌드에는 포함되지 않습니다.

  • typescript: TypeScript 컴파일러입니다. TypeScript 코드를 JavaScript로 변환해줍니다.
  • ts-node: TypeScript 실행 환경입니다. TypeScript 코드를 실행할 수 있게 해줍니다.
  • @types/node: Node.js의 타입 정의 파일입니다. TypeScript에서 Node.js 내장 모듈을 사용할 때 타입 정보를 제공합니다.
  • @types/express: Express의 타입 정의 파일입니다. TypeScript에서 Express를 사용할 때 타입 정보를 제공합니다.

Step3: TypeScript 설정

프로젝트 루트경로에 tsconfig.json 파일을 생성하고 TypeScript 설정을 구성합니다.

{
  "compilerOptions": {
    "target": "ES6",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "outDir": "./dist",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true
  },
  "include": ["src/**/*.ts"],
  "exclude": ["node_modules"]
}

 

Step4: TypeORM 구성 설정

프로젝트 루트경로에 src/data-source.ts 파일을 생성하고 설정을 구성합니다.

import "reflect-metadata";
import { DataSource } from "typeorm";
import { User } from "./entity/User";

export const AppDataSource = new DataSource({
  type: "postgres",
  host: "localhost",
  port: 5432,
  username: "your-username",
  password: "your-password",
  database: "your-database",
  synchronize: true,
  logging: false,
  entities: [User],
  migrations: [],
  subscribers: [],
});

 

'your-username', 'your-password', 'your-database'는 PostgreSQL 설치할때 설정 값으로 바꿉니다

 

Step:5 엔티티 생성

User 및 GenderStatistics 엔티티를 생성합니다.

User 엔티티

import { Entity, PrimaryGeneratedColumn, Column } from "typeorm";

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id!: number;

  @Column()
  firstName!: string;

  @Column()
  lastName!: string;

  @Column()
  age!: number;
}

 

GenderStatistics 엔티티

import { Entity, PrimaryGeneratedColumn, Column } from "typeorm";

@Entity()
export class GenderStatistics {
  @PrimaryGeneratedColumn()
  num!: number;

  @Column({ nullable: true })
  poll_id!: string;

  @Column({ nullable: true })
  trait!: string;

  @Column({ nullable: true })
  gender!: string;

  @Column({ nullable: true })
  gender_cnt!: number;

  @Column("float", { nullable: true })
  rate!: number;
}

 

rate 필드는 소수점 값으로 저장할 수 있도록 설정하였습니다.

 

Step6: 리포지토리 생성

위의서 만든 각 엔티티에 해당하는 리포지토리를 생성하여 데이터베이스 작업을 처리합니다.

 

userRepository.ts

import { AppDataSource } from "../data-source";
import { User } from "../entity/User";

const userRepository = AppDataSource.getRepository(User);

export const createUser = async (userData: Partial<User>): Promise<User> => {
  const user = userRepository.create(userData);
  return await userRepository.save(user);
};

export const getUserById = async (id: number): Promise<User | null> => {
  return await userRepository.findOneBy({ id });
};

 

genderStatisticsRepository.ts

import { AppDataSource } from "../data-source";
import { GenderStatistics } from "../entity/GenderStatistics";

const genderStatisticsRepository = AppDataSource.getRepository(GenderStatistics);

export const createGenderStatistics = async (data: Partial<GenderStatistics>): Promise<GenderStatistics> => {
  const genderStat = genderStatisticsRepository.create(data);
  return await genderStatisticsRepository.save(genderStat);
};

export const getGenderStatisticsByNum = async (num: number): Promise<GenderStatistics | null> => {
  return await genderStatisticsRepository.findOneBy({ num });
};

 

Step7: 컨트롤러 생성

컨트롤러를 생성하여 HTTP 요청을 처리합니다.

 

userController.ts

import { Request, Response } from "express";
import { createUser, getUserById } from "../repositories/userRepository";

export const createUserHandler = async (req: Request, res: Response) => {
  try {
    const user = await createUser(req.body);
    res.status(201).json(user);
  } catch (error) {
    if (error instanceof Error) {
      res.status(500).json({ message: error.message });
    } else {
      res.status(500).json({ message: "An unknown error occurred" });
    }
  }
};

export const getUserByIdHandler = async (req: Request, res: Response) => {
  try {
    const user = await getUserById(parseInt(req.params.id));
    if (user) {
      res.json(user);
    } else {
      res.status(404).json({ message: "User not found" });
    }
  } catch (error) {
    if (error instanceof Error) {
      res.status(500).json({ message: error.message });
    } else {
      res.status(500).json({ message: "An unknown error occurred" });
    }
  }
};

 

genderStatisticsController.ts

import { Request, Response } from "express";
import { createGenderStatistics, getGenderStatisticsByNum } from "../repositories/genderStatisticsRepository";

export const createGenderStatisticsHandler = async (req: Request, res: Response) => {
  try {
    const genderStat = await createGenderStatistics(req.body);
    res.status(201).json(genderStat);
  } catch (error) {
    if (error instanceof Error) {
      res.status(500).json({ message: error.message });
    } else {
      res.status(500).json({ message: "An unknown error occurred" });
    }
  }
};

export const getGenderStatisticsByNumHandler = async (req: Request, res: Response) => {
  try {
    const genderStat = await getGenderStatisticsByNum(parseInt(req.params.num));
    if (genderStat) {
      res.json(genderStat);
    } else {
      res.status(404).json({ message: "Gender statistics not found" });
    }
  } catch (error) {
    if (error instanceof Error) {
      res.status(500).json({ message: error.message });
    } else {
      res.status(500).json({ message: "An unknown error occurred" });
    }
  }
};

 

Step8: 라우트 설정

userRoutes.ts

import { Router } from "express";
import { createUserHandler, getUserByIdHandler } from "../controllers/userController";

const router = Router();

router.post("/users", createUserHandler);
router.get("/users/:id", getUserByIdHandler);

export default router;

 

genderStatisticsRoutes.ts

import { Router } from "express";
import { createGenderStatisticsHandler, getGenderStatisticsByNumHandler } from "../controllers/genderStatisticsController";

const router = Router();

router.post("/gender-statistics", createGenderStatisticsHandler);
router.get("/gender-statistics/:num", getGenderStatisticsByNumHandler);

export default router;

 

Step9: Express 서버 설정

index.ts

import "reflect-metadata";
import express from "express";
import { AppDataSource } from "./data-source";
import userRoutes from "./routes/userRoutes";
import genderStatisticsRoutes from "./routes/genderStatisticsRoutes";

AppDataSource.initialize().then(() => {
  const app = express();

  app.use(express.json());

  app.use(userRoutes);
  app.use(genderStatisticsRoutes);

  const port = process.env.PORT || 3000;
  app.listen(port, () => {
    console.log(`Server is running on port ${port}`);
  });
}).catch(error => console.log(error));

 

모두 작성 되었다면 yarn dev로 개발 서버를 실행합니다.

 

자바스크립트 계열은 자료를 찾다보면 작성자마다 구조와 형식이 달라 늘 어렵네요.

728x90
반응형
반응형

PostgreSQL에서 real, float, float8, double precision, numeric 타입은 모두 부동 소수점 숫자 또는 고정 소수점 숫자를 저장하는 데 사용됩니다. 각각의 차이점과 사용 방법, 그리고 활용 방안에 대해 알아보겠습니다.

데이터 타입 설명

  1. real
    • 설명: 4바이트 단정밀도 부동 소수점 숫자.
    • 정밀도: 약 6자리의 소수점 자릿수를 가짐.
    • 범위: -3.4E+38 ~ +3.4E+38.
    • 사용: 메모리가 제한적이고, 매우 높은 정밀도가 필요하지 않은 경우에 사용.
  2. float (또는 float8)
    • 설명: 8바이트 배정밀도 부동 소수점 숫자. float8은 double precision의 별칭입니다.
    • 정밀도: 약 15자리의 소수점 자릿수를 가짐.
    • 범위: -1.7E+308 ~ +1.7E+308.
    • 사용: 높은 정밀도가 필요한 경우에 사용.
  3. double precision
    • 설명: 8바이트 배정밀도 부동 소수점 숫자.
    • 정밀도: float와 동일, 약 15자리의 소수점 자릿수를 가짐.
    • 범위: float와 동일, -1.7E+308 ~ +1.7E+308.
    • 사용: 매우 높은 정밀도가 필요한 경우에 사용.
  4. numeric (또는 decimal)
    • 설명: 가변 길이의 정확한 숫자. 소수점 이하 자릿수와 전체 자릿수를 지정할 수 있음.
    • 정밀도: 사용자가 지정한 자릿수까지 정확함.
    • 범위: 매우 넓음, 제한이 없다고 볼 수 있음.
    • 사용: 금액 계산 등 매우 높은 정밀도가 요구되는 경우에 사용.

사용 방법과 예제

real

CREATE TABLE example_real ( id serial PRIMARY KEY, value real ); 
INSERT INTO example_real (value) VALUES (3.14), (2.71); 
SELECT * FROM example_real;

float (float8), double precision

CREATE TABLE example_float ( id serial PRIMARY KEY, value float8 ); 
CREATE TABLE example_double_precision ( id serial PRIMARY KEY, value double precision ); 
INSERT INTO example_float (value) VALUES (3.141592653589793), (2.718281828459045); 
INSERT INTO example_double_precision (value) VALUES (3.141592653589793), (2.718281828459045); 
SELECT * FROM example_float; SELECT * FROM example_double_precision;

numeric (decimal)

CREATE TABLE example_numeric ( id serial PRIMARY KEY, value numeric(10, 2) ); 
INSERT INTO example_numeric (value) VALUES (12345.67), (98765.43); 
SELECT * FROM example_numeric;
 

활용 방안

  1. real: 센서 데이터, 간단한 계산 등 정밀도가 크게 필요 없는 경우에 사용합니다.
  2. float (float8), double precision: 과학 계산, 통계 분석 등 높은 정밀도가 필요한 경우에 사용합니다. float8과 double precision은 동일한 타입이므로 동일한 용도로 사용됩니다.
  3. numeric (decimal): 금액, 금융 데이터 등 매우 높은 정밀도와 정확성이 필요한 경우에 사용합니다. 특히, 소수점 이하 자릿수가 중요한 경우 유용합니다.

요약

  • real은 4바이트 단정밀도 부동 소수점으로, 정밀도가 크게 필요 없는 경우에 사용됩니다.
  • float와 double precision은 8바이트 배정밀도 부동 소수점으로, 높은 정밀도가 필요한 경우에 사용됩니다.
  • numeric은 고정 소수점으로, 정밀도가 매우 중요한 경우에 사용됩니다.
728x90
반응형

+ Recent posts