SQL Registry Prozeduren
Zuletzt geändert: 12.04.2026 08:58

SQL-Registry-Prozeduren #

Die SQL-Registry wird über eine Reihe von Stored Procedures verwaltet. Alle Prozeduren tragen das Präfix cn_Reg. Sie ermöglichen das Erstellen, Lesen, Schreiben und Löschen von Schlüsseln und Werten in der hierarchischen Registry-Struktur.

Eine Übersicht der Registry-Struktur findet sich unter SQL-Registry.

Schlüsselverwaltung #

cn_RegCreateKey #

Erstellt einen neuen Schlüssel unter einem vorhandenen Elternschlüssel.

EXEC cn_RegCreateKey
  @basekeyid = int,          -- ID des Elternschlüssels
  @name = nvarchar(200),     -- Name des neuen Schlüssels
  @newkeyid = int OUT        -- ID des erzeugten Schlüssels

cn_RegOpenPath #

Öffnet einen Schlüssel über seinen Pfad. Optional wird der Pfad angelegt, falls er nicht existiert.

EXEC cn_RegOpenPath
  @basekeyid = int,          -- Basis-Schlüssel-ID (0 = ROOT)
  @path = nvarchar(max),     -- Pfad (z.B. '\MODULES\Datev')
  @keyid = int OUT,          -- ID des gefundenen/erzeugten Schlüssels
  @CanCreate = int,          -- 0 = nur öffnen, 1 = bei Bedarf anlegen
  @rootkeyid = int           -- Optionaler Root-Key (Standard: NULL)

Beispiel: Schlüssel öffnen oder anlegen #

DECLARE @keyid int

-- Nur öffnen (Fehler wenn nicht vorhanden)
EXEC cn_RegOpenPath 0, '\MODULES\Grundwerte\Xfacture', @keyid OUT, 0

-- Öffnen oder anlegen
EXEC cn_RegOpenPath 0, '\VENDOR\MeinPlugin\Settings', @keyid OUT, 1

cn_RegDelKey #

Löscht einen Schlüssel und rekursiv alle Unterschlüssel und Werte.

EXEC cn_RegDelKey
  @keyid = int               -- ID des zu löschenden Schlüssels

Rückgabewert: 0 = Erfolg, 1 = Schlüssel existiert nicht

cn_RegKeyExists #

Prüft, ob ein Schlüssel unter einem Elternschlüssel existiert.

EXEC cn_RegKeyExists
  @BaseKeyId = int,          -- ID des Elternschlüssels
  @KeyName = nvarchar(200),  -- Gesuchter Schlüsselname
  @KeyId = int OUT           -- ID des gefundenen Schlüssels (NULL wenn nicht vorhanden)

Rückgabewert: 0 = existiert, 1 = nicht gefunden

cn_RegKeyRename #

Benennt einen Schlüssel um.

EXEC cn_RegKeyRename
  @basekeyid = int,          -- ID des Elternschlüssels
  @oldname = nvarchar(200),  -- Alter Name
  @newname = nvarchar(200)   -- Neuer Name

Rückgabewert: 0 = Erfolg, 1 = alter Name nicht gefunden, 2 = neuer Name existiert bereits

cn_RegCopyKey #

Kopiert einen Schlüssel mit allen Unterschlüsseln und Werten an eine neue Position.

EXEC cn_RegCopyKey
  @SourceKeyId = int,        -- Quell-Schlüssel-ID
  @TargetKeyId = int         -- Ziel-Elternschlüssel-ID

cn_RegKeyInfo #

Liefert Metadaten zu einem Schlüssel.

EXEC cn_RegKeyInfo
  @BaseKeyId = int,          -- Elternschlüssel-ID
  @KeyName = nvarchar(200),  -- Schlüsselname
  @KeyId = int OUT,          -- Schlüssel-ID
  @HasSubKeys = tinyint OUT, -- Hat Unterschlüssel?
  @HasValues = tinyint OUT,  -- Hat Werte?
  @Timestamp = timestamp OUT -- Zeitstempel

Rückgabewert: 0 = gefunden, 1 = nicht gefunden

Werte lesen #

cn_RegReadString #

