2019년 3월 15일 금요일

[서울대 컴퓨터의 개념 및 실습 PA 8] 데이터베이스에 대한 연산




Programming Assignment #8

<제목 차례>

I. Introduction

1. Program

2. Condtions

2. Programming
1. Background
2. Structure of this Program
3. Procedure
3. Result

<그림 차례>
그림 1 ~ 5 연산 예시
그림 6 프로그램 예시
그림 7 ~ 16 프로그램 결과

I. Introduction
1. Problem
관계형 데이터 모델(Relational Data Model)을 기초로 하여 합집합(union), 차집합(difference), 교집합(intersection), 곱집합(cartesian product), 프로젝션(projection), 셀렉션(selection), 조인(join) 연산자를 구현하라. 해당 과정은 아래 그림과 같이 WinForm을 사용하여 표현한다.
그림 1 결과의 예시
2. Conditions
① data1.txt ~ data5.txt 데이터 셋을 받아와서 활용한다.
② 각 연산자는 버튼 형식으로 구현
II. Programming
1. Background
① Union
데이터베이스의 원소로 이루어진 집합을 생각했을 때, 그러한 두 집합의 합집합을 이루는 원소로 다시 데이터베이스를 구성하는 과정을 Union이라고 한다. 만약 두 데이터베이스 사이에 겹치는 행(row)이 있다면, Union 연산을 거친 데이터베이스에서는 이 행이 중복되어 나타나지 않고 한 번만 나타나야만 한다.
그림 1 Union 연산 예시
② Difference
데이터베이스의 원소로 이루어진 집합을 생각했을 때, 그러한 어떤 집합의 다른 집합에 대한 차집합을 이루는 원소로 다시 데이터베이스를 구성하는 과정을 Difference이라고 한다. 둘 중 어떤 데이터베이스를 기준으로 할 것인지 먼저 정해야 하는 특성이 있다.
그림 2 Difference 연산 예시
③ Intersection
데이터베이스의 원소로 이루어진 집합을 생각했을 때, 그러한 두 집합의 교집합을 이루는 원소로 다시 데이터베이스를 구성하는 과정을 Intersection이라고 한다.
그림 3 Intersection 연산 예시
④ Cartesian Product
데이터베이스의 행을 조합해서 나오는 모든 경우를 원소로 가지는 데이터베이스를 만드는 과정을 Cartesian Product, 또는 곱집합이라고 한다.
그림 4 Cartesian Product 연산 예시
⑤ Selection
Selection은 데이터베이스 안에서 내가 보고 싶은 행만을 뽑아서 추려내는 연산을 말한다.
⑥ Projection
Projection은 데이터 베이스 안에서 내가 보고 싶은 열만을 뽑아서 추려내는 연산을 말한다.
⑦ Join
데이터베이스 두 개가 공통적으로 가지는 특성(열)을 key로 참고하여 두 데이터베이스를 하나의 데이터베이스로 연결시켜주는 일을 Join 연산이라고 한다. 이때 공통적으로 가지고 있는 열을 Join의 결과에서 중복시키지 않고 하나만 내보내는 것이 깔끔하다고 할 수 있다.
그림 5 Join 연산 예시
2. Structure of the Program
프로그램을 실행하고 연산을 수행하면, 다음과 같은 화면이 생성되도록 할 것이다.
그림 6 프로그램에서 Join 연산을 수행한 결과
왼쪽의 버튼들은 모두 button(연산이름) 형식의 이름을 가지고 있고(예:buttonJoin), 왼쪽의 텍스트박스의 이름은 textBox1, 오른쪽은 textBox2이다. 왼쪽 위의 DataGridView의 이름은 dataGrid1, 오른쪽 위의 이름은 dataGridView2, 아래의 이름은 dataGridView1이다. 그리고 왼쪽 텍스트박스의 오른쪽에 있는 버튼은 button4, 오른쪽 텍스트박스의 오른쪽에 있는 버튼은 button5이다.
그리고 다음은 변수를 선언하는 파트이다.


StreamReader sr;
        string[] column1;
        string[] column2;
        string[] column;
        DataTable table1;
        DataTable table2;
        DataTable table;
int chosendata = 0;

