immer nur 7 tabellen

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MariaDB/MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
BlackJack

@janb14: Der Raspberry Pi ist letztlich ein ganz normales Linux-System, also irgendetwas in der Richtung zu programmieren was „nur der Raspi versteht” macht nicht viel Sinn. Und so stark ist der Speicher nun auch wieder nicht begrenzt. Er reicht ja schon mal um MySQL drauf laufen zu lassen. Und auch andere DBMS, sowohl kleinere als auch vergleichbar grosse, beziehungsweise auch etwas grössere. Es gibt also keinen Grund das künstlich auf MySQL zu beschränken wenn man sich auch einfach an den Standard halten kann und damit das DBMS auch austauschen kann, sowohl was die DBMS selbst als auch das Python-Modul mit der Anbindung.

Die Speichergrenze ist in diesem Fall auch nicht die fest verbaute Hardware sondern der Massenspeicher, also erst einmal die SD-Karte. Da kann man bis 32 GiB gehen. Aber auch eine kleinere Karte muss man erst einmal voll bekommen mit Messdaten. Ich würde ja erst einmal schauen wie sich der Platzbedarf entwickelt. Und dann entscheiden in welchen Abständen man die Daten vom Gerät abzieht und wo anders sichert. Wenn der Raspi Netzzugang hat kann man auch die Datenbank so konfigurieren das bestimmte andere Rechner über das Netz darauf zugreifen können. So eine Archivierung kann man also auch auf einem anderen Rechner programmieren der dann alle Daten die älter als x Tage sind zuerst abfragt und in einer geeigneten Art archiviert, und dann in der Datenbank löscht.
Benutzeravatar
/me
User
Beiträge: 3555
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

janb14 hat geschrieben:Ich denke ich habs jetzt verstanden.Daten in Headlines pfui...Nur das Problem ist ich arbeite auf einem Raspberry Pi und speicher ist stark begrenzt.Folglich wäre es von Vorteil immer nur eine begrenzte menge Datensätze zu besitzen.
Dagegen spricht ja nichts. Dafür setzt man einen Job auf der periodisch die Datenbank von alten Einträgen befreit.
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

janb14 hat geschrieben:Ich hätte gerne maximal 7 Tabellen in der Db dabei soll die erste das heutige Datum als Titel tragen und die anderen absteigend die vorherigen Daten.nun soll das Programm jede Stunde eine Wertekette in die passende Tabelle eintragen das am ende in jeder Tabelle 24 Wertketten stehen.Somit soll quasi ein 7 Tage Rückblick plan entstehen.
Rein theoretisch könnte man auch überlegen, ob man hier wirklich eine Datenbank benötigt. Eine Idee, wie man das alternativ lösen kann, wäre dass man pro Tag einfach eine eigene Datei erstellt. Die Datei wäre z.B. nach dem Schema ``ttmmyyyy`` benannt (für heute also: ``20140202.dat``). In jedem File erstellt man pro Stunde eine neue Zeile, sodass man dann am Ende 24 Zeilen pro Datei hätte. Die Messwerte selbst könnte man im CSV-Format abspeichern (und vielleicht dann auch besser die Endung ``.csv`` statt ``.dat`` verwenden).

Ich sage das deshalb, weil ich deiner bisherigen Beschreibung nach nicht sehe, wo der große Vorteil bei der Verwendung einer Datenbank für dein Vorhaben liegt. Falls irgendwann mal komplexere Abfragen benötigt werden, dann kann man den Datenbestand ja immer noch automatisiert in eine DB konvertieren.
janb14
User
Beiträge: 16
Registriert: Sonntag 5. Januar 2014, 16:53

@snafu ich verwende derzeit eine DB da ich die Daten gerne auf einer HTML Seite mittels phplot anzeigen lassen wollte.
@diesch Interessant das Thema Cronjob werde es mir mal zu Gemüte führen.
janb14
User
Beiträge: 16
Registriert: Sonntag 5. Januar 2014, 16:53

So hier noch einmal ein Stückchen Code zum auseinander nehmen.Ich bin mir bewusst das die replace Funktionen sicher besser angewandt werden können.

Code: Alles auswählen

