Χρήση Βάσεων Δεδομένων και SQL

Τι είναι μια βάση δεδομένων;

Μια βάση δεδομένων είναι ένα αρχείο που είναι οργανωμένο για την αποθήκευση δεδομένων. Οι περισσότερες βάσεις δεδομένων είναι οργανωμένες σαν λεξικό, με την έννοια ότι υπάρχει αντιστοίχιση ανάμεσα σε κλειδιά και τιμές. Η μεγαλύτερη διαφορά είναι ότι η βάση δεδομένων βρίσκεται στο δίσκο (ή σε άλλη μονάδα μόνιμης αποθήκευσης), επομένως παραμένει και μετά το τέλος του προγράμματος. Επειδή μια βάση δεδομένων αποθηκεύεται σε μονάδα μόνιμης αποθήκευσης, μπορεί να αποθηκεύσει πολύ περισσότερα δεδομένα από ένα λεξικό, το οποίο περιορίζεται στο μέγεθος της μνήμης στον υπολογιστή.

Όπως ένα λεξικό, το λογισμικό βάσης δεδομένων έχει σχεδιαστεί για να παρέχει πολύ γρήγορη εισαγωγή και πρόσβαση στα δεδομένα, ακόμη και για μεγάλες ποσότητες δεδομένων. Το λογισμικό βάσης δεδομένων διατηρεί την απόδοσή του δημιουργώντας ευρετήρια, καθώς τα δεδομένα προστίθενται στη βάση δεδομένων, για να επιτρέψουν στον υπολογιστή την γρήγορη προσπέλαση μιας συγκεκριμένης καταχώρησης.

Υπάρχουν πολλά διαφορετικά συστήματα βάσεων δεδομένων, που χρησιμοποιούνται για μια μεγάλη ποικιλία σκοπών, όπως: Oracle, MySQL, Microsoft SQL Server, PostgreSQL και SQLite. Εστιάζουμε στο SQLite, σε αυτό το βιβλίο, επειδή είναι μια πολύ κοινή βάση δεδομένων και είναι ήδη ενσωματωμένη στην Python. Το SQLite έχει σχεδιαστεί για να ενσωματώνεται σε άλλες εφαρμογές και να παρέχει υποστήριξη βάσης δεδομένων εντός της εφαρμογής. Για παράδειγμα, το πρόγραμμα περιήγησης Firefox χρησιμοποιεί, κι αυτό, τη βάση δεδομένων SQLite, εσωτερικά, όπως και πολλά άλλα προϊόντα.

http://sqlite.org/

Το SQLite είναι κατάλληλο για ορισμένα από τα προβλήματα χειρισμού δεδομένων, που βλέπουμε στην Πληροφορική, όπως η εφαρμογή ιστοσυγκομιδής του Twitter, που περιγράφουμε σε αυτό το κεφάλαιο.

Έννοιες βάσης δεδομένων

Όταν κοιτάζετε για πρώτη φορά μια βάση δεδομένων, μοιάζει με υπολογιστικό φύλλο με πολλά φύλλα. Οι κύριες δομές δεδομένων σε μια βάση δεδομένων είναι: πίνακες, γραμμές και στήλες.

Σχεσιακές βάσεις δεδομένων
Σχεσιακές βάσεις δεδομένων

Στις τεχνικές περιγραφές των σχεσιακών βάσεων δεδομένων, οι έννοιες του πίνακα, της γραμμής και της στήλης αναφέρονται και ως σχέση, εγγραφή και χαρακτηριστικό, αντίστοιχα. Θα χρησιμοποιήσουμε τους λιγότερο επίσημους όρους σε αυτό το κεφάλαιο.

Πρόγραμμα περιήγησης βάσεων δεδομένων για SQLite

Ενώ αυτό το κεφάλαιο θα επικεντρωθεί στη χρήση της Python για εργασία με δεδομένα σε αρχεία βάσης δεδομένων SQLite, πολλές λειτουργίες μπορούν να γίνουν πιο βολικά χρησιμοποιώντας το λογισμικό που ονομάζεται Database Browser for SQLite (Πρόγραμμα περιήγησης βάσης δεδομένων για SQLite) το οποίο διατίθεται δωρεάν από:

http://sqlitebrowser.org/

Χρησιμοποιώντας το πρόγραμμα περιήγησης μπορείτε εύκολα να δημιουργήσετε πίνακες, να εισαγάγετε δεδομένα, να επεξεργαστείτε δεδομένα ή να εκτελέσετε απλά ερωτήματα SQL στα δεδομένα της βάσης δεδομένων.

Κατά μία έννοια, το πρόγραμμα περιήγησης της βάσης δεδομένων είναι παρόμοιο με ένα πρόγραμμα επεξεργασίας κειμένου, όταν εργάζεστε με αρχεία κειμένου. Όταν θέλετε να κάνετε μία ή πολύ λίγες επεξεργασίες σε ένα αρχείο κειμένου, μπορείτε απλώς να το ανοίξετε σε ένα πρόγραμμα επεξεργασίας κειμένου και να κάνετε τις αλλαγές που θέλετε. Όταν έχετε πολλές αλλαγές που πρέπει να κάνετε σε ένα αρχείο κειμένου, συχνά γράφετε ένα απλό πρόγραμμα Python. Θα εντοπίσετε την ίδια λογική, όταν εργάζεστε με βάσεις δεδομένων. Θα υλοποιείται απλές λειτουργίες στον διαχειριστή βάσης δεδομένων και πιο σύνθετες λειτουργίες θα γίνονται πιο βολικά με την Python.

Δημιουργία πίνακα βάσης δεδομένων

Οι βάσεις δεδομένων απαιτούν πιο καθορισμένη δομή από τις λίστες ή τα λεξικά Python1.

Όταν δημιουργούμε έναν πίνακα βάσης δεδομένων, πρέπει να πούμε εκ των προτέρων στη βάση δεδομένων τα ονόματα καθεμιάς από τις στήλες στον πίνακα και τον τύπο των δεδομένων που σκοπεύουμε να αποθηκεύσουμε σε κάθε στήλη. Όταν το λογισμικό της βάσης δεδομένων γνωρίζει τον τύπο των δεδομένων σε κάθε στήλη, μπορεί να επιλέξει τον πιο αποτελεσματικό τρόπο αποθήκευσης και αναζήτησης των δεδομένων, με βάση τον τύπο των δεδομένων.

Μπορείτε να δείτε τους διάφορους τύπους δεδομένων που υποστηρίζονται από το SQLite στην ακόλουθη διεύθυνση url:

http://www.sqlite.org/datatypes.html

Ο καθορισμός της δομής για τα δεδομένα σας εκ των προτέρων μπορεί να φαίνεται άβολος στην αρχή, αλλά το αποτέλεσμα είναι η γρήγορη πρόσβαση στα δεδομένα σας, ακόμα και όταν η βάση δεδομένων περιέχει μεγάλο όγκο δεδομένων.

Ο κώδικας για τη δημιουργία ενός αρχείου βάσης δεδομένων και ενός πίνακα με το όνομα Tracks με δύο στήλες στη βάση δεδομένων είναι ο εξής:

import sqlite3

conn = sqlite3.connect('music.sqlite')
cur = conn.cursor()

cur.execute('DROP TABLE IF EXISTS Tracks')
cur.execute('CREATE TABLE Tracks (title TEXT, plays INTEGER)')

conn.close()

# Code: http://www.py4e.com/code3/db1.py

Η λειτουργία connect πραγματοποιεί μια “σύνδεση” με τη βάση δεδομένων, που είναι αποθηκευμένη στο αρχείο music.sqlite, στον τρέχοντα κατάλογο. Εάν το αρχείο δεν υπάρχει, θα δημιουργηθεί. Ο λόγος που αυτό ονομάζεται “σύνδεση” είναι ότι μερικές φορές η βάση δεδομένων αποθηκεύεται σε έναν ξεχωριστό “διακομιστή βάσης δεδομένων”, από τον διακομιστή στον οποίο εκτελούμε την εφαρμογή μας. Στα απλά παραδείγματά μας η βάση δεδομένων θα είναι απλώς ένα τοπικό αρχείο στον ίδιο κατάλογο με τον κώδικα Python που εκτελούμε.

Ένας κέρσορας (cursor) είναι σαν ένας περιγραφέας αρχείου, που μπορούμε να χρησιμοποιήσουμε για να εκτελέσουμε λειτουργίες επί των δεδομένων, που είναι αποθηκευμένα στη βάση δεδομένων. Η κλήση του cursor() μοιάζει πολύ εννοιολογικά με την κλήση του open(), όταν πρόκειται για αρχεία κειμένου.

Ένας Κέρσορας Βάσης Δεδομένων
Ένας Κέρσορας Βάσης Δεδομένων

Μόλις έχουμε τον κέρσορα, μπορούμε να αρχίσουμε να εκτελούμε εντολές προς τα περιεχόμενα της βάσης δεδομένων χρησιμοποιώντας τη μέθοδο execute().

Οι εντολές βάσης δεδομένων εκφράζονται σε μια ειδική γλώσσα, που έχει τυποποιηθεί σε πολλούς διαφορετικούς προμηθευτές βάσεων δεδομένων για να μας δώσουν τη συνατότηα να μάθουμε μια ενιαία γλώσσα βάσης δεδομένων. Η γλώσσα των βάσεων δεδομένων ονομάζεται Γλώσσα Δομημένων Ερωταπαντήσεων - Structured Query Language ή SQL για συντομία.

http://en.wikipedia.org/wiki/SQL

