Empfohlen

Herzlich Willkommen auf meiner Website

Hier blogge ich regelmäßig über verschiedene IT-Themen, die mir bei meiner täglichen Arbeit begegnen und die mich beschäftigen. In der Regel werden Sie hier also Artikel zu C++ mit Qt, JavaScript, HTML und/oder CSS und ähnlichen Themen finden.

Ich freue mich dabei natürlich über Kommentare, Fragen und rege Beteiligung.

Daneben finden Sie hier Informationen zu meiner selbstständigen Arbeit in den Bereichen Softwareentwicklung und Netzwerkbetreuung.

Alles, was Sie über Natural Joins in SQL wissen müssen

SQL (Structured Query Language) ist eine mächtige Sprache für die Verwaltung und Manipulation von Datenbanken. Eine häufige Aufgabe bei der Arbeit mit Datenbanken ist das Verbinden von Tabellen, um umfassendere Datensätze zu erstellen. In SQL gibt es verschiedene Möglichkeiten, Tabellen zu verbinden, darunter INNER JOIN, LEFT JOIN, RIGHT JOIN und FULL JOIN. Ein spezieller Typ des Joins, der weniger bekannt, aber dennoch nützlich ist, ist der NATURAL JOIN.

Was ist ein Natural Join?

Ein NATURAL JOIN ist eine Art von Join, der automatisch die Spalten mit denselben Namen in beiden Tabellen verknüpft. Das bedeutet, dass Sie die Spalten, die zum Verbinden der Tabellen verwendet werden, nicht explizit angeben müssen. Stattdessen übernimmt die Datenbank-Engine diese Aufgabe für Sie. Dies kann den Code kürzer und einfacher machen, insbesondere wenn viele Spalten übereinstimmen.

Syntax eines Natural Join

Die Syntax eines NATURAL JOIN ist einfach und übersichtlich. Hier ein Beispiel:

SELECT * 
FROM table1
NATURAL JOIN table2;

In diesem Beispiel sucht die Datenbank-Engine automatisch nach Spalten in table1 und table2 mit denselben Namen und verknüpft diese.

Vorteile von Natural Joins

  1. Einfachheit: Der offensichtlichste Vorteil eines NATURAL JOIN ist die Einfachheit. Sie müssen nicht explizit die Spaltennamen angeben, was den SQL-Code kürzer und leichter lesbar macht.
  2. Automatisches Mapping: NATURAL JOIN übernimmt automatisch das Mapping der Spalten, was besonders nützlich ist, wenn mehrere Spalten übereinstimmen.
  3. Weniger Fehleranfällig: Da die Spaltennamen nicht manuell eingegeben werden müssen, besteht eine geringere Wahrscheinlichkeit für Tippfehler oder falsche Zuordnungen.

Nachteile von Natural Joins

  1. Unvorhersehbarkeit: Der größte Nachteil eines NATURAL JOIN ist die Unvorhersehbarkeit. Wenn neue Spalten zu einer der Tabellen hinzugefügt werden, die denselben Namen wie bestehende Spalten haben, kann dies zu unerwarteten Ergebnissen führen.
  2. Eingeschränkte Kontrolle: Da die Spalten automatisch verknüpft werden, haben Sie weniger Kontrolle über den Join-Prozess. Dies kann problematisch sein, wenn nur bestimmte Spalten verknüpft werden sollen.
  3. Lesbarkeit für andere Entwickler: Obwohl NATURAL JOIN den Code kürzer macht, kann es für andere Entwickler, die den Code lesen und warten, weniger klar sein, welche Spalten verknüpft werden. Explizite Joins sind oft klarer und verständlicher.

Wann sollten Sie Natural Joins verwenden?

NATURAL JOINs eignen sich am besten für Situationen, in denen Sie sicher sind, dass die Tabellen nur eine oder wenige Spalten mit denselben Namen haben und diese Spalten die einzigen sind, die verknüpft werden sollen. Wenn Sie jedoch komplexere Joins benötigen oder mehr Kontrolle über den Join-Prozess wünschen, sind explizite Joins wie INNER JOIN die bessere Wahl.

Fazit