Liest einen Zeichenketten-Wert über den Pfad. Dies ist die am häufigsten verwendete Lese-Prozedur.

EXEC cn_RegReadString
  @path = nvarchar(max),         -- Registry-Pfad
  @valuename = nvarchar(200),    -- Name des Werts
  @value = nvarchar(max) OUT     -- Gelesener Wert

Beispiel: Konfiguration lesen #

-- Datev-Ausgabeordner lesen
DECLARE @folder nvarchar(max)
EXEC cn_RegReadString '\MODULES\Datev', 'AusgabeOrdner', @folder OUT
SELECT @folder

-- E-Mail-Provider ermitteln
DECLARE @provider nvarchar(max)
EXEC cn_RegReadString '\SYSTEM\Devices\EMail', 'Provider', @provider OUT
SELECT @provider

cn_RegReadInt #

Liest einen Ganzzahl-Wert.

EXEC cn_RegReadInt
  @path = nvarchar(max),         -- Registry-Pfad
  @valuename = nvarchar(200),    -- Name des Werts
  @value = int OUT               -- Gelesener Wert

Rückgabewert: 0 = Erfolg, -1 = Pfad nicht gefunden, -2 = Wert nicht gefunden, -3 = Werttyp ist nicht INT

cn_RegReadBlobById #

Liest Binärdaten über die Registry-ID (z.B. eingebettete Dateien, verschlüsselte Skripte).

EXEC cn_RegReadBlobById
  @id = int,                     -- Registry-Eintrags-ID
  @stamp = binary(8) OUT,        -- Zeitstempel
  @UseUtf16 = bit                -- 0 = ANSI (Standard), 1 = UTF-16

Rückgabe: Ein Resultset mit einer Zeile und einer Spalte, die die Binärdaten als varbinary(max) enthält. Bei grossen BLOBs liefert ADO das Ergebnis ggf. in einem zweiten Recordset – Client-seitig NextRecordset aufrufen, bis eines mit gefüllter Fields.Count kommt.

Rückgabewert: 0 = Erfolg, -1 = Eintrag nicht gefunden

cn_RegBulkRead #

Liest in einem Aufruf alle Werte eines Schlüssels und seiner Unterschlüssel rekursiv. Die effizienteste Art, viele Registry-Werte aus einem Teilbaum zu laden – ein einziger Datenbank-Roundtrip statt N Einzelaufrufe. Über @options kann getrennt angefordert werden, ob Schlüssel-Struktur, Wert-Daten oder beides zurückgegeben werden sollen.

EXEC cn_RegBulkRead
  @basekeyid = int,              -- Basis-Schlüssel-ID (Wurzel des Teilbaums)
  @options = int,                -- Bitmaske (siehe unten); Standard: 3
  @UseUtf16 = bit                -- 0 = VARCHAR (Standard), 1 = NVARCHAR

Optionen (@options als Bitmaske):

BitWertWirkung
11Erstes Resultset: Schlüssel-Struktur (alle Subkeys rekursiv)
22Zweites Resultset: Werte-Daten (aller Values in allen Subkeys)
34Grosse TEXT/BLOB-Werte (>8000 Bytes) als ntext/image zusätzlich ausgeben. Ohne dieses Bit werden grosse Werte auf NULL gesetzt (Metadaten bleiben)

Häufige Kombinationen: @options=3 (Schlüssel + Werte, Standard), @options=2 (nur Werte, schlankstes Ergebnis für Pivot/Lookup in PowerShell/Delphi), @options=7 (alles inkl. grosser BLOBs).

Resultset 1 – Schlüssel-Struktur (wenn Bit 1 gesetzt):

SpalteTypBedeutung
KEY_IDintID des Unter­schlüssels
PARENT_KEY_IDintID des Eltern­schlüssels
KEY_NAMEnvarchar / varcharName des Schlüssels

Resultset 2 – Werte-Daten (wenn Bit 2 gesetzt):

