MySQL

W tej części przedstawie projekty związane z bazą danych mysql. Pierwszym z nich będzie baza danych pomagająca przy nadzorowaniu wydatków domowych.

Dlaczego MySQL

MySQL jest prawdopodobnie najpopularniejszą bazą danych na świecie gdyż wchodzi w skład tgz. LAMP, jest więc rozwiązanie sprawdzonym i pewnym. Ponad to koszty są zerowe, gdyż rozwiązanie jest za darmo. Zaletą jest również pewność, iż rozwiązanie to będzie na rynku jeszcze przez wiele lat - co nie jest takie pewne w przypadku komercyjnych baz danych.

MySQL przez telefon komórkowy

By dobrze zarządzać wydatkami konieczne jest posiadanie możliwości dopisania wydatku w dowolnym miejscu. Zapis na kartkę, a później dopisanie do bazy na komputerze nie jest rozwiązaniem zbyt wygodnym. Lepszym rozwiązaniem jest skorzystanie z podręcznego komputera, czyli komórki. Współcześnie to one powinny być nazywane "personal computer", tym bardziej, iż są to z reguły jednostki na na procesorze ARM.

Tu przedstawie projekt mojego autorstwa który ma umożliwić takie połączenie z bazą danych by dane zapisywane w komórce dostępne były z komputera klasy pc. Nie jest więc powiedziane, iż musi być to program na komórce - co podnosi koszty i uzależnia nas od danego systemu w niej zastosowanego. Są w tej chwili na synku jedynie dwa systemy: Android i iPhone OS - oczywiście są też jeszcze niszowe rozwiązania. Czas tworzenia aplikacji dla komórki jest nadal czasochłonne, tak więc wygodniej jest zastosowanie strony internetowej tak skonstruowanej by wygodnie było z niej korzystać na tych urządzeniach. Skraca to nie tylko czas i upraszcza cały projekt, a jak wiadomo zawsze najlepszym rozwiązaniem jest to które jest najprostsze. Tak więc wystarczy założyć stronę internetową z php i połączeniem z bazą danych MySQL.

Konfiguracja bazy

Ważne jest by baza danych obsługiwała polskie znaki, jednym z rozwiązań jest zastosowanie ISO8859-2(tgz. latin2). Jednak lepiej jest jednak zastosować UTF-8.

W ubuntu 12 część ustawień posiada Latin1. Tak więc konieczne, kierując się instrukcjami ze strony https://bugs.launchpad.net/ubuntu/+source/mysql-5.5/+bug/958120, należy dopisać opcję w pliku my.cnf (w Ubutnu /etc/mysql/my.cnf):

default-character-set=utf8

By uniknąć problemów przy aktualizacji można również zastosować metodę opisaną na stronie http://blog.lesc.se/2011/06/switch-to-utf-8-charset-in-mysql-on.html. Z tym, że "default-character-set=utf8" i "default-character-set=utf8" aktualnie zastąpiło "default-character-set=utf8".

Teraz należy zrestartować bazę danych.

#/etc/init.d/mysql restart

Projekt bazy danych - dla wydatków

Pierwszą rzeczą, zanim zacznie się tworzyć bazę danych, to koniecznie trzeba ją zaprojektować.

wydatki

wydatki_id

kategoria_wydatku --->

data

wartosc_wydatku

opis

kategoria_wydatkow

kategoria_wydatkow_id

nazwa

Założyłem następujące typy zmiennych:

  • wydatki_id - int unsigned auto_increment
  • kategoria_wydatku - tinyint unsigned
  • data - date
  • wartosc_wydatku - float unsigned
  • opis - tinytext
  • kategoria_wydatkow_id - tinyint unsigned
  • nazwa - varchar(20)

Tworzenie bazy

Najpierw logowanie do bazy

mysql -u [login] -p

Opcją u podajemy użytkownika, a następnie -p każemy zapytać o hasło.

Tworzymy bazę danych:

CREATE DATABASE moje_wydatki;

Przechodzę do utworzonej bazy danych

USE moje_wydatki;

Tworze tablę, na razie z jedną kolumną, o nazwie wydatki. Tabela zawiera kolumnę wydatki_id z liczbami całkowitymi (INT), tylko dodatnimi (UNSIGNED), nigdy nie mogącą być pustą (NOT NULL) i automatycznie zwiększanym numerem (AUTO_INCREMENT). Ważne żeby w tabeli przynajmniej jedna kolumna była inkrementalna (przyrostowa), gdyż po niej się numeruje.

CREATE TABLE wydatki (wydatki_id INT UNSIGNED NOT NULL AUTO_INCREMENT, PRIMARY KEY (wydatki_id));