def tabletoday():
  cursor.execute('SHOW TABLES')
  rawtables = str(cursor.fetchall()).replace(",)","")
  rawtables2 = rawtables.replace("(","")
  rawtables3 = rawtables2.replace("'","")
  tables = rawtables3.replace(" ","")
  tablesfinal = tables.split(",")
  if "piplanter_1" not in tablesfinal:
    cursor.execute('CREATE TABLE piplanter_1(Time VARCHAR(192),Temp VARCHAR(192), Light VARCHAR (192),Water VARCHAR(192) );',)
  con.commit()
  cursor.execute("SELECT Time FROM piplanter_1")
  rawrows = str(cursor.fetchall()).replace(",)","")
  rawrows2 = rawrows.replace("(","")
  rawrows3 = rawrows2.replace("'","")
  rows = rawrows3.replace(" ","")
  rowsfinal = rows.split(",")
  timenow='{}_{}_{}:{}'.format(now.year,now.month,now.day,now.hour)
  if timenow not in rowsfinal :  
    cursor.execute('INSERT INTO piplanter_1(Time,Temp,Light,Water) VALUES(%s,%s,%s,%s)',(timenow,temp,light_prozent,water_prozent))
  con.commit()
BlackJack

@janb14: Nicht besser sondern gar nicht. Das Ergebnis von `fetchall()` solltest Du gar nicht erst in eine Zeichenkette umwandeln. Und die Spalten in der Datenbank sollten auch nicht alle den Typ VARCHAR haben, sondern einen der zum Wert passt.
janb14
User
Beiträge: 16
Registriert: Sonntag 5. Januar 2014, 16:53

@Blackjack und wie bekomme ich dann ohne fetchall() einen Vergleich zustande?
Benutzeravatar
diesch
User
Beiträge: 80
Registriert: Dienstag 14. April 2009, 13:36
Wohnort: Brandenburg a.d. Havel
Kontaktdaten:

Code: Alles auswählen

cursor.execute("""CREATE TABLE IF NOT EXISTS piplanter_1 (
 Time DATETIME UNIQUE,
 Temp VARCHAR(192),
 Light VARCHAR(192),
 Water VARCHAR(192)
 )
""")
con.commit()

dt = datetime.datetime.now().replace(minute=0, second=0, microsecond=0)

try:
    cursor.execute("""
    INSERT INTO piplanter_1 (Time, Temp, Light, Water) Values(%s, %s, %s, %s)
    """, (dt, temp, light, water))
except MySQLdb.IntegrityError:
    pass
    
con.commit()
http://www.florian-diesch.de
Benutzeravatar
/me
User
Beiträge: 3555
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

janb14 hat geschrieben:So hier noch einmal ein Stückchen Code zum auseinander nehmen.Ich bin mir bewusst das die replace Funktionen sicher besser angewandt werden können.
Auseinandernehmen? OK.

Kurze Zusammenfassung vorweg: Das ist ein Haufen Müll.


1: Das Erzeugen von Tabellen gehört nicht in den Produktivcode. Punkt. Dafür gibt es einen adminstrativen Teil der so etwas einmalig erledigt.

2: Alle Felder als VARCHAR zu deklarieren, statt den eigentlich verwendeten Datentyp zu nehmen, ist Humbug und konterkariert den Einsatz einer Datenbank.

3: Dieses fetchall-Geraffel mit den replace-Orgien ist eine softwaretechnische Katastrophe und das ist noch sehr wohlwollend ausgedrückt. Ziel ist anscheinend, die Daten pro Tag und Stunde nur einmal in die Tabelle einzutragen. Man liest dann aber nicht alles aus der Datenbank und sucht dann auf dem Client sondern sucht in der Datenbank den Eintrag der einen interessiert. Genau dafür ist eine Datenbank doch da.
In diesem Fall würde ich aber noch eine ganz andere Lösung wählen. Ich würde die Spalte time als UNIQUE definieren und einfach ein INSERT abfeuern ohne vorher zu lesen. Kommt kein Fehler ist es gut und der neue Datensatz wird eingetragen. Kommt ein Fehler dann wird der alte Datensatz beibehalten und es ist auch alles gut.
BlackJack

'SHOW TABLES' ist wie gesagt kein Standard-SQL. Man könnte auch einfach die Tabelle anlegen und entsprechend auf die Ausnahme reagieren wenn es die Tabelle schon gibt. Oder man versucht nicht jedes mal beim Eintragen eines Wertes die Tabelle zu erstellen sondern geht davon aus das die beim Installieren/Konfigurieren des Programms *einmal* angeleget wird.