Στο παράδειγμά μας, εκτελούμε δύο εντολές SQL στη βάση δεδομένων μας. Κατά σύμβαση, θα γράφουμε τις δεσμευμένες λέξεις της SQL με κεφαλαία γράμματα και τα υπόλοιπα μέρη της εντολής, που προσθέτουμε (όπως τα ονόματα του πίνακα και των στηλών) θα εμφανίζονται με πεζά.

Η πρώτη εντολή SQL διαγράφει τον πίνακα Tracks από τη βάση δεδομένων, εάν υπάρχει. Αυτό το μοτίβο χρησιμοποιείται απλώς για να μας επιτρέψει να εκτελέσουμε το ίδιο πρόγραμμα και να δημιουργήσουμε ξανά και ξανά τον πίνακα Tracks χωρίς να προκληθεί σφάλμα. Σημειώστε ότι η εντολή DROP TABLE διαγράφει τον πίνακα και όλα τα περιεχόμενά του από τη βάση δεδομένων (δηλαδή, δεν υπάρχει “αναίρεση”).

cur.execute('DROP TABLE IF EXISTS Tracks ')

Η δεύτερη εντολή δημιουργεί έναν πίνακα με όνομα Tracks, με μια στήλη κειμένου, που ονομάζεται title και μια στήλη ακεραίων, με το όνομα plays.

cur.execute('CREATE TABLE Tracks (title TEXT, plays INTEGER)')

Τώρα που δημιουργήσαμε έναν πίνακα με το όνομα Tracks, μπορούμε να εισάγουμε κάποια δεδομένα σε αυτόν τον πίνακα, χρησιμοποιώντας την εντολή SQL INSERT. Και πάλι, ξεκινάμε κάνοντας μια σύνδεση με τη βάση δεδομένων και αποκτώντας τον cursor. Στη συνέχεια, μπορούμε να εκτελέσουμε εντολές SQL, χρησιμοποιώντας τον κέρσορα.

Η εντολή SQL INSERT δηλώνει ποιον πίνακα θα χρησιμοποιήσουμε και στη συνέχεια ορίζει μια νέα σειρά, παραθέτοντας τα πεδία που θέλουμε να συμπεριλάβουμε (title, plays), ακολουθούμενα από το VALUES και τις τιμές, που θέλουμε να τοποθετηθούν στη νέα σειρά. Καθορίζουμε τις τιμές ως ερωτηματικά (?, ?), για να υποδείξουμε ότι οι πραγματικές τιμές μεταβιβάζονται ως η πλειάδα ( 'My Way', 15 ) και μάλιστα, ως η δεύτερη παράμετρος στην κλήση της execute().

import sqlite3

conn = sqlite3.connect('music.sqlite')
cur = conn.cursor()

cur.execute('INSERT INTO Tracks (title, plays) VALUES (?, ?)',
    ('Thunderstruck', 20))
cur.execute('INSERT INTO Tracks (title, plays) VALUES (?, ?)',
    ('My Way', 15))
conn.commit()

print('Tracks:')
cur.execute('SELECT title, plays FROM Tracks')
for row in cur:
     print(row)

cur.execute('DELETE FROM Tracks WHERE plays < 100')
conn.commit()

cur.close()

# Code: http://www.py4e.com/code3/db2.py

Πρώτα, με το INSERT εισαγάγουμε δύο γραμμές στον πίνακά μας και χρησιμοποιούμε την commit() για να αναγκάσουμε τα δεδομένα να εγγραφούν στο αρχείο της βάσης δεδομένων.

Γραμμές σε έναν Πίνακα
Γραμμές σε έναν Πίνακα

Στη συνέχεια, χρησιμοποιούμε την εντολή SELECT, για να ανακτήσουμε τις γραμμές που μόλις εισαγάγαμε από τον πίνακα. Στην εντολή SELECT, δηλώνουμε ποιες στήλες θέλουμε (title, plays) και δηλώνουμε επίσης από ποιον πίνακα θέλουμε να ανακτήσουμε τα δεδομένα. Αφού εκτελέσουμε την πρόταση SELECT, ο κέρσορας μπορεί να χρησιμοποιηθεί στο βρόχο for για να διατρέξουμε τις γραμμές που ανέκτησε η SELECT. Για λόγους αποτελεσματικότητας, ο κέρσορας δεν διαβάζει όλα τα δεδομένα από τη βάση δεδομένων όταν εκτελούμε την εντολή SELECT. Αντίθετα, τα δεδομένα διαβάζονται κατ’ απαίτηση καθώς κάνουμε τις απαιτούμε μία μία, με το βρόχοfor.

Η έξοδος του προγράμματος είναι η εξής:

Tracks:
('Thunderstruck', 20)
('My Way', 15)

Ο βρόχος for βρίσκει δύο γραμμές και κάθε γραμμή είναι μια πλειάδα Python με την πρώτη τιμή ως title και τη δεύτερη τιμή ως τον αριθμό των plays.

Σημείωση: Μπορεί να δείτε συμβολοσειρές που ξεκινούν με u' σε άλλα βιβλία ή στο Διαδίκτυο. Αυτό ήταν μια ένδειξη στην Python 2 ότι οι συγκεκριμένες συμβολοσειρές είναι συμβολοσειρές Unicode, που μπορούν να αποθηκεύουν σύνολα μη Λατινικών χαρακτήρων. Στην Python 3, όλες οι συμβολοσειρές είναι από προεπιλογή συμβολοσειρές unicode.

Στο τέλος του προγράμματος, εκτελούμε μια εντολή SQL DELETE για διαγραφή των γραμμών που μόλις δημιουργήσαμε, ώστε να μπορούμε να τρέχουμε ξανά και ξανά το ίδιο πρόγραμμα. Η εντολή DELETE χρησιμοποιεί τον όρο WHERE, που μας επιτρέπει να εφαρμόσουμε ένα κριτήριο επιλογής, ώστε να μπορούμε να ζητήσουμε από τη βάση δεδομένων να εφαρμόσει την εντολή μόνο στις γραμμές που ταιριάζουν με το κριτήριο. Σε αυτό το παράδειγμα το κριτήριο συμβαίνει να ισχύει για όλες τις γραμμές, έτσι αδειάζουμε τον πίνακα, ώστε να μπορούμε να τρέξουμε το πρόγραμμα επανειλημμένα. Αφού εκτελεστεί η DELETE, καλούμε επίσης την commit(), για να κατοχυρώσουμε τις αλλαγές (διαγραφές) στη βάση δεδομένων.

Σύνοψη γλώσσας δομημένων ερωταπαντήσεων

Μέχρι στιγμής, χρησιμοποιούσαμε τη γλώσσα δομημένων ερωταπαντήσεων μέσα στα παραδείγματα Python και έχουμε καλύψει πολλά από τα βασικά των εντολών SQL. Σε αυτήν την ενότητα, εξετάζουμε συγκεκριμένα τη γλώσσα SQL και δίνουμε μια επισκόπηση της σύνταξης SQL.

Δεδομένου ότι υπάρχουν τόσοι πολλοί διαφορετικοί προμηθευτές βάσεων δεδομένων, η γλώσσα δομημένων ερωταπαντήσεων (SQL) τυποποιήθηκε, ώστε να μπορούμε να επικοινωνούμε, με φορητό τρόπο, με συστήματα βάσεων δεδομένων από διαφορετικούς προμηθευτές.

Μια σχεσιακή βάση δεδομένων αποτελείται από πίνακες, γραμμές και στήλες. Οι στήλες έχουν γενικά έναν τύπο, όπως δεδομένα κειμένου, αριθμών ή ημερομηνίας. Όταν δημιουργούμε έναν πίνακα, δηλώνουμε τα ονόματα και τους τύπους των στηλών:

CREATE TABLE Tracks (title TEXT, plays INTEGER)

Για να εισάγουμε μια γραμή σε έναν πίνακα, χρησιμοποιούμε την εντολή SQL INSERT:

INSERT INTO Tracks (title, plays) VALUES ('My Way', 15)

Η εντολή INSERT καθορίζει το όνομα του πίνακα, στη συνέχεια μια λίστα με τα πεδία/στήλες που θέλετε να καταχωρήσετε στη νέα γραμμή και, στη συνέχεια, τη δεσμευμένη λέξη VALUES και μια λίστα με τις αντίστοιχες τιμές, για κάθε ένα από τα πεδία αυτά.

Η εντολή SQL SELECT χρησιμοποιείται για την ανάκτηση γραμμών και στηλών από μια βάση δεδομένων. Η εντολή SELECT σάς επιτρέπει να καθορίσετε ποιες στήλες θέλετε να ανακτήσετε όπως επίσης, η δήλωση WHERE σάς επιτρέπει να επιλέξετε ποιες γραμμές θέλετε να επιστραφούν. Μπορεί επίσης να περιέχει έναν προαιρετικό όρο ORDER BY για την ταξινόμηση των επιστρεφόμενων γραμμών.

SELECT * FROM Tracks WHERE title = 'My Way'

Η χρήση του * δηλώνει ότι θέλετε η βάση δεδομένων να επιστρέψει όλες τις στήλες, για κάθε γραμμή που ταιριάζει με την πρόταση WHERE.

Σημειώστε, σε αντίθεση με την Python, σε μια πρόταση SQL WHERE χρησιμοποιούμε ένα μόνο σύμβολο ίσου για να υποδείξουμε μια σύγκριση ισότητας, αντί για διπλό σύμβολο ίσου. Άλλοι συγκριτικοί και λογικοί τελεστές, που επιτρέπονται, σε μια πρόταση WHERE περιλαμβάνουν τα <, >, <=, >=, !=, καθώς και τα AND και OR και παρενθέσεις για τη δημιουργία των λογικών σας εκφράσεων.