NATURAL JOINs sind ein nützliches Werkzeug in SQL, das den Join-Prozess vereinfachen kann, indem es automatisch Spalten mit denselben Namen verknüpft. Während sie in bestimmten Situationen sehr praktisch sein können, ist es wichtig, ihre Einschränkungen und potenziellen Fallstricke zu kennen. Verwenden Sie NATURAL JOINs mit Bedacht und stellen Sie sicher, dass sie für Ihre spezifischen Anwendungsfälle geeignet sind. Wie bei vielen Aspekten der Softwareentwicklung gibt es keine Einheitslösung, und die Wahl des richtigen Werkzeugs hängt von den spezifischen Anforderungen und Umständen Ihres Projekts ab.

Die Bedeutung stabiler Enum-Werte in C++ Klassen

In der C++ Programmierung ist es wichtig, Enum-Werte so zu definieren, dass sie über die Zeit stabile Integer-Werte behalten, besonders wenn sie in Datenbanken gespeichert werden.

Warum ist die Stabilität von Enum-Werten wichtig?

Enums bieten eine bequeme Möglichkeit, benannte Konstanten zu definieren, aber ihre zugewiesenen Integer-Werte können sich ändern, wenn neue Werte hinzugefügt werden oder die Reihenfolge geändert wird. Dies kann zu Inkonsistenzen führen, wenn Enum-Werte in Datenbanken gespeichert und später interpretiert werden.

Beispiel: Eine Klasse mit Enum-Werten

Angenommen, wir haben eine Klasse Event, die verschiedene Ereignistypen speichert:

enum class EventType {
    UNKNOWN = 0,
    STARTED = 1,
    STOPPED = 2,
    PAUSED = 3
};

class Event
{
public:
    Event(EventType type = EventType::UNKNOWN) 
        : eventType(type)
    {
        // Weitere Initialisierungen können hier erfolgen
    }

    EventType getType() const {
        return eventType;
    }

    void setType(EventType type) {
        eventType = type;
    }

    int getTypeAsInt() const {
        return static_cast<int>(eventType);
    }

private:
    EventType eventType;
};

Sicherstellen stabiler Enum-Werte für die Datenbank

Um sicherzustellen, dass Enum-Werte über die Zeit stabil bleiben und in Datenbanken konsistent gespeichert werden können, ist es wichtig, ihnen explizite Integer-Werte zuzuweisen. Im obigen Beispiel wurden den Enum-Werten feste Integer-Werte zugewiesen (UNKNOWN = 0, STARTED = 1, usw.). Dies stellt sicher, dass sich die zugewiesenen Integer-Werte nicht ändern, wenn neue Enum-Werte hinzugefügt werden.

Fazit

Die Stabilität von Enum-Werten in C++ ist entscheidend, insbesondere wenn sie persistent in Datenbanken gespeichert werden sollen. Durch das explizite Zuweisen von festen Integer-Werten zu Enum-Konstanten stellt man sicher, dass die Interpretation dieser Werte über die Zeit konsistent bleibt.

Integration von Meld mit Git

Wenn Sie Git zur Versionskontrolle verwenden und auf der Suche nach einem benutzerfreundlichen Diff- und Merge-Tool sind, ist Meld eine ausgezeichnete Wahl. In diesem Artikel erfahren Sie, wie Sie Meld installieren und nahtlos in Ihre Git-Workflows integrieren können.

Schritt 1: Installation von Meld

Stellen Sie zunächst sicher, dass Sie Meld installiert haben. Sie können Meld von der offiziellen Website herunterladen und installieren: Meld.

Schritt 2: Konfiguration von Meld als Diff-Tool

Um Meld als Diff-Tool in Git zu konfigurieren, öffnen Sie eine Eingabeaufforderung (CMD, PowerShell, Terminal) und führen Sie die folgenden Befehle aus:

git config --global diff.tool meld
git config --global difftool.meld.path [Pfad-zu-Meld]

Ersetzen Sie [Pfad-zu-Meld] durch den tatsächlichen Pfad zur Meld-Programmdatei. Beispielsweise könnte der Pfad unter Windows so aussehen:

git config --global difftool.meld.path "C:/Program Files (x86)/Meld/Meld.exe"

Unter Linux oder macOS könnte es einfach meld sein, wenn Meld im Systempfad liegt:

git config --global difftool.meld.path meld

Schritt 3: Konfiguration von Meld als Merge-Tool

Um Meld als Merge-Tool zu konfigurieren, führen Sie diese Befehle aus:

git config --global merge.tool meld
git config --global mergetool.meld.path [Pfad-zu-Meld]
git config --global mergetool.meld.trustExitCode true

