Datenbank für kleine Website richtig aufsetzen - nur wie?

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MariaDB/MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
Antworten
radi
User
Beiträge: 8
Registriert: Freitag 15. Januar 2021, 12:15

Hi Leute,

ich baue mir eine kleine Website, mit der ich meinen Essensplan und den Wocheneinkauf managen möchte. Ja ich weiß, sowas gibts schon aber ich möchte mich einfach weiterbilden und mir selbst mal sowas aufbauen :)

Dazu habe ich mit Python und Flask mir eine eine Website aufgebaut mit einer Datenbank dahinter. Das klappt auch bisher alles super nur ich habe das Gefühl, dass ich das, was DB eigentlich ausmacht (relationships und abhängigkeiten zB), gar nicht richtig nutze.

Ich habe aktuell folgendes DB-Konstrukt (in vereinfachter Form):

Zutat:
- ID
- Name
- Energy_kcal (pro 100g)

Rezept-Zutat:
- ID
- Name
- Zutaten-ID (über foreign key/relationship)
- Menge
- Energy_kcal

Rezept:
- ID
- Rezept-Zutaten-Ids (über foreign key/relationship)
- Portionen
- Energy_kcal

Ich berechne dann in jedem Schritt, wenn ich über die Form-Interfaces ein Objekt erstelle dann auch immer, auf Basis der Ebene darunter, bspw. die Kalorien und gebe diese dem Objekt explizit mit. Das heißt: Wenn ich ein Rezept erstelle, selektiere ich die Zutaten und kombiniere sie mit einer Menge (--> Rezept-item). Dadurch hat die Rezept-Zutat eine mengenspezifische Energie, denn die energieangaben der Zutaten sind, wie es von der Nähwerttabelle bekannt, auf 100g bezogen. dadurch kann ich bspw. (neben anderen Attributen wie Preis, Fett/Carbs in g) angeben, wieviel kcal das Rezept pro Portion hat, denn das Rezept bekommt die Info, für wieviel Portionen noch die Mengen angaben sind. Kenn man alles von Chefkoch oder allen anderen Rezeptseiten.

Jetzt frage ich mich aber, ob das wirklich so gedacht ist, dass ich die Energie (hier als Beispiel) dem Rezept mit einem harten Wert in einer Spalte ablege. Ich verstehe es eigentlich so, dass ich durch die Verknüpfung (über foreign key/relationship) zwischen den Ebenen dann auf die Infos der verknüpften Klassen zugreifen kann. Hier weiß ich dass ein Rezept mit Zutaten verknüpft ist. Das Spiel geht dann auch noch weiter, wenn ich meinen Essensplan weiter aufbauen möchte. Der soll nämlich wie folgt aussehen:

Essensplan >>> Tag >>> Mahlzeit >>> Rezept * Portionen >>> Zutaten für eine Anzahl an Portionen

Am Ende möchte ich z.B. aufschlüsseln können, welche Zutaten (in welcher Menge) ich pro Plan und/oder pro Plan und/oder pro Mahlzeit brauche. Auch möchte ich angeben können was für ein Ernährungsmakro am Ende pro Plan / Tag / Mahlzeit rauskommt, wenn ich die Info Carbs/Fett/Protein über Zutatenwerte betrachte.

Ich denke es sollte eher so sein, dass man sich über die Relationships bspw. von einem Tag aus über Mahlzeit und Rezept so durchhangelt und sich dann am Ende die Werte für bspw Kalorien aus der Zutateninfo rauszieht und nicht schon die Info vorab berechnet hat für den Tag.

So sehen aktuell meine Models aus:
(richtig implementiert ist es bis zur Rezepterstellung)

Code: Alles auswählen

class User(db.Model, UserMixin):
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(150), unique=True)
    password = db.Column(db.String(150))
    first_name = db.Column(db.String(150))
    # relations
    plans = db.relationship("Plan")
    recipes = db.relationship("Recipe")


class Plan(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(50))
    date = db.Column(db.DateTime(timezone=True), default=func.now())
    # relations
    days = db.relationship("Day")
    user_id = db.Column(db.Integer, db.ForeignKey("user.id"))
    
    
class Day(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(50))

    # relations
    meals = db.relationship("Meal")
    plan_id = db.Column(db.Integer, db.ForeignKey("plan.id"))


