Archive for June, 2010
[PL] SQL Server – Interaktywny performance dashboard dla ubogich
Jun 30th
Zajawka
Wiele razy widziałem w polskich firmach, jak działy IT używały różnorakich aplikacji do interaktywnego monitorowania środowisk. Na wielkich monitorach administratorzy obserwują wskaźniki wydajnościowe, a zapalające się czerwone światełko jest zazwyczaj bodźcem do działania – pewnie coś się dzieje złego. Powstaje pytanie, czy administrator SQL Servera może samemu sprokurować sobie takie narzędzie, które będzie wyświetlało wskaźniki wydajnościowe i interaktywnie informowało o potencjalnych problemach? Spróbowałem i myślę, że to możliwe. Ba, uważam, że jest do zrobienia nawet na Express Edition. Wszystkie poniższe przemyślenia dotyczą wersji 2008 R2, ale wydaje mi się, że nawet na SQL Server 2005 jest to do powtórzenia.
Mierzyć, ważyć, oceniać
Pierwsze, czego potrzebujemy, to jakieś źródło danych wydajnościowych, najlepiej “eSQueLowe” :-) Pierwsze, co przychodzi do głowy, to perfmon, ale jak wydobyć wartości poszczególnych liczników mając w ręku SQL Server Management Studio? Niby jest taki DMV o nazwie sys.dm_os_performance_counters. Problem z tym widokiem jest jednak taki, że pokazuje wyłącznie liczniki jednej instancji SQL Servera i nie ma możliwości obejrzenia kluczowych wskaźników pochodzących z Windows. A zatem trzeba użyć czegoś innego. A może by tak CLR i kasy z przestrzeni System.Diagnostics? Spróbujmy:
using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.Diagnostics;
public partial class UserDefinedFunctions
{
[Microsoft.SqlServer.Server.SqlFunction]
public static SqlDouble ufn_clr_GetPerfCounterValue(
SqlString CategoryName,
SqlString CounterName,
SqlString InstanceName,
SqlString MachineName
)
{
MachineName = MachineName.IsNull ? "." : MachineName.Value;
PerformanceCounter p = new PerformanceCounter(
CategoryName.Value,
CounterName.Value,
InstanceName.Value,
MachineName.Value);
float value = p.NextValue();
System.Threading.Thread.Sleep(100);
value = p.NextValue();
return new SqlDouble(value);
}
};
Krótki komentarz: usypianie wątku wrzuciłem, ponieważ bez tego niektóre wartości liczników dziwacznie odbiegały od rzeczywistości (można tu trochę poeksperymentować z czasem oczekiwania). Taki kod wklejamy do pliku .cs w projekcie Visual Studio założonego w oparciu o szablon Database Project i wrzucamy skompilowany plik DLL jako assembly z PERMISSION_SET = UNSAFE (działamy poza SQL Serverem) do wybranej bazy danych. Dzięki temu mamy teraz w bazie danych funkcję, która umie zwracać wartość wskazanego licznika permona. Gdyby ktoś miał wątpliwości, jak używać tej funkcji, poniżej przykład:
SELECT
dbo.ufn_clr_GetPerfCounterValue('Procesor',
'Czas procesora (%)', '_Total', '.') AS CPU,
dbo.ufn_clr_GetPerfCounterValue('Dysk fizyczny',
'Średnia długość kolejki dysku', '0 C:', '.') AS DiskQueue,
dbo.ufn_clr_GetPerfCounterValue('MSSQL$SQL2008R2:Buffer Manager',
'Page life expectancy', '', '.') AS PageLifeExpectancy,
dbo.ufn_clr_GetPerfCounterValue('MSSQL$SQL2008R2:Buffer Manager',
'Buffer cache hit ratio', '', '.') AS BufferCacheHitRatio;
Dla każdego licznika trzeba podać: nazwę kategorii liczników (np. Procesor), nazwę licznika (np. Czas procesora (%)), instancję licznika (o ile licznik posiada instancję, np. procesor ma instacje wskazujące na poszczególne CPU logiczne oraz instancję _Total mówiącą o łącznym zużyciu wszystkich CPU), nazwę maszyny (tak, tak, jak się da, to można monitorować więcej niż jedną maszynę). Jeżeli nie wiesz, jak nazywają się kategorie, liczniki, czy instancje, otwórz perfmon.exe i dodaj interesujące Cię wskaźniki, a następnie przełącz permona w widok raportu, tam wszystko jest pokazane (patrz rysunek poniżej).
Skoro umiemy już wyświetlać wskaźniki perfmona, trzeba pomyśleć nad mechanizmem, który umożliwi informowanie administratora o ewentualnych problemach. Do tego celu założę tabelę, która będzie zawierała wskaźniki perfmona, które chcę monitorować.
USE DBAToolbox;
GO
IF OBJECT_ID('dbo.PerfCounters', 'U') IS NOT NULL
DROP TABLE dbo.PerfCounters;
GO
CREATE TABLE dbo.PerfCounters (
PerfCounterID int NOT NULL IDENTITY(1,1) PRIMARY KEY,
Category nvarchar(4000) NOT NULL,
Counter nvarchar(4000) NOT NULL,
Instance nvarchar(4000) NOT NULL DEFAULT '',
Machine nvarchar(256) NOT NULL DEFAULT '.',
Description nvarchar(4000) NULL,
WarningMinValue float NULL,
WarningMaxValue float NULL
);
GO
INSERT INTO dbo.PerfCounters (
Category, Counter, Instance, Machine,
Description, WarningMinValue, WarningMaxValue
)
VALUES
('Procesor', 'Czas procesora (%)', '_Total', '.',
'Zużycie procesora. Ma być poniżej 50%.', NULL, 50),
('Dysk fizyczny', 'Średnia długość kolejki dysku', '0 C:', '.',
'Kolejka dysku C:. Ma być poniżej 2.', NULL, 2),
('MSSQL$SQL2008R2:Buffer Manager', 'Page life expectancy', '', '.',
'Czas życia stron w pamięci. Ma być powyżej 1000.', 1000, NULL),
('MSSQL$SQL2008R2:Buffer Manager', 'Buffer cache hit ratio', '', '.',
'Procent trafień w bufor danych. Ma być powyżej 90%.', 90, NULL),
('MSSQL$SQL2008R2:General Statistics', 'User Connections', '', '.',
'Ilość połączeń użytkowników.', NULL, NULL),
('MSSQL$SQL2008R2:Memory Manager', 'Total Server Memory (KB)', '', '.',
'Pamięć zajęta przez SQL Server.', NULL, NULL),
('MSSQL$SQL2008R2:Plan Cache', 'Cache Hit Ratio', '_Total', '.',
'Procent trafień w plan cache. Ma być powyżej 95%.', 95, NULL);
GO
W tabeli podajemy wszystkie dane dotyczące licznika, a także ustalamy, jakie mają być dolne (WarningMinValue) i górne (WarningMaxValue) wartości licznika, przy których nasz monitor powinien ostrzegać administratora, że coś nie gra. Tu można podać wartości wedle uznania / przyzwyczajeń / najlepszych praktyk :-) Na podstawie tabeli budujemy zapytanie, którego następnie użyjemy do zbudowania naszego narzędzia monitorującego.
SELECT
Category + ' - ' + Counter +
CASE Instance
WHEN '' THEN ''
ELSE ' [ ' + Instance + ' ]'
END AS Counter,
dbo.ufn_clr_GetPerfCounterValue(Category, Counter, Instance, Machine) AS CounterValue,
CASE
WHEN
(dbo.ufn_clr_GetPerfCounterValue(Category, Counter,
Instance, Machine) > WarningMaxValue) OR
(dbo.ufn_clr_GetPerfCounterValue(Category, Counter,
Instance, Machine) < WarningMinValue)
THEN 1
ELSE 0
END AS Warning,
Description
FROM dbo.PerfCounters;
Powyższe zapytanie wygeneruje zestaw danych, który oprócz nazw wskaźników, ich wartości i opisów, będzie także zawierał binarną informację, czy wartość wskaźnika mieści się w normach (kolumna Warning) – wartość 1 oznacza, że norma została naruszona.
Wyświetlać, odświeżać, reagować
Czego można użyć do zaprezentowania danych zwracanych przez powyższe zapytanie? A co jest ładne, kolorowe i dostarczone w pudełku wraz z SQL Serverem? Reporting Services oczywiście :-) A zatem raport…
Budowa raportu jest prosta, niczym konstrukcja cepa (Report Builder, Visual Studio – nieważne, czego użyjesz). Jako źródło danych podajemy zapytanie opracowane powyżej. Jedyna trudność, to wygenerowanie ostrzeżenia, gdy Warning=1. Ale przecież Reporting Services oferuje nam formatowanie warunkowe. Zaznaczamy w projekcie raportu cały wiersz danych i we właściwości BackgroundColor wpisujemy:
=IIF(Fields!Warning.Value=1,"Red","White")
I w zasadzie tyle. Wielbiciele ładnych ikonek mogą się pobawić w indicators albo gauge, ale ja wyszedłem z założenia, że raport dla DBA to nie choinka i ma być jak najlżejszy (jak najmniej fajerwerków). Mój raport prezentował się tak:
Widać, że jeden wskaźnik miał wartość poza normą i został oznaczony kolorem czerwonym.
Teraz tylko trzeba spowodować, żeby taki raport odświeżał się na wielkim monitorze :-) I tu też okazuje się, że Reporting Services mają dla nas niespodziankę (?). W definicji raportu mamy taki element, jak AutoRefresh (zaraz na początku definicji raportu). Można w nim ustawić ilość sekund, co jaką raport będzie odświeżany (oczywiście, jeżeli będziemy go oglądać w Report Managerze). Wiedząc to, ustawiamy wartość AutoRefresh na przykład na 15 sekund i wrzucamy raport na serwer raportów. A potem już tylko delektujemy się samoodświeżająym się raportem :-)
Post mortem
Wszystko, co napisałem powyżej, jest tylko małym fragmentem pomysłu na większą całość. Do tego można dodać na przykład takie “bajery”, jak:
- śledzenie tego, co można zbadać za pomocą DMV (np. można zrobić procedurę składowaną, która zbiera, co trzeba, do tabeli tymczasowej, po czym zwraca wynik w postaci jednej tabeli – i tę procedurę dajemy jako źródło raportu, zaś definicje zapytań do DMV możemy trzymać w tabeli z wskaźnikami i wykonywać je dynamicznym SQL-em),
- zapisywanie historii wskazań do bazy danych w celu analizy trendów,
- zbudowanie raportów szczegółowych na temat wybranych wskazań liczników (np. dla user connections raport pokazujący sesje),
- funkcje CLR do zwracania pełnej listy kategorii, liczników i instancji (żeby się nie męczyć w przeglądanie ich w perfmonie),
- dołożenie AutoRefresh do raportów standardowych Management Studio i Performance Dashboard Reports.
Ale generalnie pocieszające jest, że sam SQL Server oferuje nam tyle możliwości, że sami możemy konstruować narzędzia do monitorowania.
I na koniec odsyłam do ciekawego “białego papierka” Microsoftu, z którego można wyczytać wiele ciekawych rzeczy o monitorowaniu SQL Servera i rozwiązywaniu problemów wydajnościowych: Troubleshooting Performance Problems in SQL Server 2008.
[PL] SQL Server – Konwersja kodowania CP620 (Mazovia) na CP1250 w CLR
Jun 22nd
W jednym z poprzednich wpisów na tym blogu (http://sqlgeek.pl/2010/06/16/sql-server-jak-wylistowac-dostepne-kodowania-funkcja-tabelaryczna-clr/) podałem rozwiązanie problemu konwersji ze standardu Mazovia do Windows-1250 zaimplementowane w T-SQL. Obiecałem sobie, że przetestuję też rozwiązanie alternatywne stworzone w CLR. Oto ono:
using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
public partial class UserDefinedFunctions
{
[Microsoft.SqlServer.Server.SqlFunction(
DataAccess=DataAccessKind.None,
SystemDataAccess=SystemDataAccessKind.None,
IsDeterministic=true,
Name="ufn_clr_MazoviaTo1250")]
[return:SqlFacet(MaxSize=-1)]
public static SqlString ufn_clr_MazoviaTo1250(
[SqlFacet(MaxSize=-1)] SqlString Input
)
{
return Input.Value
.Replace((char)260, (char)323) //Ń
.Replace((char)377, (char)260) //Ą
.Replace((char)321, (char)211) //Ó
.Replace((char)347, (char)321) //Ł
.Replace((char)8226, (char)262) //Ć
.Replace((char)144, (char)280) //Ę
.Replace((char)152, (char)346) //Ś
.Replace((char)160, (char)377) //Ź
.Replace((char)711, (char)379) //Ż
.Replace((char)8224, (char)261) //ą
.Replace((char)356, (char)263) //ć
.Replace((char)8216, (char)281) //ę
.Replace((char)8217, (char)322) //ł
.Replace((char)164, (char)324) //ń
.Replace((char)728, (char)243) //ó
.Replace((char)382, (char)347) //ś
.Replace((char)166, (char)378) //ź
.Replace((char)167, (char)380); //ż
}
};
Czemu tak na piechotę? Bo niestety, na liście obsługiwanych przez klasy z przestrzeni System.Text stron kodowych nie ma CP620 (Mazovia).
Trudność? Liczby oznaczające kody poszczególnych literek się zmieniły w porównaniu z T-SQL. Ale to dlatego, że napisy wchodzące do metod w .NET są kodowane w Unicode (UTF-16). Stąd potrzeba odrobiny cierpliwości i użycia w SQL Server funkcji UNICODE, by dowiedzieć się, jakie są kody liter i odpowiadających im znaków z kodowania Mazovia w kodowaniu UTF-16. Czyli, jeśli chciałem sprawdzić, jaki kod ma literka w UTF-16, robiłem coś takiego:
SELECT UNICODE(N'Ą');
W CLR miało być szybciej (tak twierdził jeden z uczestników dyskusji na forum portalu WSS.pl). I jest szybciej (ale musiałem sprawdzić ;-)). Wykonałem banalny test porównujący wydajność konwersji na 100 tysiącach wierszy przy użyciu obu funkcji (T-SQL i CLR):
IF OBJECT_ID('dbo.TestTableMazovia', 'U') IS NOT NULL
DROP TABLE dbo.TestTableMazovia;
GO
CREATE TABLE dbo.TestTableMazovia (
SampleText varchar(max) NOT NULL
);
GO
WITH CTE AS (
SELECT 1 AS n
UNION ALL
SELECT n + 1
FROM CTE
WHERE n < 100000
)
INSERT INTO dbo.TestTableMazovia (SampleText)
SELECT 'Ź•śĄŁ ˇ†Ť‘’¤˘ž¦§' FROM CTE OPTION (MAXRECURSION 0);
DBCC DROPCLEANBUFFERS;
DBCC FREEPROCCACHE;
DECLARE @t datetime = GETDATE();
SELECT dbo.ufn_MazoviaTo1250(SampleText) FROM dbo.TestTableMazovia;
SELECT DATEDIFF(ms, @t, GETDATE());
GO
DBCC DROPCLEANBUFFERS;
DBCC FREEPROCCACHE;
DECLARE @t datetime = GETDATE();
SELECT dbo.ufn_clr_MazoviaTo1250(SampleText) FROM dbo.TestTableMazovia;
SELECT DATEDIFF(ms, @t, GETDATE());
GO
Wynik:
T-SQL – 14.5 s
CLR – 2.5 s
Okazało się, że rozwiązanie T-SQL jest w tym przypadku około 6-7 razy wolniejsze (na wynik testu nie wpłynęła także zmiana typu danych kolumny na nvarchar(max) czy na varchar(100) – ani rozmiar, ani Unicode’owość nie zmieniły wyników pomiaru czasu trwania zapytań). Sprawdziłem obie funkcje także pod kątem działania na jednym, ale dość długim napisie (ponad 8000 znaków). Wynik niemal taki sam (XXX ms do XX ms).
Jedyne ale jest takie, że łatwiej jest wygenerować funkcję w T-SQL, jeśli mam daną tabelę z przejściami między poszczególnymi stronami kodowymi. Ale jeśli priorytetem jest wydajność, wydaje się, że CLR działa lepiej.
[PL] Materiały z prezentacji o darmowych narzędziach na Virtual Conference 2010
Jun 19th
Dzisiaj po południu miałem przyjemność poprowadzić prezentację pt. “Darmowe aplikacje wspierające rozwiązywanie problemów wydajnościowych w SQL Server 2008” w ramach pierwszej w Polsce wirtualnej konferencji poświęconej technologiom Microsoft – Virtual Conference 2010. Dziękuję tym, którzy zdecydowali się mnie obejrzeć w sobotnie popołudnie. Mam nadzieję, że informacje przeze mnie podane przydadzą się w pracy z SQL Serverem. Dla tych, co prezentacji nie obejrzeli – zapewne za jakiś czas będzie dostępne nagranie na stronie konferencji lub na portalu VirtualStudy.pl. Na stronie zasobów umieściłem materiały z prezentacji. Poniżej zamieszczam bezpośredni link do materiałów.
[PL] SQL Server – Jak wylistować dostępne kodowania funkcją tabelaryczną CLR?
Jun 16th
Ostatnimi czasy przesiaduję trochę więcej :-) na portalu WSS.pl. Na forum tegoż portalu wywiązała się całkiem interesująca dyskusja nad konwersją pomiędzy stroną kodową Mazovia (CP620) a stroną Windows-1250. Cały wątek jest tutaj. Postanowiłem drążyć temat dalej. Na razie stanęliśmy na tym, że mamy funkcję T-SQL:
IF OBJECT_ID('dbo.ufn_MazoviaTo1250', 'FN') IS NOT NULL
DROP FUNCTION dbo.ufn_MazoviaTo1250;
GO
CREATE FUNCTION dbo.ufn_MazoviaTo1250 (@String varchar(maX))
RETURNS varchar(max)
WITH SCHEMABINDING
AS
BEGIN
SET @String = @String COLLATE Polish_BIN;
RETURN
REPLACE (
REPLACE (
REPLACE (
REPLACE (
REPLACE (
REPLACE (
REPLACE (
REPLACE (
REPLACE (
REPLACE (
REPLACE (
REPLACE (
REPLACE (
REPLACE (
REPLACE (
REPLACE (
REPLACE (
REPLACE (
@String, CHAR(165), CHAR(209) -- Ń
), CHAR(143), CHAR(165) -- Ą
), CHAR(163), CHAR(211) -- Ó
), CHAR(156), CHAR(163) -- Ł
), CHAR(149), CHAR(198) -- Ć
), CHAR(144), CHAR(202) -- Ę
), CHAR(152), CHAR(140) -- Ś
), CHAR(160), CHAR(143) -- Ź
), CHAR(161), CHAR(175) -- Ż
), CHAR(134), CHAR(185) -- ą
), CHAR(141), CHAR(230) -- ć
), CHAR(145), CHAR(234) -- ę
), CHAR(146), CHAR(179) -- ł
), CHAR(164), CHAR(241) -- ń
), CHAR(162), CHAR(243) -- ó
), CHAR(158), CHAR(156) -- ś
), CHAR(166), CHAR(159) -- ź
), CHAR(167), CHAR(191) -- ż
) COLLATE database_default;
END;
GO
-- Test
SELECT dbo.ufn_MazoviaTo1250 ('Ź•śĄŁ ˇ†Ť‘’¤˘ž¦§');
Jeden z uczestników dyskusji zasugerował, by problem rozwiązać za pomocą funkcji napisanej w .NET (CLR). Wydaje się to być rozsądnym posunięciem. W końcu CLR może się spisywać lepiej w przypadku operacji na tekście (zwłaszcza długim). Na pierwszy ogień jednak postanowiłem rzucić sobie zadanie – napisanie funkcji do listowania dostępnych kodowań z poziomu .NET. Dzięki temu dowiem się, jakie kodowania mogę wykorzystać w konwersjach z użyciem metod klas z przestrzeni System.Text (np. klasy Encoding). Czyli rozchodzi się o to, by wiedzieć, jakie strony kodowe mogę konwertować niekoniecznie znak po znaku.
Po chwili skrobania wyszło mi coś takiego:
using System; using System.Data; using System.Data.SqlClient; using System.Data.SqlTypes; using Microsoft.SqlServer.Server; using System.Text; using System.Collections;
public partial class UserDefinedFunctions
{
[Microsoft.SqlServer.Server.SqlFunction(
FillRowMethodName="FillEncodings",
Name="ufn_CLR_GetEncodings",
TableDefinition="CodePage int,
EncodingName nvarchar(255),
DisplayName nvarchar(255)"
)]
public static IEnumerable ufn_CLR_GetEncodings()
{
EncodingInfo[] Encodings = Encoding.GetEncodings();
return Encodings;
}
public static void FillEncodings(
object Obj,
out int CodePage,
out string EncodingName,
out string DisplayName)
{
EncodingInfo e = (EncodingInfo) Obj;
EncodingName = e.Name;
DisplayName = e.DisplayName;
CodePage = e.CodePage;
}
};
W razie wątpliwości, co należy z tym kodem zrobić, zajrzyj na przykład tutaj: http://geekswithblogs.net/frankw/archive/2008/05/03/a-quick-walk-through-of-clr-integration-with-sql-server.aspx.
Test wdrożonej funkcji wygląda tak:
SELECT * FROM dbo.ufn_CLR_GetEncodings() ORDER BY CodePage;
I okazuje się, że Mazovia oczywiście nie znalazła się na liście. No cóż, pozostaje zatem żmudna podmiana znak po znaku. Ale o tym już napiszę następnym razem, jak tylko napiszę właściwy kod i przetestuję szybkość rozwiązania CLR vs. szybkość rozwiązania T-SQL. Ciąg dalszy nastapi ;-)
[PL] Migracja z SQL Server 2000 do 2008 – cuda i dziwy
Jun 9th
Właśnie niedawno zakończyłem projekt migracji bazy klienta z SQL Server 2000 do 2008. Tego typu projekty mają to do siebie, że zawsze po ich zakończeniu jest o czym opowiadać. Nie inaczej jest i tym razem.
Compatibility mode 80 – elastyczność czy głupota?
Zawsze wiedziałem, że SQL Server 2000 wybaczał programistom T-SQL więcej, niż obecne najnowsze wersje tego systemu. Ale nie sądziłem, że jest aż tak tolerancyjny. Przypuśćmy, że mamy bazę świeżo przeniesioną z SQL Server 2000 na SQL Server 2008 (lub 2008 R2). Zasymuluję ten przypadek tak:
CREATE DATABASE Test_80;
ALTER DATABASE Test_80 SET COMPATIBILITY_LEVEL = 80;
GO
Teraz, załóżmy, że mam w tej bazie tabelę:
USE Test_80;
GO
CREATE TABLE dbo.TestTable (Column1 int NOT NULL);
GO
W obiektach bazy danych (widokach i procedurach) znalazły się takie oto odwołania do tabeli TestTable:
SELECT a.bc.Column1 FROM dbo.TestTable AS abc;
lub
SELECT a.b.c.Column1 FROM dbo.TestTable AS abc;
Oba zapytania zwracają zawartość tabeli! Hę? Okazuje się, że przy pracy w trybie zgodnym z SQL Server 2000 (lub na samym SQL Server 2000) takie numery przechodzą, bo silnik sam sobie dopowie, że my tych kropek tam wcale nie chcieliśmy wpisać! Nie muszę dodawać, że przejście w tryb kompatybilności 100 powoduje natychmiastowy błąd przy tego typu sytuacjach.
Kolejny kwiatek – w jednym z obiektów bazy danych znalazłem taki kod:
SET;
Najkrótsze polecenie T-SQL? I to też działa w SQL Server 2000! Oczywiście robi nic / tudzież nic nie robi :-) A jak świetnie nadaje się do sesji Marka Adamczuka o odlotach w T-SQL! ;-)
I takich kwiatków zapewne jest więcej, więc z jednej strony można się cieszyć, jaki to silnik był dawniej elastyczny i łaskawy dla programisty, a z drugiej narzekać, bo przez tę “łaskawość” przy migracji trzeba się sporo napocić, by wykryć i naprawić pojawiające się znikąd błędy.
Konkatenacja sortowana
Zapewne niejednemu programiście zdarzyło się budować w T-SQL skalarną wartość dołączając kolejne rekordy z tabeli / widoku w określonej kolejności. Ale uwaga – tu też czyha pułapka! I to nie tylko w trybie kompatybilności 80.
Przypuśćmy, że chcę do zmiennej doklejać kolejne nazwy baz danych i ich collation z widoku sys.databases w kolejności wg nazwy collation. Pierwszy pomysł:
DECLARE @zmienna varchar(8000);
SET @zmienna = '';
SELECT @zmienna = @zmienna + name + '(' + collation_name + '),'
FROM sys.databases
ORDER BY collation_name;
SELECT @zmienna;
Wynik? Okazuje się, że zmienna zawiera dane tylko jednej bazy… Co trzeba zrobić? Dołożyć kauzulę TOP z czymś dużym (nie TOP 100 PERCENT – choć na SQL Server 2000 czasem to pomagało). Na przykład:
DECLARE @zmienna varchar(8000);
SET @zmienna = '';
SELECT TOP 200000000 @zmienna = @zmienna + name + '(' + collation_name + ')'
FROM sys.databases
ORDER BY collation_name;
SELECT @zmienna;
Taki kod już działa. Największa liczba, jaką można wstawić do klauzuli TOP to maksimum typu bigint, czyli liczba 9223372036854775807. Powinno wystarczyć każdemu ;-)
Nie wspominam nawet o tym, że jak ktoś chce mieć sortowany widok (powinni tego zabronić!!!), to zestaw znany z SQL Server 2000: TOP 100 PERCENT + ORDER BY już nie działa od SQL Server 2005. Działa wyłącznie ORDER BY + TOP duuuużo (nie zawsze działa w widokach także sortowanie z użyciem funkcji okienkowych!), ale ja tego zdecydowanie nie polecam. Widoki nie są po to, by sortować dane.
Czemu nie TOP 99.99… PERCENT? O tym można poczytać na blogu Marka Powichrowskiego.
Post mortem
Oczywiście to nie jedyne “cuda i dziwy”, z jakimi można się zetknąć przy okazji migracji z SQL Server 2000 do wyższych wersji. Ale te przypadki wydały mi się na tyle ciekawe, że postanowiłem je ku pamięci opisać, by w przyszłości nikt nie był zdziwiony, kiedy taki “numer” mu się przytrafi (a SQL Server 2000 nadal ma się całkiem dobrze i wiele firm używa go w najlepsze).



Nazywam się Paweł Potasiński i pracuję w polskim oddziale Microsoft w dziale Small and Midmarket Solutions & Partners (SMS&P) jako Partner Technology Advisor.