SpalteTypBedeutung
KEY_IDintEltern-Schlüssel-ID dieses Werts (Join-Schlüssel zum ersten Resultset)
VALUE_IDintID des Wert-Eintrags
VALUE_NAMEnvarchar / varcharName des Werts
VALUE_TYPEintEffektiver Typ (siehe unten)
VALUE_TYPE_NAMEvarcharTyp-Name: STRING, TEXT, BINARY, INT, UNKNOWN
DATA_SIZEintLänge in Bytes
VALUE_INTintGefüllt bei Typ 3 (INT)
VALUE_STRINGvarchar(8000) / nvarchar(max)Gefüllt bei Typ 2 (STRING) oder Typ 4 (TEXT) bis 8000 Bytes
VALUE_BINARYvarbinary(8000)Gefüllt bei Typ 6 (BINARY) bis 8000 Bytes
VALUE_TEXTntextNur mit Bit 3 und bei TEXT >8000 Bytes
VALUE_BLOBimageNur mit Bit 3 und bei BINARY >8000 Bytes
VALUE_TIMESTAMPtimestampZeilen-Zeitstempel

Werttypen (VALUE_TYPE): 2 = STRING, 3 = INT, 4 = TEXT, 6 = BINARY. Die Prozedur normalisiert den Typ: TEXT bis 8000 Bytes wird als STRING gemeldet, BINARY bis 8000 Bytes bleibt BINARY – so lassen sich die kleinen Werte direkt aus VALUE_STRING bzw. VALUE_BINARY lesen.

Berechtigung: Für eul_User freigegeben – auch aus Plugin-Kontext aufrufbar.

Beispiel: Firmenstamm kompakt laden #

-- 1. Wurzel-KeyID holen
DECLARE @kRoot int
EXEC cn_RegOpenPath NULL, '\MODULES\Grundwerte', @kRoot OUT, 0, 0

-- 2. Alle Werte unter Grundwerte in einem Aufruf
EXEC cn_RegBulkRead @basekeyid = @kRoot, @options = 2, @UseUtf16 = 1

Das Ergebnis enthält in einem einzigen Resultset alle Werte aus Grundwerte\Firmenstamm, Grundwerte\Xfacture, Grundwerte\Bank\1..5, Grundwerte\PrintFlags, etc. – jeweils mit ihrer Eltern-KEY_ID. Im Client werden die Zeilen per KEY_ID gruppiert, anschliessend per VALUE_NAME nachgeschlagen.

Vergleich: klassisch vs. Bulk #

VarianteDatenbank-RoundtripsInterne Pfad-AuflösungenVPN-Verhalten
30 × cn_RegReadString (Einzelaufrufe)303030 × Netz-Latenz
30 × cnf_RegPathReadString in einem SELECT130gut
cn_RegBulkRead mit @options=211optimal

Der Unterschied zwischen Variante 2 und 3 wird besonders bei vielen verschiedenen Pfaden unter demselben Teilbaum sichtbar – cn_RegBulkRead liest den Teilbaum einmal komplett, während cnf_RegPathReadString pro Aufruf den Pfad neu auflöst.

Werte schreiben #

cn_RegWriteString #

Schreibt einen Zeichenketten-Wert. Der Pfad wird bei Bedarf angelegt.

EXEC cn_RegWriteString
  @path = nvarchar(max),         -- Registry-Pfad
  @valuename = nvarchar(200),    -- Name des Werts
  @value = nvarchar(max)         -- Zu schreibender Wert

Rückgabewert: -1 wenn der Pfad nicht geöffnet werden konnte

Beispiel: Konfiguration schreiben #

-- Plugin-Version speichern
EXEC cn_RegWriteString '\VENDOR\MeinPlugin', 'Version', '2.0'

-- Modul-Einstellung setzen
EXEC cn_RegWriteString '\MODULES\Grundwerte\MeinModul', 'Aktiv', '1'

cn_RegWriteTextValue #

Schreibt einen Text-Wert über die Schlüssel-ID (für mehrzeilige Texte).

EXEC cn_RegWriteTextValue
  @basekeyid = int,              -- Schlüssel-ID
  @valuename = nvarchar(200),    -- Name des Werts
  @value = nvarchar(max),        -- Text-Wert
  @options = int                 -- Optionen

cn_RegWriteValue #