Μπορείτε να ζητήσετε να ταξινομηθούν οι επιστρεφόμενες γραμμές, κατά ένα από τα πεδία, ως εξής:

SELECT title,plays FROM Tracks ORDER BY title

Για να διαγράψετε μια γραμμή, χρειάζεστε μια πρόταση WHERE σε συνδυασμό με μια εντολή SQL DELETE. Η πρόταση WHERE καθορίζει ποιες σειρές θα διαγραφούν:

DELETE FROM Tracks WHERE title = 'My Way'

Είναι δυνατό να “ενημερώσετε” μια ή περισσότερες στήλες σε μία ή περισσότερες γραμμές ενός πίνακα, χρησιμοποιώντας την εντολή SQL UPDATE ως εξής:

UPDATE Tracks SET plays = 16 WHERE title = 'My Way'

Η εντολή UPDATE καθορίζει έναν πίνακα και, στη συνέχεια, μια λίστα πεδίων και τιμών που θα αλλάξουν, μετά τη δεσμευμένη λέξη SET και, στη συνέχεια, μια προαιρετική πρόταση WHERE, για να επιλέξετε τις γραμμές που πρόκειται να ενημερωθούν. Μια μεμονωμένη δήλωση UPDATE ενημερώνει όλες τις γραμμές που ταιριάζουν με την πρόταση WHERE. Εάν δεν έχει καθοριστεί η πρόταση WHERE, εκτελείται “ενημέρωση” όλων των γραμμών του πίνακα.

Αυτές οι τέσσερις βασικές εντολές SQL (INSERT, SELECT, UPDATE και DELETE) υλοποιούν τις τέσσερις βασικές λειτουργίες, που απαιτούνται για τη δημιουργία και τη συντήρηση των δεδομένων.

Ανίχνευση του Twitter με χρήση βάσης δεδομένων

Σε αυτή την ενότητα, θα δημιουργήσουμε ένα απλό πρόγραμμα ανίχνευσης (spidering), που θα διατρέξει λογαριασμούς του Twitter και θα δημιουργήσει μια βάση δεδομένων με αυτούς. Σημείωση: Να είστε πολύ προσεκτικοί όταν εκτελείτε αυτό το πρόγραμμα. Δεν θέλετε να τραβήξετε πάρα πολλά δεδομένα ή να εκτελέσετε το πρόγραμμα για πολύ μεγάλο χρονικό διάστημα και να καταλήξετε να τερματίσετε την πρόσβασή σας στο Twitter.

Ένα από τα προβλήματα κάθε είδους προγράμματος spidering είναι ότι πρέπει να μπορεί να διακοπεί και να επανεκκινηθεί πολλές φορές και δεν θέλετε να χάσετε τα δεδομένα που έχετε ανακτήσει μέχρι τώρα. Δεν θέλετε να επανεκκινείτε και η ανάκτηση των δεδομένων να αρχίζει στην αρχή, επομένως θέλουμε να αποθηκεύουμε τα δεδομένα καθώς τα ανακτούμε, ώστε το πρόγραμμά μας να μπορεί να δημιουργεί αντίγραφα ασφαλείας και να συνεχίζει από εκεί που σταμάτησε.

Θα ξεκινήσουμε με την ανάκτηση των φίλων ενός ατόμου στο Twitter και της κατάστασής τους, διατρέχοντας επαναληπτικά τη λίστα των φίλων και προσθέτοντας κάθε έναν από τους φίλους σε μια βάση δεδομένων, την οποία θα προσπελάσουμε αργότερα. Αφού επεξεργαστούμε τους φίλους ενός ατόμου στο Twitter, ελέγχουμε τη βάση δεδομένων μας και ανακτούμε έναν από τους φίλους του φίλου. Το κάνουμε αυτό ξανά και ξανά, επιλέγοντας ένα άτομο που δεν έχει “επισκεφτεί”, ανακτώντας τη λίστα φίλων του και προσθέτοντας φίλους, που δεν έχουμε δει στη λίστα μας για μελλοντική επίσκεψη.

Παρακολουθούμε επίσης πόσες φορές έχουμε δει έναν συγκεκριμένο φίλο στη βάση δεδομένων για να σχηματίσουμε μια εικόνα της «δημοτικότητάς» του.

Αποθηκεύοντας τη λίστα των γνωστών λογαριασμών μας και εάν έχουμε ανακτήσει τον λογαριασμό ή όχι και πόσο δημοφιλής είναι ο λογαριασμός σε μια βάση δεδομένων, στο δίσκο του υπολογιστή, μπορούμε να σταματήσουμε και να επανεκκινήσουμε το πρόγραμμά μας όσες φορές θέλουμε.

Αυτό το πρόγραμμα είναι λίγο περίπλοκο. Βασίζεται στον κώδικα από την άσκηση νωρίτερα στο βιβλίο, που χρησιμοποιεί το Twitter API.

Εδώ είναι ο πηγαίος κώδικας για την εφαρμογή ανίχνευσης του Twitter:

from urllib.request import urlopen
import urllib.error
import twurl
import json
import sqlite3
import ssl

TWITTER_URL = 'https://api.twitter.com/1.1/friends/list.json'

conn = sqlite3.connect('spider.sqlite')
cur = conn.cursor()

cur.execute('''
            CREATE TABLE IF NOT EXISTS Twitter
            (name TEXT, retrieved INTEGER, friends INTEGER)''')

# Αγνοήστε τα σφάλματα πιστοποιητικού SSL
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE

while True:
    acct = input('Εισαγάγετε έναν λογαριασμό Twitter ή τερματίστε την εκτέλεση με quit: ')
    if (acct == 'quit'): break
    if (len(acct) < 1):
        cur.execute('SELECT name FROM Twitter WHERE retrieved = 0 LIMIT 1')
        try:
            acct = cur.fetchone()[0]
        except:
            print('Δεν βρέθηκαν λογαριασμοί Twitter που δεν έχουν ήδη ανακτηθεί')
            continue

    url = twurl.augment(TWITTER_URL, {'screen_name': acct, 'count': '20'})
    print('Ανάκτηση του', url)
    connection = urlopen(url, context=ctx)
    data = connection.read().decode()
    headers = dict(connection.getheaders())

    print('Απομένουν', headers['x-rate-limit-remaining'])
    js = json.loads(data)
    # Εκσφαλμάτωση
    # print json.dumps(js, indent=4)

    cur.execute('UPDATE Twitter SET retrieved=1 WHERE name = ?', (acct, ))

    countnew = 0
    countold = 0
    for u in js['users']:
        friend = u['screen_name']
        print(friend)
        cur.execute('SELECT friends FROM Twitter WHERE name = ? LIMIT 1',
                    (friend, ))
        try:
            count = cur.fetchone()[0]
            cur.execute('UPDATE Twitter SET friends = ? WHERE name = ?',
                        (count+1, friend))
            countold = countold + 1
        except:
            cur.execute('''INSERT INTO Twitter (name, retrieved, friends)
                        VALUES (?, 0, 1)''', (friend, ))
            countnew = countnew + 1
    print('Νέοι λογαριασμοί =', countnew, ' Επισκέφθηκαν εκ νέου =', countold)
    conn.commit()

cur.close()

# Code: http://www.py4e.com/code3/twspider.py

Η βάση δεδομένων μας είναι αποθηκευμένη στο αρχείο spider.sqlite και έχει έναν πίνακα με όνομα Twitter. Κάθε γραμμή του πίνακα Twitter έχει μια στήλη για το όνομα του λογαριασμού, μία για το εάν έχουμε ανακτήσει τους φίλους αυτού του λογαριασμού και μια τρίτη με το πόσες φορές αυτός ο λογαριασμός έχει γίνει “φίλος”.

Στον κύριο βρόχο του προγράμματος, ζητάμε από τον χρήστη ένα όνομα λογαριασμού στο Twitter ή “quit” για έξοδο από το πρόγραμμα. Εάν ο χρήστης εισαγάγει έναν λογαριασμό Twitter, ανακτούμε τη λίστα φίλων και καταστάσεων (status) για αυτόν τον χρήστη και προσθέτουμε κάθε φίλο του στη βάση δεδομένων, εάν δεν βρίσκεται ήδη στη βάση δεδομένων. Εάν ο φίλος βρίσκεται ήδη στη λίστα, προσθέτουμε 1 στο πεδίο friends της γραμμής του, στη βάση δεδομένων.

Εάν ο χρήστης πατήσει enter, αναζητούμε στη βάση δεδομένων τον επόμενο λογαριασμό Twitter που δεν έχουμε ανακτήσει ακόμη, ανακτούμε τους φίλους και τα status για αυτόν τον λογαριασμό, τα προσθέτουμε στη βάση δεδομένων ή τους ενημερώνουμε και αυξάνουμε τον αριθμό των friends τους, εάν είναι ήδη καταχωρημένοι στη βάση δεδομένων.

Μόλις ανακτήσουμε τη λίστα των φίλων και των καταστάσεων, πραγματοποιούμε αναζήτηση σε όλα τα στοιχεία user στο επιστρεφόμενο JSON και ανακτούμε το screen_name για κάθε χρήστη. Στη συνέχεια, χρησιμοποιούμε τη δήλωση SELECT για να δούμε αν έχουμε ήδη αποθηκεύσει το συγκεκριμένο screen_name στη βάση δεδομένων και ανακτούμε τον αριθμό φίλων (friends) εάν υπάρχει η εγγραφή.

