# ๐Ÿ“Š Phase 2.3: DataflowService Raw Query ์ „ํ™˜ ๊ณ„ํš ## ๐Ÿ“‹ ๊ฐœ์š” DataflowService๋Š” **31๊ฐœ์˜ Prisma ํ˜ธ์ถœ**์ด ์žˆ๋Š” ํ•ต์‹ฌ ์„œ๋น„์Šค์ž…๋‹ˆ๋‹ค. ํ…Œ์ด๋ธ” ๊ฐ„ ๊ด€๊ณ„ ๊ด€๋ฆฌ, ๋ฐ์ดํ„ฐํ”Œ๋กœ์šฐ ๋‹ค์ด์–ด๊ทธ๋žจ, ๋ฐ์ดํ„ฐ ์—ฐ๊ฒฐ ๋ธŒ๋ฆฌ์ง€ ๋“ฑ ๋ณต์žกํ•œ ๊ธฐ๋Šฅ์„ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค. ### ๐Ÿ“Š ๊ธฐ๋ณธ ์ •๋ณด | ํ•ญ๋ชฉ | ๋‚ด์šฉ | | --------------- | ---------------------------------------------- | | ํŒŒ์ผ ์œ„์น˜ | `backend-node/src/services/dataflowService.ts` | | ํŒŒ์ผ ํฌ๊ธฐ | 1,170+ ๋ผ์ธ | | Prisma ํ˜ธ์ถœ | 0๊ฐœ (์ „ํ™˜ ์™„๋ฃŒ) | | **ํ˜„์žฌ ์ง„ํ–‰๋ฅ ** | **31/31 (100%)** โœ… **์™„๋ฃŒ** | | ๋ณต์žก๋„ | ๋งค์šฐ ๋†’์Œ (ํŠธ๋žœ์žญ์…˜ + ๋ณต์žกํ•œ ๊ด€๊ณ„ ๊ด€๋ฆฌ) | | ์šฐ์„ ์ˆœ์œ„ | ๐Ÿ”ด ์ตœ์šฐ์„  (Phase 2.3) | | **์ƒํƒœ** | โœ… **์ „ํ™˜ ์™„๋ฃŒ ๋ฐ ์ปดํŒŒ์ผ ์„ฑ๊ณต** | ### ๐ŸŽฏ ์ „ํ™˜ ๋ชฉํ‘œ - โœ… 31๊ฐœ Prisma ํ˜ธ์ถœ์„ ๋ชจ๋‘ Raw Query๋กœ ์ „ํ™˜ - โœ… ํŠธ๋žœ์žญ์…˜ ์ฒ˜๋ฆฌ ์ •์ƒ ๋™์ž‘ ํ™•์ธ - โœ… ์—๋Ÿฌ ์ฒ˜๋ฆฌ ๋ฐ ๋กค๋ฐฑ ์ •์ƒ ๋™์ž‘ - โœ… ๋ชจ๋“  ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ํ†ต๊ณผ (20๊ฐœ ์ด์ƒ) - โœ… ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ ์™„๋ฃŒ - โœ… Prisma import ์™„์ „ ์ œ๊ฑฐ --- ## ๐Ÿ” Prisma ์‚ฌ์šฉ ํ˜„ํ™ฉ ๋ถ„์„ ### 1. ํ…Œ์ด๋ธ” ๊ด€๊ณ„ ๊ด€๋ฆฌ (Table Relationships) - 22๊ฐœ #### 1.1 ๊ด€๊ณ„ ์ƒ์„ฑ (3๊ฐœ) ```typescript // Line 48: ์ตœ๋Œ€ diagram_id ์กฐํšŒ await prisma.table_relationships.findFirst({ where: { company_code }, orderBy: { diagram_id: 'desc' } }); // Line 64: ์ค‘๋ณต ๊ด€๊ณ„ ํ™•์ธ await prisma.table_relationships.findFirst({ where: { diagram_id, source_table, target_table, relationship_type } }); // Line 83: ์ƒˆ ๊ด€๊ณ„ ์ƒ์„ฑ await prisma.table_relationships.create({ data: { diagram_id, source_table, target_table, ... } }); ``` #### 1.2 ๊ด€๊ณ„ ์กฐํšŒ (6๊ฐœ) ```typescript // Line 128: ๊ด€๊ณ„ ๋ชฉ๋ก ์กฐํšŒ await prisma.table_relationships.findMany({ where: whereCondition, orderBy: { created_at: 'desc' } }); // Line 164: ๋‹จ์ผ ๊ด€๊ณ„ ์กฐํšŒ await prisma.table_relationships.findFirst({ where: whereCondition }); // Line 287: ํšŒ์‚ฌ๋ณ„ ๊ด€๊ณ„ ์กฐํšŒ await prisma.table_relationships.findMany({ where: { company_code, is_active: 'Y' }, orderBy: { diagram_id: 'asc' } }); // Line 326: ํ…Œ์ด๋ธ”๋ณ„ ๊ด€๊ณ„ ์กฐํšŒ await prisma.table_relationships.findMany({ where: whereCondition, orderBy: { relationship_type: 'asc' } }); // Line 784: diagram_id๋ณ„ ๊ด€๊ณ„ ์กฐํšŒ await prisma.table_relationships.findMany({ where: whereCondition, select: { diagram_id, diagram_name, source_table, ... } }); // Line 883: ํšŒ์‚ฌ ์ฝ”๋“œ๋กœ ์ „์ฒด ์กฐํšŒ await prisma.table_relationships.findMany({ where: { company_code, is_active: 'Y' } }); ``` #### 1.3 ํ†ต๊ณ„ ์กฐํšŒ (3๊ฐœ) ```typescript // Line 362: ์ „์ฒด ๊ด€๊ณ„ ์ˆ˜ await prisma.table_relationships.count({ where: whereCondition, }); // Line 367: ๊ด€๊ณ„ ํƒ€์ž…๋ณ„ ํ†ต๊ณ„ await prisma.table_relationships.groupBy({ by: ["relationship_type"], where: whereCondition, _count: { relationship_id: true }, }); // Line 376: ์—ฐ๊ฒฐ ํƒ€์ž…๋ณ„ ํ†ต๊ณ„ await prisma.table_relationships.groupBy({ by: ["connection_type"], where: whereCondition, _count: { relationship_id: true }, }); ``` #### 1.4 ๊ด€๊ณ„ ์ˆ˜์ •/์‚ญ์ œ (5๊ฐœ) ```typescript // Line 209: ๊ด€๊ณ„ ์ˆ˜์ • await prisma.table_relationships.update({ where: { relationship_id }, data: { source_table, target_table, ... } }); // Line 248: ์†Œํ”„ํŠธ ์‚ญ์ œ await prisma.table_relationships.update({ where: { relationship_id }, data: { is_active: 'N', updated_at: new Date() } }); // Line 936: ์ค‘๋ณต diagram_name ํ™•์ธ await prisma.table_relationships.findFirst({ where: { company_code, diagram_name, is_active: 'Y' } }); // Line 953: ์ตœ๋Œ€ diagram_id ์กฐํšŒ (๋ณต์‚ฌ์šฉ) await prisma.table_relationships.findFirst({ where: { company_code }, orderBy: { diagram_id: 'desc' } }); // Line 1015: ๊ด€๊ณ„๋„ ์™„์ „ ์‚ญ์ œ await prisma.table_relationships.deleteMany({ where: { company_code, diagram_id, is_active: 'Y' } }); ``` #### 1.5 ๋ณต์žกํ•œ ์กฐํšŒ (5๊ฐœ) ```typescript // Line 919: ์›๋ณธ ๊ด€๊ณ„๋„ ์กฐํšŒ await prisma.table_relationships.findMany({ where: { company_code, diagram_id: sourceDiagramId, is_active: "Y" }, }); // Line 1046: diagram_id๋กœ ๋ชจ๋“  ๊ด€๊ณ„ ์กฐํšŒ await prisma.table_relationships.findMany({ where: { diagram_id, is_active: "Y" }, orderBy: { created_at: "asc" }, }); // Line 1085: ํŠน์ • relationship_id์˜ diagram_id ์ฐพ๊ธฐ await prisma.table_relationships.findFirst({ where: { relationship_id, company_code }, }); ``` ### 2. ๋ฐ์ดํ„ฐ ์—ฐ๊ฒฐ ๋ธŒ๋ฆฌ์ง€ (Data Relationship Bridge) - 8๊ฐœ #### 2.1 ๋ธŒ๋ฆฌ์ง€ ์ƒ์„ฑ/์ˆ˜์ • (4๊ฐœ) ```typescript // Line 425: ๋ธŒ๋ฆฌ์ง€ ์ƒ์„ฑ await prisma.data_relationship_bridge.create({ data: { relationship_id, source_record_id, target_record_id, ... } }); // Line 554: ๋ธŒ๋ฆฌ์ง€ ์ˆ˜์ • await prisma.data_relationship_bridge.update({ where: whereCondition, data: { target_record_id, ... } }); // Line 595: ๋ธŒ๋ฆฌ์ง€ ์†Œํ”„ํŠธ ์‚ญ์ œ await prisma.data_relationship_bridge.update({ where: whereCondition, data: { is_active: 'N', updated_at: new Date() } }); // Line 637: ๋ธŒ๋ฆฌ์ง€ ์ผ๊ด„ ์‚ญ์ œ await prisma.data_relationship_bridge.updateMany({ where: whereCondition, data: { is_active: 'N', updated_at: new Date() } }); ``` #### 2.2 ๋ธŒ๋ฆฌ์ง€ ์กฐํšŒ (4๊ฐœ) ```typescript // Line 471: relationship_id๋กœ ๋ธŒ๋ฆฌ์ง€ ์กฐํšŒ await prisma.data_relationship_bridge.findMany({ where: whereCondition, orderBy: { created_at: "desc" }, }); // Line 512: ๋ ˆ์ฝ”๋“œ๋ณ„ ๋ธŒ๋ฆฌ์ง€ ์กฐํšŒ await prisma.data_relationship_bridge.findMany({ where: whereCondition, orderBy: { created_at: "desc" }, }); ``` ### 3. Raw Query ์‚ฌ์šฉ (์ด๋ฏธ ์žˆ์Œ) - 1๊ฐœ ```typescript // Line 673: ํ…Œ์ด๋ธ” ์กด์žฌ ํ™•์ธ await prisma.$queryRaw` SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_name = ${tableName} `; ``` ### 4. ํŠธ๋žœ์žญ์…˜ ์‚ฌ์šฉ - 1๊ฐœ ```typescript // Line 968: ๊ด€๊ณ„๋„ ๋ณต์‚ฌ ํŠธ๋žœ์žญ์…˜ await prisma.$transaction( originalRelationships.map((rel) => prisma.table_relationships.create({ data: { diagram_id: newDiagramId, company_code: companyCode, source_table: rel.source_table, target_table: rel.target_table, ... } }) ) ); ``` --- ## ๐Ÿ› ๏ธ ์ „ํ™˜ ์ „๋žต ### ์ „๋žต 1: ๋‹จ๊ณ„์  ์ „ํ™˜ 1. **1๋‹จ๊ณ„**: ๋‹จ์ˆœ CRUD ์ „ํ™˜ (findFirst, findMany, create, update, delete) 2. **2๋‹จ๊ณ„**: ๋ณต์žกํ•œ ์กฐํšŒ ์ „ํ™˜ (groupBy, count, ์กฐ๊ฑด๋ถ€ ์กฐํšŒ) 3. **3๋‹จ๊ณ„**: ํŠธ๋žœ์žญ์…˜ ์ „ํ™˜ 4. **4๋‹จ๊ณ„**: Raw Query ๊ฐœ์„  ### ์ „๋žต 2: ํ•จ์ˆ˜๋ณ„ ์ „ํ™˜ ์šฐ์„ ์ˆœ์œ„ #### ๐Ÿ”ด ์ตœ์šฐ์„  (๊ธฐ๋ณธ CRUD) - `createRelationship()` - Line 83 - `getRelationships()` - Line 128 - `getRelationshipById()` - Line 164 - `updateRelationship()` - Line 209 - `deleteRelationship()` - Line 248 #### ๐ŸŸก 2์ˆœ์œ„ (๋ธŒ๋ฆฌ์ง€ ๊ด€๋ฆฌ) - `createDataLink()` - Line 425 - `getLinkedData()` - Line 471 - `getLinkedDataByRecord()` - Line 512 - `updateDataLink()` - Line 554 - `deleteDataLink()` - Line 595 #### ๐ŸŸข 3์ˆœ์œ„ (ํ†ต๊ณ„ & ์กฐํšŒ) - `getRelationshipStats()` - Line 362-376 - `getAllRelationshipsByCompany()` - Line 287 - `getRelationshipsByTable()` - Line 326 - `getDiagrams()` - Line 784 #### ๐Ÿ”ต 4์ˆœ์œ„ (๋ณต์žกํ•œ ๊ธฐ๋Šฅ) - `copyDiagram()` - Line 968 (ํŠธ๋žœ์žญ์…˜) - `deleteDiagram()` - Line 1015 - `getRelationshipsForDiagram()` - Line 1046 --- ## ๐Ÿ“ ์ „ํ™˜ ์˜ˆ์‹œ ### ์˜ˆ์‹œ 1: createRelationship() ์ „ํ™˜ **๊ธฐ์กด Prisma ์ฝ”๋“œ:** ```typescript // Line 48: ์ตœ๋Œ€ diagram_id ์กฐํšŒ const maxDiagramId = await prisma.table_relationships.findFirst({ where: { company_code: data.companyCode }, orderBy: { diagram_id: 'desc' } }); // Line 64: ์ค‘๋ณต ๊ด€๊ณ„ ํ™•์ธ const existingRelationship = await prisma.table_relationships.findFirst({ where: { diagram_id: diagramId, source_table: data.sourceTable, target_table: data.targetTable, relationship_type: data.relationshipType } }); // Line 83: ์ƒˆ ๊ด€๊ณ„ ์ƒ์„ฑ const relationship = await prisma.table_relationships.create({ data: { diagram_id: diagramId, company_code: data.companyCode, diagram_name: data.diagramName, source_table: data.sourceTable, target_table: data.targetTable, relationship_type: data.relationshipType, ... } }); ``` **์ƒˆ๋กœ์šด Raw Query ์ฝ”๋“œ:** ```typescript import { query } from "../database/db"; // ์ตœ๋Œ€ diagram_id ์กฐํšŒ const maxDiagramResult = await query<{ diagram_id: number }>( `SELECT diagram_id FROM table_relationships WHERE company_code = $1 ORDER BY diagram_id DESC LIMIT 1`, [data.companyCode] ); const diagramId = data.diagramId || (maxDiagramResult.length > 0 ? maxDiagramResult[0].diagram_id + 1 : 1); // ์ค‘๋ณต ๊ด€๊ณ„ ํ™•์ธ const existingResult = await query<{ relationship_id: number }>( `SELECT relationship_id FROM table_relationships WHERE diagram_id = $1 AND source_table = $2 AND target_table = $3 AND relationship_type = $4 LIMIT 1`, [diagramId, data.sourceTable, data.targetTable, data.relationshipType] ); if (existingResult.length > 0) { throw new Error("์ด๋ฏธ ์กด์žฌํ•˜๋Š” ๊ด€๊ณ„์ž…๋‹ˆ๋‹ค."); } // ์ƒˆ ๊ด€๊ณ„ ์ƒ์„ฑ const [relationship] = await query( `INSERT INTO table_relationships ( diagram_id, company_code, diagram_name, source_table, target_table, relationship_type, connection_type, source_column, target_column, is_active, created_at, updated_at ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, 'Y', NOW(), NOW()) RETURNING *`, [ diagramId, data.companyCode, data.diagramName, data.sourceTable, data.targetTable, data.relationshipType, data.connectionType, data.sourceColumn, data.targetColumn, ] ); ``` ### ์˜ˆ์‹œ 2: getRelationshipStats() ์ „ํ™˜ (ํ†ต๊ณ„ ์กฐํšŒ) **๊ธฐ์กด Prisma ์ฝ”๋“œ:** ```typescript // Line 362: ์ „์ฒด ๊ด€๊ณ„ ์ˆ˜ const totalCount = await prisma.table_relationships.count({ where: whereCondition, }); // Line 367: ๊ด€๊ณ„ ํƒ€์ž…๋ณ„ ํ†ต๊ณ„ const relationshipTypeStats = await prisma.table_relationships.groupBy({ by: ["relationship_type"], where: whereCondition, _count: { relationship_id: true }, }); // Line 376: ์—ฐ๊ฒฐ ํƒ€์ž…๋ณ„ ํ†ต๊ณ„ const connectionTypeStats = await prisma.table_relationships.groupBy({ by: ["connection_type"], where: whereCondition, _count: { relationship_id: true }, }); ``` **์ƒˆ๋กœ์šด Raw Query ์ฝ”๋“œ:** ```typescript // WHERE ์กฐ๊ฑด ๋™์  ์ƒ์„ฑ const whereParams: any[] = []; let whereSQL = ""; let paramIndex = 1; if (companyCode) { whereSQL += `WHERE company_code = $${paramIndex}`; whereParams.push(companyCode); paramIndex++; if (isActive !== undefined) { whereSQL += ` AND is_active = $${paramIndex}`; whereParams.push(isActive ? "Y" : "N"); paramIndex++; } } // ์ „์ฒด ๊ด€๊ณ„ ์ˆ˜ const [totalResult] = await query<{ count: number }>( `SELECT COUNT(*) as count FROM table_relationships ${whereSQL}`, whereParams ); const totalCount = totalResult?.count || 0; // ๊ด€๊ณ„ ํƒ€์ž…๋ณ„ ํ†ต๊ณ„ const relationshipTypeStats = await query<{ relationship_type: string; count: number; }>( `SELECT relationship_type, COUNT(*) as count FROM table_relationships ${whereSQL} GROUP BY relationship_type ORDER BY count DESC`, whereParams ); // ์—ฐ๊ฒฐ ํƒ€์ž…๋ณ„ ํ†ต๊ณ„ const connectionTypeStats = await query<{ connection_type: string; count: number; }>( `SELECT connection_type, COUNT(*) as count FROM table_relationships ${whereSQL} GROUP BY connection_type ORDER BY count DESC`, whereParams ); ``` ### ์˜ˆ์‹œ 3: copyDiagram() ํŠธ๋žœ์žญ์…˜ ์ „ํ™˜ **๊ธฐ์กด Prisma ์ฝ”๋“œ:** ```typescript // Line 968: ํŠธ๋žœ์žญ์…˜์œผ๋กœ ๋ชจ๋“  ๊ด€๊ณ„ ๋ณต์‚ฌ const copiedRelationships = await prisma.$transaction( originalRelationships.map((rel) => prisma.table_relationships.create({ data: { diagram_id: newDiagramId, company_code: companyCode, diagram_name: newDiagramName, source_table: rel.source_table, target_table: rel.target_table, ... } }) ) ); ``` **์ƒˆ๋กœ์šด Raw Query ์ฝ”๋“œ:** ```typescript import { transaction } from "../database/db"; const copiedRelationships = await transaction(async (client) => { const results: TableRelationship[] = []; for (const rel of originalRelationships) { const [copiedRel] = await client.query( `INSERT INTO table_relationships ( diagram_id, company_code, diagram_name, source_table, target_table, relationship_type, connection_type, source_column, target_column, is_active, created_at, updated_at ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, 'Y', NOW(), NOW()) RETURNING *`, [ newDiagramId, companyCode, newDiagramName, rel.source_table, rel.target_table, rel.relationship_type, rel.connection_type, rel.source_column, rel.target_column, ] ); results.push(copiedRel); } return results; }); ``` --- ## ๐Ÿงช ํ…Œ์ŠคํŠธ ๊ณ„ํš ### ๋‹จ์œ„ ํ…Œ์ŠคํŠธ (20๊ฐœ ์ด์ƒ) ```typescript describe('DataflowService Raw Query ์ „ํ™˜ ํ…Œ์ŠคํŠธ', () => { describe('createRelationship', () => { test('๊ด€๊ณ„ ์ƒ์„ฑ ์„ฑ๊ณต', async () => { ... }); test('์ค‘๋ณต ๊ด€๊ณ„ ์—๋Ÿฌ', async () => { ... }); test('diagram_id ์ž๋™ ์ƒ์„ฑ', async () => { ... }); }); describe('getRelationships', () => { test('์ „์ฒด ๊ด€๊ณ„ ์กฐํšŒ ์„ฑ๊ณต', async () => { ... }); test('ํšŒ์‚ฌ๋ณ„ ํ•„ํ„ฐ๋ง', async () => { ... }); test('diagram_id๋ณ„ ํ•„ํ„ฐ๋ง', async () => { ... }); }); describe('getRelationshipStats', () => { test('ํ†ต๊ณ„ ์กฐํšŒ ์„ฑ๊ณต', async () => { ... }); test('๊ด€๊ณ„ ํƒ€์ž…๋ณ„ ๊ทธ๋ฃนํ™”', async () => { ... }); test('์—ฐ๊ฒฐ ํƒ€์ž…๋ณ„ ๊ทธ๋ฃนํ™”', async () => { ... }); }); describe('copyDiagram', () => { test('๊ด€๊ณ„๋„ ๋ณต์‚ฌ ์„ฑ๊ณต (ํŠธ๋žœ์žญ์…˜)', async () => { ... }); test('diagram_name ์ค‘๋ณต ์—๋Ÿฌ', async () => { ... }); }); describe('createDataLink', () => { test('๋ฐ์ดํ„ฐ ์—ฐ๊ฒฐ ์ƒ์„ฑ ์„ฑ๊ณต', async () => { ... }); test('๋ธŒ๋ฆฌ์ง€ ๋ ˆ์ฝ”๋“œ ์ €์žฅ', async () => { ... }); }); describe('getLinkedData', () => { test('์—ฐ๊ฒฐ๋œ ๋ฐ์ดํ„ฐ ์กฐํšŒ', async () => { ... }); test('relationship_id๋ณ„ ํ•„ํ„ฐ๋ง', async () => { ... }); }); }); ``` ### ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ (7๊ฐœ ์‹œ๋‚˜๋ฆฌ์˜ค) ```typescript describe('Dataflow ๊ด€๋ฆฌ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ', () => { test('๊ด€๊ณ„ ์ƒ๋ช…์ฃผ๊ธฐ (์ƒ์„ฑ โ†’ ์กฐํšŒ โ†’ ์ˆ˜์ • โ†’ ์‚ญ์ œ)', async () => { ... }); test('๊ด€๊ณ„๋„ ๋ณต์‚ฌ ๋ฐ ๊ฒ€์ฆ', async () => { ... }); test('๋ฐ์ดํ„ฐ ์—ฐ๊ฒฐ ๋ธŒ๋ฆฌ์ง€ ์ƒ์„ฑ ๋ฐ ์กฐํšŒ', async () => { ... }); test('ํ†ต๊ณ„ ์ •๋ณด ์กฐํšŒ', async () => { ... }); test('ํ…Œ์ด๋ธ”๋ณ„ ๊ด€๊ณ„ ์กฐํšŒ', async () => { ... }); test('diagram_id๋ณ„ ๊ด€๊ณ„ ์กฐํšŒ', async () => { ... }); test('๊ด€๊ณ„๋„ ์™„์ „ ์‚ญ์ œ', async () => { ... }); }); ``` --- ## ๐Ÿ“‹ ์ฒดํฌ๋ฆฌ์ŠคํŠธ ### 1๋‹จ๊ณ„: ๊ธฐ๋ณธ CRUD (8๊ฐœ ํ•จ์ˆ˜) โœ… **์™„๋ฃŒ** - [x] `createTableRelationship()` - ๊ด€๊ณ„ ์ƒ์„ฑ - [x] `getTableRelationships()` - ๊ด€๊ณ„ ๋ชฉ๋ก ์กฐํšŒ - [x] `getTableRelationship()` - ๋‹จ์ผ ๊ด€๊ณ„ ์กฐํšŒ - [x] `updateTableRelationship()` - ๊ด€๊ณ„ ์ˆ˜์ • - [x] `deleteTableRelationship()` - ๊ด€๊ณ„ ์‚ญ์ œ (์†Œํ”„ํŠธ) - [x] `getRelationshipsByTable()` - ํ…Œ์ด๋ธ”๋ณ„ ์กฐํšŒ - [x] `getRelationshipsByConnectionType()` - ์—ฐ๊ฒฐํƒ€์ž…๋ณ„ ์กฐํšŒ - [x] `getDataFlowDiagrams()` - diagram_id๋ณ„ ๊ทธ๋ฃน ์กฐํšŒ ### 2๋‹จ๊ณ„: ๋ธŒ๋ฆฌ์ง€ ๊ด€๋ฆฌ (6๊ฐœ ํ•จ์ˆ˜) โœ… **์™„๋ฃŒ** - [x] `createDataLink()` - ๋ฐ์ดํ„ฐ ์—ฐ๊ฒฐ ์ƒ์„ฑ - [x] `getLinkedDataByRelationship()` - ๊ด€๊ณ„๋ณ„ ์—ฐ๊ฒฐ ๋ฐ์ดํ„ฐ ์กฐํšŒ - [x] `getLinkedDataByTable()` - ํ…Œ์ด๋ธ”๋ณ„ ์—ฐ๊ฒฐ ๋ฐ์ดํ„ฐ ์กฐํšŒ - [x] `updateDataLink()` - ์—ฐ๊ฒฐ ์ˆ˜์ • - [x] `deleteDataLink()` - ์—ฐ๊ฒฐ ์‚ญ์ œ (์†Œํ”„ํŠธ) - [x] `deleteAllLinkedDataByRelationship()` - ๊ด€๊ณ„๋ณ„ ๋ชจ๋“  ์—ฐ๊ฒฐ ์‚ญ์ œ ### 3๋‹จ๊ณ„: ํ†ต๊ณ„ & ๋ณต์žกํ•œ ์กฐํšŒ (4๊ฐœ ํ•จ์ˆ˜) โœ… **์™„๋ฃŒ** - [x] `getRelationshipStats()` - ํ†ต๊ณ„ ์กฐํšŒ - [x] count ์ฟผ๋ฆฌ ์ „ํ™˜ - [x] groupBy ์ฟผ๋ฆฌ ์ „ํ™˜ (๊ด€๊ณ„ ํƒ€์ž…๋ณ„) - [x] groupBy ์ฟผ๋ฆฌ ์ „ํ™˜ (์—ฐ๊ฒฐ ํƒ€์ž…๋ณ„) - [x] `getTableData()` - ํ…Œ์ด๋ธ” ๋ฐ์ดํ„ฐ ์กฐํšŒ (ํŽ˜์ด์ง•) - [x] `getDiagramRelationships()` - ๊ด€๊ณ„๋„ ๊ด€๊ณ„ ์กฐํšŒ - [x] `getDiagramRelationshipsByDiagramId()` - diagram_id๋ณ„ ๊ด€๊ณ„ ์กฐํšŒ ### 4๋‹จ๊ณ„: ๋ณต์žกํ•œ ๊ธฐ๋Šฅ (3๊ฐœ ํ•จ์ˆ˜) โœ… **์™„๋ฃŒ** - [x] `copyDiagram()` - ๊ด€๊ณ„๋„ ๋ณต์‚ฌ (ํŠธ๋žœ์žญ์…˜) - [x] `deleteDiagram()` - ๊ด€๊ณ„๋„ ์™„์ „ ์‚ญ์ œ - [x] `getDiagramRelationshipsByRelationshipId()` - relationship_id๋กœ ์กฐํšŒ ### 5๋‹จ๊ณ„: ํ…Œ์ŠคํŠธ & ๊ฒ€์ฆ โณ **์ง„ํ–‰ ํ•„์š”** - [ ] ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ (20๊ฐœ ์ด์ƒ) - createTableRelationship, updateTableRelationship, deleteTableRelationship - getTableRelationships, getTableRelationship - createDataLink, getLinkedDataByRelationship - getRelationshipStats - copyDiagram - [ ] ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ (7๊ฐœ ์‹œ๋‚˜๋ฆฌ์˜ค) - ๊ด€๊ณ„ ์ƒ๋ช…์ฃผ๊ธฐ ํ…Œ์ŠคํŠธ - ๊ด€๊ณ„๋„ ๋ณต์‚ฌ ํ…Œ์ŠคํŠธ - ๋ฐ์ดํ„ฐ ๋ธŒ๋ฆฌ์ง€ ํ…Œ์ŠคํŠธ - ํ†ต๊ณ„ ์กฐํšŒ ํ…Œ์ŠคํŠธ - [x] Prisma import ์™„์ „ ์ œ๊ฑฐ ํ™•์ธ - [ ] ์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ --- ## ๐ŸŽฏ ์™„๋ฃŒ ๊ธฐ์ค€ - [x] **31๊ฐœ Prisma ํ˜ธ์ถœ ๋ชจ๋‘ Raw Query๋กœ ์ „ํ™˜ ์™„๋ฃŒ** โœ… - [x] **๋ชจ๋“  TypeScript ์ปดํŒŒ์ผ ์˜ค๋ฅ˜ ํ•ด๊ฒฐ** โœ… - [x] **ํŠธ๋žœ์žญ์…˜ ์ •์ƒ ๋™์ž‘ ํ™•์ธ** โœ… - [x] **์—๋Ÿฌ ์ฒ˜๋ฆฌ ๋ฐ ๋กค๋ฐฑ ์ •์ƒ ๋™์ž‘** โœ… - [ ] **๋ชจ๋“  ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ํ†ต๊ณผ (20๊ฐœ ์ด์ƒ)** โณ - [ ] **๋ชจ๋“  ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ ์™„๋ฃŒ (7๊ฐœ ์‹œ๋‚˜๋ฆฌ์˜ค)** โณ - [x] **Prisma import ์™„์ „ ์ œ๊ฑฐ** โœ… - [ ] **์„ฑ๋Šฅ ์ €ํ•˜ ์—†์Œ (๊ธฐ์กด ๋Œ€๋น„ ยฑ10% ์ด๋‚ด)** โณ --- ## ๐ŸŽฏ ์ฃผ์š” ๊ธฐ์ˆ ์  ๋„์ „ ๊ณผ์ œ ### 1. groupBy ์ฟผ๋ฆฌ ์ „ํ™˜ **๋ฌธ์ œ**: Prisma์˜ `groupBy`๋ฅผ Raw Query๋กœ ์ „ํ™˜ **ํ•ด๊ฒฐ**: PostgreSQL์˜ `GROUP BY` ๋ฐ ์ง‘๊ณ„ ํ•จ์ˆ˜ ์‚ฌ์šฉ ```sql SELECT relationship_type, COUNT(*) as count FROM table_relationships WHERE company_code = $1 AND is_active = 'Y' GROUP BY relationship_type ORDER BY count DESC ``` ### 2. ํŠธ๋žœ์žญ์…˜ ๋ฐฐ์—ด ์ฒ˜๋ฆฌ **๋ฌธ์ œ**: Prisma์˜ `$transaction([...])` ๋ฐฐ์—ด ๋ฐฉ์‹์„ Raw Query๋กœ ์ „ํ™˜ **ํ•ด๊ฒฐ**: `transaction` ํ•จ์ˆ˜ ๋‚ด์—์„œ ์ˆœ์ฐจ ์‹คํ–‰ ```typescript await transaction(async (client) => { const results = []; for (const item of items) { const result = await client.query(...); results.push(result); } return results; }); ``` ### 3. ๋™์  WHERE ์กฐ๊ฑด ์ƒ์„ฑ **๋ฌธ์ œ**: ๋‹ค์–‘ํ•œ ํ•„ํ„ฐ ์กฐ๊ฑด์„ ๋™์ ์œผ๋กœ ๊ตฌ์„ฑ **ํ•ด๊ฒฐ**: ์กฐ๊ฑด๋ถ€ ํŒŒ๋ผ๋ฏธํ„ฐ ์ธ๋ฑ์Šค ๊ด€๋ฆฌ ```typescript const whereParams: any[] = []; const whereConditions: string[] = []; let paramIndex = 1; if (companyCode) { whereConditions.push(`company_code = $${paramIndex++}`); whereParams.push(companyCode); } if (diagramId) { whereConditions.push(`diagram_id = $${paramIndex++}`); whereParams.push(diagramId); } const whereSQL = whereConditions.length > 0 ? `WHERE ${whereConditions.join(" AND ")}` : ""; ``` --- ## ๐Ÿ“Š ์ „ํ™˜ ์™„๋ฃŒ ์š”์•ฝ ### โœ… ์„ฑ๊ณต์ ์œผ๋กœ ์ „ํ™˜๋œ ํ•ญ๋ชฉ 1. **๊ธฐ๋ณธ CRUD (8๊ฐœ)**: ๋ชจ๋“  ํ…Œ์ด๋ธ” ๊ด€๊ณ„ CRUD ์ž‘์—…์„ Raw Query๋กœ ์ „ํ™˜ 2. **๋ธŒ๋ฆฌ์ง€ ๊ด€๋ฆฌ (6๊ฐœ)**: ๋ฐ์ดํ„ฐ ์—ฐ๊ฒฐ ๋ธŒ๋ฆฌ์ง€์˜ ๋ชจ๋“  ์ž‘์—… ์ „ํ™˜ 3. **ํ†ต๊ณ„ & ์กฐํšŒ (4๊ฐœ)**: COUNT, GROUP BY ๋“ฑ ๋ณต์žกํ•œ ํ†ต๊ณ„ ์ฟผ๋ฆฌ ์ „ํ™˜ 4. **๋ณต์žกํ•œ ๊ธฐ๋Šฅ (3๊ฐœ)**: ํŠธ๋žœ์žญ์…˜ ๊ธฐ๋ฐ˜ ๊ด€๊ณ„๋„ ๋ณต์‚ฌ ๋“ฑ ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ ์ „ํ™˜ ### ๐Ÿ”ง ์ฃผ์š” ๊ธฐ์ˆ ์  ํ•ด๊ฒฐ ์‚ฌํ•ญ 1. **ํŠธ๋žœ์žญ์…˜ ์ฒ˜๋ฆฌ**: `transaction()` ํ•จ์ˆ˜ ๋‚ด์—์„œ `client.query().rows` ์‚ฌ์šฉ 2. **๋™์  WHERE ์กฐ๊ฑด**: ํŒŒ๋ผ๋ฏธํ„ฐ ์ธ๋ฑ์Šค๋ฅผ ๋™์ ์œผ๋กœ ๊ด€๋ฆฌํ•˜์—ฌ ์œ ์—ฐํ•œ ์ฟผ๋ฆฌ ์ƒ์„ฑ 3. **GROUP BY ์ „ํ™˜**: Prisma์˜ `groupBy`๋ฅผ PostgreSQL์˜ ๋„ค์ดํ‹ฐ๋ธŒ GROUP BY๋กœ ์ „ํ™˜ 4. **ํƒ€์ž… ์•ˆ์ „์„ฑ**: ๋ชจ๋“  ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ์— TypeScript ํƒ€์ž… ์ง€์ • ### ๐Ÿ“ˆ ๋‹ค์Œ ๋‹จ๊ณ„ - [ ] ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ ๋ฐ ์‹คํ–‰ - [ ] ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ ์‹œ๋‚˜๋ฆฌ์˜ค ๊ตฌํ˜„ - [ ] ์„ฑ๋Šฅ ๋ฒค์น˜๋งˆํฌ ํ…Œ์ŠคํŠธ - [ ] ํ”„๋กœ๋•์…˜ ๋ฐฐํฌ ์ค€๋น„ --- **์ž‘์„ฑ์ผ**: 2025-09-30 **์™„๋ฃŒ์ผ**: 2025-10-01 **์†Œ์š” ์‹œ๊ฐ„**: 1์ผ **๋‹ด๋‹น์ž**: ๋ฐฑ์—”๋“œ ๊ฐœ๋ฐœํŒ€ **์šฐ์„ ์ˆœ์œ„**: ๐Ÿ”ด ์ตœ์šฐ์„  (Phase 2.3) **์ƒํƒœ**: โœ… **์ „ํ™˜ ์™„๋ฃŒ** (ํ…Œ์ŠคํŠธ ํ•„์š”)