class Meal(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    portions = db.Column(db.Numeric)
    prize = db.Column(db.Numeric)
    energy_kcal = db.Column(db.Numeric)
    energy_kJ = db.Column(db.Numeric)
    fat = db.Column(db.Numeric)
    protein = db.Column(db.Numeric)
    carbs = db.Column(db.Numeric)
    sugar = db.Column(db.Numeric)
    type = db.Column(db.String(30))
    # relations
    recipe = db.relationship("Recipe")
    day_ids = db.Column(db.Integer, db.ForeignKey("day.id"))


class Recipe(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(50))
    author = db.Column(db.String(50))
    duration = db.Column(db.Numeric)
    difficulty = db.Column(db.String(50))
    instruction = db.Column(db.String(10000))
    date = db.Column(db.DateTime(timezone=True), default=func.now())
    portions = db.Column(db.Numeric)
    # cost and caloric information per portion
    cost = db.Column(db.Numeric)
    energy_kcal = db.Column(db.Numeric)
    energy_kJ = db.Column(db.Numeric)
    fat = db.Column(db.Numeric)
    protein = db.Column(db.Numeric)
    carbs = db.Column(db.Numeric)
    sugar = db.Column(db.Numeric)
    category = db.Column(db.String(30))
    # relations
    recipeitems = db.relationship("Recipeitem")
    user_id = db.Column(db.Integer, db.ForeignKey("user.id"))
    meal_ids = db.Column(db.Integer, db.ForeignKey("meal.id"))


class Recipeitem(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(30))
    amount = db.Column(db.Numeric)
    # prize and caloric information absolut (per Item)
    prize = db.Column(db.Numeric)
    energy_kcal = db.Column(db.Numeric)
    energy_kJ = db.Column(db.Numeric)
    fat = db.Column(db.Numeric)
    protein = db.Column(db.Numeric)
    carbs = db.Column(db.Numeric)
    sugar = db.Column(db.Numeric)
    type = db.Column(db.String(30))
    # relations
    ingredient = db.relationship("Ingredient")
    recipe_ids = db.Column(db.Integer, db.ForeignKey("recipe.id"))


class Ingredient(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(30))
    brand = db.Column(db.String(30))
    type = db.Column(db.String(30))
    prize = db.Column(db.Numeric)
    energy_kj = db.Column(db.Numeric)
    energy_kcal = db.Column(db.Numeric)
    fat = db.Column(db.Numeric)
    carbohydrates = db.Column(db.Numeric)
    sugar = db.Column(db.Numeric)
    protein = db.Column(db.Numeric)
    date = db.Column(db.DateTime(timezone=True), default=func.now())
    recipeitem_ids = db.Column(db.Integer, db.ForeignKey("recipeitem.id"))
Ich mache das nebenher und habe das weder akademisch noch professionell gelernt, sowas in einer echten Datenbank abzubilden, deshalb bin ich mir unsicher wie es richtig geht. Könnt ihr mir da weiterhelfen, damit ich da besser durchsteige, wie man so ein System richtig aufbaut und nutzt?

Ich wäre euch so dankbar!

Viele Grüße,
radi
naheliegend
User
Beiträge: 439
Registriert: Mittwoch 8. August 2018, 16:42

In ein Datenbankschema kommt nur das rein, was auch wirklich gebraucht wird. So wie das beim Programmieren und dem Code ja auch ist.

Erfahrungsgemäß steckt man etwas mehr Gehirnschmalz in ein DB-Schema, da es möglichst lange konsistent gehalten werden sollte. Eine Schema im laufenden Produktionsbetrieb komplett zu ändern ist nicht so Knüller.

Du solltest dir mal die Normalformen (Normalisierung) einer DB anschauen. Das wird in der Theorie immer gerne herangezogen. In der Praxis sieht es aber meistens immer anders aus.

Was mir immer hilft ist, dass ich die Tabellen mit relationships auf ein Blatt Papier male.

Zu deinem Fall, würde es beispielsweise im Grunde ausreichen, wenn du die Makros, Mikros, whatever nur im Recipeitem speicherst, da du darauf aufbauen alles für das Recipe und Meal ausrechnen kannst. Du kannst natürlich diese Information auch redundant in den Meals und Recipes als Additionswert speichern, sofern dir die jeweilige Berechnung aus den einzelnen Recipeitem zu lange dauert und du deshalb diesen Gesamtwert berechnest und dort im Model ablegst. Kommt aber immer auf den Anwendungsfall an und wie oft etwas gebraucht wird und auf welchen Attributen ein Index liegt, etc.
__backjack__: "Jemand der VB oder PHP kann, der also was Programmieren angeht irgendwo im negativen Bereich liegt (...)"
radi
User
Beiträge: 8
Registriert: Freitag 15. Januar 2021, 12:15

Danke für deine Antwort!

Das mit der Verlagerung des Codes vom Backend (Infos vom Frontend ziehen -> Werte Berechnung und Eigenschaft in die Objects hart reinschreiben) habe ich gestern hinbekommen. Ich habe es über die hybrid_properties von SQLAlchemy hinbekommen.

Nur die Ingredients haben jetzt feste Columns mit den Informationen und die RecipeItems haben dann jeweils eine Property, die auf die jeweilige Spalte des Ingredients zugreift und das gleichzeitig mit der Menge im Recipeitem kombiniert, gleiches auch für das Recipe.

Dadurch ist natürlich viel mehr Umfang im Modell, aber natürlich entsprechend deutlich weniger im Code. Zusätzlich kann man nun Daten von Zutaten direkt in der Datenbank verändern (bspw. Preisanpassungen) und es proliferiert sich das in die komplette Verwertungskette :)
Antworten