260901

u·2026년 9월 1일

TIL

목록 보기
12/30

Cypher

중요
import 파일에 데이터 파일 넣기

파일 적재 / 데이터 접근

LOAD CSV WITH HEADERS FROM $path AS row
→ CSV 한 행을 row로 받아 컬럼명으로 값에 접근한다.

row['역명']
→ CSV의 실제 '역명' 값을 가져오며 괄호·공백 등이 있는 컬럼명도 [] 방식으로 안전하게 접근한다.

CALL apoc.load.json('file:///파일.json') YIELD value
→ JSON 데이터를 읽어 현재 항목을 value로 받는다.

UNWIND value['요일별'] AS item
→ JSON 내부 리스트를 하나씩 행처럼 펼쳐 item으로 처리한다.

'value' vs value['역명']
→ 'value'는 문자 그대로의 문자열이고 value['역명']은 실제 JSON 데이터를 꺼낸 값이다.

★ 데이터 위치 구분

row['컬럼']
→ 현재 CSV 행에 들어 있는 값이다.

value['키']
→ 현재 JSON 객체에 들어 있는 값이다.

item['키']
→ UNWIND로 펼친 JSON 배열의 현재 항목 값이다.

t.count
→ Neo4j의 t 관계에 저장된 count 속성이다.

count(t) vs t.count
→ count(t)는 관계 개수를 세는 함수이고 t.count는 관계에 저장된 속성값이다.

MERGE / 적재

MERGE (s:Station {name: value['역명']})
→ name이 같은 Station을 재사용하고 없을 때만 새로 만든다.

MERGE (d:DayType {name: item['요일']})
→ 같은 요일 이름을 하나의 노드로 공유하게 만들어 평일·토요일·일요일 3개로 묶는다.

MERGE (s)-[t:TRANSFERS]->(d)
→ 두 노드 사이 관계가 없으면 생성하고 있으면 기존 관계를 사용한다.

SET t.count = item['인원']
→ 파일에서 읽은 인원 값을 TRANSFERS 관계의 속성으로 저장한다.

MATCH vs MERGE
→ MATCH는 존재하는 것을 찾기만 하고 MERGE는 없으면 생성하므로 관계 적재에서는 둘의 역할을 구분해야 한다.

★ 조용한 실패

MATCH (s:Metro {name: ...})
→ 존재하지 않는 레이블을 MATCH해도 에러가 아니라 결과 0행이 되어 이후 관계 생성도 실행되지 않을 수 있다.

적재 성공 ≠ 정상 적재
→ 쿼리가 에러 없이 끝나도 MATCH 실패로 0건 적재될 수 있으므로 반드시 count로 결과를 검증한다.

집계 / 그룹핑

RETURN d.name AS 요일, sum(t.count) AS 합계
→ 집계 함수와 일반 값을 함께 반환하면 d.name별로 자동 그룹핑되어 합계를 계산한다.

count(t)
→ 관계에 저장된 값이 아니라 관계 자체가 몇 개인지를 센다.

sum(t.count)
→ 각 관계의 count 속성값을 모두 더한다.

ORDER BY 값 DESC LIMIT 5
→ 값을 큰 순서로 정렬한 뒤 상위 5개만 남긴다.

WITH / 변수 전달

WITH s, sd
→ 다음 쿼리 단계에서도 사용할 변수 s와 sd를 명시적으로 넘긴다.

WITH sd
→ 이전에 있던 s를 넘기지 않았으므로 이후 단계에서는 s를 사용할 수 없다.

배치 트랜잭션

CALL (s) { ... } IN TRANSACTIONS OF 20 ROWS
→ 바깥에서 찾은 s를 블록 안으로 넘겨 20행씩 나누어 갱신한다.

CALL (s) { MATCH ... SET ... }
→ 배치 블록 안에서도 필요한 관계를 다시 MATCH한 뒤 해당 값을 이용해 속성을 갱신할 수 있다.

파생 속성

SET s.sat_ratio = round(toFloat(sa.count) / w.count, 3)
→ 토요일 관계 인원을 평일 관계 인원으로 나눈 뒤 실수 계산하여 소수 셋째 자리까지 저장한다.

toFloat(sa.count) / w.count
→ 정수 나눗셈 문제를 피하기 위해 한쪽 값을 실수로 변환한다.

SET 속성 = null
→ Neo4j에서는 null을 저장하는 것이 아니라 해당 속성이 제거되므로 원본 값 존재 여부를 먼저 확인해야 한다.

날짜 / 그래프 모델링

SET s.surveyed = date('2025-11-30')
→ 문자열이 아닌 Neo4j Date 자료형으로 기준일을 저장한다.

MERGE (sd:SurveyDay {date: date('2025-11-30')})
→ 같은 날짜를 여러 Station이 공유하는 하나의 날짜 노드로 모델링한다.

MERGE (s)-[:SURVEYED_ON]->(sd)
→ Station에서 조사 날짜 노드 방향으로 관계를 생성한다.

MERGE (sy:SurveyYear {year: sd.date.year})
→ 날짜 노드의 Date 값에서 연도만 꺼내 연도 노드를 만든다.

MERGE (sy)-[:HAS_DAY]->(sd)
→ SurveyYear → SurveyDay 방향으로 시간 계층을 만든다.

★ 같은 쌍 세기

WHERE a.surveyed = b.surveyed AND a.name < b.name
→ 같은 날짜인 두 역을 찾되 이름 순서를 강제하여 A-B와 B-A 중복 집계를 막는다.

MATCH (a)-[:SURVEYED_ON]->(d)<-[:SURVEYED_ON]-(b)
→ 두 노드가 같은 가운데 노드를 공유하는 그래프 패턴으로 '같은 날'을 표현한다.

Python에서 Cypher 결과 꺼내기

run_cypher(query)
→ Cypher 결과가 [{'n': 73}] 같은 dict 리스트 형태로 반환된다.

run_cypher(query)[0]['n']
→ 첫 번째 결과 행에서 n 값만 꺼낸다.

[r['역명'] for r in rows]
→ 결과의 역명만 뽑아 실제 list로 만든다.

(r['역명'] for r in rows)
→ 리스트가 아니라 generator를 만들기 때문에 리스트가 필요한 문제에서는 []를 사용한다.

오늘 제일 중요한 사고방식

파일값 → row / value / item
노드값 → s.xxx / d.xxx
관계값 → t.xxx
→ 계산 전에 '내가 필요한 값이 지금 어디에 저장되어 있는가?'부터 확인한다.

찾는다 → MATCH
없으면 만든다 → MERGE
속성을 넣는다 → SET
배열을 펼친다 → UNWIND
몇 개인지 → count()
값을 더한다 → sum()
~별로 묶는다 → 일반값 + 집계함수
→ 문제의 동사를 Cypher 문법으로 먼저 번역하고 코드를 작성한다.

적재 → 검증 → 조회
→ 데이터를 만든 뒤 count, keys, 샘플 조회로 실제 DB 상태를 확인하고 다음 단계로 넘어간다.

0개의 댓글