StreamReader sr은 각 textBox에 입력된 파일 경로에 위치한 파일에서 데이터를 읽어내는 역할을 한다. column1은 왼쪽에 입력된 파일에서 읽어낸 데이터베이스의 열 목록을 의미하고, column2는 오른쪽에서 입력된 파일에서 읽어낸 데이터베이스의 열 목록을 의미한다. column은 연산 결과로 얻어낸 데이터베이스의 열 목록을 의미한다. table1은 왼쪽 그리드뷰에 쓰일 DataTable, table2는 오른쪽에 쓰일 것이며, table은 연산 결과를 내보낼 아래 그리드뷰에 쓰일 DataTable이다. int chosendata는 마지막으로 클릭한 그리뷰가 왼쪽인지 오른쪽인지를 말해주는 정수값이며, 0이면 왼쪽, 1이면 오른쪽을 의미한다.
3. Process
private void button4_Click(object sender, EventArgs e)
    {
        if (File.Exists(textBox1.Text))
        {
            sr = new StreamReader(textBox1.Text);
            string tempCol = sr.ReadLine();
            string[] tempColArr = tempCol.Split(' ');
            string temp = sr.ReadLine();
            string[] tempArr = temp.Split(' ');
            // populate datagridview from datatable
            table1 = new DataTable();
            // add columns to datatable
           column1 = new string[tempColArr.Length];
            for (int i = 0; i < tempColArr.Length; i++)
            {
                if (int.TryParse(tempArr[i], out int n))
                {
                       table1.Columns.Add(tempColArr[i], typeof(int));
                    column1[i] = tempColArr[i];
                }
                else
                {
                       table1.Columns.Add(tempColArr[i], typeof(string));
                    column1[i] = tempColArr[i];
                }
            }
            table1.Rows.Add(tempArr);
            do
            {
                temp = sr.ReadLine();
                tempArr = temp.Split(' ');
                table1.Rows.Add(tempArr);
            }
            while (!sr.EndOfStream);
               sr.Close();
            dataGrid1.DataSource = table1;
        }
        else
        {
            MessageBox.Show("No such file!");
        }
    }
우선, textBox1에 입력된 파일 경로에 진짜로 파일이 있는지 확인해서, 없으면 메시지박스로 그런 파일 없다는 문구를 띄운다. 그리고 첫 번째로 읽은 줄을 string 타입 변수 tempCol에 저장하고, 이를 공백마다 분리한 문자열을 tempColArr에 저장한다. 둘째 줄을 string 타입 변수 temp에 저장하고, 이를 공백마다 분리한 문자열을 tempArr에 저장한다. column1의 메모리 할당을 해 주고, for문을 이용해서 tempColArr에 저장된 내용을 column1에 복사한다. 복사하는 동시에 table1의 Columns에 열을 추가시키는데, if 문을 이용해서 tempArr의 i번째 문자를 정수형으로 바꿔보고, 바꿀 수 있다면 열에 추가시킬 때 int형으로, 바꿀 수 없다면 열에 추가시킬 때 string 형으로 추가시킨다. 그리고, table1의 Rows에 tempArr를 추가시킨다. 그리고 do와 while문을 이용해서 파일이 끝나기 전까지(!sr.EndofStream) 파일을 읽으며 읽은 줄을 temp에 저장하고, 그 temp를 공백마다 분리하여 문자열을 만든 것을 tempArr에 저장한 뒤,  tempArr을 table1의 Rows로 추가하여 더하는 과정을 반복한다. while 문이 끝나면, sr을 닫고, dataGrid1의 DataSource를 table1으로 잡으면서 왼쪽의 그리드뷰에 데이터를 띄운다.
이러한 코드는 button5를 클릭했을 때 일어나는 이벤트에 동일한 방식으로 적용한다.
private void buttonJoin_Click(object sender, EventArgs e)
   {
        column = new string[column1.Length + column2.Length-1];
        int sel1 = dataGrid1.CurrentCell.ColumnIndex;
        int sel2 = dataGridView2.CurrentCell.ColumnIndex;
        if (column1[sel1] == column2[sel2])
        {
            table = new DataTable();
            for (int i = 0; i < column1.Length; i++)
            {
                column[i] = column1[i];
                   table.Columns.Add(column[i]);
            }
            for(int i = column1.Length; i < column1.Length + sel2; i++)
            {
                column[i] = column2[i - column1.Length];
                   table.Columns.Add(column[i]);
            }
            for (int i = column1.Length + sel2; i < column.Length; i++)
            {
               column[i] = column2[i - column1.Length + 1];
                   table.Columns.Add(column[i]);
            }
            // add rows to datatable
            for(int i = 0; i < table1.Rows.Count; i++)
            {
                List<int> indexlist = new List<int>();
                for(int j = 0; j < table2.Rows.Count; j++)
                {
                    if(table1.Rows[i][sel1].ToString() == table2.Rows[j][sel2].ToString())
                    {
                        indexlist.Add(j);
                    }
                }
                for (int k = 0; k < indexlist.Count; k++)
                {
                    string[] tempArr = new string[column.Length];
                    for (int j = 0; j < column1.Length; j++)
                    {
                        tempArr[j] = table1.Rows[i][j].ToString();
                    }
                    for (int j = column1.Length; j < column1.Length + sel2; j++)
                    {
                        tempArr[j] = table2.Rows[indexlist[k]][j - column1.Length].ToString();
                    }
                    for (int j = column1.Length + sel2; j < column.Length; j++)
                    {
                        tempArr[j] = table2.Rows[indexlist[k]][j - column1.Length + 1].ToString();
                    }
                    table.Rows.Add(tempArr);
                }
               }
            dataGridView1.DataSource = table;
        }
        else
        {
            MessageBox.Show("Please select cells again");
        }
    }

