Prisma에는 왜 JOIN이 없을까: ORM 패턴, 스키마, 내부 동작 정리

개요 Prisma로 관계형 데이터베이스를 다루다 보면 자연스럽게 드는 질문이 있음 Prisma에서는 JOIN이 어디로 갔나 하는 질문임 개발자가 작성하는 Prisma Client API에는 JOIN이 없고, 서브쿼리도 보이지 않음 정말로 없는지, 없다면 왜 그런지, 어떤 트레이드오프가 있는지 정리함 ORM이란 무엇인가 ORM은 객체와 관계형 데이터베이스 간의 매핑을 제공하는 아이디어이자 구현체 집합을 의미함 애플리케이션에서 모델을 통해 데이터베이스 테이블을 간접 제어하는 추상화 계층 제공 SQL을 직접 작성하지 않고 데이터 접근 로직을 일관된 API로 수행 가능 데이터베이스 의존성 완화 효과 기대 ...

November 23, 2025

Prisma Client index.d.ts로 읽는 ObjektInventory 집계·그룹화 타입 구조

@prisma/client의 index.d.ts 개요 Prisma Client가 생성하는 index.d.ts는 모델별 쿼리 메서드와 그 입출력 타입을 정의하는 타입 소스임 여기서는 예시 모델 ObjektInventory를 기준으로 집계 aggregation, 그룹화 groupBy, 카운트 count, 조회 find 계열 메서드의 타입 구조와 사용 맥락 정리 핵심은 타입 안전성과 결과 형태의 명시적 제어이며, 모든 타입은 Prisma 스키마를 기반으로 자동 생성됨 핵심 개념 Aggregation 집계 평균, 합, 최소, 최대, 카운트 같은 통계 요약 계산 결과 형태를 명확히 보장하는 Output 타입과 어떤 필드를 집계할지 지정하는 Input 타입으로 구성 Group By 그룹화 특정 필드 기준으로 그룹 묶음 생성 후 각 그룹에 집계 적용 입력 인자 검증이 엄격하며 by, orderBy, having, take, skip 조합 규칙 존재 Count 카운트 조건에 맞는 레코드 개수 반환 _count를 다른 집계와 함께 요청하거나 단독 count 메서드로 호출 가능 Find 조회 findUnique, findFirst, findMany로 조건, 정렬, 페이징, select, include를 조합해 조회 반환 타입은 선택한 필드와 관계에 따라 좁혀짐 타입 구조와 동작 집계 결과 타입 ObjektInventoryAvgAggregateOutputType, ObjektInventorySumAggregateOutputType, ObjektInventoryMinAggregateOutputType, ObjektInventoryMaxAggregateOutputType, ObjektInventoryCountAggregateOutputType 등으로 구성 각 필드는 해당 타입의 스칼라값 또는 null을 가질 수 있음 집계 입력 타입 ObjektInventoryAvgAggregateInputType, ObjektInventorySumAggregateInputType 등에서 true로 지정한 필드만 결과에 포함됨 그룹화 인자와 출력 타입 ObjektInventoryGroupByArgs에 by로 그룹 기준 필드 지정, _count, _avg, _sum, _min, _max 중 필요한 집계 선택 출력은 ObjektInventoryGroupByOutputType으로 각 그룹 키 필드 + 선택한 집계 결과 반환 조회 인자 타입 ObjektInventoryFindUniqueArgs, ObjektInventoryFindFirstArgs, ObjektInventoryFindManyArgs에서 where, orderBy, cursor, take, skip, select, include 등을 타입 안전하게 지정 메서드 반환 타입 추론 제네릭과 조건부 타입을 통해 선택한 select 또는 집계 키에 맞춰 반환 타입이 자동으로 좁혀짐 _count를 true로 둘 경우 숫자 반환, 객체 형태를 원하면 _count { _all: true } 형태 사용 사용 예시 집계 예시 const aggregationResult = await prisma.objektInventory.aggregate({ _avg: { objektId: true }, _sum: { lenticularPairTokenId: true }, where: { owner: 'john' }, }) 그룹화 예시 const groupByResult = await prisma.objektInventory.groupBy({ by: ['status'], _count: { _all: true }, _avg: { objektId: true }, }) 카운트 예시 const totalCount = await prisma.objektInventory.count({ where: { owner: 'john' }, }) 조건부 조회 예시 const inventories = await prisma.objektInventory.findMany({ where: { owner: 'john' }, orderBy: { updatedAt: 'desc' }, take: 10, }) 제약과 주의 사항 groupBy 규칙 by에 포함되지 않은 필드로 orderBy 또는 having을 사용할 수 없음 take 또는 skip을 사용하는 경우 orderBy 필수 런타임 전 타입 레벨에서 오류로 차단되어 쿼리 일관성 확보 null 가능성 집계 결과는 조건에 맞는 레코드가 없으면 각 필드가 null이 될 수 있음 연산 전 null 체크 필요 _count 반환 형태 _count: true는 숫자 단일 값, _count: { _all: true }는 카운트 내역을 가진 객체 반환 사용 목적에 맞는 형태 선택 권장 성능 고려 불필요한 필드 조회 지양, select로 최소화 where 조건에 인덱스 친화적 필드 사용 권장 대규모 groupBy는 데이터베이스 리소스 부담 가능, 필요한 필드와 조건만 사용 타입 안전성 스키마 변경 시 재생성된 index.d.ts를 기준으로 컴파일 타임 검증 수행 any 캐스팅으로 타입 보호 우회 금지 맥락과 활용 포인트 데이터 분석 워크로드에서 서버 측 집계 활용으로 불필요한 애플리케이션 레벨 후처리 감소 리포트, 대시보드, 통계 API에서 groupBy + 다중 집계 조합이 유용 타입 정의가 강제하는 인자 조합 규칙으로 런타임 쿼리 오류를 사전에 방지 가능 반환 타입이 선택된 입력에 따라 좁혀지므로 응답 스키마를 명확히 설계 가능 마무리 ObjektInventory를 예로 본 index.d.ts의 집계와 그룹화 타입은 Prisma Client가 제공하는 타입 안전 쿼리의 핵심 축임 입력 타입으로 의도를 명시하고, 출력 타입으로 결과 스키마를 보장하는 흐름이 데이터 품질과 안정성을 높임 그룹화 규칙과 _count 반환 형태, null 가능성 같은 세부 제약만 지키면 복잡한 통계 요구사항도 간결하게 커버 가능 ...

