Skip to content

Commit 3b1c236

Browse files
committed
add column with sql query to ToC sheet
1 parent 459c3cc commit 3b1c236

File tree

5 files changed

+222
-13
lines changed

5 files changed

+222
-13
lines changed

queries/test-toc-query.xml

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<queries>
2+
<excel db="sampleDB" output="d:/temp/ToC_테스트_2024.xlsx" separateToc="false" maxRows="10" style="business">
3+
<!-- ToC 시트에 쿼리문 표시 테스트 -->
4+
</excel>
5+
<vars>
6+
<var name="startDate">2024-01-01</var>
7+
<var name="endDate">2024-12-31</var>
8+
</vars>
9+
<sheet name="고객_목록" use="true" aggregateColumn="지역">
10+
<![CDATA[
11+
SELECT
12+
CustomerCode as 고객코드,
13+
CustomerName as 고객명,
14+
ContactName as 담당자명,
15+
City as 도시,
16+
Region as 지역,
17+
CustomerType as 고객유형,
18+
FORMAT(CreditLimit, 'N0') as 신용한도
19+
FROM SampleDB.dbo.Customers
20+
WHERE IsActive = 1
21+
ORDER BY CreditLimit DESC
22+
]]>
23+
</sheet>
24+
<sheet name="주문_목록" use="true" aggregateColumn="결제방법">
25+
<![CDATA[
26+
SELECT
27+
OrderNumber as 주문번호,
28+
FORMAT(OrderDate, 'yyyy-MM-dd') as 주문일,
29+
OrderStatus as 주문상태,
30+
PaymentStatus as 결제상태,
31+
FORMAT(TotalAmount, 'N0') as 총금액,
32+
PaymentMethod as 결제방법
33+
FROM SampleDB.dbo.Orders
34+
WHERE OrderDate >= '${startDate}' AND OrderDate <= '${endDate}'
35+
ORDER BY OrderDate DESC
36+
]]>
37+
</sheet>
38+
<sheet name="복잡한_쿼리_테스트" use="true" aggregateColumn="카테고리">
39+
<![CDATA[
40+
SELECT
41+
p.ProductID as 상품ID,
42+
p.ProductName as 상품명,
43+
c.CategoryName as 카테고리,
44+
FORMAT(p.UnitPrice, 'N0') as 단가,
45+
p.UnitsInStock as 재고수량,
46+
p.Discontinued as 단종여부,
47+
CASE
48+
WHEN p.UnitsInStock > 100 THEN '충분'
49+
WHEN p.UnitsInStock > 50 THEN '보통'
50+
ELSE '부족'
51+
END as 재고상태
52+
FROM SampleDB.dbo.Products p
53+
INNER JOIN SampleDB.dbo.Categories c ON p.CategoryID = c.CategoryID
54+
WHERE p.Discontinued = 0
55+
ORDER BY c.CategoryName, p.ProductName
56+
]]>
57+
</sheet>
58+
</queries>

src/excel-generator.js

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ class ExcelGenerator {
6666
tabName: actualSheetName,
6767
recordCount: recordCount,
6868
aggregateColumn: sheetDef.aggregateColumn,
69-
aggregateData: aggregateData
69+
aggregateData: aggregateData,
70+
query: sheetDef.query || '' // 쿼리문 정보 추가
7071
});
7172