이 메소드에게는 어떤 정보가 요구되는데, 바로 key가 되는 특성이 무엇인가이다. 여기서는 사용자가 직접 key를 선택하도록 하는데, key가 되는 열에 있는 cell 중 하나를 사용자가 각 데이터그리드뷰에서 선택하면 메소드가 각 cell에 해당하는 열을 읽어와서 작업을 수행할 수 있도록 할 것이다.
우선 column을 초기화하는데, key가 되는 열이 중복되는 것 하나를 빼야 하므로 column의 길이는 column1과 column2의 길이를 더한 것에서 1을 뺀 값이 된다. 그리고 데이터그리드뷰의 CurrentCell의 ColumnIndex를 읽어와서 왼쪽 데이터그리드뷰의 것은 sel1에, 오른쪽의 것은 sel2에 저장하는데, 이는 각각 key 열이 데이터베이스 열의 어디에 위치하는지 알려주는 역할을 한다.
그 다음에는 실제로 column1에서 인덱스 sel1에 있는 열을 읽어오고, column2에서 인덱스 sel2에 있는 열을 읽어와 그 둘이 같은 지 if문을 통해 확인하고, 만약 다른 경우에는 경고 메시지를 띄운다.
이제는 column 배열에 열을 채우고 table에 Column을 더할 차례이다. 사실 table에 그냥 더해도 상관없으나 확장성을 위해서 column 배열에 데이터를 채워 넣었다. 우선, table을 초기화하고, for문을 이용해서 column1과 column2의 내용을 column에 베껴 넣는다. 하지만, 도중에 중복되는 열이 있기 때문에 그 중복되는 열에 있는 내용은 column에 베껴 넣을 때 빼고 베껴 넣어야 한다. 그래서 for문을 세 번 쓰도록 한다. 첫 번째 for문은 column1을 처음부터 끝까지 끊김없이 column에 내용을 복사하는 역할을 하며, 두 번째 for문은 column2를 key 열의 인덱스가 되기 바로 전까지 그 내용을 복사하고, 세 번째 for문은 column2를 key 열의 인덱스를 건너뛴 바로 다음부터 끝까지 그 내용을 복사한다. 이때 for문에 쓰인 i는 column의 인덱스 기준으로 증가하는데, 그렇기 때문에 두 번째 for 문에서 복사하는 column2의 값은 column2[i - column1.Length]여야 하고, 세 번째 for 문에서 복사하는 column2의 값은 column2[i - column1.Length + 1]이어야 한다.
다음은 row를 table에 차례차례 더해 나갈 차례이다. table1을 기준으로 삼을 때, table1의 특정 행에 대하여, table2의 행들 중 table1의 특정 행과 같은 key 값을 가지는 것이 무엇이 있고, 그것이 어디에 있는지 알아내는 것이 최우선 사항이 되어야 한다. 그래서 현재 조사하고 있는 table1 행의 인덱스를 상징하는 정수형 변수 i에 대한 for문을 바깥에 미리 설정하고, 그 안에 리스트 indexlist를 새로 선언하는데, 이는 현재 인덱스 i에 위치한 table1 행과 같은 key 값을 가지는 table2 행의 인덱스 목록을 의미한다. 현재 조사하고 있는 table2 행의 인덱스를 상징하는 정수형 변수 j에 대한 for문을 안쪽에 한 번 더 설정하고, 각 key 값을 비교하는 조건문 if문을 넣어 key 값이 같으면 j 값을 indexlist에 더하는 작업을 한다. 이렇게 하면 table1의 인덱스 i행에 대한 정보는 모두 찾은 것이니, 이제 table의 row에 더할 차례이다.
column과 같은 크기를 가진 string 배열 tempArr을 설정하고, 아까 column에 column1과 column2의 내용을 복사한 방법 그대로 table1의 row[i]의 내용과 table2의 row[indexlist[k]](table1.Row[i]와 같은 key 값을 가지고 있는 table2.Row)의 내용을 tempArr에 복사한다. 그리고 table의 Rows에 tempArr을 더하는 과정을 계속 반복한다. table에 Row를 추가하는 과정이 끝나면(i=table1.Rows.Count가 됨) dataGridView1의 DataSource를 table로 설정해 주며 연산한 데이터를 dataGridView1에 띄운다.

