Archiv

Archiv für die Kategorie ‘Datenbank’

Sprung in der Identity Spalte beim SQL Server 2012

23. August 2016 Keine Kommentare

Kurz nach der Migration einiger Datenbank von einem SQL Server 2008 auf einen SQL Server 2016 ist aufgefallen, dass es große Sprünge in den Identity-Spalten gab, die nicht durch verworfene Datensätze erklärt werden konnten. Dabei ging es jeweils um Sprünge von ca. 1000 oder sogar 10.000. Die Ursache ist in der MSDN zu finden:

The identity property on a column does not guarantee the following: … Consecutive values after server restart or other failures – SQL Server might cache identity values for performance reasons and some of the assigned values can be lost during a database failure or server restart. This can result in gaps in the identity value upon insert.

Dieser Sprung beträgt bei Spalten mit bigint aber bis zu 10.000, bei Spalten mit int immerhin noch 1.000. Da ist sehr viel und bringt einen in einen Erklärungsbedarf. Basierend auf diesem Beitrag lässt sich das Problem leicht nachstellen:

CREATE TABLE MyTestTable(Id INT IDENTITY(1,1), Name VARCHAR(255));
 
DECLARE @i INT = 100000
 
While @i > 0
BEGIN
    INSERT INTO MytestTable (Name) VALUES ('Test2')
    SET @i = @i-1
END

Während diese Schleife läuft (das Problem tritt nur bei Tabellen in Verwendung auf!), den SQL Server neu starten. Dann

INSERT INTO MyTestTable(Name) VALUES ('Mr.Jackson'); 
 
SELECT Id, Name FROM MyTestTable ORDER BY Id DESC;

SQL Start Option T272
Damit sollte das Problem sichtbar sein. Eine Option ist die Nutzung von Sequenzen mit der NOCACHE Option, anstatt auf IDENTITY Spalten zu setzen. Wenn diese Option zu aufwendig ist, kann auch die Option -T272 für die komplette Instanz genutzt werden. Damit ist das alte Verhalten (bis SQL 2008) wiederhergestellt.

Es bleibt noch anzumerken, dass das TRACE Flag T272 das Caching-Verhalten für die Generierung von Identity-Werten deaktiviert. Negative Auswirkungen auf die Performance beim Erzeugen neuer Datensätze sind somit möglich.

KategorienDatenbank Tags:

Daten per SQL INSERT ohne Schlüsselspalte kopieren

5. August 2015 Keine Kommentare

Immer wieder steht man vor der Aufgabe, Datenmengen innerhalb einer Tabelle zu kopieren. Typisierte Datenzugriffsschichten, wie das Entity Framework, bieten zwar viel Komfort, lassen aber zukünftige DDL Updates außen vor. Ein Update des Datenmodells, speziell mit ergänzten oder gelöschten Spalten erzwingt dann automatisch ein Update der Anwendung, wenn die Kopierfunktion vollständige Kopien anlegen soll. Bei oft geänderten Tabellen wir das zu einem Problem. Zudem sind komplexere Kopieroperationen direkt auf dem Server mittels einer StoredProcedure meist wesentlich schneller.

Hier bietet sich natürlich die Lösung mit einem INSERT SQL an:

INSERT INTO MyTable SELECT mt.* FROM MyTable mt;

Das Problem mit diesem Befehl sind aber IDENTITY Spalten, die nicht übergeben werden dürfen. Dies kann man natürlich durch Angabe der Spaltennamen umgehen. Damit landet man aber wieder am Anfang des Problems. Die Spaltennamen müssen fest definiert oder zuvor aufwändig ermittelt werden.

Eine andere Lösung lässt sich jedoch über temporäre Tabellen entwickeln. Man erstellt mit SELECT INTO eine temporäre Tabelle mit den benötigten Daten. Deren Identity-Spalte kann man problemlos verwerfen und danach funktioniert ein INSERT INTO ohne Problem:

CREATE PROCEDURE dbo.CopyTable @SourceId BIGINT
AS 
  SET NOCOUNT ON;
 
  IF OBJECT_ID('tempdb..#MyTable') IS NOT NULL DROP TABLE #MyTable;
 
  SELECT mt.* INTO #MyTable FROM MyTable mt WHERE mt.Id = @SourceId;
 
  ALTER TABLE #MyTable DROP COLUMN Id;
 
  INSERT INTO MyTable SELECT mt.* FROM #MyTable mt;
 
  IF OBJECT_ID('tempdb..#MyTable') IS NOT NULL DROP TABLE #MyTable;
 
  SET NOCOUNT OFF;
GO

Ein weiterer Vorteil ist das einfach Aktualisieren der neuen Datenmenge in der temporären Tabelle.

KategorienDatenbank Tags:

