(0)
Artikel
bewerten
(100% positiv)
(1)

Um nicht für jede Kleinigkeit gleich das Datenbanklayout erweitern oder anpassen zu müssen, wollte ich einen Key-Value-Ansatz versuchen. 

Im Unterschied zu einer klassischen Tabelle können mit einer Key-Value-Tabelle beliebige Felder gespeichert werden, ohne zusätzliche Spalten in der Datenbank erstellen zu müssen. Eine mögliche Alternative zu Key-Value könnte das Speichern als JSON-Datentyp darstellen, siehe:  Laravel Store JSON .

Eine Key-Value-Datenbank ist zwar extrem flexibel, verliert dafür aber einige Möglichkeiten die eine Datenbank von Haus aus bieten würde. Nicht nur, dass den Feldern keine Datentypen mehr zugewiesen werden können, mit einer Key-Value-DB werden einfache Queries oder das Sortieren und Filtern nach bestimmten Kriterien zur Herausforderung. Zudem muss auch die Performance berücksichtigt werden: denn ein Index auf bestimmte Felder ist dann auf Spalten-Ebene nicht mehr möglich ...

Die Verwendung dieses Ansatzes sollte gut überlegt werden: In den meisten Fällen ist Key-Value nicht die richtige Lösung ...

 

Hier ein kurzes Beispiel von den Daten meiner Heizung:

 

eine klassiche Tabelle für meine Heizungsdaten würde ca. so aussehen:

Day count_R accuracy_perc sum_H_min sum_W_min sum_U_min sum_S_min sum_power_wh avg_c
20200201 0 99 200 100 10 10 100 3
20200202 1 88 202 101 11 11 101 2

 

Die gleichen Daten sind in meiner Key-Value Datenbank wie folgt abgelegt: 

type scope key value
heating_day 20200201 count_r 0
heating_day 20200201 accuracy_perc 99
heating_day 20200201 sum_H_min 200
heating_day 20200201 sum_W_min 100
heating_day 20200201 sum_U_min 10
heating_day 20200201 sum_S_min 10
heating_day 20200201 sum_power_wh 100
heating_day 20200201 avg_c  3
heating_day 20200202 count_r 1
heating_day 20200202 accuracy_perc 88
heating_day 20200202 sum_H_min 202
heating_day 20200202 sum_W_min 101
heating_day 20200202 sum_U_min 11
heating_day 20200202 sum_S_min 11
heating_day 20200202 sum_power_wh 101
heating_day 20200202 avg_c  2

Mittels "type" wird definiert um was es sich handelt, "scope" ist dann z.B. ein Zeitpunkt, ein Datum oder andere Kriterien die ich später für das Filtern der Daten verwenden kann.

Hier auch zum Vergleich wie eine Tabelle mit einet JSON-Spalte aussehen könnte:

type scope data
heating_day  20200201 {"count_r":0,"accuracy_perc":99,"sum_H_min":200,"sum_W_min":100,"sum_U_min":100,"sum_S_min":10,"sum_power_wh":100,"avg_C":3}
heating_day 20200201 {"count_r":1,"accuracy_perc":88,"sum_H_min":202,"sum_W_min":101,"sum_U_min":11,"sum_S_min":11,"sum_power_wh":101,"avg_C":2}

siehe:  Laravel Store JSON 

 

Der Aufbau

Migration 

        Schema::create('key_values', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('type')->nullable();
            $table->string('scope')->nullable();
            $table->string('key');
            $table->string('value');
            $table->timestamps();
        });​

Model

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class key_value extends Model
{
    protected $fillable = [
        'type','key', 'value', 'scope'
        ];
}

schreiben:

use App\key_value;

..		
					key_value::updateOrCreate(
						['type' => 'heating_day',   'scope' => $day, 'key' => 'sum_0_min'],['value' => $val]
					);

Der Speichervorgang ist noch relativ einfach, kniffeliger wird es, wenn wir Daten aus der Datenbank lesen wollen:

lesen:

hier habe ich eine Weile herumexperimentiert, am schönsten: bitte korrigiert mich wenn ihr da eine besser Idee habt, habe ich es mit der Funkton mapToGroups hinbekommen:

use App\key_value;

..
		$weather_days= (key_value::where('type','heating_day')->orderBy('scope', 'DESC')->get())
			->mapToGroups(function ($item, $key) {
				return [$item['scope'] => [$item['key'] => $item['value']]];
			});	

		foreach ($weather_days as  $day => $weather_day) {
			$count_h=head($weather_day->where("sum_H_min")->first()?? array())?: 0;

mapToGroups liefert mir pro Datengruppe eine Collection mit den entsprechenden Feldern als Array, schaut ca. so aus:

Illuminate\Support\Collection {#858 ▼
  #items: array:32 [▼
    "2020-01-25" => Illuminate\Database\Eloquent\Collection {#384 ▼
      #items: array:8 [▼
        0 => array:1 [▼
          "count_R" => "1"
        ]
        1 => array:1 [▼
          "accuracy_perc" => "93.68"
        ]
        2 => array:1 [▼
          "sum_H_min" => "243"
        ]
        3 => array:1 [▼
          "sum_W_min" => "116"
        ]
        4 => array:1 [▼
          "sum_U_min" => "398"
        ]
        5 => array:1 [▼
          "sum_S_min" => "4"
        ]
        6 => array:1 [▼
          "sum_0_min" => "588"
        ]
        7 => array:1 [▼
          "sum_power_wh" => "1476"
        ]
      ]
    }
    "2020-01-24" => Illuminate\Database\Eloquent\Collection {#826 ▶}
...

Dadurch dass ich die Typen bereits vorfiltere, habe ich aktuell mit diesem Ansatz kein Performance-Thema, zusätzlich könnte der Filter auch noch mit "WhereIn" auf bestimmte Keys eingeschränkt werden.

letzte Änderung dieses Artikels: 26.01.2020 19:59




Kommentare