private void buttonProduct_Click(object sender, EventArgs e)

    {
        bool different = true;
        for(int i = 0; i < column1.Length; i++)
        {
            for(int j = 0; j < column2.Length; j++)
            {
                if(column1[i] == column2[j])
               {
                    different = false;
                    break;
                }
               }
        }
        table = new DataTable();
        if(different)
        {
            column = new string[column1.Length + column2.Length];
            for(int i = 0; i < column1.Length; i++)
            {
                   column[i] = column1[i];
                   table.Columns.Add(column[i]);
            }
            for(int i = column1.Length; i < column.Length; i++)
            {
                column[i] = column2[i - column1.Length];
                   table.Columns.Add(column[i]);
            }
            for (int i = 0; i < table1.Rows.Count; i++)
            {
               for (int j = 0; j < table2.Rows.Count; j++)
                {
                    string[] tempArr = new string[column.Length];
                    for(int k = 0; k < column1.Length; k++)
                    {
                       tempArr[k] = table1.Rows[i][k].ToString();
                    }
                    for (int k = column1.Length; k < column.Length; k++)
                    {
                        tempArr[k] = table2.Rows[j][k - column1.Length].ToString();
                    }
                    table.Rows.Add(tempArr);
                }
            }
            dataGridView1.DataSource = table;
        }
        else
        {
            MessageBox.Show("Columns are not totally different!");
        }
    }

Cartesian Product 연산을 수행하기 위해서는 연산 되는 두 데이터베이스의 열에 공통된 열이 없어야 한다는 특성이 있다. 그래서 현 상황이 이 특성을 만족하는지 먼저 확인해야 한다.
우선 두 데이터베이스의 열에 공통된 열이 없는지를 나타내는 bool 변수 different를 선언하고, 기본값을 true로 둔다. 이중 for 문을 설정해서 column1과 column2를 비교하는데, 만약 column1[i]와 column2[j]가 같으면, 즉 하나라도 같은 열이 검출되면 different는 false로 바꾸고, break를 넣는다.
table의 메모리를 할당한 뒤, different가 true인지 확인해서 그렇지 않으면 경고 메시지 박스를 출력하도록 하고, true이면 if문 아래의 코드를 실행하도록 한다.
different가 true라면, 프로그램은 column의 메모리 할당을 진행하는데, 중복되는 열이 없으므로 column의 크기는 column1의 크기와 column2의 크기의 합이 된다. 그 다음에는, for문을 두 번 진행하여 각각 column1과 column2의 내용을 column2에 복사하는 동시에 table에 Column을 더하는데, for문의 i는 column의 인덱스를 기준으로 해서 증가한다. 그래서 두 번째 for문, 즉 column2의 내용을 복사하는 for문에서 복사하는 column2의 인덱스는 i – column1.Length가 돼야만 한다.
다음에는 이중 for문을 이용해서 table에 Row를 더한다. 바깥쪽 for문의 i는 현재 검사하고 있는 table1 행의 인덱스, 안쪽 for문의 j는 현재 검사하고 있는 table2 행의 인덱스가 된다. 이중 for문의 안쪽에는 string 배열 tempArr를 선언하고, 그 크기는 column 배열의 크기와 같도록 한다. 그리고 다시 한 번 for문을 설정해서 차례차례 tempArr에 table1.Rows[i]의 내용과 table2.Rows[j]의 내용을 복사한다. 이 원리는 위에서 column에 내용을 복사한 원리와 같기 때문에 다시 한 번 설명하지 않겠다. for 문을 통해서 복사한 뒤에는 tempArr을 table.Rows에 추가시키며, 이러한 일을 계속 반복한다. 그러면 table에는 Cartesian Product의 연산 결과가 남게 되며, dataGridView1의 DataSource를 table로 설정하면서 결과를 그리드뷰에 띄운다.
private void dataGrid1_CellClick(object sender, DataGridViewCellEventArgs e)
    {
        chosendata = 0;
    }
    private void dataGridView2_CellClick(object sender, DataGridViewCellEventArgs e)
    {
        chosendata = 1;
    }

이번에는 Selection 연산 등등에 필요한 밑밥 작업을 한다. Selection 연산 등등을 수행하기 위해서는 대상이 되는 데이터베이스를 알고 있어야 한다. 이 프로그램에서 그 대상이 되는 데이터베이스는 사용자가 마지막으로 셀을 클릭한 데이터그리드뷰에 해당하는 데이터베이스이다. dataGrid1을 사용자가 클릭하면 chosendata 값은 0이 되고, dataGridView2를 사용자가 클릭하면 chosendata 값이 1이 되도록 설정하면서 후에 연산을 수행할 때 연산 수행 대상이 되는 데이터베이스가 무엇인지 쉽게 알 수 있도록 한다.
private void buttonSelection_Click(object sender, EventArgs e)

    {
        table = new DataTable();
        if(chosendata == 0)
        {
           column = new string[column1.Length];
            string[] tempArr = new string[column.Length];
            for(int i = 0; i < column.Length; i++)
            {
                   table.Columns.Add(column1[i]);
            }
           foreach (DataGridViewRow row in dataGrid1.SelectedRows)
            {
                for(int i = 0; i < column.Length; i++)
                {
                    tempArr[i] = table1.Rows[row.Index][i].ToString();
                }
                table.Rows.Add(tempArr);
            }
        }
        else
        {
            column = new string[column2.Length];
            string[] tempArr = new string[column.Length];
            for (int i = 0; i < column.Length; i++)
            {
                   table.Columns.Add(column2[i]);
            }
            foreach (DataGridViewRow row in dataGridView2.SelectedRows)
            {
                for (int i = 0; i < column.Length; i++)
               {
                    tempArr[i] = table2.Rows[row.Index][i].ToString();
                }
                table.Rows.Add(tempArr);
            }
        }
        dataGridView1.DataSource = table;
       }


