Databaser TDDD80 Mobila och sociala applikationer
Översikt Relationsdatabaser SQL Relationsmodeller Många-till-många relationer Alchemy Enhetstestning Modulär kod designmönstret MVC
Olika typer av databaser Relationsdatabaser (SQL-databaser) Fokus på entiteter och relationer mellan dessa Tabeller Unika rader No-SQL databaser Dokument-orienterade databaser (t.ex. MongoDB) JSON-databaser (t.ex. Firebase)
SQLite Liten SQL-databas som kan lagras på en fil Oftast lokalt på egen dator Används med fördel för utveckling och testning
Relationsdatabaser har tabeller
Schema Bestämmer vilka kolumner som ingår i tabellen Namn Typ av tillåtna värden Integer String Constraints Unik Non-null
Nycklar Varje tabell har Primary key (ID, dvs. primär nyckel) Alltid unik för varje rad Dvs. varje relationstupel kan adresseras entydigt via ID Foreign key Referens till annan tabells primary key
Tabeller (exempel) Foreign key (kopplar till annan tabell) primär nyckel (för denna tabell) primär nyckel (för denna tabell) data saknas (motsv. ingen som har läst detta meddelande)
Data = tupler Data fylls på vid initialisering, genom anrop, etc. Krux Singulära värden i tabellen, dvs. ej listor, etc. Ställer krav på hur data organiseras i tabeller Relationsmodellering
Relationsmodeller ER (Entity-Relationship diagrams )
many one
Modell = data + relationer mellan dessa One-to-one relation Kan läggas i samma tabell Men, kanske konceptuellt tydligare i två tabeller One-to-many Två tabeller, där den ena länkar till den andra via foreign key
Many-to-many associationstabell
SQL Structured Query Language
Structured Query Language (SQL) Microsoft Access Vill ha ut dessa två kolumner SELECT [Firstname], [Lastname] FROM [Employees] WHERE [Lastname] = "Davolio"; Från dennatabell Dettakriterium
Ibland måste tabeller kombineras Ge mig alla avdelningar och dess chef, i hela företaget Två tabeller behöver kombineras! SELECT Employees.[Department], Managers.[Manager] FROM Employees INNER JOIN Managers WHERE Employees.[Department] = Managers.[Department];
Måste använda join När olika delar av en query finns i olika tabeller T.ex. Hitta alla länder i Asien som har arbetslöshet > 3% Geographical location Socioeconomic conditions Country ID Name Continent 1 Japan Asia 2 India Asia 3 USA America Country ID Unemployment rate 3 10% 1 2 4% 2 1 2.6% 4
Join Förutsätter att båda tabeller har kolumn med matchande värden A: target table B: join table 1 1 2 1 3 2 Unik kolumn: primary key
Inner join B 1 D 2 F 3 A B C D E B 1 D 2 A INNER JOIN B behåll bara matchande rader
Left join (outer join) B 1 D 2 F 3 A B C D E A B 1 C D 2 E A LEFT JOIN B behåll alla rader från B
Right join (outer join) B 1 D 2 F 3 A B C D E x z g h p B 1 z D 2 h F 3 A RIGHT JOIN B alla rader från A
Join Läs mer på: https://community.qlik.com/thread/39177
SQLAlchemy
17-01-23 Sätta upp en databas from flask_sqlalchemy import SQLAlchemy db_path = os.path.join(os.path.dirname( file ), 'app.db') db_uri = 'sqlite:///{}'.format(db_path) app.config['sqlalchemy_database_uri'] = config.db_uri app.config['sqlalchemy_track_modifications'] = True db = SQLAlchemy(app)
Flask-SQLAlchemy Configurerar Alchemy, ger db-objekt Tabell: class User(db.Model): tablename = 'users' id = db.column(db.integer, primary_key=true) name = db.column(db.string(5))
17-01-23 SQLAlchemy Object-Relational Mapper (ORM) Du kan definiera klasser och metoder som vanligt SQLAlchemy översätter Klass till tabell Variabler till kolumner Metoder till queries
17-01-23 Exempel (Flask-SQLAlchemy) class User(Base): tablename = 'users' id = Column(Integer, Sequence('user_id_seq'), primary_key=true) name = Column(String(50)) fullname = Column(String(50)) password = Column(String(12)) def repr (self): return "<User(name='%s', fullname='%s', password='%s')>" % ( self.name, self.fullname, self.password)
17-01-23 Flask-SQLAlchemy query class User(db.Model): #... def follow(self, user): if not self.is_following(user): self.followed.append(user) return self def unfollow(self, user): if self.is_following(user): self.followed.remove(user) return self https://blog.miguelgrinberg.com/post/the-flask-megatutorial-part-viii-followers-contacts-and-friends def is_following(self, user): return self.followed.filter(followers.c.followed_id == user.id).count() > 0
17-01-23 SQLAlchemy query (forts) Tillåter vanlig python -syntax Dina egna data-repr i form av klasser och objekt T.ex. message.users.append(user) Bakom ligger databas-tabeller och SQL-queries
Tre lager av query-syntax Flask-SQLAlchemy Konfigurerar Alchemy åt oss, förenklad syntax (query_by, etc.) Alchemy Kan använda Klasser och vanliga metoder istf lågnivå SQL-queries SQL Allt översätts till SQL som loggas om man är i debug-mode
17-01-23 Exempel1 SELECT [lastname] FROM [Employees] WHERE [lastname] = "Davolio"; db.session.query(employees.lastname). filter_by(lastname= Davalio ).all() Employees.query. filter_by(last_name= Davalio ).all()
17-01-23 Exempel2 SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password FROM users WHERE users.name LIKE? ORDER BY users.id ('%ed',) db.session.query(user). filter(user.name.like('%ed')).order_by(user.id)
17-01-23 Many-to-many associationstabell
17-01-23 Many-to-many relationer survey_questions = db.table( 'survey_questions', db.column('survey_id', db.integer, db.foreignkey('surveys.id')), db.column('question_id', db.integer, db.foreignkey('questions.id'))) class Survey(db.Model): tablename = 'surveys' id = db.column(db.integer, primary_key=true) name = db.column(db.string(50), unique=true) ending = db.column(db.datetime) class Question(db.Model): tablename = 'questions' id = db.column(db.integer, primary_key=true) text = db.column(db.text) help = db.column(db.text)
17-01-23 Ytterligare exempel read_by = db.table( 'read_by', db.column('user_id', db.integer, db.foreignkey('users.id'), primary_key=true), db.column('message_id', db.integer, db.foreignkey('messages.id'), primary_key=true))
Associationstabell read_by = db.table('read_by', db.model.metadata, db.column('user_id', db.integer, db.foreignkey('users.id'), primary_key=true), db.column('message_id', db.integer, db.foreignkey('messages.id'), primary_key=true)) class User(db.Message): tablename = messages
17-01-23 Exempel på vanlig tabell class Message(db.Model): tablename = 'messages' id = db.column(db.integer, primary_key=true) mess_id = db.column(db.integer, unique=true) message = db.column(db.string(140)) users = db.relationship('user', secondary=read_by, primaryjoin=(read_by.c.message_id == id), # back_populates='messages', backref=db.backref('messages', lazy='dynamic'), lazy="dynamic")
Many-to-many relationships Läs vidare på: http://www.ergo.io/blog/sqlalchemy-relationshipsfrom-beginner-to-advanced/ http://docs.sqlalchemy.org/en/latest/orm/tutorial.h tml Klicka på navigationsfältet: Building a Many to Many Relationship
17-01-23 Join i Alchemy (exempel) db.session.query(message).join(message.users). filter(user.name == Kalle ).all() Skapar en sammanslagen tmp-tabell, med kolumner från båda tabellerna och de rader som matchar (jfr. outerjoin)
Dynamic queries Om man anger lazy= dynamic i sin tabell-def: Query exekveras inte utan kan byggas på Kan lägga på ytterligare filter på en query Exekveras vid trigger, t.ex. all() Ändringar i databasen måste commit:as
17-01-23 Alchemy session Ändringar i databasen måste commit:as Tex. skapa nytt objekt från User db.session.add(user1) Session.dirty flaggar nu True db.session.commit() Lägg in ändringar i databasen
Statiskt schema dynamiska data
Statiska klasser dynamiskt skapade objekt Java Klasser statiska (finns i koden, filerna) Objekt skapas när ett javaprogram körs igång Skilda världar Kan t.ex. inte komma åt vanliga variabler förrän ett objekt har skapats som är värd för variabeln Detta eftersom olika objekt kan ha olika värden på samma variabel mytriangle = new Triangle().setColor( Red ) histriangle = new Triangle().setColor( Blue )
Modell databas Modell (databas-schema) statisk Anger vilken typ av data som ska repr Databas dynamisk Innehåller data från en speciell körning Data för företag X vs. data för företag Y Data under utveckling och testning!= data för deployment (sjösatt system)
Kod som ska sjösättas All kod Utom log-filer, etc. Datamodell Ej själva databasen Databasen skapas dynamiskt på plats (på den server som ska hantera de riktiga kunderna) Databasen ska inte versionshanteras Om man inte vill ha hårdlödd databas
Enhetstestning
Motsvarar gamla requests-filen med anrop Sätter upp databas med start-data Kör ett antal testfall Varje testfall: sekvens av anrop som sätter scenen T.ex. Nu bör 2 meddelanden ligga i databasen, med första meddelandet läst av Kalle Slutligt anrop som utvärderas i scenen som skapades
Exempel def test_mark_and_retrieve_message(self): payload = {'message': 'hej3'} rv = self.app.post('/messages', data=json.dumps(payload), content_type='application/json') message_id1 = rv.data assert rv.status_code == 200 assert len(message_id1) == 8 rv = self.app.post('/messages/' + message_id1 + '/flag/' + user_name1) assert rv = self.app.get('/messages/unread/' + user_name2) assert b'hej3' in rv.data assert b'hejhej3' in rv.data
Sätta upp/ta ner tmp-databas def setup(self): self.db_fd, app.config['database'] = tempfile.mkstemp() app.config['testing'] = True self.app = app.test_client() with app.app_context(): data.initialize_db() Anrop till egen databashanteringsmodul def teardown(self): os.close(self.db_fd) os.unlink(app.config['database'])
MVC (Model-View-Controler)
MVC Databashanteringskod Serverkod (views)
Modulär kod Bra om kod kan återanvändas T.ex. db-anrop direkt från unit_test Bra om db och views kan bytas oberoende av varandra, dvs. plug-n-play Lägg @app.route metoderna i server.py eller views.py Lägg databas-hantering i egen modul
Cirkulära referenser Problem att vyn är beroende av data och vice versa (gäller vid initiering) Läs mer på: http://flask.pocoo.org/docs/0.10/patterns/packa ges/
17-01-24 Modulär kodstruktur init.py from lab2 import config app = Flask( name ) # app.config import lab2.server server.py from lab2 import app... from lab2 import data data.initialize_db() data.py from lab2 import app db = SQLAlchemy(app) run_server.py from lab2 import app, config app.run(port=config.port)
rita.kovordanyi@liu.se www.liu.se