countnew = 0
countold = 0
for u in js['users'] :
    friend = u['screen_name']
    print(friend)
    cur.execute('SELECT friends FROM Twitter WHERE name = ? LIMIT 1',
        (friend, ) )
    try:
        count = cur.fetchone()[0]
        cur.execute('UPDATE Twitter SET friends = ? WHERE name = ?',
            (count+1, friend) )
        countold = countold + 1
    except:
        cur.execute('''INSERT INTO Twitter (name, retrieved, friends)
            VALUES ( ?, 0, 1 )''', ( friend, ) )
        countnew = countnew + 1
print('Νέοι λογαριασμοί=',countnew,' Επισκεύθηκαν εκ νέου=',countold)
conn.commit()

Μόλις ο κέρσορας εκτελέσει την πρόταση SELECT, πρέπει να ανακτήσουμε τις γραμμές. Θα μπορούσαμε να το κάνουμε αυτό με μια δήλωση for, αλλά επειδή ανακτούμε μόνο μία γραμμή (LIMIT 1), μπορούμε να χρησιμοποιήσουμε τη μέθοδο fetchone() για να ανακτήσουμε την πρώτη (και μοναδική) σειρά που είναι το αποτέλεσμα τη λειτουργία SELECT. Επειδή το fetchone() επιστρέφει τη γραμμή ως πλειάδα (ακόμα και αν υπάρχει μόνο ένα πεδίο), χρησιμοποιούμε την πρώτη τιμή της πλειάδας για να εκχωρήσουμε τον τρέχοντα αριθμό φίλων στη μεταβλητή count.

Εάν αυτή η ανάκτηση είναι επιτυχής, χρησιμοποιούμε την εντολή UPDATE της SQL με μια δήλωση WHERE, για να προσθέσουμε 1 στη στήλη friends της γραμμής που ταιριάζει με τον λογαριασμό του φίλου. Σημειώστε ότι υπάρχουν δύο σύμβολα κράτησης θέσης (δηλαδή, ερωτηματικά) στον κώδικα SQL και ότι η δεύτερη παράμετρος της execute() είναι μια πλειάδα δύο στοιχείων, που περιέχει τις τιμές, που πρέπει να αντικαταστήσουν στον κώδικα SQL, τα ερωτηματικά.

Εάν ο κώδικας στο μπλοκ try αποτύχει, αυτό είναι πιθανό να συμβεί αν καμία εγγραφή δεν ταιριάζει με τον όρο WHERE name = ? στη δήλωση SELECT. Έτσι, στο μπλοκ except, χρησιμοποιούμε την εντολή SQL INSERT, για να προσθέσουμε το screen_name του φίλου στον πίνακα με ένδειξη ότι δεν έχουμε ακόμη ανακτήσει το screen_name και ορίζουμε τον αριθμό φίλων (friends) σε ένα.

Έτσι την πρώτη φορά που τρέχει το πρόγραμμα και μπαίνουμε σε λογαριασμό Twitter, το πρόγραμμα εκτελείται ως εξής:

Εισαγάγετε έναν λογαριασμό Twitter ή τερματίστε την εκτέλεση με quit: drchuck
Ανάκτηση του http://api.twitter.com/1.1/friends ...
Νέοι λογαριασμοί = 20  Επισκεύθηκαν εκ νέου = 0
Εισαγάγετε έναν λογαριασμό Twitter ή τερματίστε την εκτέλεση με quit: quit

Επειδή είναι η πρώτη φορά που τρέχουμε το πρόγραμμα, η βάση δεδομένων δεν υπάρχει, δημιουργούμε τη βάση στο αρχείο spider.sqlite και προσθέτουμε έναν πίνακα με το όνομα Twitter στη βάση δεδομένων. Στη συνέχεια ανακτούμε μερικούς φίλους και τους προσθέτουμε όλους στη βάση δεδομένων, αφού η βάση είναι άδεια.

Σε αυτό το σημείο, μπορεί να θελήσουμε να γράψουμε ένα απλό πρόγραμμα για να ρίξουμε μια ματιά στο περιεχόμενο του αρχείου spider.sqlite:

import sqlite3

conn = sqlite3.connect('spider.sqlite')
cur = conn.cursor()
cur.execute('SELECT * FROM Twitter')
count = 0
for row in cur:
    print(row)
    count = count + 1
print(count, 'γραμμές.')
cur.close()

# Code: http://www.py4e.com/code3/twdump.py

Αυτό το πρόγραμμα απλώς ανοίγει τη βάση δεδομένων και επιλέγει όλες τις στήλες, όλων των γραμμών του πίνακα Twitter, στη συνέχεια διατρέχει τις γραμμές και εκτυπώνει κάθε μία από αυτές.

Εάν εκτελέσουμε αυτό το πρόγραμμα μετά την πρώτη εκτέλεση του Twitter spider παραπάνω, η έξοδος του θα είναι η εξής:

('opencontent', 0, 1)
('lhawthorn', 0, 1)
('steve_coppin', 0, 1)
('davidkocher', 0, 1)
('hrheingold', 0, 1)
...
20 γραμμές.

Βλέπουμε μια σειρά για κάθε screen_name, ότι δεν έχουμε ανακτήσει τα δεδομένα για αυτό το screen_name (η δεύτερη τιμή της πλειάδας που αντιστοιχεί στη στήλη retrieved είναι 0) και όλοι στη βάση δεδομένων έχουν έναν φίλο.

Τώρα η βάση δεδομένων μας αντικατοπτρίζει την ανάκτηση των φίλων του πρώτου μας λογαριασμού Twitter (drchuck). Μπορούμε να εκτελέσουμε ξανά το πρόγραμμα και να του πούμε να ανακτήσει τους φίλους του επόμενου “μη επεξεργασμένου” λογαριασμού πατώντας απλώς enter αντί για λογαριασμό Twitter ως εξής:

Εισαγάγετε έναν λογαριασμό Twitter ή τερματίστε την εκτέλεση με quit:
Ανάκτηση του http://api.twitter.com/1.1/friends ...
Νέοι λογαριασμοί = 18  Επισκεύθηκαν εκ νέου = 2
Εισαγάγετε έναν λογαριασμό Twitter ή τερματίστε την εκτέλεση με quit:
Ανάκτηση του http://api.twitter.com/1.1/friends ...
Νέοι λογαριασμοί = 17  Επισκεύθηκαν εκ νέου = 3
Εισαγάγετε έναν λογαριασμό Twitter ή τερματίστε την εκτέλεση με quit: quit

Αφού πατήσαμε enter (δηλαδή, δεν καθορίσαμε λογαριασμό Twitter), εκτελείται ο ακόλουθος κώδικας:

if ( len(acct) < 1 ) :
    cur.execute('SELECT name FROM Twitter WHERE retrieved = 0 LIMIT 1')
    try:
        acct = cur.fetchone()[0]
    except:
        print('Δεν βρέθηκαν λογαριασμοί Twitter που δεν έχουν ήδη ανακτηθεί')
        continue

Χρησιμοποιούμε την εντολή SQL SELECT για να ανακτήσουμε το όνομα του πρώτου (LIMIT 1) χρήστη, που εξακολουθεί να έχει μηδενική τιμή στο “έχουμε ανακτήσει αυτόν τον χρήστη”. Χρησιμοποιούμε επίσης το μοτίβο fetchone()[0], σε ένα μπλοκ try/except, για να εξαγάγουμε ένα screen_name από τα δεδομένα που ανακτήθηκαν ή για να εμφανίσουμε ένα μήνυμα σφάλματος και να τερματίσουμε την τρέχουσα εκτέλεση του βρόχου.

Εάν ανακτήσαμε με επιτυχία ένα μη επεξεργασμένο screen_name, ανακτούμε τα δεδομένα του ως εξής:

url=twurl.augment(TWITTER_URL,{'screen_name': acct,'count': '20'})
print('Ανάκτηση του', url)
connection = urllib.urlopen(url)
data = connection.read()
js = json.loads(data)

cur.execute('UPDATE Twitter SET retrieved=1 WHERE name = ?',(acct, ))

Μόλις ανακτήσουμε τα δεδομένα επιτυχώς, χρησιμοποιούμε την εντολή UPDATE για να θέσουμε στη στήλη retrieved το 1, για να υποδείξουμε ότι έχουμε ολοκληρώσει την ανάκτηση των φίλων αυτού του λογαριασμού. Αυτό μας εμποδίζει από το να ανακτούμε τα ίδια δεδομένα ξανά και ξανά και μας επιτρέπει να προχωράμε στο δικτύο φίλων Twitter.

Εάν τρέξουμε το πρόγραμμα φίλου και πατήσουμε το enter δύο φορές για να ανακτήσουμε τους επόμενους φίλους, που δεν έχουν επισκεφτεί και μετά εκτελέσουμε το πρόγραμμα εμφάνισης (twdump.py), θα μας δώσει την ακόλουθη έξοδο:

('opencontent', 1, 1)
('lhawthorn', 1, 1)
('steve_coppin', 0, 1)
('davidkocher', 0, 1)
('hrheingold', 0, 1)
...
('cnxorg', 0, 2)
('knoop', 0, 1)
('kthanos', 0, 2)
('LectureTools', 0, 1)
...
55 γραμμές.

Μπορούμε να δούμε ότι έχουμε καταγράψει σωστά ότι έχουμε επισκεφτεί το lhawthorn και το opencontent. Επίσης οι λογαριασμοί cnxorg και kthanos έχουν ήδη δύο ακόλουθους. Εφόσον τώρα έχουμε ανακτήσει τους φίλους τριών ατόμων (drchuck, opencontent και lhawthorn), ο πίνακας μας έχει 52 (=55-3) γραμμές φίλων για ανάκτηση.