Selection 연산을 수행하기 위해서는 두 가지 정보가 필요한데, 바로 대상이 되는 데이터베이스가 무엇인지, 또 선택된 데이터베이스의 행이 무엇인지이다. 전자의 정보는 위에서 이미 구했고, 후자의 정보는 사용자가 데이터그리드뷰에서 선택한 행(셀이 아니라 행 전체를 선택해야 한다)이 무엇인지 알아내서 사용할 것이다.
처음에는 table의 메모리 할당을 해주고, if 문을 통해 chosendata, 즉 대상이 되는 데이터베이스가 무엇인지에 따라 갈림길이 나뉜다. 우선 chosendata == 0인 경우를 살펴 보자. column은 column1과 동일해야 하므로, 메모리 할당을 할 때에도 그 크기가 colum1과 같도록 한다. 하지만 그 안의 내용은 column1과 같아 별 쓸모가 없으므로 table.Columns에는 그냥 column에 복사하지 않고 column1의 성분을 이용하여 더한다.
다음, table에 Row를 더하는데, 이를 위하여 foreach 문을 사용한다. dataGrid1.SelectedRows가 바로 데이터그리드뷰에서 사용자에 의해 선택된 행들이므로, foreach 문을 이용하여 이 각각의 행의 내용을 column.Length의 크기만큼 메모리가 할당된 tempArr에 복사하고, tempArr을 table.Rows에 더한다. 마지막엔 table을 dataGridView1.DataSource로 두면서 마무리한다.
chosendata != 0인 경우에 대해서도 위와 같은 방법으로 할 수 있다.
void buttonProjection_Click(object sender, EventArgs e)

    {
        table = new DataTable();
        if(chosendata == 0)
        {
            List<int> index = new List<int>();
            index.Add(dataGrid1.SelectedCells[0].ColumnIndex);
            foreach(DataGridViewCell cell in dataGrid1.SelectedCells)
            {
                int issame = 0;
                for(int i = 0; i < index.Count; i++)
                {
                   if(index[i] == cell.ColumnIndex)
                    {
                        issame = 1;
                        break;
                    }
                }
                if(issame == 0)
                {
                   index.Add(cell.ColumnIndex);
                }
            }
            column = new string[index.Count];
            for(int i = 0; i < column.Length; i++)
            {
                column[i] = column1[index[i]];
                   table.Columns.Add(column[i]);
            }
            string[] tempArr = new string[column.Length];
            for(int i = 0; i < table1.Rows.Count; i++)
            {
                for(int j = 0; j < tempArr.Length; j++)
                {
                    tempArr[j] = table1.Rows[i][index[j]].ToString();
                }
                table.Rows.Add(tempArr);
            }
            dataGridView1.DataSource = table;
        }
        else
        {
           
        }
    }