7273
// 시트명이 잘렸는지 확인하고 로그 출력
@@ -137,19 +138,43 @@ class ExcelGenerator {
137138
async createSeparateTocFile(outputPath, createdSheetNames, createdSheetCounts) {
138139
const tocWb = new ExcelJS.Workbook();
139140
const tocOnly = tocWb.addWorksheet('목차');
140-
tocOnly.addRow(['No', 'Sheet Name', 'Data Count']);
141+
tocOnly.addRow(['No', 'Sheet Name', 'Data Count', 'Query']);
141142

142143
createdSheetNames.forEach((obj, idx) => {
143-
const row = tocOnly.addRow([idx + 1, obj.displayName, createdSheetCounts[idx]]);
144+
// 쿼리문 정보 추출 (최대 80자로 제한)
145+
let queryText = '';
146+
if (obj.query) {
147+
queryText = obj.query.replace(/\s+/g, ' ').trim(); // 연속 공백 제거
148+
if (queryText.length > 80) {
149+
queryText = queryText.substring(0, 77) + '...';
150+
}
151+
}
152+
153+
const row = tocOnly.addRow([idx + 1, obj.displayName, createdSheetCounts[idx], queryText]);
144154
row.getCell(2).font = { color: { argb: '0563C1' }, underline: true };
145155
row.getCell(3).font = { color: { argb: '0563C1' }, underline: true };
156+
157+
// 쿼리문 셀 스타일링
158+
const queryCell = row.getCell(4);
159+
if (queryText) {
160+
queryCell.font = {
161+
size: 9,
162+
color: { argb: '2F5597' }
163+
};
164+
queryCell.alignment = {
165+
horizontal: 'left',
166+
vertical: 'middle',
167+
wrapText: true
168+
};
169+
}
146170
});
147171

148172
tocOnly.getRow(1).font = { bold: true };
149173
tocOnly.columns = [
150174
{ header: 'No', key: 'no', width: 6 },
151175
{ header: 'Sheet Name', key: 'name', width: 30 },
152-
{ header: 'Data Count', key: 'count', width: 12 }
176+
{ header: 'Data Count', key: 'count', width: 12 },
177+
{ header: 'Query', key: 'query', width: 50 }
153178
];
154179

155180
const tocExt = FileUtils.getExtension(outputPath);

src/excel-style-helper.js

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -396,8 +396,8 @@ function populateTableOfContents(tocSheet, sheetNames) {
396396
// 기존 내용 모두 삭제
397397
tocSheet.spliceRows(1, tocSheet.rowCount);
398398

399-
// 헤더 추가
400-
tocSheet.addRow(['No', 'Sheet Name', 'Records', 'Aggregate Info', 'Note']);
399+
// 헤더 추가 (쿼리문 컬럼 추가)
400+
tocSheet.addRow(['No', 'Sheet Name', 'Records', 'Aggregate Info', 'Query', 'Note']);
401401

402402
// 시트 목록 추가
403403
sheetNames.forEach((obj, idx) => {
@@ -418,7 +418,16 @@ function populateTableOfContents(tocSheet, sheetNames) {
418418
}
419419
aggregateInfo = `관련데이터 ${obj.recordCount}${aggregateInfo}`
420420

421-
const row = tocSheet.addRow([idx + 1, obj.displayName, obj.recordCount || 0, aggregateInfo]);
421+
// 쿼리문 정보 추출 (최대 100자로 제한)
422+
let queryText = '';
423+
if (obj.query) {
424+
queryText = obj.query.replace(/\s+/g, ' ').trim(); // 연속 공백 제거
425+
if (queryText.length > 100) {
426+
queryText = queryText.substring(0, 97) + '...';
427+
}
428+
}
429+
430+
const row = tocSheet.addRow([idx + 1, obj.displayName, obj.recordCount || 0, aggregateInfo, queryText]);
422431

423432
// 하이퍼링크 설정 - 실제 시트명(tabName) 사용
424433
const sheetNameForLink = obj.tabName.replace(/'/g, "''"); // 작은따옴표 이스케이프
@@ -534,9 +543,26 @@ function populateTableOfContents(tocSheet, sheetNames) {
534543
aggregateCell.font = { color: { argb: '999999' } };
535544
}
536545

537-
// 비고 컬럼 스타일링 (5번째 컬럼)
546+
// 쿼리문 컬럼 스타일링 (5번째 컬럼)
547+
const queryCell = row.getCell(5);
548+
if (queryText) {
549+
queryCell.font = {
550+
size: 9,
551+
color: { argb: '2F5597' }
552+
};
553+
queryCell.alignment = {
554+
horizontal: 'left',
555+
vertical: 'middle',
556+
wrapText: true
557+
};
558+
} else {
559+
queryCell.value = '';
560+
queryCell.font = { color: { argb: '999999' } };
561+
}
562+
563+
// 비고 컬럼 스타일링 (6번째 컬럼)
538564
if (isTruncated) {
539-
row.getCell(5).font = {
565+
row.getCell(6).font = {
540566
italic: true,
541567
color: { argb: 'D2691E' } // 주황색으로 경고 표시
542568
};
@@ -554,11 +580,13 @@ function populateTableOfContents(tocSheet, sheetNames) {
554580
}
555581
});
556582

557-
// 컬럼 설정
583+
// 컬럼 설정 (쿼리문 컬럼 추가)
558584
tocSheet.columns = [
559585
{ header: 'No', key: 'no', width: 6 },
560586
{ header: 'Sheet Name', key: 'name', width: 25 },
561587
{ header: 'Records', key: 'records', width: 12 },
588+
{ header: 'Aggregate Info', key: 'aggregate', width: 20 },
589+
{ header: 'Query', key: 'query', width: 40 },
562590
{ header: 'Note', key: 'note', width: 18 }
563591
];
564592

test/test-excel-style-helper.js

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,10 +130,20 @@ const excelStyleHelper = require('../src/excel-style-helper');
130130

131131
console.log('8. 목차 시트 생성 테스트...');
132132
const sheetNames = [
133-
{ displayName: '기본 스타일 테스트', tabName: '기본 스타일 테스트' },
134-
{ displayName: '개별 함수 테스트', tabName: '개별 함수 테스트' }
133+
{
134+
displayName: '기본 스타일 테스트',
135+
tabName: '기본 스타일 테스트',
136+
recordCount: 5,
137+
query: 'SELECT * FROM test_table WHERE status = "active"'
138+
},
139+
{
140+
displayName: '개별 함수 테스트',
141+
tabName: '개별 함수 테스트',
142+
recordCount: 3,
143+
query: 'SELECT id, name, email FROM users ORDER BY name'
144+
}
135145
];
136-
excelStyleHelper.createTableOfContents(workbook, sheetNames);
146+
excelStyleHelper.populateTableOfContents(workbook.addWorksheet('목차'), sheetNames);
137147
console.log('✅ 목차 시트 생성 완료');
138148

139149
// 워크시트 순서 조정 (목차를 첫 번째로)

test/test-toc-query.js

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
const ExcelJS = require('exceljs');
2+
const path = require('path');
3+
4+
// excel-style-helper 모듈 로드
5+
const excelStyleHelper = require('../src/excel-style-helper');
6+
7+
(async () => {
8+
console.log('🔍 ToC 시트 쿼리문 표시 테스트');
9+
console.log('=====================================');
10+
11+
try {
12+
// 워크북 생성
13+
const workbook = new ExcelJS.Workbook();
14+
15+
// 목차 시트 생성
16+
const tocSheet = workbook.addWorksheet('목차');
17+
18+
// 테스트용 시트 정보 (쿼리문 포함)
19+
const sheetNames = [
20+
{
21+
displayName: '고객_목록',
22+
originalName: '고객_목록',
23+
tabName: '고객_목록',
24+
recordCount: 150,
25+
aggregateColumn: '지역',
26+
aggregateData: [
27+
{ key: '서울', count: 45 },
28+
{ key: '부산', count: 32 },
29+
{ key: '대구', count: 28 },
30+
{ key: '인천', count: 25 }
31+
],
32+
query: 'SELECT CustomerCode as 고객코드, CustomerName as 고객명, ContactName as 담당자명, City as 도시, Region as 지역, CustomerType as 고객유형, FORMAT(CreditLimit, \'N0\') as 신용한도 FROM SampleDB.dbo.Customers WHERE IsActive = 1 ORDER BY CreditLimit DESC'
33+
},
34+
{
35+
displayName: '주문_목록',
36+
originalName: '주문_목록',
37+
tabName: '주문_목록',
38+
recordCount: 89,
39+
aggregateColumn: '결제방법',
40+
aggregateData: [
41+
{ key: '신용카드', count: 35 },
42+
{ key: '현금', count: 28 },
43+
{ key: '계좌이체', count: 26 }
44+
],
45+
query: 'SELECT OrderNumber as 주문번호, FORMAT(OrderDate, \'yyyy-MM-dd\') as 주문일, OrderStatus as 주문상태, PaymentStatus as 결제상태, FORMAT(TotalAmount, \'N0\') as 총금액, PaymentMethod as 결제방법 FROM SampleDB.dbo.Orders WHERE OrderDate >= \'2024-01-01\' AND OrderDate <= \'2024-12-31\' ORDER BY OrderDate DESC'
46+
},
47+
{
48+
displayName: '복잡한_쿼리_테스트',
49+
originalName: '복잡한_쿼리_테스트',
50+
tabName: '복잡한_쿼리_테스트',
51+
recordCount: 67,
52+
aggregateColumn: '카테고리',
53+
aggregateData: [
54+
{ key: '전자제품', count: 25 },
55+
{ key: '의류', count: 22 },
56+
{ key: '식품', count: 20 }
57+
],
58+
query: 'SELECT p.ProductID as 상품ID, p.ProductName as 상품명, c.CategoryName as 카테고리, FORMAT(p.UnitPrice, \'N0\') as 단가, p.UnitsInStock as 재고수량, p.Discontinued as 단종여부, CASE WHEN p.UnitsInStock > 100 THEN \'충분\' WHEN p.UnitsInStock > 50 THEN \'보통\' ELSE \'부족\' END as 재고상태 FROM SampleDB.dbo.Products p INNER JOIN SampleDB.dbo.Categories c ON p.CategoryID = c.CategoryID WHERE p.Discontinued = 0 ORDER BY c.CategoryName, p.ProductName'
59+
}
60+
];
61+
62+
console.log('📋 ToC 시트에 쿼리문 정보 추가 중...');
63+
64+
// ToC 시트에 내용 채우기 (쿼리문 포함)
65+
excelStyleHelper.populateTableOfContents(tocSheet, sheetNames);
66+
67+
console.log('✅ ToC 시트 생성 완료');
68+
console.log(` - 총 ${sheetNames.length}개 시트 정보`);
69+
console.log(' - 쿼리문 컬럼 포함');
70+
console.log(' - 집계 정보 포함');
71+
72+
// 파일 저장
73+
const fileName = 'test-toc-with-query.xlsx';
74+
await workbook.xlsx.writeFile(fileName);
75+
console.log(`\n🎉 테스트 완료! 결과 파일: ${fileName}`);
76+
console.log('\n📋 ToC 시트 구성:');
77+
console.log('- No: 시트 번호');
78+
console.log('- Sheet Name: 시트명 (하이퍼링크)');
79+
console.log('- Records: 데이터 건수 (하이퍼링크)');
80+
console.log('- Aggregate Info: 집계 정보 (하이퍼링크)');
81+
console.log('- Query: 사용된 쿼리문 (최대 100자)');
82+
console.log('- Note: 비고사항');
83+
84+
} catch (error) {
85+
console.error('❌ 테스트 중 오류 발생:', error);
86+
process.exit(1);
87+
}
88+
})();

0 commit comments

Comments
 (0)