Auch hier sollten Sie [Pfad-zu-Meld] durch den tatsächlichen Pfad zur Meld-Programmdatei ersetzen.

Schritt 4: Zusätzliche Einstellungen für Benutzerfreundlichkeit

Um sicherzustellen, dass Git Sie nicht jedes Mal nach einer Bestätigung fragt, wenn Sie Meld verwenden, können Sie die folgenden Befehle ausführen:

git config --global difftool.prompt false
git config --global mergetool.prompt false

Verwendung von Meld mit Git

Nachdem Sie die Konfiguration abgeschlossen haben, können Sie Meld sowohl für Diffs als auch für Merges verwenden.

Diff-Tool verwenden: Um Meld als Diff-Tool zu nutzen, verwenden Sie git difftool anstelle von git diff:

git difftool [commit1] [commit2] -- [dateiname]

Wenn Sie beispielsweise die Änderungen des letzten Commits vergleichen möchten, führen Sie Folgendes aus:

git difftool HEAD~1 HEAD -- [dateiname]

Merge-Tool verwenden: Bei Merge-Konflikten können Sie Meld verwenden, um diese zu lösen:

git mergetool

Fazit

Durch die Integration von Meld in Ihre Git-Workflows profitieren Sie von einer benutzerfreundlichen und leistungsfähigen Umgebung zum Vergleichen und Zusammenführen von Dateien. Die oben beschriebenen Schritte sollten Ihnen helfen, Meld schnell und effektiv zu konfigurieren. Probieren Sie es aus und erleben Sie, wie Meld Ihre Arbeit mit Git noch produktiver macht.

qCompress und qUncompress auf der Kommandozeile

Die in Qt eingebaute Methode zur Komprimierung von Datenströmen mit qCompress und qUncompress generiert leider keine standardkonformen Dateien, die mit externen Tools geöffnet werden können.

Ich habe daher ein kleines Tool geschrieben, das den Umgang mit diesen Dateien auf der Kommandozeile erleichtert. Die einzige Abhängigkeit besteht dabei zu Qt Core (Qt5Core.dll unter Windows). Der Quelltext steht unter GPL 3 zur freien Verfügung und kann hier runtergeladen werden:

https://github.com/thomasbutzbach/qtArchiver

Datensicherung für Ubuntu-Server verschlüsselt auf Strato HiDrive

Eine automatische, nächtliche Datensicherung für einen Ubuntu-Server ist schnell eingerichtet. Dabei werden die Daten lokal verschlüsselt und dann per SFTP verschlüsselt auf einen Strato HiDrive-Account übertragen. Dazu verwende ich das Linux-Tool Duplicity.

Zunächst richtet man ein kleines Shell-Skript ein und legt es im eigenen Home-Verzeichnis an. Mein Beispiel-Skript führt monatlich eine vollständige Datensicherung durch. Alle weiteren nächtlichen Sicherungen erfolgen dann inkrementell. Außerdem lasse ich durch Duplicity automatisch alle Sicherungen entfernen, die älter als 3 Monate sind. So muss ich mir keine Gedanken darüber machen, dass der Strato HiDrive-Speicher auf Dauer vollläuft.

#!/bin/bash
export PASSPHRASE="MeineStrengGeheimePassphrase"
export FTP_PASSWORD=<SFTP-Passwort von Strato>

# monatliche Vollsicherung (1M)
duplicity --full-if-older-than 1M /home/user sftp://share-xxxx@sftp.hidrive.strato.com/users/share-xxxx/Serversicherungen

# Backups wegräumen, die älter als 3 Monate sind
duplicity remove-older-than 3M --force sftp://share-xxxx@sftp.hidrive.strato.com/users/share-xxxx/Serversicherungen

unset PASSPHRASE
unset FTP_PASSWORD

Hier sind nur noch die markierten Stellen individuell anzupassen bzw. mit den Strato-Zugangsdaten zu füllen.

Beim ersten Aufruf muss man zunächst den SSH-Fingerabdruck des Strato-Server bestätigen. Dieser wird dabei in der .ssh/known_hosts abgelegt. Daher sollte man das Sicherungsskript einmal manuell durchlaufen lassen, bevor man die Datensicherung als Cronjob anlegt. Ist der SSH-Fingerabdruck hinterlegt, läuft das Sicherungsskript ohne weitere Nachfrage automatisch durch.