Dynamische LINQ-Expressions

3. September 2014 Keine Kommentare

Wenn man LINQ-Abfragen etwas dynamischer zusammen bauen möchte, sehnt man sich schnell nach den alten SQL Strings zurück. Das war völlig flexibel, aber auch fehleranfällig. Ein von mir abonnierter Blog hat das Thema aufgegriffen und eine kleine Lösung dafür vorgestellt: Klick hier.

Allerdings fand ich das Vorgehen mit Expressions sehr kompliziert, zumindest für diesen Sachverhalt. Ich habe darum das Beispiel einmal auf meine Herangehensweise umgebaut:

private static void Main(string[] args)
{
    var people = new List<Person>()
    {
        new Person()
        {
            Firstname = "Carl",
            Lastname = "Sample"
        },
        new Person()
        {
            Firstname = "Mark",
            Lastname = "Schulz"
        }
    };
 
    var filtered = people.AsQueryable();
 
    var queryFirstnames = true;
    var queryLastnames = true;
    var searchValue = "l";
 
    if (queryFirstnames)
    {
        filtered = filtered.Where(p => p.Firstname.Contains(searchValue));
    }
    if (queryLastnames)
    {
        filtered = filtered.Where(p => p.Lastname.Contains(searchValue));
    }
 
    Console.WriteLine(filtered.Count());
    Console.ReadKey();
}

Da die Abfrage von LINQ erst so spät als möglich ausgeführt wird, können auch mehrere Bedingungen hintereinander gehängt werden, oder eben auch nicht. Dadurch entsteht automatisch eine AND Verknüpfung. Möchte man eine OR Verknüpfung, so leidet die Lesbarkeit etwas, da man dann mit LINQ Union arbeiten muss.

Nachteil meiner Lösung ist aber, dass man den Zugriff auf die Daten und den Zusammenbau der Abfrage zusammen codieren muss. Sofern das kein Problem darstellt, finde ich diesen Weg etwas übersichtlicher.

KategorienDatenbank, Microsoft .NET Tags:

SQL User mit Login verbinden

12. Februar 2014 Keine Kommentare

Regelmäßig nach einer Wiederherstellung einer bestehenden Datenbank auf einem neuen Server müssen die Logins, die serverseitig gespeichert und angelegt werden, mit dem User der Datenbank verbunden werden. Ansonsten erhält der Login auf die Datenbanken einfach keinen Zugriff.

Dafür gibt es drei Möglichkeiten:

  • Man kann den User in der Datenbank entfernen und den User einfach auf Serverebene neu anlegen. Das SQL Managmentstudio bietet hierbei die Möglichkeit, die Rechte auf einer Datenbank zu erteilen. Aber dabei verliert man ALLE bereits konfigurierten Rechte, die der User in der Datenbank hatte. Diese Lösung ist somit nicht empfehlenswert.
  • Der Microsoft SQL Server bietet die Procedure sp_change_users_login, mit der ein neu angelegter Login im Server mit dem User der Datenbank verbunden werden kann. Ich nehme dafür eigentlich immer einen identischen Namen, um Verwechslungen zu vermeiden:
    EXEC sp_change_users_login 'Update_One', 'User1', 'User1';

    Dieser Befehl muss für jeden User in jeder Datenbank ausgeführt werden, kann aber leicht in einem Skript vorbereitet werden.

  • Abgelöst wird diese vorstehende Procedure durch folgenden SQL Befehl: ALTER USER. In der Anwendung kann man dann schreiben:
    ALTER USER User1 WITH LOGIN = User1;

    Auch dieser Befehl muss für jeden User in jeder Datenbank ausgeführt werden und kann leicht in einem Skript vorbereitet werden.

KategorienDatenbank Tags:

SQL Deadlock durch Autoclose

9. Januar 2014 Keine Kommentare

Seit einigen Monaten nervte in einer Microsoft SQL 2008 Datenbank ein Problem, welches gelegentlich auftrat. Auf die betreffende Datenbank waren ab einem scheinbar zufälligen Zeitpunkt keine schreibenden Zugriffe mehr möglich, lesende Zugriffe aber schon. Ein einfacher Blick nach der Verfügbarkeit reichte also nicht, um die Lauffähigkeit der Datenbank zu testen.

Ein Blick in die Log-Dateien brachte die Erkenntnis, dass diese Datenbank ziemlich oft gestartet wurde (im Gegensatz zu den anderen Datenbanken) und plötzlich ein Fehler 1205 auftrat:

Transaction (Process ID …) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.

Danach war die Datenbank schreibend nicht mehr verfügbar, der Fehler 9001 trat auf:

The log for database ‚…‘ is not available. Check the event log for related error messages. Resolve any errors and restart the database.