Do tablicy dołączam kolejną zmienną, analogicznie następne. Można zmienić kolejność kolumn używając opcji AFTER.

ALTER TABLE wydatki ADD COLUMN (kategoria_wydatku TINYINT UNSIGNED NOT NULL);

Wynik końcowy można zobaczyć po podaniu komendy:

SHOW COLUMNS FROM wydatki;

Ukarze się wywczas tabela:

+-------------------+---------------------+------+-----+---------+----------------+
| Field             | Type                | Null | Key | Default | Extra          |
+-------------------+---------------------+------+-----+---------+----------------+
| wydatki_id        | int(10) unsigned    | NO   | PRI | NULL    | auto_increment |
| kategoria_wydatku | tinyint(3) unsigned | NO   |     | NULL    |                |
| data              | date                | NO   |     | NULL    |                |
| wartosc_wydatku   | float unsigned      | NO   |     | NULL    |                |
| opis              | tinytext            | YES  |     | NULL    |                |
+-------------------+---------------------+------+-----+---------+----------------+

po stworzeniu drugiej tabeli, można zobaczyć:

+------------------------+
| Tables_in_moje_wydatki |
+------------------------+
| kategoria_wydatkow     |
| wydatki                |
+------------------------+

Baza danych jest już gotowa do zapełnienia.

Program do zapisu danych

Bazę danych można oczywiście zapełnić używając polecenia INSERT, ale jest to dość niewygodna metoda. Wygodniej jest stworzyć program który będzie odpytywał o poszczególne wartości i je zapisze w bazie. W tym celu w tym miejscu wstawię kod programu, który w trybie tekstowym potrafi to wykonać. Program w trybie tekstowym jest łatwiej przeanalizować i sprawdzić działania, a później wykonać do niego interfejs graficzny. Poniższy kod jest napisany w języku python.

Jak wygląda program podczas działania

Najpierw trzeba uruchomić program ("$ ./dodawanie_wydatku.py"). Program wyświetla wszystkie kategorie wydatków. Kategorie bardzo łatwo poprać z serwera i je później wypisać. W wersji z trybem graficznym będzie to zastąpione listą wyboru, gdzie wydatek będzie wybierany myszką. Program następnie pyta o: kategoria wydatku, koszt i opis.

$ ./dodawanie_wydatku.py

1 - jedzenie
2 - prąd
3 - gaz
4 - mieszkanie-fundusz
5 - mieszkanie-zaliczka
6 - telefon
7 - internet
8 - szkolenia

kategoria wydatku = 6
koszt = 30
opis =        

Jak działa program

Program składa się z dwóch funkcji: zbieranie_danych() i zapis_do_bazy(dane). W przypadku wersji graficznej zmieniona zostanie jedynie funkcja zbieranie_danych()

Funkcja zbieranie_danych() wywołano za pomocą "dane = zbieranie_danych()", dane które zwróci trafią więc do listy. Sama funkcja używa tylko "print" i "raw_input" by na standardowym wyściu wyświetlić informację. Dodatkowo "raw_input" pobiera również dane.

W funkcja "zapis_do_bazy(dane)" przy wywołaniu dostaje listę "dane". Następnie otwiera ona połączenia z bazą tanych (tu lokalną, ale może to być serwer zewnętrzny):

DB = MySQLdb.connect(user='nazwa_urzytkownika', passwd="hasło", charset="utf8")

Zamiast nazwa_urzytkownika i hasło powinny zostać wpisane dane w zależności od stworzonych użytkowników w bazie. Najlepiej też by hasło nie było wpisane na stałe, tylko program o nie pytał. Ja tworzę ten program na komputerze domowym, więc pozwoliłem sobie do testów wpisać hasło na stałe.

Ważna jest tu opcja charset="utf8" - definiuje ona kodowanie. Bez tego polskie znaki, np. w opisie, zamieniły by się nie do poznania.

Program komunikuje się z bazą danych dzięki tgz. kursorowi. Wówczas w nawiasach podaje się polecenia samej bazy: Np. zapytanie o czas.

DBcursor.execute("SELECT CURDATE()")

Odpowiedzieć zapisuje się w zmiennej date.

date = DBcursor.fetchall()

Przy zapisie do bazy polecenie najpierw jest zapisywane w zmiennej tekstowej "command", a następnie wysyłanie na serwer:

DBcursor.execute(command)

Istotne jest by po wykonanym zadaniu baza danych została zamknięta

Program został przetestowany i jest gotowy by dopisać do niego interfejs graficzny w bibliotece QT4.

Kontrola wersji

