第23章.単純データベース


データベースの基礎


データベースの概念


日常生活では、ニュースやネット、SNSなどでデータベースという言葉に触れることが多い.データベースは、モバイル、サーバコンピュータ、ネットワーク環境に不可欠な要素です.
Pythonが作成したプログラムのデータはファイルに保存できますが、いくつかの限界があります.例えば,1人で登録したサイト,支払い金額,ID,Passwordなどのデータが存在し,数千個のデータをファイルとして管理することは限られている.この問題を解決したのはデータベースです.
データベース管理システム(DBMS)は、前述した様々な形式の、持続的、大量のデータを管理するために登場する.データベースは、大量のデータをシステム的に格納するように簡単に定義することができ、データベースは複数のユーザまたはシステムによって共有される必要がある.
データベース管理システムは、これらのデータベースを管理するシステムです.
Pythonプログラミングを熟知するために、Pythonソフトウェアをインストールした後、データベースを熟知するために、データベースソフトウェアをインストールする必要があります.データベースソフトウェアは、データベース管理システム(DBMS)とも呼ばれます.これらのDBMS製品は、現在最もよく使用されているOracleからSQL Server、MySQL、Access、SQLiteなど様々です.

リレーショナル・データベース


DBMSは、大別して階層型DBMS、網状DBMS、リレーショナルDBMS、オブジェクト向けDBMS、オブジェクトリレーショナルDBMSなどに分けられる.
リレーショナルDBMSの使用が最も多く、一部のマルチメディア分野では、オブジェクト向けまたはオブジェクトリレーショナルDBMSのトレンドが使用されています.
先のOracle、SQL Server、Access、MySQLなどは、いずれもリレーショナルDBMS(RDBMS)と言えます.
RDBMSの最大の欠点は大量のシステム資源を占有し、全体の速度が遅いことである.しかし、ハードウェアの発展により、以前よりずっとよくなりました.

データベース用語


建物を建てるには、まず設計図を作成し、データベースの構築はまずデータベースモデリングから始まります.データベースモデリングは、現実世界で使用されているデータをDBMSに転送する方法を決定するプロセスです.
例えば、ポータルサイトに会員登録する場合は、DBMSに「会員」の情報をどのように入れるかを考えるべきである.すなわち,メンバ自体をコンピュータに入れることができないため,メンバを表すことができる特性(属性)を抽出し,その特性をDBMSに入れる必要がある.すなわち、名前、生年月日、電子メールアドレス、連絡先などの情報をDBMSに記憶しなければならない.
これらの情報は表形式のフレームワークで入れるべきです.

以下は、画像による簡単なデータベース関連用語です.
用語表データJohn,John Bann,[email protected]などの断片的な情報を指す.表会員データは表形式で表現されています.現在,NAVERデータベースを実現するために会員情報を保存する会員表が作成されている.データベース・テーブル・リポジトリ.主に円柱状に表示されます.上の図にはNAVER、A、Bデータベースがあり、各データベースに一意の名前が必要です.DBMSデータベースを管理するシステムまたはソフトウェア.SQLiteソフトウェアはそうです.カラム(カラムまたはフィールド)各テーブルは、1つ以上のカラムで構成されます.会員表は、身分、会員名、電子メール、生年月日の4つから構成されています.カラム名は、各カラムを区別する名前です.カラム名は各テーブルで繰り返すことはできません.データ型カラムのデータ型を使用してテーブルを作成する場合は、カラム名とともに入力するデータ型を指定する必要があります.「たとえば、出生年の資料型はintであるべきです.」「はや94」と同じ値段では使えないからです.行の実際のデータ、john/johnbann/[email protected]/1990は1行のデータです.SQL(構造化クエリー言語)DBMS(SQLite)でアクションを実行する場合は、DBMSに従って指示する必要があります.SQLはDBMSの言語です.

データベースの構築



1-SQLiteを実行します。