Κάθε φορά που εκτελούμε το πρόγραμμα και πατάμε enter, θα επιλέγει τον επόμενο λογαριασμό, που δεν έχει επισκεφτεί (π.χ., ο επόμενος λογαριασμός θα είναι ο steve_coppin), θα ανακτά τους φίλους του, θα τον επισημαίνει ως ανακτημένο και κάθε έναν από τους φίλους του steve_coppin είτε τον προσθέτει στο τέλος της βάσης δεδομένων είτε ενημερώνει τον αριθμό των φίλων του, εάν βρίσκονται ήδη στη βάση δεδομένων.

Μιας και τα δεδομένα του προγράμματος αποθηκεύονται όλα στο δίσκο, σε μια βάση δεδομένων, η δραστηριότητα spidering μπορεί να ανασταλεί και να συνεχιστεί όσες φορές θέλετε χωρίς απώλεια δεδομένων.

Στοιχειώδης μοντελοποίηση δεδομένων

Η πραγματική ισχύς μιας σχεσιακής βάσης δεδομένων φαίνεται όταν δημιουργούμε πολλούς πίνακες και τους συνδέουμε. Το να αποφασίσετε πώς να χωρίσετε τα δεδομένα της εφαρμογής σας σε πολλούς πίνακες και η δημιουργία των σχέσεων μεταξύ των πινάκων ονομάζεται μοντελοποίηση δεδομένων. Το έγγραφο με το σχεδιάγραμμα, που δείχνει τους πίνακες και τις σχέσεις μεταξύ τους, ονομάζεται μοντέλο δεδομένων.

Η μοντελοποίηση δεδομένων είναι μια σχετικά περίπλοκη διαδικασία και θα αναφερθούμε μόνο στις πιο βασικές έννοιες της μοντελοποίησης σχεσιακών δεδομένων σε αυτήν την ενότητα. Για περισσότερες λεπτομέρειες σχετικά με τη μοντελοποίηση δεδομένων, μπορείτε να ξεκινήσετε από:

http://en.wikipedia.org/wiki/Relational_model

Ας πούμε ότι στην εφαρμογή αντίχνευσης του Twitter, αντί να μετράμε απλώς τους φίλους ενός ατόμου, θέλαμε να κρατήσουμε μια λίστα με όλες τις εισερχόμενες σχέσεις, ώστε να μπορούμε να δημιουργήσουμς μια λίστα με όλους όσους ακολουθούν έναν συγκεκριμένο λογαριασμό.

Δεδομένου ότι όλοι θα έχουν, πιθανώς, πολλούς λογαριασμούς που τους ακολουθούν, δεν μπορούμε απλώς να προσθέσουμε μία στήλη στον πίνακα Twitter. Δημιουργούμε λοιπόν έναν νέο πίνακα που καταγράφει τα ζεύγη φίλων. Ο παρακάτω είναι ένας απλός τρόπος για να φτιάξετε έναν τέτοιο πίνακα:

CREATE TABLE Pals (from_friend TEXT, to_friend TEXT)

Κάθε φορά που συναντάμε ένα άτομο που ακολουθεί ο drchuck, εισάγουμε μια γραμμή της μορφής:

INSERT INTO Pals (from_friend,to_friend) VALUES ('drchuck', 'lhawthorn')

Καθώς επεξεργαζόμαστε τους 20 φίλους από τη ροή Twitter του drchuck, θα εισαγάγουμε 20 εγγραφές με το “drchuck” ως πρώτη παράμετρο, οπότε θα καταλήξουμε να αντιγράφουμε την ίδια συμβολοσειρά, πολλές φορές, στη βάση δεδομένων.

Αυτή η αντιγραφή δεδομένων συμβολοσειράς παραβιάζει μία από τις βασικές πρακτικές της κανονικοποίησης βάσων δεδομένων, η οποία ουσιαστικά δηλώνει ότι δεν πρέπει, ποτέ, να αποθηκεύουμε τα ίδια δεδομένα συμβολοσειράς στη βάση δεδομένων, περισσότερες από μία φορές. Εάν χρειαζόμαστε τα δεδομένα περισσότερες από μία φορές, δημιουργούμε ένα αριθμητικό κλειδί για τα δεδομένα και αναφερόμαστε στα πραγματικά δεδομένα χρησιμοποιώντας αυτό το κλειδί.

Πρακτικά, μια συμβολοσειρά καταλαμβάνει πολύ περισσότερο χώρο από έναν ακέραιο στο δίσκο και στη μνήμη του υπολογιστή μας και απαιτείται περισσότερος χρόνος από τον επεξεργαστή για σύγκριση και ταξινόμηση. Εάν έχουμε μόνο μερικές εκατοντάδες καταχωρήσεις, ο χρόνος αποθήκευσης και επεξεργασίας δεν έχουν σημασία. Ωστόσο, εάν έχουμε ένα εκατομμύριο άτομα στη βάση δεδομένων μας και την πιθανότητα 100 εκατομμυρίων συνδέσμων φίλων, είναι σημαντικό να μπορούμε να σαρώνουμε δεδομένα όσο το δυνατόν γρηγορότερα.

Θα αποθηκεύσουμε τους λογαριασμούς μας στο Twitter σε έναν πίνακα με το όνομα People, αντί του πίνακα Twitter, που χρησιμοποιήθηκε στο προηγούμενο παράδειγμα. Ο πίνακας People έχει μια πρόσθετη στήλη, για την αποθήκευση του αριθμητικού κλειδιού που σχετίζεται με τη γραμμή αυτού του χρήστη του Twitter. Το SQLite έχει μια δυνατότητα που προσθέτει αυτόματα την τιμή κλειδιού σε κάθε γραμμή που εισάγουμε σε έναν πίνακα, χρησιμοποιώντας έναν ειδικό τύπο στήλης δεδομένων (INTEGER PRIMARY KEY).

Μπορούμε να δημιουργήσουμε τον πίνακα People με αυτήν την πρόσθετη στήλη id ως εξής:

CREATE TABLE People
    (id INTEGER PRIMARY KEY, name TEXT UNIQUE, retrieved INTEGER)

Παρατηρήστε ότι δεν διατηρούμε πλέον τον αριθμό φίλων, σε κάθε γραμμή του πίνακα People. Όταν επιλέγουμε το INTEGER PRIMARY KEY ως τον τύπο της στήλης id, υποδεικνύουμε ότι θα θέλαμε η SQLite να διαχειρίζεται αυτή τη στήλη και να εκχωρεί αυτόματα, σε κάθε γραμμή που εισάγουμε, ένα μοναδικό αριθμητικό κλειδί. Προσθέτουμε επίσης τη δεσμευμένη λέξη UNIQUE για να επιβάλλουμε έναν περιορισμό, να υποδείξουμε ότι δεν θα επιτρέψουμε στην SQLite να εισάγει δύο γραμμές με την ίδια τιμή για το name.

Τώρα αντί να δημιουργήσουμε τον παραπάνω πίνακα Pals, δημιουργούμε έναν πίνακα που ονομάζεται Follows, με δύο στήλες ακεραίων, from_id και to_id και έναν περιορισμό στον πίνακα ότι ο συνδυασμός των from_id και to_id πρέπει να να είναι μοναδικός σε αυτόν τον πίνακα (δηλαδή, δεν μπορούμε να εισαγάγουμε διπλότυπες εγγραφές) στη βάση δεδομένων μας.

CREATE TABLE Follows
    (from_id INTEGER, to_id INTEGER, UNIQUE(from_id, to_id) )

Όταν προσθέτουμε περιορισμό UNIQUE στους πίνακές μας, επικοινωνούμε ένα σύνολο κανόνων που ζητάμε από τη βάση δεδομένων να τους επιβάλει, όταν προσπαθούμε να εισαγάγουμε εγγραφές. Δημιουργούμε αυτούς τους περιορισμούς στα προγράμματά μας για ευκολία, όπως θα δούμε σε λίγο. Οι περιορισμοί μας εμποδίζουν από το να κάνουμε λάθη και μας διευκολύνουν να γράψουμε μέρος του κώδικά μας.

Στην ουσία, δημιουργώντας αυτόν τον πίνακα Follows, διαμορφώνουμε μια “σχέση”, όπου ένα άτομο “ακολουθεί” κάποιον άλλο και την αναπαριστάνουμε με ένα ζεύγος αριθμών που υποδεικνύουν ότι (α) τα άτομα είναι συνδεδεμένα και (β) την κατεύθυνση της σχέσης.

Σχέσεις Μεταξύ Πινάκων
Σχέσεις Μεταξύ Πινάκων

Χρήση πολλών πινάκων

Τώρα θα επαναλάβουμε το πρόγραμμα αντίχνευσης Twitter χρησιμοποιώντας δύο πίνακες, τα κύρια κλειδιά και τις αναφορές κλειδιών όπως περιγράφηκαν παραπάνω. Εδώ είναι ο κώδικας για τη νέα έκδοση του προγράμματος:

import urllib.request, urllib.parse, urllib.error
import twurl
import json
import sqlite3
import ssl

TWITTER_URL = 'https://api.twitter.com/1.1/friends/list.json'

conn = sqlite3.connect('friends.sqlite')
cur = conn.cursor()

cur.execute('''CREATE TABLE IF NOT EXISTS People
            (id INTEGER PRIMARY KEY, name TEXT UNIQUE, retrieved INTEGER)''')
cur.execute('''CREATE TABLE IF NOT EXISTS Follows
            (from_id INTEGER, to_id INTEGER, UNIQUE(from_id, to_id))''')