Dieses Sicherungsskript kann man nun z.B. über einen Cronjob jede Nacht  um 23:00 Uhr aufrufen. Um einen solchen Cronjob anzulegen, wechselt man mit crontab -e in den Cronjob-Editor und legt folgende neue Zeile an:

0 23 * * * /home/user/backup.sh >> /home/user/logs/backup.log 2> /home/user/logs/backup.error.log

Dabei ist darauf zu achten, dass das logs-Verzeichnis bereits angelegt und schreibbar ist, da die Datensicherung sonst fehlschlägt.

Für die Wiederherstellung der so gesicherten Daten habe ich ein kleines Restore-Skript zusammengestellt, das ich im Notfall nur noch schnell anpassen muss, ohne erst zu recherchieren, wie genau eine Datenwiederherstellung mit Duplicity funktioniert.

export PASSPHRASE="MeineStrengGeheimePassphrase"
export FTP_PASSWORD=<SFTP-Passwort von Strato>

duplicity restore sftp://share-xxxx@sftp.hidrive.strato.com/users/share-xxxx/Serversicherungen /home/user/backup

unset PASSPHRASE
unset FTP_PASSWORD

Die beiden Skriptdateien sollte man dann noch mit einem chmod 700 backup.sh bzw. chmod 700 restore.sh vor den neugierigen Blicken anderer User auf dem System schützen.

Gewichtetes Zufallsergebnis aus einer SQLite-Tabelle

Aus einer SQLite-Tabelle soll mit einem einfachen SELECT-Query ein gewichtetes Zufallsergebnis, also eine zufällig ausgewählte Zeile selektiert werden. Die in der Tabelle vorhandenen Zeilen sollen aber nicht mit der gleichen Wahrscheinlichkeit auftreten, sondern nach einem zeilenspezifischen Integer-Wert gewichtet werden.

In der Tabelle Event liegen folgende Spalten vor:

id | bezeichnung | gewichtung

Eine Zeile mit gewichtung = 2 soll also doppelt so häufig selektiert werden wie eine Zeile mit gewichtung = 1.

Eine (pseudo-)zufällig ausgewählte, dabei aber nach gewichtung gewichtete Zeile erhält man nun, unter Zuhilfenahme der kumulierten Gewichtung, durch folgende Abfrage:

SELECT
  t.id, t.bezeichnung, t.gewichtung
FROM
  Event t
INNER JOIN
(
  SELECT t.id, SUM(tt.gewichtung) AS cum_weight
  FROM Event t
  INNER JOIN Event tt ON tt.id <= t.id
  GROUP BY t.id
) tc ON tc.id = t.id
,( SELECT ABS(RANDOM() % SUM(gewichtung)) AS rnd FROM Event)
WHERE
  rnd >= (cum_weight - gewichtung)
  AND rnd < cum_weight
ORDER BY
  t.id ASC;

 

Echte Zufallszahlen mit Qt/C++

In einer Qt/C++-Application habe ich zum Erstellen von Zufallszahlen die eingebaute qrand()-Methode verwendet. Dazu hatte ich einmal beim Programmstart den Seed über die qsrand()-Methode initialisiert:

QTime time = QTime::currentTime();
qsrand((uint)time.msec());

Nach kurzer Zeit ist mir aufgefallen, dass die so generierten Zufallszahlen nur scheinbar zufällig sind. Ein großes Problem war insbesondere, dass alle Zahlen, die in der gleichen Millisekunde generiert wurden, gleich waren. Das war in meiner Anwendung häufig der Fall, weil ich zum Teil in einer Schleife (nahezu) gleichzeitig sehr viele Zufallszahlen generieren musste.

Schließlich bin ich beim C++-Standardheader <random> gelandet, der eine ganze Reihe unterschiedlicher Zufallsgeneratoren zur Verfügung stellt. Unter anderem steht mit dem Generator random_device ein Non-deterministic true random number generator bereit, den ich in meiner Applikation wie folgt implementiert habe:

std::random_device generator;
std::uniform_int_distribution<int> distribution(low, high);
int result = distribution(generator);
return result;

Ein schneller Test hat ergeben, dass die so generierten Zufallszahlen tatsächlich nicht nur bei jedem Programmstart unterschiedlich sind, sondern sich auch bei der Massengenerierung in einer Schleife immer unterscheiden.

Eine gute Quelle für die Möglichkeiten des <random>-Headers habe ich hier gefunden:

http://www.cplusplus.com/reference/random/