Ważne jest by na każdym etapie pracy archiwizować uzyskane efekty. W tym przypadku stosuję system kontroli wersji "git"

W pierwszej kolejności, wydaje się polecenie

$git init

Dodaje się pliki z rozszeżeniem "py".

$git add *.py

Następnie stosuje się opcję commit. Później by nie powtarzać polecenia z "add", wygonie jest użyć obcji -a, by załączył pliki te same co ostatnim razem.

$git commit -a

Przepisanie kodu na wersje obiektową

Skoro program działa poprawnie, wygodnie jest stworzenie odpowiedniej klasy. Będzie to istotne w dalszym rozwoju programu. Tak więc podzielono program na dwa pliki:

dodawanie_wydatku.py

baza_danych.py

Generowanie raportów

W tym celu w klasie "Baza_danych" dopisano cztery funkcje. Pierwsza z nich "polaczenie_z_baza_i_zapytanie_o_date", jak nazwa wskazuje łączy się z bazą danych i zapisuje aktualną datę. Następne zapisują w zmiennej "self.command" dane polecenie i uruchamiają "wyslanie_zapytania_o_sume_zamkniecie_bazy()". Uzyskany wynik jest wyciągany z tabeli i zaokrąglany do drugiego miejsca po przecinku. Funkcja "wszystkie_wydatki" jest wyjątkowa pod tym względem, iż nie pyta o datę.

Generowanie raportów w zależności od kategorii

Istotne jest by posiadać informację jakie są wydatki na poszczególne kategorię. Tak więc do programu dopisano jeszcze trzy funkcję:

  • suma_wydatkow_z_aktualnego_miesiaca_na_dana_kategorie(self, nazwa_kategorii)
  • suma_wydatkow_z_aktualnego_roku_na_dana_kategorie(self, nazwa_kategorii)
  • suma_wydatkow_na_dana_kategorie(self, nazwa_kategorii)

Dla zapytania o sumę kwot z miesiąca dla kategorii "jedzenie" zapytanie wyglądało by jak poniżej:

SELECT SUM(wartosc_wydatku) FROM wydatki, kategoria_wydatkow WHERE 
(kategoria_wydatku = kategoria_wydatkow_id) AND (nazwa='jedzenie') AND (data>'2013-05-01');

Kod więc umieszczanu w zmiennej "self.commit" wygląda następująco:

self.command = "SELECT SUM(wartosc_wydatku) FROM wydatki, 
	kategoria_wydatkow WHERE (kategoria_wydatku = kategoria_wydatkow_id) AND "
	
self.command += "(nazwa='%s') AND " % nazwa_kategorii

self.command += "(data > '%s-%s-01')" % (self.aktualna_data_w_liscie[0], 
	self.aktualna_data_w_liscie[1])

Program zawiera już wszystkie potrzebne elementy, zarówno dopisywanie danych jak i raporty.

Można oczywiście dopisać część która będzie się pytać czy wyświetlić raport czy dopisać wydatek. Tu warto zwrócić uwagę na krotkę decyzja_dopisanie_wydatku. Posiada ona wartości do których będzie porównywał zapytanie "if": "if decyzja.lower() in decyzja_dopisanie_wydatku:". Dodatkowo znaki w zmiennej decyzja zostały zamienione na małe (".lower()"). W następnej wersji już mogę tworzyć interfejs graficzny.

decyzja = raw_input("raport czy dopisanie wydatku [r/w] = ")

decyzja_dopisanie_wydatku = ('w', 'wydatek', 'dopisz')
decyzja_raport = ('r', 'raport')

baza = baza_danych.Baza_danych()

if decyzja.lower() in decyzja_dopisanie_wydatku:
	# zebranie danych do wysłana
	dane = zbieranie_danych()

	# wysłanie danych
	baza.zapis_do_bazy(dane)

elif decyzja.lower() in decyzja_raport:
	print "wydatki w tym miesiącu", baza.suma_wydatkow_z_aktualnego_miesiaca()
	print "wydatki w tym roku", baza.suma_wydatkow_z_aktualnego_roku()


kod wersji z interfejsem tekstowym

Program w wersji z interfejsem graficznym

W programie napisałem dwie klasy. Pierwsza klasa to "class Baza_danych()", i dzięki niej powstaje obiekt "baza". Obiekt "baza" tylko wysyła i odbiera informację, wszelkie obliczenia wykonuje MySQL. Drugą klasą jet "class WprowadzanieDanej(QtGui.QWidget)" odpowiedzialna za główne okno i to co się w nim znajduję.

Widok części zapisującej dane

Widok części raportującej


kod wersji z interfejsem graficznym

Wykresy