Select – Abfragen waren davon aber nicht betroffen, da diese ja keine Log-Einträge erzeugen.

Die Lösung wurde in SQL ServerCentral.com beschrieben: Abschalten der Option AutoClose in den Eigenschaften der Datenbank! Da diese Eigenschaft weder bewußt gesetzt worden war, noch benötigt wurde, konnte das Problem so einfach gelöst werden.

KategorienDatenbank Tags: ,

Administratorzugriff auf SQL Server wiederherstellen

18. September 2013 Keine Kommentare

Manchmal ist man in der Situation, dass man keinen Zugriff mehr auf einen SQL Server mehr hat, obwohl man selbst Administrator der darunter liegenden Windows-Version ist. In diesem Fall hat Microsoft dafür gesorgt, dass man recht einfach die Administrationsrechte für den SQL Server erlangen kann. Der meiner Meinung nach einfachste Weg geht so:

SQL-Server-KonfigurationsmanagerMan startet den SQL-Server-Konfigurationsmanager und öffnet die Eigenschaften des gewünschten SQL-Dienstes. Bei aktuellen Version hat man nun die Möglichkeit, direkt den Parameter -m hinzuzufügen. Dieser Parameter startet den SQL-Server Einzelbenutzermodus und gibt allen lokalen Administratoren Zugriff. Alternativ muss man bei älteren Versionen auf der Seite erweitert den Parameter direkt in der Auflistung angeben. Aber Achtung, dort werden die Parameter mit ; getrennt. Es muss dann also lauten:

-m;--dC:\Program Files\Microsoft S...

Anschließend muss der Dienst neu gestartet werden. Nun kann man sich mit dem SQL Server Managementstudio direkt verbinden. Aber auch hier Achtung: Das Programm muss dazu unbedingt als Administrator gestartet werden, selbst wenn unter Windows als Admin angemeldet ist! Nun kann man seinen Admin-Login hinzufügen und mit den notwendigen Rechten versehen. Zum Abschluss nicht vergessen, den Parameter -m wieder zu entfernen, damit der Server normal zur Verfügung steht.

KategorienDatenbank Tags: ,

Bestimmen einer Zeitdifferenz mit SQL

24. Oktober 2011 Keine Kommentare

Eine Abfrage im SQL Server enthält eine time-Spalte:

SELECT [Activity], [Duration] FROM TableName

Die Aufgabenstellung bestand darin, die Zeiten zu addieren. Leider wurde dieser Versuch

SELECT SUM([Duration]) FROM TableName

abgelehnt:

Der Operanddatentyp time ist für den sum-Operator ungültig.

was zwar nicht einleuchtend, aber auch nicht zu ändern war.
Die Lösung war die vorherige Umwandlung der Zeit in eine Datumsdifferenz vom Typ Minuten:

SELECT SUM(DATEDIFF(MINUTE, '00:00:00', [Duration])) FROM TableName
KategorienDatenbank Tags:

VistaDB.Net’s letzte Aktualisierung

29. August 2010 Keine Kommentare

Leider hat der Hersteller von VistaDB den Vertrieb dieser Datenbank eingestellt. Als kleinen Trost konnte man zum Abschluss noch den Sourcecode sehr günstig erwerben und mit diesem auch eine unverschlüsselte Version ohne Lizenzabfragen herunterladen.

Im Folgenden möchte ich den somit letzten Umstieg von VistaDB.Net 3 auf 4.1, die bei mir noch in einigen Projekten aktiv ist, zeigen:

1. Installation von VistaDB.Net 4.1. Diese Version 4.1 ließ sich problemlos parallel zur Version 3 installieren und nutzen.

2. Upgraden der Datenbank auf die 4er Version. Das geht im VistaDB Data Builder im Menü Database unter dem Punkt Upgrade VDB3 Database sehr einfach. Leider ändert sich die Dateiendung von .vdb3 auf .vdb4. Setup-Skripte müssen hier evtl. auch angepasst werden.

3. Öffnen der Projekte in Microsoft Visual Studio. In den Projekten ist ein Verweis auf VistaDB.NET20 vorhanden. Dieser wird entfernt und durch den VistaDB 4 Provider ersetzt.

4. Die Datasets (.xsd) müssen in einem externen Editor geöffnet werden, um den XML-Code direkt zu bearbeiten. Man findet darin mindestens einmal Provider=“VistaDB.NET20″, was durch Provider=“System.Data.VistaDB“ ersetzt werden muss.

