DB 에서 시험 결과를 한 번에 DataTable 로 받아 두고, 시험지별·난이도별로 정답 수나 점수 합계를 집계해야 할 때가 있습니다. 그때마다 DB 에 쿼리를 다시 날리기보다는 메모리에 있는 DataTable 에서 바로 세는 편이 빠르고 코드도 짧습니다. 아래 예제의 D 는 testPaperNum(시험지 번호), Difficulty(난이도), correct(정답 여부), Scored(점수) 열을 가진 DataTable 입니다.

정렬한 뒤 중복 없는 값만 뽑기

'// 정렬 후 유일한 값 추출
Dim DA() As String = D.Select("", "testPaperNum ASC").CopyToDataTable.AsEnumerable().Select(Function(DR)
                                        Return DR.Field(Of Long)("testPaperNum").ToString
                                    End Function).Distinct().ToArray

D.Select("", "testPaperNum ASC") 는 필터 없이 시험지 번호 오름차순으로 정렬한 행 배열을 돌려줍니다. 이걸 CopyToDataTable 로 다시 테이블로 만든 다음 LINQ 로 testPaperNum 값만 문자열로 뽑고, Distinct 로 중복을 걸러 배열로 만듭니다. 결과는 "시험지 번호 목록"이고, 보통 이 배열을 돌면서 시험지마다 아래 집계를 합니다.

광고

Field(Of Long) 은 해당 열의 실제 형식과 맞아야 합니다. DB 에서 INT 로 받아 온 열이면 Integer 로 바꿔야 형식 변환 오류가 나지 않습니다. 그리고 AsEnumerable 과 Field(Of T) 는 System.Data.DataSetExtensions 참조가 있어야 쓸 수 있습니다.

조건에 맞는 행 개수

'// 특정 조건 데이터 갯수
Count = D.Select(String.Format("[testPaperNum]='{0}' AND [Difficulty]='{1}' AND [correct]='{2}'", DA, DB, 0)).Length

또는

Count = D.Compute("count(testPaperNum)", String.Format("[testPaperNum]='{0}' AND [Difficulty]='{1}' AND [correct]='{2}'", DA, DB, 0))

방법은 두 가지입니다. Select 에 조건식을 주면 맞는 행이 배열로 돌아오니 Length 가 곧 개수입니다. Compute 는 첫 번째 인수로 집계식(count(열)), 두 번째 인수로 조건식을 받아 결과 하나를 돌려줍니다. 행 자체가 필요 없고 숫자만 필요하다면 Compute 쪽이 의도가 잘 보입니다. 다만 Compute 는 Object 를 돌려주므로 Option Strict On 이라면 CInt 로 변환해서 받아야 합니다.

조건식 문법은 SQL 의 WHERE 와 비슷해서 열 이름은 대괄호로, 문자열 값은 작은따옴표로 감쌉니다. 숫자 열이라도 작은따옴표로 감싸면 비교할 때 형식을 맞춰 주지만, 열 형식에 맞게 쓰는 편이 헷갈리지 않습니다.

조건에 맞는 합계

'// 조건에 부합하는 합계
sum = Convert.ToInt32(getCellValue(D.Compute("SUM(Scored)", String.Format("[testPaperNum]='{0}' AND [Difficulty]='{1}' AND [correct]='{2}'", DA, DB, 0))))

합계도 Compute 에 SUM(열) 을 주면 됩니다. 여기서 조심할 점은 조건에 맞는 행이 하나도 없을 때입니다. 이때 Compute 는 0 이 아니라 DBNull 을 돌려주고, 그대로 Convert.ToInt32 에 넣으면 예외가 납니다. 원 코드에서 getCellValue 로 한 번 감싼 이유가 이것으로, DBNull 이면 0 같은 기본값으로 바꿔 주는 도우미 함수입니다. 직접 만든다면 If(IsDBNull(v), 0, v) 정도면 충분합니다.

한 가지 짚어 둘 것은 Count·Sum 예제에서 조건식에 넣는 DA 입니다. 첫 예제의 배열과 이름이 같지만, 여기 들어가야 하는 건 배열을 돌면서 꺼낸 시험지 번호 하나입니다. 배열을 통째로 넣으면 조건식에 "System.String[]" 이 들어가 결과가 늘 0 이 됩니다. For Each paper In DA 처럼 반복 변수를 따로 두고 그 변수를 넣으세요. 행이 수만 개이고 조합이 많다면 Compute 를 반복 호출하는 것보다 LINQ GroupBy 로 한 번에 묶어 집계하는 편이 빠릅니다.