Przy każdej bazie danych ważne są raporty z wykresami. W tym celu najlepiej zastosować gotową biblioteką np. matplotlib. Kod tworzący wykres już zsumowanych wydatków na każdy dzień przedstawiam poniżej (sam wykres to trzy linijki kodu):

def wykres_wydatkow_w_tym_miesiacu(self):
		
		dane = baza.wydatki_w_tym_miesiacu()
		
		plt.plot(dane[0], dane[1])
		plt.ylabel('some numbers')
		plt.show()

Metode którą wywołuje by odebrać dane i je posegregować jest: baza.wydatki_w_tym_miesiacu():

def wydatki_w_tym_miesiacu(self):
		
		self.polaczenie_z_baza_i_zapytanie_o_date()
			
		self.command = "SELECT data, wartosc_wydatku FROM wydatki WHERE data > '%s-%s-01'" % (self.aktualna_data_w_liscie[0], self.aktualna_data_w_liscie[1])
				
		self.DBcursor.execute(self.command)
		
		wynik_zapytania = self.DBcursor.fetchall()
		
		koszt = []
		dzien = []
		
		for i in wynik_zapytania:
			koszt.append(i[1])
			
		for i in wynik_zapytania:
			dzien.append(int(str(i[0]).split("-")[2]))
	
		dzien_set = list(set(dzien))
		
		koszt_set = []
		for k in dzien_set:
			koszt_set.append(0)
		
		for i in range(len(dzien)):
			for k in range(len(dzien_set)):
				if dzien[i] == dzien_set[k]:
					koszt_set[k] += koszt[i]
					break;
			
		dane = [dzien_set, koszt_set]
			
				
		return(dane)

Poniżej widać efekt wywołania "wykres_wydatkow_w_tym_miesiacu()" - zsumowane wydaki na każdy dzień, posegregowane i wyświetlone na wykresie. Odrazu w tym miejszu jest możliwość zapisania wykresu w różnych formatach plików.

Za co odpowiedzialny jest kod:

plt.bar(dane[0], dane[1], color='r')
plt.show()

Zewnętrzny serwer

Do tej pory program pracował na lokalnej bazie danych. Ale nic nie szkodzi by skorzystać z zewnętrznego serwera. Założyłem więc bezpłatne konto serwera z bazą MySQl i stworzyłem na niej odpowiednie tabele. Konieczne było zmienienie w programie dwóch linijek. Dopisana została opcja "host" i zmienna self.host - z podanym adresem. Oczywiście inny jest też login, hasło i database - ale jest to tylko zmiana wartości w zmiennej.

self.DB = MySQLdb.connect(host=self.host, user=self.user, passwd=self.passwd, charset=self.charset)

Oczywiście w danej bazie są ustalone zasady co do nazw DATABASES - tak więc opcja też została zmieniona.

Pozostały tylko dwie kwestie:

  • Program powinien pytać o hasło, nie powinno ono być wpisane na stałe,
  • Odpowiednia strona by można było wpisywać dane przez komórkę

backap bazy danych

Oprócz samego istnienia bazy danych nie ma nic ważniejszego niż jej backap co jakiś czas. Konkretną DATABASE z MySql można bardzo łatwo przenieść do pliku:

mysqldump -u user -p --opt nazwa_bazy > nazwa_pliku_backup.sql

Lub przywrócić (tu bardzo ostrożnie):

mysql -u user -p  -h localhost nazwa_bazy < nazwa_pliku_backup.sql

Oczywiście możliwy jest backap jednej tabli lub wszystkich danych użytkownika.

Zapis do bazy danych za pomocą komórki

Czas stworzyć połączenie między telefonem komórkowym a bazą danych. Najprostszym rozwiązaniem jest zastosowanie strony internetowej, wówczas zapis do bazy będzie z każdego miejsca na świecie. Dodatkowym atutem jest prostota tego rozwiązania, a co za tym idzie i niezawodność. Ponadto czas wykonania jest najkrótszy. Poniżej przedstawiono jak wygląda najprostsza forma zapisu do bazy:

Kategoria:
data:
kwota:
opis:

Kod wymaga poprawek funkcjonalności, a sama strona poprawek wizualnych, ale kod już działa i został przetestowany. Oczywiście musi zostać wprowadzony wybór kategorii - na pewno nie może być to zapisane liczbowo. Umieściłem go na stronie i wykonałem za jego pomocą dodatkowe wpisy do bazy. Następnie już na komputerze PC wykonałem odbiór danych i raporty. Aktualnie zapis do bazy został wyłączony. Musi zostać dopisana część dotycząca hasła, inaczej każdy mógłby coś dopisać.