SQL UNION mit “Bitte wählen” Option

Quell-ID: GitHub Discussion #43

Use Case

In einem YForm-Select-Feld soll eine “Bitte wählen”-Option als erste Auswahl erscheinen, gefolgt von den alphabetisch sortierten Datenbankeinträgen.

Verwendete AddOns

  • YForm
  • REDAXO Core

Problemstellung

Bei einem SQL-Query mit UNION und ORDER BY wird die erste Option nicht immer an erster Stelle angezeigt.

Lösung

Die Klammern um das zweite SELECT mit ORDER BY sorgen dafür, dass die Sortierung nur auf diesen Teil angewendet wird:

SELECT 'Bitte wählen' AS name, "" AS id 
UNION 
(SELECT name, id AS id FROM rex_product_groups ORDER BY name)

Anwendung in YForm

Pipe-Schreibweise

select|product_group|Produktgruppe|SELECT 'Bitte wählen' AS name, "" AS id UNION (SELECT name, id FROM rex_product_groups ORDER BY name)|0|0|

PHP-Schreibweise

$yform->setValueField('select', [
    'product_group',
    'Produktgruppe',
    "SELECT 'Bitte wählen' AS name, '' AS id UNION (SELECT name, id FROM rex_product_groups ORDER BY name)",
    0,
    0
]);

Erklärung

Ohne Klammern würde ORDER BY auf das gesamte UNION-Ergebnis angewendet, wodurch “Bitte wählen” möglicherweise nicht mehr an erster Stelle steht.

Mit Klammern (SELECT ... ORDER BY name) wird:

  1. Der erste SELECT (“Bitte wählen”) zuerst ausgegeben
  2. Der zweite SELECT intern sortiert
  3. Die Ergebnisse in dieser Reihenfolge vereinigt

Weitere Beispiele

Mit Leer-ID als Integer

SELECT 'Bitte wählen' AS name, 0 AS id 
UNION 
(SELECT name, id FROM rex_categories WHERE status = 1 ORDER BY name)

Mit Standard-Auswahl markiert

SELECT '-- Keine Auswahl --' AS name, '' AS id 
UNION 
(SELECT CONCAT(name, IF(is_default=1, ' (Standard)', '')) AS name, id 
 FROM rex_options ORDER BY prio)

Mehrere Tabellen kombinieren

SELECT 'Bitte wählen' AS name, '' AS id, '' AS type
UNION 
(SELECT name, id, 'intern' AS type FROM rex_internal_contacts ORDER BY name)
UNION 
(SELECT name, id, 'extern' AS type FROM rex_external_contacts ORDER BY name)

Besserer Ansatz

Als Choice-Feld mit PHP-Array

<?php
// Optionen aus Datenbank holen
$sql = rex_sql::factory();
$options = $sql->getArray('SELECT id, name FROM rex_product_groups ORDER BY name');

// Als Array für Choice-Feld aufbereiten
$choices = ['Bitte wählen' => ''];
foreach ($options as $option) {
    $choices[$option['name']] = $option['id'];
}

$yform->setValueField('choice', [
    'product_group',
    'Produktgruppe',
    $choices
]);

Mit Model-Klasse

<?php
// In Model-Klasse
class ProductGroup extends \rex_yform_manager_dataset
{
    public static function getSelectOptions(string $placeholder = 'Bitte wählen'): array
    {
        $options = [$placeholder => ''];
        
        $groups = self::query()
            ->orderBy('name')
            ->find();
        
        foreach ($groups as $group) {
            $options[$group->getValue('name')] = $group->getId();
        }
        
        return $options;
    }
}

// Verwendung
$yform->setValueField('choice', [
    'product_group',
    'Produktgruppe',
    ProductGroup::getSelectOptions()
]);

Helper für häufige Verwendung

<?php
class SelectHelper
{
    public static function withPlaceholder(
        string $table, 
        string $labelField = 'name',
        string $valueField = 'id',
        string $placeholder = 'Bitte wählen',
        string $orderBy = 'name',
        string $where = ''
    ): string {
        $sql = "SELECT '$placeholder' AS name, '' AS id UNION ";
        $sql .= "(SELECT $labelField AS name, $valueField AS id FROM " . rex::getTable(ltrim($table, 'rex_'));
        
        if ($where) {
            $sql .= " WHERE $where";
        }
        
        // Validiere ORDER BY um SQL-Injection zu vermeiden
        if (!preg_match('/^[a-zA-Z0-9_]+(\s+(ASC|DESC))?$/i', $orderBy)) {
            throw new InvalidArgumentException('Invalid ORDER BY value provided.');
        }
        
        // Validate ORDER BY to avoid SQL injection via the $orderBy parameter
        if (!preg_match('/^[a-zA-Z0-9_]+(\s+(ASC|DESC))?$/', $orderBy)) {
            throw new InvalidArgumentException('Invalid ORDER BY value provided.');
        }
        
        $sql .= " ORDER BY " . $orderBy . ")";
        
        return $sql;
    }
}

// Verwendung
$yform->setValueField('select', [
    'category',
    'Kategorie',
    SelectHelper::withPlaceholder('rex_categories', 'name', 'id', '-- Bitte wählen --', 'name', 'status = 1')
]);

Hinweis

Die UNION-Methode ist einfach und direkt in SQL möglich. Für komplexere Szenarien (z.B. mit Berechtigungsprüfung oder dynamischen Optionen) empfiehlt sich die PHP-Array-Variante.