Neben den zu den Werten passenden Typen sollte die Tabelle auch einen Primärschlüssel besitzen. Entweder einen künstlichen oder wenn es einen einfachen, letztlich numerischen Wert in den Daten gibt, der einen Schlüsselkandidaten abgibt, dann auch den. Dem Code nach könnte das der Zeitstempel sein. Dann braucht man da auch nicht mehr Testen ob es da schon einen Wert gibt, denn das prüft die Datenbank dann schon.

Bei Tabellennamen gilt das gleiche wie für Namen in Programmen wenn es um das durchnummerieren geht: Das ist in aller Regel ein Zeichen dafür das man einen Entwurfsfehler gemacht hat. Bei Quelltext weisen durchnummerierte Namen oft darauf hin das man eigentlich eine passende Datenstruktur verwenden sollte. Listen, Tupel, oder Wörterbücher (`dict`). Bei Tabellennamen, dass man *eine* Tabelle mit einer zusätzlichen Spalte für die Nummer aus dem Namen haben möchte.

In der `tabletoday()`-Funktion ist die Nummerierung folge davon das jeder Zwischenwert an einen Namen gebunden wird, was keinen Sinn macht wenn man für das Zwischenergebnis nicht mal einen aussagekräftigen eigenen Namen finden kann.

Einrückung ist per Konvention vier Leerzeichen pro Ebene.

`tabletoday()` ist ein schlechter Name. Ein Name sollte dem Leser vermitteln was ein Wert im Programm bedeutet. Bei einer Funktion also was diese Funktion *tut*, weshalb Funktionsnamen üblicherweise Tätigkeiten beschreiben sollten. Bei `tabletoday()` kommt niemand darauf das da Messwerte in eine Datenbank eingetragen werden. `insert_values()` wäre zum Beispiel ein besserer Name. Immer noch nicht perfekt, da sehr allgemein, aber zumindest weiss man eher was die Funktion macht wenn man den Funktionsaufruf liest.

Des weiteren sollte man tatsächlich eine Funktion daraus machen, also innerhalb der Funktion nur auf Konstanten von ausserhalb zugreifen. Alles andere sollte eine Funktion als Argument übergeben werden. Nur dann erfüllt eine Funktion auch tatsächlich ihren Zweck eine in sich geschlossene Code-Einheit zu sein, die man leicht isoliert ausführen und testen kann.

Letztendlich übrig bleiben würde dann so etwas hier:

Code: Alles auswählen

def insert_values(
    connection, timenow, temperature, light_percentage, water_percentage
):
    cursor = connection.cursor()
    cursor.execute(
        'INSERT INTO piplanter(Time, Temp, Light, Water) VALUES (%s,%s,%s,%s)',
        (timenow, temperature, light_percentage, water_percentage)
    )
    connection.commit()
Benutzeravatar
diesch
User
Beiträge: 80
Registriert: Dienstag 14. April 2009, 13:36
Wohnort: Brandenburg a.d. Havel
Kontaktdaten:

/me hat geschrieben: 1: Das Erzeugen von Tabellen gehört nicht in den Produktivcode. Punkt. Dafür gibt es einen adminstrativen Teil der so etwas einmalig erledigt.
Das ist für komplexere Datenbanken sicher richtig. Bei einem einfachen Skript, dass Messwerte in eine Tabelle schreibt, halte ich es aber oft für sinnvoll, wenn es seine Tabelle bei Bedarf anlegt. Dafür bekommt es natürlich eine eigene Datenbank und einen Benutzer, der nur genau da Rechte hat.
http://www.florian-diesch.de
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@diesch: ob es sich jetzt immer um ein komplett getrenntes Programm handeln muß, sei dahingestellt. Jedenfalls hat das Erzeugen einer Tabelle nichts in einer Funktion zu suchen, die Werte in diese Tabelle einträgt.
janb14
User
Beiträge: 16
Registriert: Sonntag 5. Januar 2014, 16:53