Projection 연산을 수행하기 위해서는 데이터베이스에서 선택된 열이 무엇인지 알아야 하는데, 여기서는 사용자가 선택한 셀의 열 인덱스를 가져오는 방식으로 그 정보를 알아냈다.
처음에는 table의 메모리 할당을 해 주고, Selection과 마찬가지로 chosendata의 값에 따라 분기문이 갈린다. 사용자가 선택한 셀의 열 인덱스가 저장되는 리스트 index가 선언되고, index에는 사용자가 선택한 셀의 목록 중 첫 번째 셀의 열 인덱스가 저장된다. 이는 후에 등장할 foreach 문 안의 for 문이 처음부터 중지해버리지 않도록 방지하는 역할을 한다.
다음에는 foreach 문을 통해서 사용자가 선택한 cell에 대해 하나하나 검사할 것이다. foreach 문 안에는 int형 변수 issame을 선언하는데, 이는 현재 조사하고 있는 cell의 열 인덱스와 이미 index에 추가한 열 인덱스가 중복되는지 나타낸다. issame = 0이면 인덱스가 중복되지 않는다는 뜻이며, 초기화될 때에는 issame=0으로 초기화된다. 그리고 for문과 if문을 이용하여 중복 여부를 조사하며, 만약 중복이 되는 게 발견될 경우 issame = 1로 바꾸고 break를 건다. 그리고 issame == 0인지 확인하여 true이면 현재 조사하고 있는 cell의 열 인덱스를 index에 추가한다.
index를 모두 추가시켰다면 for문을 이용하여 index에 해당하는 열 내용을 전부 column으로 옮기는 동시에 table.Columns에 열을 추가시킨다.
string 배열 tempArr을 선언하고, 그 크기가 column.Length와 같도록 한다. 이중 for문을 설정하는데, 외부 for문이 이용하는 i는 현재 주시하고 있는 cell의 행 인덱스, 내부 for문의 j는 열 인덱스를 의미한다. 내부 for문에서 tempArr에 table1.Rows[i]의 내용을 모두 옮기면 외부 for문에서는 tempArr을 table.Rows에 추가하고, 이 작업은 모두 추가될 때까지 반복된다. 반복이 끝나면 그 연산 결과를 그리드뷰에 출력한다.
chosendata == 1인 경우 또한 마찬가지로 진행된다.
다음은 Union, Difference, Intersection 연산을 위한 밑밥 작업이다.
public int isSameCol(string[] col1, string[] col2)
    {
        int whether = 1;
        for(int i = 0; i < col1.Length; i++)
        {
            int issame = 0;
            for(int j = 0; j < col2.Length; j++)
            {
                if(col1[i] == col2[j])
                {
                    issame = 1;
                    break;
                }
            }
           if(issame == 0)
            {
                whether = 0;
                break;
           }
        }
        return whether;
    }
    public int[] arrange(string[] col1, string[] col2)
    {
        int[] index = new int[col2.Length];
        for(int i = 0; i < col2.Length; i++)
        {
            for(int j = 0; j < col1.Length; j++)
            {
                if(col2[i] == col1[j])
                {
                    index[i] = j;
                }
            }
        }
        return index;
       }

isSameCol 메소드는 두 문자열을 비교해서 둘이 모두 서로 같은 원소를 가지고 있는지 확인하는 메소드이다. whether가 1이면 같은 것이다. 이중 for문에 대해 현재 주시하고 있는 col1의 원소 인덱스를 i라고 하고, 현재 주시 중인 col2의 원소 인덱스를 j라고 하자. 우선 for문 사이에 issame을 선언하는데, 이는 col1[i]와 col2의 어떤 원소 중 하나라도 같은지를 말해주는 정수이며, 같으면 1이 된다. 안쪽의 for문에 col[i]와 col2[j]를 비교하는 if문을 넣고, 같으면 issame=1로 하고 break를 건다. 그 for문을 빠져나오면 issame이 0인지 확인하는 if문이 나오는데, 0이면 whether=0으로 하고 break를 걸며, 아니면 계속 for문이 반복된다. 작업이 끝나면 메소드는 whether을 리턴한다.
arrange 메소드는 isSameCol 값이 1인 문자열에 대한 메소드인데, col2의 원소가 각각 col1에서는 무슨 인덱스 값을 가지는지에 대해 알려주는 메소드이다. col2의 크기를 가진 정수형 배열 index를 선언하고, isSameCol와 비슷하게 이중 for문을 통해 col2[i]와 col1[j]가 같으면 index[i]=j로 하고, 작업이 끝나면 index 배열을 리턴하는 형식으로 되어 있다. (이번에 i는 주시하고 있는 col2의 원소 인덱스이고, j는 그 반대)

private void buttonUnion_Click(object sender, EventArgs e)

    {
        int issamecol = isSameCol(column1, column2);
        int[] colindex = arrange(column1, column2);
        if(issamecol == 1)
        {
            table = new DataTable();
            column = new string[column1.Length];
            for(int i = 0; i < column1.Length; i++)
            {
               column[i] = column1[i];
                   table.Columns.Add(column[i]);
            }
            for(int i = 0; i < table1.Rows.Count; i++)
            {
                   table.Rows.Add(table1.Rows[i].ItemArray);
            }
            for(int i = 0; i < table2.Rows.Count; i++)
            {
                int issame = 0;
                for(int j = 0; j < table1.Rows.Count; j++)
                {
                    int rowsame = 1;
                   for(int k = 0; k < column.Length; k++)
                    {
                        if(table2.Rows[i][colindex[k]].ToString() != table1.Rows[j][k].ToString())
                        {
                            rowsame = 0;
                               break;
                        }
                   }
                    if(rowsame == 1)
                    {
                        issame = 1;
                        break;
                   }
                }
                if(issame == 0)
               {
                    string[] tempArr = new string[colindex.Length];
                    for(int j = 0; j < tempArr.Length; j++)
                    {
                       tempArr[j] = table2.Rows[i][colindex[j]].ToString();
                   }
                       table.Rows.Add(tempArr);
                }
            }
            dataGridView1.DataSource = table;
        }
      else
        {
            MessageBox.Show("Columns are different!");
        }
    }