5. Jetzt kommt der schwierigste Teil. Die Connectionstrings müssen aktualisiert werden und die Datasets nochmal neu geschrieben werden. Dazu reicht es eigentlich, den Connectionsstring in den Einstellungen auf die neue Datei (die Dateiendung hat sich ja geändert) zu setzen und einen TableAdapter im Dataset neu zu konfigurieren (ohne Änderung). Jedoch war bei mir das Problem, dass die ConnectionsStrings nicht mehr gültig waren. Das ist zwar ein Problem von Visual Studio und nicht von VistaDB, es steht hier aber trotzdem im Weg. Darum gehe ich einen anderen Weg. Darum habe ich einen anderen Weg gewählt.

Ich habe mir den Namen des Connectionstrings notiert und ihn aus den Einstellungen gelöscht. Dann habe ich ein neues Dataset erzeugt und dort den Connectionstring neu eingegeben und unter dem alten Namen gespeichert. Damit wurde dann als Dummy ein Tableadapter konfiguriert. Das Dataset kann zum Schluss wieder gelöscht werden. Nun können die alten Datasets geöffnet und ein Tableadater neu erzeugt werden.

KategorienDatenbank, Microsoft .NET Tags:

SQL Count, das unbekannte Wesen

5. August 2009 Keine Kommentare

Bei der Suche nach einem praktischen SQL-Skript für eine Statistik, bin ich auf die Möglichkeit gestoßen, den SQL Count Befehl zu verwenden:

SELECT a.BewertungId, 	
 COUNT(*) Gesamt,
 COUNT(E_Datum) Offen, 
 COUNT(CASE WHEN ((a.Abgelehnt = 0) OR (b.OFI = 0)) THEN NULL ELSE 1 END) Abgelehnt
FROM Aktivitaeten a, Bewertung b
WHERE a.BewertungId = b.Id
GROUP BY a.BewertungId
ORDER BY a.BewertungId

Die Nutzungsmöglichkeit des COUNTs ist dabei vielfältig:

  • Üblicherweise wird Count mit dem Parameter (*) aufgerufen. Dieser Aufruf führt zur vollständigen Zählung der Datenmenge.
  • Gibt man aber als Parameter einen Spaltennamen ein, so werden nur die Datensätze gezählt, die in der gewünschten Spalte einen Eintrag ungleich NULL haben.
  • Aber auch die Angabe eines Ausdrucks, der NULL oder einen Wert liefert, ist möglich.

Das aufgeführte Beispiel zählt also auf einmal: alle Datensätze, alle Datensätze mit einem Eintrag in der Spalte E_Datum und als letzten Punkt alle Datensätze, die eine Ablehnung enthalten.
kick it on dotnet-kicks.de

KategorienDatenbank Tags:

Alle Datenbanken vom MSSQL sichern

14. November 2008 Keine Kommentare

Immer wieder habe ich den Fall gehabt, eine Sicherung eines Microsoft SQL Servers einzurichten. Dabei ging es meist nur um kleine Datenbanken, manchmal sogar unter der MSDE 2000 oder dem SQL Server 2005 Express. Dazu wurden dann einfach die Dienste gestoppt und die Dateien wegkopiert.

Gerade auf Webserver war die Lösung unbefriedigend. Darum habe ich mir eine neue Lösung gebastelt. Der SQL-Server bietet zwei Kommandozeilenbefehle: bcp und osql.

bcp bietet einem die Möglichkeit, die Ausgabe eines SQL-Befehls in eine Text-Datei zu leiten. Nutzt man nun einen Select, um alle Datenbank zu ermitteln:

SELECT [name] FROM master..sysdatabases

und erweitertert das Ergebnis um den BACKUP DATABASE Befehl, so hat man eine gute Möglichkeit, sich ein Backupskript dynamisch zu erzeugen:

bcp "Select N' use master', N'GO' UNION Select N'BACKUP DATABASE [' + [name] + N'] TO DISK = N''C:\BackupDB\' + [name] + N'.bak'' WITH FORMAT', N'GO' From master..sysdatabases"
 queryout C:\BackupDB\backupskript.sql -S ".\SQLEXPRESS" -T -c -t \r\n

Das sieht ein wenig unübersichtlich aus, da man jeden Befehl in eine neue Zeile bekommen muss (-t \r\n) und zudem nach jedem BACKUP DATABASE Befehl einen GO benötigt. Ist der Befehl durchgelaufen, sollte das Backupskript vorliegen.

Nun ist nur noch dafür zu sorgen, dass das Skript auch aufgerufen wird:

osql -S .\SQLEXPRESS -E -i C:\BackupDB\backupskript.sql

Läuft alles problemlos durch, wird so bei dem Aufruf beider Befehle ein vollständiges Backup alle Datenbanken des angegebenen SQL-Servers gemacht. Getestet habe ich es mit dem Microsoft SQL Server 2005, der MSDE 2000 und dem Microsoft SQL Server 2005 Express Edition.

KategorienDatenbank Tags: ,