From c31b0540aa2bfcb9e170c2f0512b9d6ffe5b7102 Mon Sep 17 00:00:00 2001 From: SeongHyun Kim Date: Tue, 20 Jan 2026 16:08:38 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=EB=B6=84=ED=95=A0=ED=8C=A8=EB=84=90=20?= =?UTF-8?q?=EC=97=B0=EA=B2=B0=20=ED=95=84=ED=84=B0=EC=97=90=20operator=20e?= =?UTF-8?q?quals=20=EB=88=84=EB=9D=BD=EC=9C=BC=EB=A1=9C=20=EC=9D=B8?= =?UTF-8?q?=ED=95=9C=20=EC=A1=B0=ED=9A=8C=20=EC=8B=A4=ED=8C=A8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20:=20=EC=9A=B0=EC=B8=A1=20=ED=8C=A8=EB=84=90?= =?UTF-8?q?=EC=97=90=20=EC=97=B0=EA=B4=80=20=EB=8D=B0=EC=9D=B4=ED=84=B0(?= =?UTF-8?q?=EB=B6=80=EC=84=9C=EC=9D=B8=EC=9B=90)=EA=B0=80=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=EB=90=98=EC=A7=80=20=EC=95=8A=EB=8D=98=20=EB=AC=B8?= =?UTF-8?q?=EC=A0=9C=20=EC=88=98=EC=A0=95=20-=20=EC=9E=AC=EA=B3=A0?= =?UTF-8?q?=EC=9D=B4=EB=A0=A5=EC=97=90=EC=84=9C=20=ED=92=88=EB=B2=88?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=B6=9C=EB=A0=A5=EC=95=88=EB=90=98?= =?UTF-8?q?=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0=20:=20?= =?UTF-8?q?=EC=97=94=ED=8B=B0=ED=8B=B0=20=EC=A1=B0=EC=9D=B8=20=EC=BF=BC?= =?UTF-8?q?=EB=A6=AC=20=EC=83=9D=EC=84=B1=20=EC=8B=9C=20=EB=8F=99=EC=9D=BC?= =?UTF-8?q?=ED=95=9C=20=EC=BB=AC=EB=9F=BC=20=EB=B3=84=EC=B9=AD=EC=9D=B4=20?= =?UTF-8?q?=EC=A4=91=EB=B3=B5=20=EC=83=9D=EC=84=B1=EB=90=98=EC=96=B4=20SQL?= =?UTF-8?q?=20=EC=97=90=EB=9F=AC=20=EB=B0=9C=EC=83=9D=ED=95=98=EB=8D=98=20?= =?UTF-8?q?=EB=AC=B8=EC=A0=9C=20=EB=B0=A9=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/services/entityJoinService.ts | 36 +++++++++++++++---- .../SplitPanelLayoutComponent.tsx | 19 ++++++++-- 2 files changed, 45 insertions(+), 10 deletions(-) diff --git a/backend-node/src/services/entityJoinService.ts b/backend-node/src/services/entityJoinService.ts index 25d96927..86762b64 100644 --- a/backend-node/src/services/entityJoinService.ts +++ b/backend-node/src/services/entityJoinService.ts @@ -334,6 +334,10 @@ export class EntityJoinService { ); }); + // 🔧 _label 별칭 중복 방지를 위한 Set + // 같은 sourceColumn에서 여러 조인 설정이 있을 때 _label은 첫 번째만 생성 + const generatedLabelAliases = new Set(); + const joinColumns = joinConfigs .map((config) => { const aliasKey = `${config.referenceTable}:${config.sourceColumn}`; @@ -368,16 +372,26 @@ export class EntityJoinService { // _label 필드도 함께 SELECT (프론트엔드 getColumnUniqueValues용) // sourceColumn_label 형식으로 추가 - resultColumns.push( - `COALESCE(${alias}.${col}::TEXT, '') AS ${config.sourceColumn}_label` - ); + // 🔧 중복 방지: 같은 sourceColumn에서 _label은 첫 번째만 생성 + const labelAlias = `${config.sourceColumn}_label`; + if (!generatedLabelAliases.has(labelAlias)) { + resultColumns.push( + `COALESCE(${alias}.${col}::TEXT, '') AS ${labelAlias}` + ); + generatedLabelAliases.add(labelAlias); + } // 🆕 referenceColumn (PK)도 항상 SELECT (parentDataMapping용) // 예: customer_code, item_number 등 // col과 동일해도 별도의 alias로 추가 (customer_code as customer_code) - resultColumns.push( - `COALESCE(${alias}.${config.referenceColumn}::TEXT, '') AS ${config.referenceColumn}` - ); + // 🔧 중복 방지: referenceColumn도 한 번만 추가 + const refColAlias = config.referenceColumn; + if (!generatedLabelAliases.has(refColAlias)) { + resultColumns.push( + `COALESCE(${alias}.${config.referenceColumn}::TEXT, '') AS ${refColAlias}` + ); + generatedLabelAliases.add(refColAlias); + } } else { resultColumns.push( `COALESCE(main.${col}::TEXT, '') AS ${config.aliasColumn}` @@ -392,6 +406,11 @@ export class EntityJoinService { const individualAlias = `${config.sourceColumn}_${col}`; + // 🔧 중복 방지: 같은 alias가 이미 생성되었으면 스킵 + if (generatedLabelAliases.has(individualAlias)) { + return; + } + if (isJoinTableColumn) { // 조인 테이블 컬럼은 조인 별칭 사용 resultColumns.push( @@ -403,6 +422,7 @@ export class EntityJoinService { `COALESCE(main.${col}::TEXT, '') AS ${individualAlias}` ); } + generatedLabelAliases.add(individualAlias); }); // 🆕 referenceColumn (PK)도 함께 SELECT (parentDataMapping용) @@ -410,11 +430,13 @@ export class EntityJoinService { config.referenceTable && config.referenceTable !== tableName; if ( isJoinTableColumn && - !displayColumns.includes(config.referenceColumn) + !displayColumns.includes(config.referenceColumn) && + !generatedLabelAliases.has(config.referenceColumn) // 🔧 중복 방지 ) { resultColumns.push( `COALESCE(${alias}.${config.referenceColumn}::TEXT, '') AS ${config.referenceColumn}` ); + generatedLabelAliases.add(config.referenceColumn); } } diff --git a/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx b/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx index 869d2c3c..ab387348 100644 --- a/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx +++ b/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx @@ -924,10 +924,15 @@ export const SplitPanelLayoutComponent: React.FC const { entityJoinApi } = await import("@/lib/api/entityJoin"); // 복합키 조건 생성 + // 🔧 entity 타입 컬럼은 코드 값으로 정확히 매칭해야 하므로 operator: 'equals' 사용 const searchConditions: Record = {}; keys.forEach((key) => { if (key.leftColumn && key.rightColumn && leftItem[key.leftColumn] !== undefined) { - searchConditions[key.rightColumn] = leftItem[key.leftColumn]; + // 연결 필터는 정확한 값 매칭이 필요하므로 equals 연산자 사용 + searchConditions[key.rightColumn] = { + value: leftItem[key.leftColumn], + operator: "equals", + }; } }); @@ -1033,16 +1038,24 @@ export const SplitPanelLayoutComponent: React.FC if (keys && keys.length > 0) { // 복합키 + // 🔧 entity 타입 컬럼은 코드 값으로 정확히 매칭해야 하므로 operator: 'equals' 사용 keys.forEach((key) => { if (key.leftColumn && key.rightColumn && leftItem[key.leftColumn] !== undefined) { - searchConditions[key.rightColumn] = leftItem[key.leftColumn]; + searchConditions[key.rightColumn] = { + value: leftItem[key.leftColumn], + operator: "equals", + }; } }); } else { // 단일키 + // 🔧 entity 타입 컬럼은 코드 값으로 정확히 매칭해야 하므로 operator: 'equals' 사용 const leftValue = leftItem[leftColumn]; if (leftValue !== undefined) { - searchConditions[rightColumn] = leftValue; + searchConditions[rightColumn] = { + value: leftValue, + operator: "equals", + }; } }