고급 Cypher: 경로 탐색과 영향도 분석
Neo4j & Cypher
고급 Cypher: 경로 탐색과 영향도 분석
온톨로지 & Knowledge Graph > Neo4j & Cypher
가변 길이 경로 탐색(*1..N)을 수행한다 shortestPath로 최단 경로를 찾는다 집계 함수(COUNT, COLLECT, REDUCE)를 활용한다 제조 현장의 영향도 분석 쿼리를 작성한다
고급 Cypher: "이 설비가 멈추면 무슨 일이 벌어지나?"
1. 가변 길이 경로 탐색
// 알람에서 최종 조치까지 전체 경로 (깊이 1~5)
MATCH path = (f:Fault {alarmCode: "E-201"})
-[:CAUSED_BY|RESOLVED_BY*1..5]->(end)
WHERE NOT (end)-->() // 리프 노드까지
RETURN path;
// 설비에서 도달 가능한 모든 노드 (3홉 이내)
MATCH path = (e:Equipment {name: "CNC밀링 3호기"})-[*1..3]->(connected)
RETURN DISTINCT labels(connected)[0] AS 타입,
connected.name AS 이름,
length(path) AS 거리;
// 고장 원인의 깊이별 탐색
MATCH (f:Fault {alarmCode: "E-201"})-[:CAUSED_BY*1..3]->(cause)
RETURN cause.name AS 원인,
length(shortestPath((f)-[:CAUSED_BY*]->(cause))) AS 깊이;
2. 영향도 분석: "이 설비가 멈추면?"
// 설비 정지 시 영향받는 공정 파악
MATCH (e:Equipment {name: "CNC밀링 3호기"})
<-[:USES_EQUIPMENT]-(process:Process)
OPTIONAL MATCH (process)-[:NEXT_PROCESS*1..5]->(downstream:Process)
RETURN e.name AS 정지설비,
process.name AS 직접영향_공정,
collect(DISTINCT downstream.name) AS 후속영향_공정;
// 부품 교체 시 필요한 인력과 시간 산정
MATCH (p:Part {partNumber: "BRG-6205-2RS"})
-[:CAN_CAUSE]->(f:Fault)
-[:CAUSED_BY]->(cause:RootCause)
-[:RESOLVED_BY]->(action:MaintenanceAction)
-[:REQUIRES_SKILL]->(skill:Skill)
OPTIONAL MATCH (w:Worker)-[:HAS_SKILL]->(skill)
RETURN p.name AS 부품,
f.name AS 관련고장,
action.name AS 정비조치,
action.estimatedTime AS 소요시간,
skill.name AS 필요스킬,
collect(w.name) AS 가용인력;
3. 집계와 통계
// 설비별 등록된 부품 수와 고장 위험 부품 수
MATCH (e:Equipment)-[:HAS_COMPONENT]->()-[:HAS_PART]->(p:Part)
WITH e, count(p) AS totalParts,
sum(CASE WHEN toFloat(p.currentHours)/p.lifespan > 0.8 THEN 1 ELSE 0 END) AS riskParts
RETURN e.name AS 설비,
totalParts AS 총부품수,
riskParts AS 위험부품수,
round(toFloat(riskParts)/totalParts * 100, 1) + "%" AS 위험비율
ORDER BY riskParts DESC;
// 정비 난이도별 조치 목록
MATCH (a:MaintenanceAction)
WITH a.difficulty AS difficulty, collect(a.name) AS actions, count(a) AS cnt
RETURN difficulty AS 난이도,
cnt AS 조치수,
actions AS 조치목록
ORDER BY CASE difficulty WHEN "쉬움" THEN 1 WHEN "보통" THEN 2 ELSE 3 END;
// 작업자별 대응 가능한 고장 유형 수
MATCH (w:Worker)-[:HAS_SKILL]->(s:Skill)
<-[:REQUIRES_SKILL]-(a:MaintenanceAction)
<-[:RESOLVED_BY]-()-[:CAUSED_BY]-(f:Fault)
RETURN w.name AS 작업자,
w.experience AS 경력,
count(DISTINCT f) AS 대응가능_고장수,
collect(DISTINCT f.alarmCode) AS 대응가능_알람;
4. 실전 분석 쿼리 모음
// Q1: "알람 E-201이 떴는데 뭐부터 확인하나요?"
// → 확률 높은 순으로 원인-조치-필요인력 안내
MATCH (f:Fault {alarmCode: "E-201"})
-[:CAUSED_BY]->(cause:RootCause)
-[:RESOLVED_BY]->(action:MaintenanceAction)
-[:REQUIRES_SKILL]->(skill:Skill)
OPTIONAL MATCH (w:Worker)-[:HAS_SKILL]->(skill)
RETURN cause.name AS 원인,
cause.probability AS 확률,
action.name AS 조치,
action.estimatedTime + "분" AS 소요시간,
skill.name AS 필요스킬,
collect(w.name) AS 가용인력
ORDER BY cause.probability DESC;
// Q2: "다음 달 교체 예정 부품은?"
MATCH (e:Equipment)-[:HAS_COMPONENT]->()-[:HAS_PART]->(p:Part)
WHERE toFloat(p.currentHours) / p.lifespan > 0.8
RETURN e.name AS 설비, p.name AS 부품, p.partNumber AS 부품번호,
p.currentHours AS 현재시간, p.lifespan AS 수명,
round(toFloat(p.currentHours)/p.lifespan*100, 1) + "%" AS 사용률,
p.cost AS 비용, p.manufacturer AS 제조사
ORDER BY toFloat(p.currentHours)/p.lifespan DESC;
// Q3: "김기술이 휴가가면 대응 못하는 고장은?"
MATCH (kim:Worker {name: "김기술"})-[:HAS_SKILL]->(kimSkill:Skill)
WITH collect(kimSkill) AS kimSkills
MATCH (f:Fault)-[:CAUSED_BY]->()-[:RESOLVED_BY]->(a:MaintenanceAction)
-[:REQUIRES_SKILL]->(required:Skill)
WHERE NOT required IN kimSkills
AND NOT EXISTS {
MATCH (other:Worker)-[:HAS_SKILL]->(required)
WHERE other.name <> "김기술"
}
RETURN DISTINCT f.alarmCode AS 알람, f.name AS 고장,
required.name AS 필요스킬,
"대응 인력 없음 (김기술 부재 시)" AS 상태;
4. 자주 빠뜨리는 핵심 4가지 (실무 직결)
4-1. WITH 절 — 쿼리 체이닝의 핵심
WITH 가 없으면 복잡한 쿼리는 사실상 불가능하다. 중간 결과를 다음 절에 전달하는 "파이프" 역할.
// 설비별 부품 수 → 5개 이상인 설비만 → 정렬
MATCH (e:Equipment)-[:HAS_COMPONENT]->()-[:HAS_PART]->(p:Part)
WITH e, count(p) AS partCount // 1단계 결과를 다음으로
WHERE partCount >= 5 // WHERE 는 WITH 뒤에서만 사용 가능
RETURN e.name, partCount
ORDER BY partCount DESC;
함정:
WHERE partCount >= 5를WITH없이 쓰면 에러.count()같은 집계 후 필터는 항상WITH가 필요하다.
4-2. MERGE ON CREATE / ON MATCH — 업서트 분기
MERGE 단독은 "있으면 매칭, 없으면 생성". 두 경우에 다른 동작이 필요할 때:
MERGE (e:Equipment {serial: "CNC-001"})
ON CREATE SET e.installedAt = datetime(), e.status = "신규"
ON MATCH SET e.lastSeenAt = datetime(), e.checkCount = coalesce(e.checkCount, 0) + 1
RETURN e;
함정:
MERGE (e:Equipment {serial: "...", status: "운전중"})처럼 속성을 여러 개 묶으면 그 조합 모두 일치할 때만 매칭. status 가 다르면 새 노드를 만든다 — 의도와 어긋나는 가장 흔한 실수.
4-3. 인덱스 + UNIQUE 제약 — 성능과 무결성 동시에
100만 노드부터는 인덱스 없으면 단순 lookup 도 초 단위가 든다.
// 자주 찾는 속성은 인덱스
CREATE INDEX equipment_serial IF NOT EXISTS FOR (e:Equipment) ON (e.serial);
// 중복 절대 금지 속성은 UNIQUE 제약 (인덱스 자동 생성)
CREATE CONSTRAINT equipment_serial_unique IF NOT EXISTS
FOR (e:Equipment) REQUIRE e.serial IS UNIQUE;
// 현재 인덱스 확인
SHOW INDEXES;
운영 룰: KG 적재 전에 인덱스/제약을 먼저 만든다. 적재 후 인덱스 생성은 비싸다.
MERGE가 빠르려면 매칭 속성에 반드시 인덱스 또는 UNIQUE 가 있어야 한다.
4-4. APOC — Neo4j 표준 확장 라이브러리
실무 운영의 절반은 APOC 로 해결된다. Neo4j 설치 시 같이 깔거나 apoc.conf 로 활성.
// 대용량 적재: 1만 건 단위로 배치 (트랜잭션 분할 → 메모리 안전)
CALL apoc.periodic.iterate(
"MATCH (p:Part) WHERE p.imported IS NULL RETURN p",
"SET p.imported = true",
{batchSize: 10000, parallel: false}
);
// 전체 그래프 JSON export (백업/디버깅)
CALL apoc.export.json.all('graph.json', {});
// 동적 라벨 추가
CALL apoc.create.addLabels(node, ['Critical', 'HighPriority']);
추천 출발점 3개:
apoc.periodic.iterate(대용량 처리),apoc.export.json.all(백업),apoc.load.json(외부 JSON 로드). 이 셋만 알아도 운영 부담이 절반으로 준다.
다음 task 와의 연결
다음 reading "그래프 데이터 모델링 패턴: 좋은 KG vs 나쁜 KG" 에서 위 4가지가 "좋은 KG" 의 필수 조건으로 다시 등장한다. 특히 인덱스 누락과 MERGE 오용은 "나쁜 KG" 의 가장 흔한 패턴이다.
제조 설비 고장의 파급 영향을 가변 길이 경로 탐색으로 분석하는 실전 Cypher를 AI에게 요청하세요.
Neo4j에서 제조 공정 KG(Equipment→FEEDS_INTO→Process→USES→Component 구조)를 대상으로, 특정 부품 결함이 몇 단계 공정까지 영향을 미치는지 확인하는 가변 길이 경로 쿼리(*1..5)를 작성해줘. shortestPath와 allShortestPaths 결과 차이도 같이 보여줘.
- • 가변 길이 경로(*1..N)로 다단계 원인 추적 가능
- • 영향도 분석: 설비 정지 시 후속 공정까지 파악
- • 집계 함수로 부품 수명 위험도, 인력 가용성 분석
- • 실전 쿼리: 알람 대응, 교체 예정, 인력 공백 분석