int 형 변수 issamecol이 새로 선언되는데, 이는 column1과 column2가 모두 같은 원소를 가지고 있는지에 대한 값이다. 위에서 선언했던 isSameCol 메소드를 이용하여 값을 받는다.
그리고 int형 배열 colIndex를 선언하는데, column2의 각 원소가 column1에서는 어떤 인덱스를 가지는지에 대한 배열이다. 위에서 선언했던 arrange 메소드를 이용한다.
if문을 통해 issamecol이 1인지 확인하는데, 아니면 메시지박스로 불가능하다는 경고 메시지만을 띄운다. 만약 참이면 if 아래의 코드를 실행한다.
table의 메모리 할당을 진행하고, column 또한 column1의 크기만큼 할당한다. 그리고 for문을 이용해서 column에 column1의 내용을 복사하고, 동시에 column의 내용을 table.Columns에 추가한다.
이제는 table에 Row를 더할 차례이다. 우선, table1의 Rows를 몽땅 table에 옮기는데, table.Rows.Add(table1.Rows[i].ItemArray);라고 써서 이를 실현하였다. 다음은 table2의 Row를 table에 옮겨야 하는데, 이미 중복되는 행은 다시 table에 더하는 일이 없도록 해줘야 한다. 외부의 for문을 선언하고, i는 현재 주시하고 있는 table2 행의 인덱스가 되도록 한다. 그리고 int형 변수 issame을 선언하는데, 이번에는 현재 주시하고 있는 table2의 행과 완전히 같은 내용을 가지는 행이 table1에 있는지를 의미한다. 있으면 1이고 없으면 0인데, 초기화는 0으로 해준다.
다음엔 내부 for문을 한 번 더 선언한다. j는 현재 주시하고 있는 table1 행의 인덱스가 되도록 한다. 이번엔 새로운 int 형 변수 rowsame을 선언하는데, 초기화 값이 1이다. 이는 현재 주시하고 있는 table2 행과 현재 주시하고 있는 table1 행의 내용이 완전히 일치하는지를 나타내는 값이다. 0이면 다르고, 1이면 같다. 다음에 for문과 if문을 한 번 더 선언해서 둘의 원소를 비교하고, 원소가 다르면 rowsame = 0으로 하고 break를 건다. (이때 비교 대상 중 table2의 원소의 열 인덱스는 colIndex[k]로 하여 비교가 정상적으로 이루어지도록 한다.) 그 for문에서 빠져나오면 rowsame이 1인지 확인해서 맞으면 issame도 1로 바꾸고, break를 건다. 두 번째 for문에서도 빠져나오면 issame이 0인지, 즉 중복되는 행이 없는지 확인해서 없으면 현재 주시하고 있는 table2의 행을 table에 더한다. 더할 때 행의 성분이 column1의 순서에 맞게 나와야 하므로 for문을 이용해서 그 순서가 맞도록 집어 넣어준다. (tempArr string 배열을 선언하여 그 배열에 순서대로 성분을 집어넣은 뒤, table.Rows에 tempArr을 더한다.) for문이 완전히 끝나면, table을 dataGridView1.DataSource로 설정해서 결과를 창에 띄우며 마무리한다.
private void buttonDifference_Click(object sender, EventArgs e)
    {
        int issamecol = isSameCol(column1, column2);
        if (issamecol == 1)
        {
            table = new DataTable();
            column = new string[column1.Length];
            for (int i = 0; i < column1.Length; i++)
            {
                column[i] = column2[i];
               table.Columns.Add(column[i]);
            }
            if (chosendata == 0)
            {
                int[] colindex = arrange(column2, column1);
                for (int i = 0; i < table1.Rows.Count; i++)
               {
                       int issame = 0;
                    for (int j = 0; j < table2.Rows.Count; j++)
                    {
                        int rowsame = 1;
                        for (int k = 0; k < column.Length; k++)
                       {
                            if (table1.Rows[i][colindex[k]].ToString() != table2.Rows[j][k].ToString())
                            {
                                rowsame = 0;
                               break;
                               }
                        }
                        if (rowsame == 1)
                        {
                            issame = 1;
                            break;
                       }
                   }
                    if (issame == 0)
                    {
                        string[] tempArr = new string[colindex.Length];
                        for (int j = 0; j < tempArr.Length; j++)
                       {
                            tempArr[j] = table1.Rows[i][colindex[j]].ToString();
                        }
                           table.Rows.Add(tempArr);
                    }
                }
               dataGridView1.DataSource = table;
            }
            else
            {
                int[] colindex = arrange(column1, column2);
                for (int i = 0; i < table2.Rows.Count; i++)
                {
                   int issame = 0;
                    for (int j = 0; j < table1.Rows.Count; j++)
                    {
                        int rowsame = 1;
                        for (int k = 0; k < column.Length; k++)
                       {
                            if (table2.Rows[i][colindex[k]].ToString() != table1.Rows[j][k].ToString())
                            {
                                rowsame = 0;
                                break;
                           }
                        }
                        if (rowsame == 1)
                        {
                            issame = 1;
                            break;
                        }
                   }
                    if (issame == 0)
                    {
                        string[] tempArr = new string[colindex.Length];
                       for (int j = 0; j < tempArr.Length; j++)
                        {
                           tempArr[j] = table2.Rows[i][colindex[j]].ToString();
                        }
                           table.Rows.Add(tempArr);
                   }
                }
                dataGridView1.DataSource = table;
            }
        }
        else
        {
            MessageBox.Show("Columns are different!");
        }

Difference 연산 과정은 Union보다 더 길지만, 서로에 대한 유사도가 매우 높다. Difference를 수행하려면 우선 연산 대상이 되는 데이터베이스를 알아야 하는데, 이를 chosendata의 값을 통해서 알아낸다. 설명은 chosendata가 1일 때를 기준으로 한다.
그 과정은 Union 연산 과정과 거의 동일한데, 차이점이라면 중복 여부를 확인하며 table2의 행을 table에 더하기 전에 table1의 행을 table에 통째로 옮기는 과정이 Difference에는 존재하지 않는다는 것이다. 그것이 유일한 차이점이며, 나머지는 같다. 이 과정은 chosendata가 0일 때도 마찬가지이다.
private void buttonIntersection_Click(object sender, EventArgs e)
    {
        int issamecol = isSameCol(column1, column2);
        int[] colindex = arrange(column1, column2);
        if (issamecol == 1)
        {
            table = new DataTable();
            column = new string[column1.Length];
            for (int i = 0; i < column1.Length; i++)
            {
               column[i] = column1[i];
                   table.Columns.Add(column[i]);
            }
            for (int i = 0; i < table2.Rows.Count; i++)
            {
               int issame = 0;
                for (int j = 0; j < table1.Rows.Count; j++)
                {
                    int rowsame = 1;
                    for (int k = 0; k < column.Length; k++)
                    {
                       if (table2.Rows[i][colindex[k]].ToString() != table1.Rows[j][k].ToString())
                        {
                            rowsame = 0;
                            break;
                        }
                    }
                   if (rowsame == 1)
                    {
                        issame = 1;
                        break;
                    }
                }
                if (issame == 1)
                {
                   string[] tempArr = new string[colindex.Length];
                    for (int j = 0; j < tempArr.Length; j++)
                    {
                        tempArr[j] = table2.Rows[i][colindex[j]].ToString();
                   }
                       table.Rows.Add(tempArr);
                }
            }
            dataGridView1.DataSource = table;
        }
        else
        {
            MessageBox.Show("Columns are different!");
       }
    }

Intersection을 계산하는 과정도 Difference와 매우 유사하다. 물론 이 연산의 대상은 table1과 table2 모두이기 때문에 분기를 나누는 if문은 없다. 하지만 table1의 내용을 통째로 table에 옮기는 과정이 없다는 점은 매우 유사한데, critical한 차이점은 여기 있다. 지금까지는 issame이 0일 때, 즉 중복되지 않는 행을 table에 옮겼다면, 이번에는 중복되는 행, 즉 issame이 1일 때 행을 옮겨야 한다는 것이다. 그 점을 제외하고는 위의 과정과 별 다를 바 없이 모두 동일하다.
private void textBox1_KeyDown(object sender, KeyEventArgs e)
    {
        if(e.KeyCode == Keys.Enter)
        {
            button4_Click(this, new EventArgs());
        }
    }
    private void textBox2_KeyDown(object sender, KeyEventArgs e)
    {
        if(e.KeyCode == Keys.Enter)
        {
            button5_Click(this, new EventArgs());
        }
    }

그리고 편의성을 위해 textBox1을 입력할 때 엔터 키를 누르면 button4을 누르는 효과가 나도록, 그리고 textBox2를 입력할 때 엔터 키를 누르면 button5를 누르는 효과가 나도록 하였다.
III. Result
다음은 Selection 연산을 수행한 결과이다.
그림 7 왼쪽 Selection

그림 8 오른쪽 Selection
다음은 Projection 연산을 수행한 결과이다.
그림 9 왼쪽 Projection

그림 10 오른쪽 Projection
다음은 Join 연산자에 대한 결과이다.
그림 11 Join 결과

다음은 Union, Difference, Intersection 연산자에 대한 결과이다.
그림 12 Union 결과
그림 13 왼쪽 대상 Difference 결과
그림 14 오른쪽 대상 Difference 결과
그림 15 Intersection 결과
그림 16 Product 결과