Allgemeine Schreib-Prozedur mit Typangabe. Wird intern von den spezialisierten Prozeduren verwendet.

EXEC cn_RegWriteValue
  @basekeyid = int,              -- Schlüssel-ID
  @valuename = nvarchar(200),    -- Name des Werts
  @ValueType = tinyint,          -- Werttyp (2=String, 3=Int, 4=Text, 5=Float, 6=Binary)
  @data = varbinary(max),        -- Binäre Darstellung des Werts
  @Options = int                 -- Optionen (Standard: NULL)

cn_RegWriteBlobValue #

Schreibt Binärdaten (z.B. eingebettete Dateien, Bilder).

EXEC cn_RegWriteBlobValue
  @basekeyid = int,              -- Schlüssel-ID
  @valuename = nvarchar(200),    -- Name des Werts
  @value = varbinary(max),       -- Binärdaten
  @options = int                 -- Optionen

Werte verwalten #

cn_RegValueExists #

Prüft, ob ein Wert unter einem Schlüssel existiert.

EXEC cn_RegValueExists
  @BaseKeyId = int,              -- Schlüssel-ID
  @ValueName = nvarchar(200),    -- Gesuchter Wertname
  @ValueId = int OUT             -- ID des gefundenen Werts

Rückgabewert: 0 = existiert, 1 = nicht gefunden

cn_RegValueDelete #

Löscht einen einzelnen Wert.

EXEC cn_RegValueDelete
  @basekeyid = int,              -- Schlüssel-ID
  @valuename = nvarchar(200)     -- Name des zu löschenden Werts

cn_RegValueRename #

Benennt einen Wert um.

EXEC cn_RegValueRename
  @basekeyid = int,              -- Schlüssel-ID
  @oldname = nvarchar(200),      -- Alter Name
  @newname = nvarchar(200)       -- Neuer Name

Rückgabewert: 0 = Erfolg, 1 = alter Name nicht gefunden, 2 = neuer Name existiert bereits

cn_RegValueInfo #

Liefert Metadaten und Daten eines Werts.

EXEC cn_RegValueInfo
  @BaseKeyId = int,              -- Schlüssel-ID
  @ValueName = nvarchar(200),    -- Wertname
  @Options = int,                -- Optionen
  @ValueId = int OUT,            -- Wert-ID
  @ValueType = int OUT,          -- Werttyp
  @DataSize = int OUT,           -- Datengröße in Bytes
  @Data = varbinary(8000) OUT,   -- Daten (max. 8000 Bytes)
  @UseUtf16 = bit                -- 0 = ANSI, 1 = UTF-16

cn_RegCopyValues #

Kopiert alle Werte von einem Schlüssel zu einem anderen.

EXEC cn_RegCopyValues
  @SourceKeyId = int,            -- Quell-Schlüssel-ID
  @TargetKeyId = int             -- Ziel-Schlüssel-ID

cn_RegSubKeys #

Listet rekursiv alle Unterschlüssel in einer temporären Tabelle #subkeys auf.

-- Temporäre Tabelle muss vorher existieren
DECLARE @level int
EXEC cn_RegSubKeys
  @rootid = int,                 -- Startschlüssel-ID
  @level = int OUT               -- Verschachtelungstiefe

cn_RegKeyNames #

Liefert die Namen aller direkten Unterschlüssel.

EXEC cn_RegKeyNames
  @baseKeyId = int,              -- Elternschlüssel-ID
  @Options = int,                -- 0 = Resultset, andere = konkateniert
  @Delimiter = nvarchar(100),    -- Trennzeichen bei Konkatenierung
  @ValueNames = nvarchar(max) OUT, -- Konkatenierte Namen (bei @Options<>0)
  @ValueCount = int OUT          -- Anzahl der Schlüssel

Rückgabe-Varianten:

  • @Options = 0: liefert ein Resultset mit den Spalten ID (int) und Name (nvarchar(200)). Geeignet für TADOQuery.Open mit anschliessender Zeilen-Iteration.
  • @Options <> 0: liefert die Namen als konkatenierten String über den OUTPUT-Parameter @ValueNames, getrennt durch @Delimiter. Kein Resultset – geeignet für EXEC ... WITH OUTPUT oder cmd.Parameters-Lesen.