@diesch: vielen dank für den überarbeiteten Code den werde ich dann bis auf weiters verwenden.Eine Frage hätte ich aber dennoch dein Code wirft in Zeile 7 eine Warnmeldung aus das der Table bereits existiert.Wie kann ich diese unterdrücken?
janb14
User
Beiträge: 16
Registriert: Sonntag 5. Januar 2014, 16:53

ok das mit der Warung hat sich erledig

Code: Alles auswählen

def tabletoday():
  try:
    cursor.execute("""CREATE TABLE piplanter_1 (
    Time DATETIME UNIQUE,
    Temp VARCHAR(192),
    Light VARCHAR(192),
    Water VARCHAR(192)
    )
    """)
  except MySQLdb.OperationalError:
    pass
  con.commit()
   
  dt = datetime.datetime.now().replace(minute=0, second=0, microsecond=0)
   
  try:
      cursor.execute("""
     INSERT INTO piplanter_1 (Time, Temp, Light, Water) Values(%s, %s, %s, %s)
     """, (dt, temp, light_prozent,water_prozent))
  except MySQLdb.IntegrityError:
      pass
   
  con.commit()
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@janb14: Sind Temp, Light und Water wirklich 192-Zeichenlange Strings? Falls es Zahlen sind, sollten sie in der Datenbank auch vom Typ NUMBER sein. Wie BlackJack schon geschrieben hat, sollte alles was innerhalb der Funktion benutzt wird, ihr auch über Parameter übergeben werden, konkret also cursor, con, temp, light_prozent und water_prozent. Das Erzeugen einer Tabelle hat in einer Funktion, die einen Datensatz schreibt auch nichts verloren. Warum? Programme werden sehr schnell sehr komplex. Um überhaupt noch einen Überblick zu behalten, gibt es Funktionen. Das sind in sich abgeschlossene Einheiten, die eine bestimmte Aufgabe erledigen. Was eine Funktion tut, sollte am besten schon an der "def"-Zeile erkennbar sein, indem die Funktion einen sprechenden Namen hat, und alles was die Funktion braucht, wird als Parameter übergeben, die auch wieder sprechende Namen haben. Dann sollte nichts Überraschendes passieren, wie zum Bespiel, dass eine Tabelle erzeugt wird. Genau das ist der Unterschied, zwischen der Funktion, die BlackJack gepostet hat, und dem was Du zusammengeschrieben hast.
ice polar
User
Beiträge: 3
Registriert: Montag 3. Februar 2014, 16:23

Hallo, bin zwar neu hier aber was ich da lese ist schon abenteuerlich...

Es wurde bereits geschrieben, dass es da genau 1 Tabelle ("Messwerte") braucht mehr nicht. Diese eine Tabelle hat ein paar Spalten, z.B. "Datum", "Temp", "Light" und "Water" und ganz wichtig: Jede Spalte hat den korrekten Datentyp, also [datetime] für "Datum" und z.B. [integer] für die anderen Spalten -> damit wird meistens massiv Speicherplatz eingespaart!

Ein bisschen Pseudocode:

Code: Alles auswählen

CREATE TABLE MESSWERTE("Datum" datetime, "Temp" int, "Light" int, "Water" int)
CREATE UNIQUE INDEX IX_Datum FOR "Datum" ON MESSWERTE
Es kommt ja auch niemand auf die Idee für jeden Tag eine neue Datenbank anlegen zu wollen... Es ist eben so: 1 Datenbank und 1 Tabelle genügen vollauf, ein Index auf dem Datum wurde auch bereits erwähnt und damit ist das Thema Datenbank komplett abgeschlossen, da gibt es nichts mehr daran zu pfriemeln.

Alles andere kann dann die "Applikation" erledigen und zwar "straight forward" mit einer Datenbank und 1 Tabelle und den klar definierten Spalten in der Tabelle. Dafür gibt es in diesem Beispiel eigentlich genau 2 Befehle, nähmlich INSERT und SELECT.

INSERT schreibt die Daten in die Tabelle, immer schön alle 4 Werte "Datum", "Temp", "Light" und "Water". Fertig, da gibts nichts was kompliziert ist mit einer Datenbank (ausser man macht es sich so konsequent kompliziert wie in diesem Thread).

Code: Alles auswählen