# Ignore SSL certificate errors
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE

while True:
    acct = input('Εισαγάγετε έναν λογαριασμό Twitter ή τερματίστε την εκτέλεση με quit: ')
    if (acct == 'quit'): break
    if (len(acct) < 1):
        cur.execute('SELECT id, name FROM People WHERE retrieved=0 LIMIT 1')
        try:
            (id, acct) = cur.fetchone()
        except:
            print('Δεν βρέθηκαν λογαριασμοί Twitter που δεν έχουν ήδη ανακτηθεί')
            continue
    else:
        cur.execute('SELECT id FROM People WHERE name = ? LIMIT 1',
                    (acct, ))
        try:
            id = cur.fetchone()[0]
        except:
            cur.execute('''INSERT OR IGNORE INTO People
                        (name, retrieved) VALUES (?, 0)''', (acct, ))
            conn.commit()
            if cur.rowcount != 1:
                print('Σφάλμα κατά την εισαγωγή του λογαριασμού:', acct)
                continue
            id = cur.lastrowid

    url = twurl.augment(TWITTER_URL, {'screen_name': acct, 'count': '100'})
    print('Ανάκτηση του', acct)
    try:
        connection = urllib.request.urlopen(url, context=ctx)
    except Exception as err:
        print('Η ανάκτηση απέτυχε ', err)
        break

    data = connection.read().decode()
    headers = dict(connection.getheaders())

    print('Απομένουν', headers['x-rate-limit-remaining'])

    try:
        js = json.loads(data)
    except:
        print('Δεν είναι δυνατή η ανάλυση του json')
        print(data)
        break

    # Εκσφαλμάτωση
    # print(json.dumps(js, indent=4))

    if 'users' not in js:
        print('Λήφθηκε λάθος JSON')
        print(json.dumps(js, indent=4))
        continue

    cur.execute('UPDATE People SET retrieved=1 WHERE name = ?', (acct, ))

    countnew = 0
    countold = 0
    for u in js['users']:
        friend = u['screen_name']
        print(friend)
        cur.execute('SELECT id FROM People WHERE name = ? LIMIT 1',
                    (friend, ))
        try:
            friend_id = cur.fetchone()[0]
            countold = countold + 1
        except:
            cur.execute('''INSERT OR IGNORE INTO People (name, retrieved)
                        VALUES (?, 0)''', (friend, ))
            conn.commit()
            if cur.rowcount != 1:
                print('Σφάλμα κατά την εισαγωγή λογαριασμού', friend)
                continue
            friend_id = cur.lastrowid
            countnew = countnew + 1
        cur.execute('''INSERT OR IGNORE INTO Follows (from_id, to_id)
                    VALUES (?, ?)''', (id, friend_id))
    print('Νέοι λογαριασμοί =', countnew, ' Επισκέφθηκαν εκ νέου =', countold)
    print('Απομένουν', headers['x-rate-limit-remaining'])
    conn.commit()
cur.close()

# Code: http://www.py4e.com/code3/twfriends.py

Αυτό το πρόγραμμα αρχίζει να γίνεται λίγο περίπλοκο, αλλά δείχνει τα μοτίβα που πρέπει να ακολουθούμε όταν χρησιμοποιούμε ακέραια κλειδιά για σύνδεση πινάκων. Τα βασικά σημεία είναι:

  1. Δημιουργήστε πίνακες με πρωτεύοντα κλειδιά και περιορισμούς.

  2. Όταν έχουμε ένα λογικό κλειδί για ένα άτομο (π.χ. όνομα λογαριασμού) και χρειαζόμαστε την τιμή id για το άτομο, ανάλογα με το αν το άτομο βρίσκεται ήδη στον πίνακα People ή όχι, χρειάζεται: (1) να αναζητήστε το άτομο στον πίνακα People και να ανακτήστε την τιμή id για το άτομο ή
    1. να προσθέστε το άτομο στον πίνακα People και να κρατήσετε την τιμή id της νέας σειράς που προστέθηκε.
  3. Εισαγάγετε τη γραμμή που καταγράφει τη σχέση Follows (“ακολουθεί”).

Θα καλύψουμε καθένα από αυτά με τη σειρά του.

Περιορισμοί σε πίνακες βάσης δεδομένων

Καθώς σχεδιάζουμε τις δομές των πινάκων μας, μπορούμε να πούμε στο σύστημα βάσης δεδομένων ότι θα θέλαμε να επιβάλει μερικούς περιορισμούς. Αυτοί οι περιορισμοί μας βοηθούν να αποφύγουμε λάθη και εισαγωγή εσφαλμένων δεδομένων στους πίνακές μας, όταν δημιουργούμε τους πίνακές μας:

cur.execute('''CREATE TABLE IF NOT EXISTS People
    (id INTEGER PRIMARY KEY, name TEXT UNIQUE, retrieved INTEGER)''')
cur.execute('''CREATE TABLE IF NOT EXISTS Follows
    (from_id INTEGER, to_id INTEGER, UNIQUE(from_id, to_id))''')

Υποδεικνύουμε ότι η στήλη name στον πίνακα People πρέπει να είναι UNIQUE (ΜΟΝΑΔΙΚΗ). Υποδεικνύουμε επίσης ότι ο συνδυασμός των δύο αριθμών (from_id και to_id), σε κάθε γραμμή του πίνακα Follows, πρέπει να είναι μοναδικός. Αυτοί οι περιορισμοί μας εμποδίζουν να κάνουμε λάθη, όπως η προσθήκη της ίδιας σχέσης, περισσότερες από μία φορές.

Μπορούμε να εκμεταλλευτούμε αυτούς τους περιορισμούς στον ακόλουθο κώδικα:

cur.execute('''INSERT OR IGNORE INTO People (name, retrieved)
    VALUES ( ?, 0)''', ( friend, ) )

Προσθέτουμε τον όρο OR IGNORE στην εντολή INSERT για να υποδείξουμε ότι εάν αυτό το συγκεκριμένο INSERT προκαλεί παραβίαση του περιορισμού “Το name πρέπει να είναι μοναδικό”, το σύστημα βάσης δεδομένων επιτρέπεται να αγνοήσει το INSERT. Χρησιμοποιούμε τον περιορισμό της βάσης δεδομένων ως δίχτυ ασφαλείας, για να βεβαιωθούμε ότι δεν θα κάνουμε άθελά μας κάτι λάθος.

Ομοίως, ο ακόλουθος κώδικας διασφαλίζει ότι δεν θα προσθέσουμε την ίδια ακριβώς σχέση Follows δύο φορές.

cur.execute('''INSERT OR IGNORE INTO Follows
    (from_id, to_id) VALUES (?, ?)''', (id, friend_id) )

Και πάλι, απλώς λέμε στη βάση δεδομένων να αγνοήσει τo INSERT (ΕΙΣΑΓΩΓΗ), εάν παραβιάζει τον περιορισμό μοναδικότητας, που καθορίσαμε για τις γραμμές του Follows.

Ανάκτηση και/ή εισαγωγή εγγραφής

Όταν ζητάμε από τον χρήστη έναν λογαριασμό Twitter, εάν ο λογαριασμός υπάρχει, πρέπει να αναζητήσουμε την τιμή id του. Εάν ο λογαριασμός δεν υπάρχει ακόμα στον πίνακα People, πρέπει να εισαγάγουμε την εγγραφή και να λάβουμε την τιμή id της εισαγόμενης γραμμής.

Αυτό είναι ένα πολύ κοινό μοτίβο και γίνεται δύο φορές στο παραπάνω πρόγραμμα. Αυτός ο κώδικας δείχνει πώς αναζητούμε το id, για τον λογαριασμό ενός φίλου, όταν έχουμε εξαγάγει ένα screen_name από έναν κόμβο user στο ανακτηθέν Twitter JSON.

Δεδομένου ότι μετά από αρκετές εκτελέσεις, του παραπάνω κώδικα, θα είναι όλο και πιο πιθανό ο λογαριασμός να βρίσκεται ήδη στη βάση δεδομένων, πρώτα ελέγχουμε αν υπάρχει η εγγραφή στον People, χρησιμοποιώντας μια εντολή SELECT.

Αν όλα πάνε καλά2 μέσα στην ενότητα try, ανακτούμε την εγγραφή χρησιμοποιώντας το fetchone() και στη συνέχεια ανακτάμε το πρώτο (και μοναδικό) στοιχείο της πλειάδας που επιστράφηκε και το αποθηκεύουμε στο friend_id.

Εάν το SELECT αποτύχει, ο κώδικας fetchone()[0] θα αποτύχει επίσης και ο έλεγχος θα μεταφερθεί στην ενότητα except.

    friend = u['screen_name']
    cur.execute('SELECT id FROM People WHERE name = ? LIMIT 1',
        (friend, ) )
    try:
        friend_id = cur.fetchone()[0]
        countold = countold + 1
    except:
        cur.execute('''INSERT OR IGNORE INTO People (name, retrieved)
            VALUES ( ?, 0)''', ( friend, ) )
        conn.commit()
        if cur.rowcount != 1 :
            print('Error inserting account:',friend)
            continue
        friend_id = cur.lastrowid
        countnew = countnew + 1

Αν καταλήξουμε στον κώδικα του except, σημαίνει απλώς ότι η σειρά δεν βρέθηκε, επομένως πρέπει να εισαγάγουμε τη σειρά. Χρησιμοποιούμε INSERT OR IGNORE απλώς για να αποφύγουμε σφάλματα και στη συνέχεια καλούμε το commit(), για να αναγκάσουμε τη βάση δεδομένων να ενημερωθεί. Αφού ολοκληρωθεί η εγγραφή, μπορούμε να ελέγξουμε το cur.rowcount για να δούμε πόσες σειρές επηρεάστηκαν. Εφόσον προσπαθούμε να εισαγάγουμε μία μόνο σειρά, εάν ο αριθμός των επηρεαζόμενων σειρών είναι κάτι διαφορετικό από 1, είναι σφάλμα.