November 22, 2025

Prisma where 관계 필터 some vs every 동작 차이와 주의점

관계형 데이터에서 Prisma의 where 절은 자식 레코드 기준으로 부모를 거르는 필터를 제공함. 특히 some과 every는 겉보기엔 비슷하지만 결과 집합을 크게 바꾸는 핵심 차이가 있음. 단일 필드만으로 필터링해도 동일하지 않을 수 있어 주의 필요 개념 some 관계된 레코드 중 적어도 하나가 조건을 만족하면 부모 포함 존재성 검사에 해당, 하나라도 매칭되면 true every 모든 관계된 레코드가 조건을 만족해야 부모 포함 단 하나라도 위배되면 제외됨 관계된 레코드가 하나도 없으면 vacuously true로 간주되어 조건 만족으로 처리됨 동작 원리 some은 존재량화, every는 전칭량화에 해당함 차이는 자식 레코드가 0개이거나 2개 이상일 때 두드러짐. 1개일 때는 조건이 동일하다면 결과가 같아질 수 있음 특히 자식이 없는 경우 every는 기본적으로 참으로 평가되어 부모가 포함됨. 빈 결과를 제외하려면 추가 조건 필요 간단 예시 model Post { id Int @id @default(autoincrement()) title String comments Comment[] } model Comment { id Int @id @default(autoincrement()) text String postId Int post Post @relation(fields: [postId], references: [id]) } 댓글 text가 ‘interesting’인 항목을 기준으로 게시글을 거르는 케이스를 가정 ...

November 17, 2025

Prisma findUnique에서 where와 include 제대로 쓰기

개요 findUnique로 단일 레코드 조회하면서 관련된 데이터까지 한 번에 가져오고 싶을 때 where와 include를 어떻게 조합해야 하는지 정리함 관계 필터링을 where에 넣을 수 있는지, include에서 필터가 가능한지 헷갈리기 쉬운 지점 정리 핵심 개념 findUnique는 유니크 키로 정확히 하나의 레코드를 찾는 용도 findUnique의 where는 유니크 필드만 허용됨, 관계 필터나 일반 조건 결합 불가 include는 조회된 레코드에 대한 연관 레코드를 함께 가져오는 옵션 to-many 관계에 한해 include 내부에서 where 사용 가능, to-one 관계는 where 불가 where 사용 패턴 잘못된 예시 // findUnique에 관계 필터 결합 시도 → 타입 에러 // (User(1) → Post(N) → Metadata(1) 관계) await db.user.findUnique({ where: { id: userId, // 'posts'는 유니크 필드가 아니므로 'where'에서 관계 필터링 불가 posts: { some: { metadata: { editorEmail: email } } }, }, }); 올바른 최소 조건 await db.user.findUnique({ where: { id: userId }, // 'id'는 유니크 필드 }); 관계 조건이 필요하면 findFirst 또는 findMany 사용 // 'findUnique'가 아닌 'findFirst'를 사용하면 // 'where'에 관계 필터를 포함할 수 있음 await db.user.findFirst({ where: { id: userId, posts: { some: { metadata: { editorEmail: email } } }, }, }); 요약하면 findUnique에는 유니크 조건만, 관계 기반 필터는 findFirst 또는 findMany로 처리함 ...

November 12, 2025

Prisma findMany 가이드: where, select/include, 정렬·페이징, in/비교 연산자

개요 Prisma의 findMany는 다중 레코드 조회용 메서드 기본값은 대상 모델의 모든 레코드 반환 where 필터, select/include, 정렬, 페이징, 중복 제거 등 옵션 지원 옵션 조합으로 조건 기반 조회를 간결하게 구성 가능 기본 사용법 가장 단순한 호출 형태 const users = await prisma.user.findMany(); 주요 옵션 옵션은 필요한 것만 선택적으로 사용 where: 조건 필터링 select: 필드 서브셋 선택 include: 관계 데이터 로드 orderBy: 정렬 기준 지정 skip, take: 오프셋 기반 페이징 distinct: 특정 필드 기준 중복 제거 where로 조건 필터링 단일 조건부터 복합 조건까지 표현 가능 ...

October 28, 2025