함수 심화(3): map()과 filter()로 관계형 데이터 제어하기


노션 데이터베이스를 연결해서 쓰다 보면 관계형(Relation) 속성의 한계에 부딪히는 순간이 반드시 옵니다. 단순히 연결된 페이지의 개수를 세거나 합계를 구하는 건 롤업(Rollup)으로 가능한데, 특정 조건에 맞는 데이터만 쏙 뽑아오고 싶을 때는 롤업 버튼이 회색으로 비활성화되어 있어 답답했던 경험, 다들 있으실 거예요. 

오늘은 노션 함수 2.0의 꽃이자, 관계형 데이터를 자유자재로 주무를 수 있게 해주는 map()과 filter() 함수에 대해 깊이 있게 파헤쳐 보겠습니다!



1. 90%가 포기하는 롤업의 치명적인 한계


우리는 흔히 프로젝트 DB와 할 일 DB를 연결해서 사용합니다. 그리고 프로젝트 페이지에서 완료된 할 일의 개수만 보고 싶어 하죠. 하지만 롤업 기능을 켜보면 다음과 같은 답답한 상황에 직면하게 됩니다.


  • 롤업은 모든 연결된 데이터를 가져오거나, 전체 합계만 보여줍니다.
  • 완료된 일만 따로 계산하고 싶은데, 롤업에는 필터 기능이 없습니다.
  • 결국 불필요한 롤업 속성을 3~4개씩 만들어서 지저분하게 관리해야 합니다.


많은 분들이 이 단계에서 포기하고 그냥 눈으로 숫자를 세거나, 수동으로 입력을 하곤 해요. 하지만 map()과 filter()를 알면 이 문제를 단 한 줄의 수식으로 해결할 수 있습니다. 

제가 컨설팅할 때 가장 반응이 좋았던 기능이니 꼭 집중해 주세요!


이1



2. 데이터의 터널을 뚫어주는 map()


map() 함수는 쉽게 말해 리스트의 각 항목을 변환해 주는 함수입니다. 관계형 속성으로 연결된 데이터는 사실 하나의 리스트(List) 덩어리입니다. map()은 이 리스트 안으로 들어가서 우리가 원하는 특정 속성값만 쏙쏙 뽑아서 새로운 리스트로 만들어줍니다.


기본 구조와 작동 원리는 다음과 같습니다.


  • 기본 문법: prop("관계형속성").map(current.prop("가져올속성"))
  • current의 의미: 리스트에 있는 현재 아이템을 가리킵니다. 반복문이 돌면서 하나씩 짚어내는 것이죠.
  • 역할: 관계형으로 연결된 페이지 전체를 가져오는 게 아니라, 그 페이지 안에 있는 가격이나 상태 같은 세부 정보만 추출합니다.


예를 들어 프로젝트와 연결된 할 일들의 작업 시간을 모두 가져오고 싶다면 이렇게 작성해 보세요.


  • prop("할일_DB").map(current.prop("작업시간"))
  • 결과: [1시간, 2시간, 4시간] 처럼 숫자 리스트가 생성됩니다.
  • 활용: 여기에 .sum()을 붙이면 프로젝트의 총 작업 시간이 계산됩니다.



3. 원하는 것만 걸러내는 거름망 filter()


map()이 데이터를 가져오는 역할이라면, filter()는 가져온 데이터 중에서 조건에 맞는 것만 남기는 거름망 역할을 합니다. 이 함수가 바로 롤업이 못하는 조건부 계산을 가능하게 해주는 핵심 열쇠입니다.


사용 방법은 map()과 매우 유사하지만, 결과가 참(True)인 것만 남긴다는 점이 다릅니다.


  • 기본 문법: prop("관계형속성").filter(current.prop("속성") == "조건값")
  • 활용 예시 1: 완료된 할 일만 남기기 -> prop("할일_DB").filter(current.prop("완료여부") == true)
  • 활용 예시 2: 특정 담당자의 업무만 남기기 -> prop("할일_DB").filter(current.prop("담당자").contains("김철수"))


이 filter() 함수를 거치고 나면, 수십 개의 연결된 데이터 중 내가 보고 싶은 알짜배기 데이터만 리스트에 남게 됩니다.


이2



4. 실전 응용: map()과 filter()의 콤비네이션


이제 두 함수를 조합해서 실무에서 진짜 필요한 지표를 만들어볼게요. 가장 많이 요청하시는 기능인 특정 조건의 달성률 구하기를 예로 들어보겠습니다. 전체 할 일 중에서 완료된 할 일의 비율을 구하고 싶다면 다음과 같은 논리로 수식을 짜야 합니다.


  • 분모(전체 개수): prop("할일_DB").length()
  • 분자(완료 개수): prop("할일_DB").filter(current.prop("완료") == true).length()
  • 최종 수식: (분자 / 분모) * 100


이걸 응용하면 다음과 같은 고급 대시보드 구현이 가능해집니다.


  • 내 업무 진행률: 전체 업무가 아니라 나에게 할당된 업무 중 완료된 비율만 계산합니다.
  • 이번 달 매출 합계: 연결된 전체 매출 내역 중 날짜가 이번 달에 해당하는 건들만 필터링하여 합계를 구합니다.
  • 미완료 과제 리스트: 연결된 과제 중 완료되지 않은 건들의 제목만 map()으로 뽑아서 텍스트로 보여줍니다.



5. 함수 사용 시 주의해야 할 디테일


이 강력한 함수들을 사용할 때 90%가 겪는 시행착오가 있습니다. 수식 창에서 에러가 뜬다면 다음 사항들을 먼저 체크해 보세요.


  • 점(.) 찍는 위치: map()과 filter()는 속성 뒤에 점을 찍어서 연결하는 메서드 체이닝(Method Chaining) 방식을 씁니다. 괄호 위치가 헷갈릴 수 있으니 주의가 필요합니다.
  • 데이터 타입 불일치: filter() 안에서 비교할 때는 데이터 타입이 같아야 합니다. 텍스트는 텍스트끼리, 숫자는 숫자끼리 비교해야 에러가 나지 않습니다.
  • current의 범위: current는 해당 함수(map 또는 filter) 내부에서만 유효합니다. 괄호 밖으로 나오면 current는 힘을 잃습니다.

이3



특히 관계형 속성이 비어있는 경우, 분모가 0이 되어 에러가 날 수 있습니다. 지난 포스팅에서 배운 empty()나 if() 함수를 섞어서 예외 처리를 해주면 훨씬 안정적인 시스템을 만들 수 있어요.


처음에는 current니 prop이니 하는 용어들이 낯설어서 어렵게 느껴질 수 있습니다. 하지만 이 map()과 filter()야말로 노션을 단순한 메모장에서 데이터베이스 관리 툴로 격상시켜 주는 일등 공신입니다. 

오늘 당장 여러분의 프로젝트 페이지에 들어가서 죽어있는 롤업 속성들을 이 함수들로 교체해 보세요. 데이터가 살아 움직이는 것을 느끼실 수 있을 거예요!






댓글