データベースを作成するには、まずSQLiteに接続します.図に示すように、ファイルブラウザではsqlite 3が使用されます.exeを実行すると、コマンドプロンプトウィンドウが開き、splite>として表示されます.

2-SQLの作成


1.データベースの作成
データベースを作成または開くには、次の手順に従います.
データベース名を開く
データベースが存在する場合はコマンドを開き、存在しない場合はコマンドを再作成します.次の図に示すように、作成コマンドはNAVERデータベース(名前:naverDB)を生成します.

2.テーブルの作成
naverDBにテーブルを作成する必要があります.このため、SQL文のフォーマットは次のとおりです.
CREATE TABLEテーブル名(カラム名1データ型、カラム名2データ型、...);

3.入力データ
生成された「メンバーテーブル」(UserTable)に行データを入力する必要があります.形態は以下の通り.
INSERT INTOテーブル名VALES(値1、値2、...);

行データの削除と変更は次のとおりです.
削除行データ:「DELETE FROMテーブル名WHEREカラム名=値;
修正フォームデータ:「UPDATEテーブル名SET修正するデータの列名=新規値、WHERE条件列名=値」
4.データの照会と利用
データを検索して使用するSQL文はSELECTです.
SELECT列名称FROM表名称;

データのロード時に条件を追加する場合は、WHEREを後ろに貼り付けます.
SELECT列名1、列名2...FROM表名WHERE条件;
データをソートする場合は、ORDER BYを使用します.
SELECT列名1、列名2...FROMテーブル名ORDER BY列名;
sqlite> SELECT id, birthYear FROM userTable WHERE birthYear<=1990; 
john 1990
lee 1988
park 1980
sqlite) SELECT * FROM userTable WHERE id = ‘lee’; 
lee  Lee Pal lee@paran.com  1998
sqlite> SELECT * FROM userTable ORDER BY birthyear; 
park Park Su park@gmail.com 1980
lee Lee Pal lee@paran.com 1988
john John Bannijohn@naver.com 1990
kim Kim Chi kim@daum.net 1992
sqlite>.quit 

SQLite簡単なコマンドクリーンアップ


コマンドの説明tableは、現在のデータベースのテーブルのリストを表示します.schemaテーブル名テーブルのカラムやデータ型などの情報が表示されます.ヘッダーonSELECT文出力時にヘッダーが表示されます.モードcolumnSELECT文出力の場合、columnモードで出力します.quitSQLiteを終了します.DROP TABLEテーブル名で生成されたテーブルを削除します.

Pythonとsqlite 3モジュール