INSERT MESSWERTE( "Datum", "Temp", "Light", "Water")
VALUES("Datum", "Temp", "Light", "Water")
Ist "Datum" ein Datentyp [datetime], so werden darin nebst dem Datum auch die Zeit (ziemlich genau sogar) abgespeichert, so dass es auch bei einem eindeutigen Index nie zu einem Schreibfehler (wie z.B. Duplikate Key) kommt. Es ist auch völlig Wurst, ob die Applikation die Messwerte alle 2 Stunden jede Minute oder jede Sekunde (oder unregelmässig) in die Datenbank schreibt, die Datenbank speichert die Werte einfach schnell und unkompliziert: So soll's sein.

SELECT braucht man, um die Daten für den erwähnten Plot wieder auszulesen. Python ist mächtig genug jeweils vom aktuellen Tagesdatum aus 7 Tage zurückzurechnen um so die Messewerte der vergangenen 7 Tage aus der Datenbank zu bekommen:

Code: Alles auswählen

SELECT "Datum", "Temp", "Light", "Water" FROM MESSWERTE WHERE "Datum" >= (Tagesdatum - 7 Tage)
Sollte es einmal nötig sein, ältere Daten zu löschen (z.B. alles was älter als 1 Jahr ist), so geht das mit diesem Datenmmodell ebenfalls auf einer einzigen Zeile:

Code: Alles auswählen

DELETE MESSWERTE WHERE "Datum" <= (Tagesdatum - 365Tage)


Es ist also offensichtlich, dass für das Vorhaben im Zusammenhang mit einer Datenbank keine besondere Magie notwendig ist, sondern einfach ein paar komplett unspektakuläre SQL-Befehle, nachdem die Datenbank und die Tabelle einmal(!) angelegt worden sind.

Es ist mir rätselhaft mit welchen Vorurteilen gewisse Programmierer einer Datenbank begegnen und eigentlich würde sich ja dafür auch SQLite3 eignen, ausser der "RASPI" hängt im LAN und man möchte die Messwerte vom PC aus übers Netzwerk aus der MySQL-Datenbank auslesen...

Viel Spass mit dem Vorhaben und wenn mit den gezeigten SQL-Befehlen noch etwas nicht verständlich ist, gebe ich gerne Auskunft.

Ice

PS: Ein anderes Datenmodell müsste zuerst ausführlichst begründet werden
BlackJack

Wäre das einfügen eines künstlichen Primärschlüssels schon ein anderes Datenmodell? Falls ja, hier die ausführliche Begründung: `sqlalchemy.orm`. SCNR. :-)
ice polar
User
Beiträge: 3
Registriert: Montag 3. Februar 2014, 16:23

@BlackJack: Streng genommen schon. Ein "künstlicher" Primärschlüssel besagt bereits, dass dies etwas zusätzliches eben künstliches ist unter der Annahme, dass wir bereits einen guten Primärschlüssel haben, nähmlich das "Datum" mit dem Datentyp datetime wo die Zeit bis auf die Tausenstelsekunde mit dabei ist.
Die ausführliche Begründung kann durchaus in einer Beschränkung des ORM sein, wobei ich SQLAlchemy nicht als diesbezüglich beschränkt wahrnehme:
In SQLAlchemy, primary and foreign keys are represented as sets of columns; truly composite behavior is implemented from the ground up. The ORM has industrial strength support for meaningful (non-surrogate) primary keys, including mutability and compatibility with ON UPDATE CASCADE, as well as explicit support for other common composite PK patterns such as "association" objects (many-to-many relationships with extra meaning attached to each association).
Mein Grundsatz lautet: Keep it simple and stupid! kompliziert wird es von alleine...
janb14
User
Beiträge: 16
Registriert: Sonntag 5. Januar 2014, 16:53

Ersteinmal vielen Dank für all die tollen/langen Tutorials und Hilfestellungen.Ich bin mir bewusst das ich am Anfang viel falsch gemacht habe ...aber hey ich stehe halt auf die Methode "lerning by doing".Als Ergänzung vllt. noch ich habe jetzt auf vielerlei Anrat eine DB mit einem Table erstellt und es funktioniert :D .Dafür erstmal ein DICKES DANKE.Anbei sende ich euch die ersten Bilder meiner Projektseite.Nun wird erstmal noch ein bisschen an den Feinheiten gefeilt :) z.B. den Phplot graphen spiegeln :shock: .Bis dahin DANKE und PEACE

Bild
Antworten