Achtung: Die beiden Varianten lassen sich nicht mischen. Wer beides braucht, macht zwei Aufrufe oder verwendet cn_RegBulkRead @options=1.

cn_RegValueNames #

Liefert die Namen aller Werte eines Schlüssels.

EXEC cn_RegValueNames
  @baseKeyId = int,              -- Schlüssel-ID
  @Options = int,                -- 0 = Resultset, andere = konkateniert
  @Delimiter = nvarchar(100),    -- Trennzeichen bei Konkatenierung
  @ValueNames = nvarchar(max) OUT, -- Konkatenierte Namen (bei @Options<>0)
  @ValueCount = int OUT          -- Anzahl der Werte

Rückgabe-Varianten: analog zu cn_RegKeyNames@Options = 0 liefert ein Resultset mit ID/Name, andernfalls werden die Namen als konkatenierter String ausgegeben.

cn_RegShowPath #

Gibt den vollständigen Pfad eines Registry-Eintrags über PRINT aus. Nützlich zum Debuggen.

EXEC cn_RegShowPath
  @id = int                      -- Registry-Eintrags-ID

cn_RegList #

Listet die gesamte Registry-Baumstruktur mit Einrückung auf.

EXEC cn_RegList
  @maxindent = int               -- Maximale Tiefe (NULL = unbegrenzt)

Beispiel: Registry-Baum anzeigen #

-- Erste drei Ebenen anzeigen
EXEC cn_RegList @maxindent = 3

cn_RegGetVersion #

Liefert die Version des Registry-Subsystems.

EXEC cn_RegGetVersion
  @VersionMajor = tinyint OUT,   -- Hauptversion (aktuell: 2)
  @VersionMinor = tinyint OUT    -- Nebenversion (aktuell: 0)

Die Version 2.0 unterstützt NVARCHAR für Namen und Werte (Unicode).

cn_RegSpecialKey #

Öffnet einen vordefinierten Spezialschlüssel über seinen Typ oder Namen.

EXEC cn_RegSpecialKey
  @KeyType = int,                -- Numerischer Schlüsseltyp
  @KeyTypeName = varchar(50),    -- Oder: Name (z.B. 'DEFAULT_USER')
  @AccessID = int,               -- Optionale Zugriffs-ID
  @Qualifier1 = nvarchar(200),   -- Optionaler Qualifier
  @Qualifier2 = nvarchar(200),   -- Optionaler Qualifier
  @KeyId = int OUT,              -- Gefundene Schlüssel-ID
  @CanCreate = int               -- 0 = nur öffnen, 1 = anlegen

Praxisbeispiel: Plugin-Konfiguration #

Das folgende Beispiel zeigt den typischen Ablauf beim Zugriff auf die SQL-Registry – hier am Beispiel des Xfacture-Plugins für elektronische Rechnungen:

-- 1. Prüfen ob das Plugin installiert ist
DECLARE @keyid int
EXEC cn_RegOpenPath NULL, '\VENDOR\ESOL\MODULES\DMS', @keyid OUT, 0, 0
IF @keyid IS NULL
  PRINT 'DMS-Modul nicht installiert'

-- 2. Konfiguration lesen
DECLARE @format nvarchar(max), @email nvarchar(max)
EXEC cn_RegReadString '\MODULES\Grundwerte\Xfacture', 'Format', @format OUT
EXEC cn_RegReadString '\MODULES\Grundwerte\Xfacture', 'EMail', @email OUT
SELECT @format AS Format, @email AS EMail

-- 3. E-Mail-Provider ermitteln
DECLARE @provider nvarchar(max)
EXEC cn_RegReadString '\SYSTEM\Devices\EMail', 'Provider', @provider OUT

-- 4. Eigene Einstellung speichern
EXEC cn_RegWriteString '\VENDOR\MeinPlugin', 'LetzterExport', CONVERT(varchar, GETDATE(), 126)

Performance-Muster #

Mehrere Werte aus einem Teilbaum laden #

