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.
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.
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.
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.
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
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:
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.
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.
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 =
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.
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
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
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ę.
Istotne jest by posiadać informację jakie są wydatki na poszczególne kategorię. Tak więc do programu dopisano jeszcze trzy funkcję:
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()
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
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()
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:
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.
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:
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ć.