Pythonからデータの符号化順を入力


  • データベース接続
    PythonでSQLiteを使用する場合は、sqlite 3モジュールを有効にする必要があります.そしてsqlite 3データベースにconnt(「DB名」)で接続する必要があります.データベースが存在する場合は接続、存在しない場合は生成されます.
  • import sqlite3
    con = sqlite3.connect("C:/PythonDB/naverDB")	# 소스 코드가 저장된 폴더에 생성
    
    출력 결과:
    아무것도 나오지 않음
    上のコードでは,naverDBは前のSQLiteを用いて生成したnaverDBとは異なる.
  • カーソルの作成
    カーソル(Cursor)は、データベースでSQL文を実行したり、実行結果を返したりするチャネルに似ています.
    前に作成したコネクタconにカーソルを作成する必要があります.
  • cur = con.cursor()
    
    출력 결과:
    아무것도 나오지 않음
  • テーブルの作成
    表を作成するSQL文のカーソル名.execute()関数のパラメータに渡されると、SQL文はデータベースで実行されます.
  • cur.execute("CREATE TABLE userTable (id char(4), userName char(15), email char(15), birthYear int)")
    
    출력 결과:
    <sqlite3.Cursor object at 개체번호)
    入力
  • データ
    データの入力は、execute()関数を使用して、データ入力SQL文をパラメータに渡すこともできます.
  • cur.execute("INSERT INTO userTable VALUES('john', 'John Bann', '[email protected]', 1990)")
    cur.execute("INSERT INTO userTable VALUES('kim', 'Kim Chi', '[email protected]', 1992)")
    cur.execute("INSERT INTO userTable VALUES('lee', 'Lee Pal', '[email protected]', 1988)")
    cur.execute("INSERT INTO userTable VALUES('park', 'Park Su', '[email protected]', 1980)")
    
    출력 결과:
    sqlite3.Cursor object at 개체번호)가 각각 4회 출력됨
  • 入力データを格納
    4番のデータ入力は、データベースに完全に格納されているのではなく、一時的に格納されています.コミット(commit)に正確に渡します.
  • con.commit()
    
    출력 결과:
    아무것도 나오지 않음
  • データベースのシャットダウン
    データベースもリソースであり、使用が完了したら閉じる必要があります.
  • con.close()
    
    출력 결과:
    아무것도 나오지 않음
    次のコードは、データを受信してデータベースに格納するコードです.
    # 사용자로부터 데이터를 입력받아서 DB에 저장하는 실습
    import sqlite3
    
    # 전역 변수 선언
    con, cur = None, None # 연결자, 커서를 저장하는 변수 초기화
    id, userName, email, birthYear = "","","",""
    
    # 메인 코드 부분
    if __name__ == '__main__':
        con = sqlite3.connect("C:/PythonDB/naverDB")
        cur = con.cursor()
    
        # 무한 루프를 돌면서 사용자로부터 데이터를 입력받는 코드
        while True:
            id = input("사용자 ID 기입: ")
            # 무한 루프의 탈출
            # 사용자가 입력을 하지않고 엔터키를 입력한 경우 루프를 빠져나온다.
            if id == "":
                break
            userName = input("사용자 이름 기입: ")
            email = input("사용자 이메일 기입: ")
            birthYear = (input("사용자 출생년도 기입: "))
    
            # 사용자가 입력한 데이터를 이용하여 쿼리문 작성하는 코드
            # 1번 statement 식의 insert into 방식
            # sql = "insert into userTable values('"+id+"','"+userName+"','"+email+"',"+ birthYear+")"
    
            # 2번 preparedStatement 식의 insert into 방식(와일드 카드 사용 방식)
            # print(sql)
            # statement 식을 이용하는 것보다 가독성이 좋다. 그리고 혼란을 야기하지 않는다.
            # 와일드 카드의 개수를 헤아려서 그에 맞는 저장할 데이터를 제공시켜주면된다.
            cur.execute("insert into userTable (id, userName, email, birthYear) values(?,?,?,?)",(id,userName,email,birthYear))
        con.commit()
    
        # 데이터 조회
        # cur.execute("select * from userTable")
        # while True:
        #     row = cur.fetchone()
        #     if row == None:
        #         break
        #     id = row[0]
        #     userName = row[1]
        #     email = row[2]
        #     birthYear = row[3]
        #     print("%5s %5s   %5s     %5d" %(id, userName, email, birthYear))
    
        con.close()

    Pythonでのデータの符号化順序の照会



    上図の3番目の手順では、SELECT文でexecuteのSQL文でデータを問合せます.このとき、カーソルクエリの内容はすべてカーソルに保存されます(メモリが格納されます).
    次にカーソル名です.fetchone()を使用してデータを取得します.(fetchall()すべてのデータを取得します.)この操作を繰り返して、すべてのデータを出力します.入力、変更データではないため、commit()を使用する必要はありません.
    
    import sqlite3
    # 전역변수 선언
    con, cur = None, None
    id, userName, email, birthYear = "","","",""
    row = None # 한 행을 가져와서 저장할 전역변수
    rows = None # 전체 행을 가져와서 저장할 전역변수
    
    if __name__ == '__main__':
        # DB 연결
        con = sqlite3.connect("c:/PythonDB/naverDB")
        # 커서 생성
        cur = con.cursor()
        cur.execute("select * from userTable")  # 조회된 데이터 전부 저장됨
        # cur.execute("select * from userTable where birthYear>=1990")
    
        # 아래 쿼리는 출생연도 컬럼을 기준으로 오름차순 정렬을 하는 쿼리이다.
        # 단, 기본값이 asc 이기 때문에 asc는 생략이 가능하다.
        # cur.execute("select * from userTable order by birthYear")
    
        # 아래 쿼리는 출생연도 컬럼을 기준으로 내림차순 정렬을 하는 쿼리이다.
        # 단, 기본값이 asc 이기 때문에 asc는 생략이 가능하다. 하지만 내림차순을 하는
        # desc 생략은 불가하다
        # cur.execute("select * from userTable order by birthYear desc")
    
        # 아래는 한 조건에 의해 출력하는 쿼리문이다.
        # cur.execute("select * from userTable where id = 'park'")
        
        print("사용자ID         사용자이름         이메일           출생년도")
        print('--------------------------------------------')
        # row = cur.fetchone()    # 튜플형태로 행의 값을 리턴해준다
        # print(row)  # 튜플형태로 행의 값을 리턴해준다.
        # 무한루프를 돌면서 1행씩  가져와서 출력을 한다.
        while True:
            row = cur.fetchone() # 행을 하나씩 가져온다.
            if row == None: # 더이상 가져올 데이터가 없다면 탈출
                break
            # 한 행에 있는 데이터를 각각 전역변수에 저장후 출력
            id = row[0]
            userName = row[1]
            email = row[2]
            birthYear = row[3]
            print("%5s      %15s     %15s     %5d" %(id, userName, email, birthYear))
        # print('전체 행을 한번에 가져오는 fetchall() 함수 사용')
        # fetchall() 함수를 사용하면 튜플리스트 혀여태로 전체 행을 반환해준디.
        # rows = cur.fetchall()
        # for data in rows:
        #     print(data)
    
        # DB연결 해제
        con.close()
    
    출력 결과:
    사용자ID         사용자이름         이메일           출생년도
    --------------------------------------------
       xi           xi jinping     china@naver.com      1990
    chirs            chris pan     chirs@google.com      1949
     moon           moon jaein       moon@daum.com      1911
     park               kinhae      park@korea.com      1998
      aaa                 aaaa       aaa@naver.com      1999
                                                            

    例。GUIとデータの入力と表示

    # GUI(tkinter 모듈 이용) 환경을 만들어서 데이터를 입력, 출력해주는 프로그램 실습
    import sqlite3
    from tkinter import *
    from tkinter import messagebox
    
    # 전역 변수 선언 및 초기화
    con, cur = None, None
    id, userName, email, birthYear = "", "", "", ""
    # "저장"버튼을 클릭했을 때, 처리하는 이벤트 핸들러 함수 insertData() 선언 및 구현
    def insertData():
        con = sqlite3.connect("c:/PythonDB/naverDB")
        cur = con.cursor()
    
        id = edit1.get()
        userName = edit2.get()
        email = edit3.get()
        birthYear = edit4.get()
        try:
            cur.execute("insert into userTable(id, userName, email, birthYear) values(?, ?, ?, ?)",
                        (id, userName, email, birthYear))
        except:
            messagebox.showerror("오류발생","데이터 입력 오류 발생")
        else:
            messagebox.showinfo("저장 완료","데이터 저장 완료")
    
        con.commit()
        con.close()
    
    def selectData():
        strData1 = []; strData2 = []; strData3 = []; strData4 = []
        con = sqlite3.connect("c:/PythonDB/naverDB")
        cur = con.cursor()
        cur.execute("select * from userTable")
        # 위의 strData1 ~ 4까지의 초기화되어진 리스트에 데이터를 추가함
        strData1.append("사용자 ID");      strData2.append("사용자 이름")
        strData3.append("이메일");         strData4.append("출생연도")
        strData1.append("----------");    strData2.append("----------")
        strData3.append("----------");    strData4.append("----------")
    
        while True:
            row = cur.fetchone()
            if row == None:
                break
            strData1.append(row[0]);  strData2.append(row[1])
            strData3.append(row[2]);  strData4.append(row[3])
    
        # 조회를 재차 클릭을 했을 때 리스트 박스의 내용을 전체 삭제 후 내용 출력하게끔 함.
        listData1.delete(0, listData1.size()-1); listData2.delete(0, listData2.size()-1)
        listData3.delete(0, listData3.size()-1); listData4.delete(0, listData4.size() - 1)
    
    
        # zip() 함수는 여러 개의 순회 가능한(iterable) 객체를 인자로 받고, 각 해당 객체가 담고 있는
        # 요소를 차례로 접근할 수 있게끔 반복자(iterator)를 반환한다.
        # 루프를 돌면서 각각의 해당하는 요소들의 값들을 ListBox 에 추가하고 있다.
        for id, userName, email, birthYear in zip(strData1, strData2, strData3, strData4):
            listData1.insert(END, id);      listData2.insert(END, userName)
            listData3.insert(END, email);   listData4.insert(END, birthYear)
    
        con.close()
    
    if __name__ == "__main__":
        window = Tk()                       # 윈도우 생성
        window.geometry("600x300")          # 화면크기 설정
        window.title("GUI 데이터 입출력")     # 윈도우의 제목 설정
    
        editFrame = Frame(window)           # Frame 컨네이너를 윈도우에 생성함
        # pack()는 기본 값으로 side 매개변수에 TOP 값을 가진다.하여 위로 정렬시키면서
        # Frame 컨테이너 속의 위젯들을 중앙 배치를 해준다.
        editFrame.pack()
        # 조회결과를 출력할 Frame 컨테이너를 윈도우에 생성함.
        listFrame = Frame(window)
        listFrame.pack(side=BOTTOM, fill=BOTH, expand=1)
    
        # 위젯들 배치(editFrame 배치)
        # Entry 위젯에서 width 속성은 픽셀값이 아니고, 텍스트 단위임을 상기하자.
        edit1 = Entry(editFrame, width=10); edit1.pack(side=LEFT, padx=10, pady=10)
        edit2 = Entry(editFrame, width=10); edit2.pack(side=LEFT, padx=10, pady=10)
        edit3 = Entry(editFrame, width=10); edit3.pack(side=LEFT, padx=10, pady=10)
        edit4 = Entry(editFrame, width=10); edit4.pack(side=LEFT, padx=10, pady=10)
    
        # 데이터를 저장하는 버튼이며 insertData()를 이벤트 핸들러로 등록함.
        btnInsert = Button(editFrame, text="저장", command=insertData)
        btnInsert.pack(side=LEFT, padx=10, pady=10)
        # 데이터를 조회하는 버튼이며 selectData()를 이벤트 핸들러로 등록함.
        btnSelect = Button(editFrame, text="조회", command=selectData)
        btnSelect.pack(side=LEFT, padx=10, pady=10)
    
        # listFrame 컨테이너에 ListBox 위젯을 만들어서 레이아웃 배치를 한다.
        # 리스트 박스는 여러 문자열을 위에서 아래로 나열할 때 사용하는 위젯이다.
        listData1 = Listbox(listFrame, bg="yellow")
        # pack()는 기본 값으로 side 매개변수에 LEFT 값을 주어 왼쪽 기준 정렬이 되며,
        # 할당된 공간 양쪽을 다 채우고, 할당되지 않은 미사용 공간을 현재 위젯의 할당된
        # 공간으로 변경한다.
        listData1.pack(side=LEFT, fill=BOTH, expand=1)
        listData2 = Listbox(listFrame, bg="yellow")
        listData2.pack(side=LEFT, fill=BOTH, expand=1)
        listData3 = Listbox(listFrame, bg="yellow")
        listData3.pack(side=LEFT, fill=BOTH, expand=1)
        listData4 = Listbox(listFrame, bg="yellow")
        listData4.pack(side=LEFT, fill=BOTH, expand=1)
    
        window.mainloop()