Bei Plugins und Reports müssen häufig viele Werte aus demselben Registry-Teilbaum gelesen werden – etwa der komplette Firmenstamm, alle Druck-Flags oder die Bank-Verbindungen. Hier lohnt sich die Wahl der richtigen API, besonders über VPN-Verbindungen mit nicht-vernachlässigbarer Netz-Latenz.

Faustregel:

  • Ein einzelner Wert: cn_RegReadString / cn_RegReadInt – einfach, direkt per Pfad.
  • Mehrere Werte, derselbe Pfad, festes Ergebnis-Schema: DECLARE @k = dbo.cnf_RegKeyIdFromPath(...) + N × dbo.cnf_RegReadString(@k, N'Name') AS [Name] in einem SELECT. Gibt eine feste Spalten-Row zurück, die vom Client unverändert weiterverarbeitet werden kann.
  • Mehrere Werte aus einem Teilbaum (mit verschiedenen Unterschlüsseln): cn_RegBulkRead mit @options=2. Gibt alle Werte rekursiv zurück; der Client gruppiert per KEY_ID.
  • Kompletten Teilbaum mit Struktur-Information benötigt: cn_RegBulkRead mit @options=3. Liefert Schlüssel-Hierarchie und Werte in zwei Resultsets.

Anti-Muster: N einzelne Reads auf denselben Pfad #

-- Schlecht: 10 separate Prozedur-Aufrufe, jeder mit eigener Pfad-Auflösung
EXEC cn_RegReadString '\MODULES\Grundwerte\Firmenstamm', 'Firma',    @firma    OUT
EXEC cn_RegReadString '\MODULES\Grundwerte\Firmenstamm', 'Strasse',  @strasse  OUT
EXEC cn_RegReadString '\MODULES\Grundwerte\Firmenstamm', 'Plz',      @plz      OUT
-- ... 7 weitere Aufrufe

Auf lokalem Netz kaum spürbar, auf VPN bei 25 ms Roundtrip-Latenz: 250 ms nur für diesen Block. Der Batch wird oft gecachet, die einzelnen Aufrufe aber nicht.

Besser:

-- Variante A: Ein SELECT mit gecached KeyID
DECLARE @k int = dbo.cnf_RegKeyIdFromPath(N'\MODULES\Grundwerte\Firmenstamm', 0, 0)
SELECT
  dbo.cnf_RegReadString(@k, N'Firma')    AS Firma,
  dbo.cnf_RegReadString(@k, N'Strasse')  AS Strasse,
  dbo.cnf_RegReadString(@k, N'Plz')      AS Plz
  -- ... weitere Spalten

-- Variante B: BulkRead auf den Teilbaum, Client gruppiert
DECLARE @kGW int
EXEC cn_RegOpenPath NULL, '\MODULES\Grundwerte', @kGW OUT, 0, 0
EXEC cn_RegBulkRead @basekeyid = @kGW, @options = 2, @UseUtf16 = 1

Variante A ist einfacher wenn das Client-Code-Modell ein flaches Resultset erwartet. Variante B ist leistungsfähiger wenn mehrere Unterschlüssel gleichzeitig gebraucht werden (Firmenstamm + Xfacture + Bank\1-5 etc.) – ein Aufruf für den ganzen Teilbaum.

Werttyp-Beschränkungen beachten #

  • cn_RegReadString gibt nvarchar(max) zurück – geeignet für beliebig lange Werte.
  • Einige OUTPUT-Parameter in SP-Aufrufen sind auf kleinere Grössen deklariert (z.B. nvarchar(1024)), was bei langen HTML-Templates oder Mail-Bodies zum Abschneiden führen kann. In solchen Fällen stattdessen die Scalar-Funktion dbo.cnf_RegReadString(@k, N'Name') im SELECT verwenden – sie liefert garantiert nvarchar(max).
  • VALUE_STRING im Resultset von cn_RegBulkRead ist bei @UseUtf16=0 auf varchar(8000) begrenzt, bei @UseUtf16=1 auf die ersten 8000 Bytes (also ca. 4000 Unicode-Zeichen). Für längere Werte zusätzlich @options | 4 setzen und VALUE_TEXT auslesen.

Siehe auch #