Ideale Spielplan-Berechnung mit Kantenfärbung

Das Problem

Für ein privates Projekt bin ich auf die Herausforderung gestoßen, aus einer gegebenen Liste von Mannschaften einen Spielplan zu erstellen. Das klingt zunächst extrem trivial, hat sich aber schnell als durchaus spannend herausgestellt.

Die Liste der Mannschaften liegt als Liste von Vereins-IDs vor. Das kann ja eigentlich nicht so kompliziert sein – man bestimmt einfach über eine simple verschachtelte for-Schleife alle Kombinationsmöglichkeiten der Listeneinträge und hat das Problem gelöst. Interessant wurde es erst, als ich mir die so generierten Paare genauer angeschaut habe. Dabei waren mehrere Randbedingungen verletzt, die ich implizit vorausgesetzt hatte.

Die Randbedingungen

A. Eine Mannschaft soll an jedem Spieltag nur höchstens einmal spielen.

Die einfache verschachtelte for-Schleife führt durch die beiden Laufvariablen zwangsläufig dazu, dass an jedem Spieltag entweder die Heimmannschaft oder die Gastmannschaft mehrere Spiele zu absolvieren hat. Das ist in der praktischen Anwendung nicht sinnvoll, keine Mannschaft kann mehr als ein Spiel an einem Spieltag spielen. Es ist daher zwingend notwendig, die Paarungen passend über alle Spieltage zu verteilen.

B. Nach möglichst wenigen Spieltagen soll jede Mannschaft gegen jede andere Mannschaft gespielt haben.

Diese Bedingung erfüllt auch die einfache Schleife (wenn auch in einer unbrauchbaren Reihenfolge). Am Ende der Saison soll jede denkbare Paarung zweimal stattgefunden haben. Dabei soll die Saison so kurz wie möglich gehalten werden, d.h. die Anzahl der Spieltage soll nicht größer sein als unbedingt notwendig um alle anderen Bedingungen zu erfüllen. Daraus ergibt sich, dass etwa bei 18 Mannschaften nur 2×17 Spieltage generiert werden sollen.

C. Heim- und Auswärtsspiele sollen sich idealerweise abwechseln.

Jede Mannschaft soll im Verlauf der Saison gleich viele Heim- und Auswärtsspiele haben. Dabei sollen sich Heim- und Auswärtsspiele abwechseln, so dass auf ein Heimspiel immer ein Auswärtsspiel folgt. Dieses Problem ist nicht ideal lösbar.

Die Lösung

Nach einer anregenden Diskussion mit zwei Kollegen stand schnell fest, dass die Berechnung eines solchen „idealen“ Spielplans nicht so trivial ist wie zunächst gedacht.

Nachdem eine Kollegin mich mit den Stichworten „Graph“ und „Kantenfärbung“ in die richtige Richtung geschubst hatte, konnte ich die mathematischen Hintergründe und die Lösung für das Problem auf einer Seite der RWTH Aachen finden. Dort wird das „Spielplan-Problem“ mithilfe eines Kantenfärbungs-Algorithmus gelöst.

In der Linkliste dieses Artikels war außerdem eine Implementierung dieses Algorithmus in PHP von Andy Theiler zu finden. Andy Theiler hat seine Implementierung dankenswerterweise unter eine freie BSD-Lizenz gestellt, so dass ich seinen Code für meine Bedürfnisse anpassen und nach C++/Qt portieren konnte.

Diese angepasste Portierung stelle ich unter gleicher Lizenz bei Github zur Verfügung.

Website-Relaunch

Nachdem der letzte Relaunch meiner Website nun schon wieder 4 Jahre her ist, war es Zeit für eine Neugestaltung.

Technisch hat sich in dieser Zeit viel getan. Die neue Seite wird nun mit einem modernen WebCMS betrieben und sieht dank Responsive Design auch auf unterschiedlichen Endgeräten gleichermaßen gut aus. Auch das Aussehen insgesamt habe ich heller und lesefreundlicher gestaltet, die Inhalte neu geordnet und klarer gefasst und neue Texte geschrieben.

Bei dieser Gelegenheit habe ich auch die Möglichkeit integriert, Blogeinträge zu verfassen, sodass ich hier in Zukunft regelmäßig zu den unterschiedlichsten IT-Themen bloggen kann.

Über Feedback, Hinweise und Fragen zur neuen Website freue ich mich natürlich sehr.