Εάν το INSERT είναι επιτυχές, μπορούμε να κοιτάξουμε το cur.lastrowid για να ελέγξουμε ποια τιμή έχει εκχωρήσει η βάση δεδομένων στη στήλη id, στη νεοδημιουργηθήσα γραμμή.

Αποθήκευση της σχέσης φίλου

Από τη στιγμή που ξέρετε την τιμή κλειδιού, τόσο για τον χρήστη του Twitter όσο και για τον φίλο, στο JSON, είναι απλό να εισαγάγετε τους δύο αριθμούς στον πίνακα `Follows με τον ακόλουθο κώδικα:

cur.execute('INSERT OR IGNORE INTO Follows (from_id, to_id) VALUES (?, ?)',
    (id, friend_id) )

Σημειώστε ότι αφήνουμε τη βάση δεδομένων να φροντίσει για την αποφυγή “διπλής εισαγωγής” μιας σχέσης, δημιουργώντας τον πίνακα με έναν περιορισμό μοναδικότητας και, στη συνέχεια, προσθέτοντας OR IGNORE στην εντολή INSERT.

Ακολουθεί ένα δείγμα εκτέλεσης αυτού του προγράμματος:

Εισαγάγετε έναν λογαριασμό Twitter ή τερματίστε την εκτέλεση με quit:
Δεν βρέθηκαν λογαριασμοί Twitter που δεν έχουν ήδη ανακτηθεί
Εισαγάγετε έναν λογαριασμό Twitter ή τερματίστε την εκτέλεση με quit: drchuck
Ανάκτηση του http://api.twitter.com/1.1/friends ...
Νέοι λογαριασμοί = 20  Επισκεύθηκαν εκ νέου = 0
Εισαγάγετε έναν λογαριασμό Twitter ή τερματίστε την εκτέλεση με quit:
Ανάκτηση του http://api.twitter.com/1.1/friends ...
Νέοι λογαριασμοί = 17  Επισκεύθηκαν εκ νέου = 3
Εισαγάγετε έναν λογαριασμό Twitter ή τερματίστε την εκτέλεση με quit:
Ανάκτηση του http://api.twitter.com/1.1/friends ...
Νέοι λογαριασμοί = 17  Επισκεύθηκαν εκ νέου = 3
Εισαγάγετε έναν λογαριασμό Twitter ή τερματίστε την εκτέλεση με quit: quit

Ξεκινήσαμε με τον λογαριασμό drchuck και μετά αφήσαμε το πρόγραμμα να επιλέξει αυτόματα τους επόμενους δύο λογαριασμούς για ανάκτηση και προσθήκη στη βάση δεδομένων μας.

Ακολουθούν οι πρώτες λίγες σειρές στους πίνακες People και Follows, μετά την ολοκλήρωση αυτής της εκτέλεσης:

People:
(1, 'drchuck', 1)
(2, 'opencontent', 1)
(3, 'lhawthorn', 1)
(4, 'steve_coppin', 0)
(5, 'davidkocher', 0)
55 γραμμές.
Follows:
(1, 2)
(1, 3)
(1, 4)
(1, 5)
(1, 6)
60 γραμμές.

Μπορείτε να δείτε τα πεδία id, name και visited στον πίνακα People και βλέπετε τους αριθμούς και των δύο άκρων των σχέσεων στον πίνακα Follows. Στον πίνακα People, μπορούμε να δούμε ότι έχουμε επισκεφθεί τα τρία πρώτα άτομα και τα δεδομένα τους έχουν ανακτηθεί. Τα δεδομένα στον πίνακα Follows υποδεικνύουν ότι ο drchuck (χρήστης 1) είναι φίλος με όλα τα άτομα που εμφανίζονται στις πρώτες πέντε σειρές. Αυτό είναι λογικό, γιατί τα πρώτα δεδομένα που ανακτήσαμε και αποθηκεύσαμε ήταν οι φίλοι Twitter του drchuck. Εάν επρόκειτο να εκτυπώσετε περισσότερες σειρές από τον πίνακα Follows, θα βλέπατε και τους φίλους των χρηστών 2 και 3.

Τριών ειδών κλειδιά

Τώρα που αρχίσαμε να χτίζουμε ένα μοντέλο δεδομένων, τοποθετώντας τα δεδομένα μας σε πολλούς συνδεδεμένους πίνακες και συνδέοντας τις σειρές αυτών των πινάκων χρησιμοποιώντας κλειδιά, πρέπει να δούμε κάποια ορολογία γύρω από τα κλειδιά. Υπάρχουν γενικά τρία είδη κλειδιών που χρησιμοποιούνται σε ένα μοντέλο βάσης δεδομένων.

Χρησιμοποιούμε μια σύμβαση ονομασίας που πάντα το όνομα πεδίου του πρωτεύοντος κλειδιού id και προσαρτάμε το επίθημα _id σε οποιοδήποτε όνομα πεδίου που είναι ξένο κλειδί.

Χρήση JOIN για ανάκτηση δεδομένων

Τώρα που έχουμε ακολουθήσει τους κανόνες κανονικοποίησης της βάσης δεδομένων και τα δεδομένα χωρίζονται σε δύο πίνακες, συνδεδεμένα μεταξύ τους χρησιμοποιώντας πρωτεύοντα και ξένα κλειδιά, πρέπει να είμαστε σε θέση να δημιουργήσουμε ένα SELECT που επανασυναρμολογεί τα δεδομένα στους πίνακες.

Η SQL χρησιμοποιεί τον όρο JOIN για να επανασυνδέσει αυτούς τους πίνακες. Στον όρο JOIN καθορίζετε τα πεδία που χρησιμοποιούνται για την επανασύνδεση των σειρών των πινάκων.

Το παρακάτω είναι ένα παράδειγμα SELECT με ρήτρα JOIN:

SELECT * FROM Follows JOIN People
    ON Follows.from_id = People.id WHERE People.id = 1

Ο όρος JOIN υποδηλώνει ότι τα πεδία που επιλέγουμε διασχίζουν και τους πίνακες Follows και People. Η ρήτρα ON υποδεικνύει πώς θα ενωθούν οι δύο πίνακες: Πάρε τις σειρές από το Follows και προσθέστε τη σειρά του People, όπου το πεδίο from_id στο Follows είναι το ίδιο με την τιμή id του πίνακα People.

Σύνδεση πινάκων με χρήση JOIN
Σύνδεση πινάκων με χρήση JOIN

Το αποτέλεσμα του JOIN είναι να δημιουργηθούν πολύ μεγάλες “meta-γραμμές” οι οποίες έχουν και τα δύο πεδία από τον People και τα αντίστοιχα πεδία από τον Follows. Όπου υπάρχουν περισσότερες από μία αντιστοιχίσεις μεταξύ του πεδίου id από τον People και του from_id από τον Follows, τότε το JOIN δημιουργεί μια meta-γραμμή για κάθε ένα από τα αντίστοιχα ζεύγη σειρών, αντιγράφοντας δεδομένα όπως απαιτείται.

Ο παρακάτω κώδικας δείχνει τα δεδομένα που θα έχουμε στη βάση δεδομένων μετά την εκτέλεση του προγράμματος πολλαπλών πινάκων Twitter spider (παραπάνω) πολλές φορές.

import sqlite3

conn = sqlite3.connect('friends.sqlite')
cur = conn.cursor()

cur.execute('SELECT * FROM People')
count = 0
print('People:')
for row in cur:
    if count < 5: print(row)
    count = count + 1
print(count, 'γραμμές.')

cur.execute('SELECT * FROM Follows')
count = 0
print('Follows:')
for row in cur:
    if count < 5: print(row)
    count = count + 1
print(count, 'γραμμές.')

cur.execute('''SELECT * FROM Follows JOIN People
            ON Follows.to_id = People.id
            WHERE Follows.from_id = 2''')
count = 0
print('Συνδέσεις για το id=2:')
for row in cur:
    if count < 5: print(row)
    count = count + 1
print(count, 'γραμμές.')

cur.close()

# Code: http://www.py4e.com/code3/twjoin.py

Σε αυτό το πρόγραμμα, καταργούμε πρώτα τους πίνακες People και Follows και, στη συνέχεια, απορρίπτουμε ένα υποσύνολο των δεδομένων στους πίνακες που είναι ενωμένοι μεταξύ τους.

Εδώ είναι η έξοδος του προγράμματος:

python twjoin.py
People:
(1, 'drchuck', 1)
(2, 'opencontent', 1)
(3, 'lhawthorn', 1)
(4, 'steve_coppin', 0)
(5, 'davidkocher', 0)
55 γραμμές.
Follows:
(1, 2)
(1, 3)
(1, 4)
(1, 5)
(1, 6)
60 γραμμές.
Συνδέσεις για το id=2:
(2, 1, 1, 'drchuck', 1)
(2, 28, 28, 'cnxorg', 0)
(2, 30, 30, 'kthanos', 0)
(2, 102, 102, 'SomethingGirl', 0)
(2, 103, 103, 'ja_Pac', 0)
20 γραμμές.

Βλέπετε τις στήλες από τους πίνακες People και Follows και το τελευταίο σύνολο σειρών είναι το αποτέλεσμα του SELECT με τον όρο JOIN.

Στην τελευταία επιλογή, αναζητούμε λογαριασμούς που είναι φίλοι του “opencontent” (δηλαδή, People.id=2).

Σε κάθε μία από τις “meta-γραμμές” στην τελευταία επιλογή, οι δύο πρώτες στήλες είναι από τον πίνακα Follows ακολουθούμενες από τις στήλες τρία έως πέντε του πίνακα People. Μπορείτε επίσης να δείτε ότι η δεύτερη στήλη (Follows.to_id) ταιριάζει με την τρίτη στήλη (People.id) σε κάθε μία από τις ενωμένες “meta-γραμμές”.

Περίληψη

Αυτό το κεφάλαιο έχει καλύψει πολλά, για να σας δώσει μια επισκόπηση των βασικών στοιχείων της χρήσης μιας βάσης δεδομένων στην Python. Είναι πιο περίπλοκο να γράψετε τον κώδικα για να χρησιμοποιήσετε μια βάση δεδομένων για την αποθήκευση δεδομένων, από τα λεξικά Python ή τα επίπεδα αρχεία, επομένως δεν υπάρχει λόγος να χρησιμοποιήσετε μια βάση δεδομένων, εκτός εάν η εφαρμογή σας χρειάζεται πραγματικά τις δυνατότητες μιας βάσης δεδομένων. Οι περιπτώσεις όπου μια βάση δεδομένων μπορεί να είναι αρκετά χρήσιμη είναι: (1) όταν η εφαρμογή σας χρειάζεται να κάνει πολλές μικρές τυχαίες ενημερώσεις μέσα σε ένα μεγάλο σύνολο δεδομένων, (2) όταν τα δεδομένα σας είναι τόσο μεγάλα που δεν χωρούν σε ένα λεξικό και πρέπει να αναζητήσετε πληροφορίες επαναληπτικά ή (3) όταν έχετε μια χρονοβόρα διαδικασία, που θέλετε να μπορείτε να σταματήσετε και να επανεκκινήσετε, και να διατηρήσετε τα δεδομένα από τη μια εκτέλεση στην άλλη.

Μπορείτε να δημιουργήσετε μια απλή βάση δεδομένων με έναν μόνο πίνακα για να ταιριάζει σε πολλές ανάγκες εφαρμογών, αλλά τα περισσότερα προβλήματα απαιτούν πολλούς πίνακες και συνδέσμους/σχέσεις μεταξύ σειρών σε διαφορετικούς πίνακες. Όταν ξεκινάτε να δημιουργείτε σχέσεις μεταξύ πινάκων, είναι σημαντικό να τις σχεδιάσετε προσεκτικά και να ακολουθήσετε τους κανόνες κανονικοποίησης της βάσης δεδομένων, για να αξιοποιήσετε με τον καλύτερο τρόπο τις δυνατότητες της βάσης δεδομένων. Δεδομένου ότι το κύριο κίνητρο για τη χρήση μιας βάσης δεδομένων είναι ότι έχετε μεγάλο όγκο δεδομένων για επεξεργασία, είναι σημαντικό να μοντελοποιήσετε τα δεδομένα σας αποτελεσματικά, ώστε τα προγράμματά σας να εκτελούνται όσο το δυνατόν γρηγορότερα.

Εκσφαλμάτωση

Ένα κοινό μοτίβο, όταν αναπτύσσετε ένα πρόγραμμα Python για να συνδεθείτε σε μια βάση δεδομένων SQLite, θα είναι η εκτέλεση ενός προγράμματος Python και ο έλεγχος των αποτελεσμάτων χρησιμοποιώντας το πρόγραμμα περιήγησης βάσης δεδομένων για SQLite (Database Browser for SQLite). Το πρόγραμμα περιήγησης σάς επιτρέπει να ελέγχετε γρήγορα εάν το πρόγραμμά σας λειτουργεί σωστά.

Πρέπει να είστε προσεκτικοί γιατί το SQLite φροντίζει να εμποδίζει την αλλαγή των ίδιων δεδομένων από δύο προγράμματα ταυτόχρονα. Για παράδειγμα, εάν ανοίξετε μια βάση δεδομένων στο πρόγραμμα περιήγησης και κάνετε μια αλλαγή στη βάση δεδομένων και δεν έχετε πατήσει ακόμη το κουμπί “αποθήκευση” στο πρόγραμμα περιήγησης, το πρόγραμμα περιήγησης “κλειδώνει” το αρχείο βάσης δεδομένων και εμποδίζει οποιοδήποτε άλλο πρόγραμμα να έχει πρόσβαση στο αρχείο. Συγκεκριμένα, το πρόγραμμα Python σας δεν θα μπορεί να έχει πρόσβαση στο αρχείο εάν είναι κλειδωμένο.

Επομένως, μια λύση είναι να βεβαιωθείτε ότι είτε κλείσατε το πρόγραμμα περιήγησης της βάσης δεδομένων είτε χρησιμοποιήσετε το μενού Αρχείο για να κλείσετε τη βάση δεδομένων στο πρόγραμμα περιήγησης πριν επιχειρήσετε να αποκτήσετε πρόσβαση στη βάση δεδομένων από την Python, για να αποφύγετε το πρόβλημα της αποτυχίας του κώδικα Python, επειδή η βάση δεδομένων είναι κλειδωμένη.

Γλωσσάριο

ευρετήριο
Πρόσθετα δεδομένα, που το λογισμικό της βάσης δεδομένων διατηρεί ως γραμμές και τα εισάγει σε έναν πίνακα για να πραγματοποιεί τις αναζητήσεις πιο γρήγορα.
κανονικοποίηση
Σχεδιασμός ενός μοντέλου δεδομένων έτσι ώστε να μην γίνεται αναπαραγωγή δεδομένων. Αποθηκεύουμε κάθε στοιχείο δεδομένων σε ένα σημείο της βάσης δεδομένων και το αναφέρουμε αλλού χρησιμοποιώντας ένα ξένο κλειδί.
κέρσορας
Ένας κέρσορας σάς επιτρέπει να εκτελείτε εντολές SQL σε μια βάση δεδομένων και να ανακτάτε δεδομένα από τη βάση δεδομένων. Ο κέρσορας είναι παρόμοιος με μια υποδοχή ή έναν περιγραφέα αρχείου για συνδέσεις δικτύου και αρχεία, αντίστοιχα.
λογικό κλειδί
Ένα κλειδί που χρησιμοποιεί ο “έξω κόσμος” για να αναζητήσει μια συγκεκριμένη σειρά. Για παράδειγμα, σε έναν πίνακα λογαριασμών χρηστών, η διεύθυνση email ενός ατόμου μπορεί να είναι καλός υποψήφιος ως λογικό κλειδί για τα δεδομένα του χρήστη.
ξένο κλειδί - foreign key
Ένα αριθμητικό κλειδί που δείχνει το πρωτεύον κλειδί μιας γραμμής ενός άλλου πίνακα. Τα ξένα κλειδιά δημιουργούν σχέσεις μεταξύ γραμμών, που είναι αποθηκευμένες σε διαφορετικούς πίνακες.
περιορισμός
Όταν λέμε στη βάση δεδομένων να επιβάλει έναν περιορισμός σε ένα πεδίο ή μια γραμμή ενός πίνακα. Ένας κοινός περιορισμός είναι να επιμείνουμε ότι δεν μπορούν να υπάρχουν διπλότυπες τιμές σε ένα συγκεκριμένο πεδίο (δηλαδή, όλες οι τιμές πρέπει να είναι μοναδικές).
πλειάδα
Μια μεμονωμένη καταχώρηση σε έναν πίνακα βάσης δεδομένων, που είναι ένα σύνολο χαρακτηριστικών. Συνήθως αναφέρεται ως “γραμμή”.
πρόγραμμα περιήγησης βάσης δεδομένων
Ένα κομμάτι λογισμικού που σας επιτρέπει να συνδεθείτε απευθείας σε μια βάση δεδομένων και να χειριστείτε τη βάση δεδομένων απευθείας χωρίς να γράψετε πρόγραμμα.
πρωτεύων κλειδί
Ένα αριθμητικό κλειδί, που εκχωρείται σε κάθε γραμμή και χρησιμοποιείται για να αναφερθούμε σε μια γραμμή ενός πίνακα από έναν άλλο πίνακα. Συχνά η βάση δεδομένων ρυθμίζεται ώστε να εκχωρεί αυτόματα πρωτεύοντα κλειδιά, καθώς εισάγονται γραμμές.
σχέση
Μια περιοχή μέσα σε μια βάση δεδομένων που περιέχει πλειάδες και χαρακτηριστικά. Συνήθως ονομάζεται “πίνακας”.
χαρακτηριστικό
Μία από τις τιμές μέσα σε μια πλειάδα. Συνήθως ονομάζεται “στήλη” ή “πεδίο”.

  1. Το SQLite στην πραγματικότητα επιτρέπει κάποια ευελιξία στον τύπο των δεδομένων που αποθηκεύονται σε μια στήλη, αλλά θα διατηρήσουμε τους τύπους δεδομένων μας αυστηρούς σε αυτό το κεφάλαιο, ώστε οι έννοιες να ισχύουν εξίσου και σε άλλα συστήματα βάσεων δεδομένων, όπως π.χ. MySQL.

  2. Γενικά, όταν μια πρόταση ξεκινά με “αν όλα πάνε καλά” θα διαπιστώσετε ότι ο κώδικας πρέπει να χρησιμοποιεί το try/except.


Αν εντοπίσετε κάποιο λάθος σε αυτό το βιβλίο μην διστάσετε να μου στείλετε τη διόρθωση στο Github.