RE:NODE

ბაზები12 წუთის საკითხავი

T-SQL-ის საფუძვლები სხვა ბაზებიდან მოსული დეველოპერებისთვის

T-SQL, რომელიც MySQL-სა და PostgreSQL-ისგან განსხვავდება: TOP და OFFSET FETCH, IDENTITY და OUTPUT, MERGE და upsert-ები, ტრანზაქციები, ტიპები, თარიღები და NULL.

0 მკითხველი

თუ SQL PostgreSQL-იდან ან MySQL-იდან იცი, T-SQL-ის უმეტესი ნაწილი უკვე იცი. SELECT, join-ები, GROUP BY, window ფუნქციები და subquery-ები ისე მუშაობს, როგორც ელი. დეველოპერებს ხელს უშლის განსხვავებების მოკლე სია: LIMIT არ არსებობს (გამოიყენე TOP ან OFFSET ... FETCH), არც RETURNING (გამოიყენე OUTPUT), არც boolean (გამოიყენე bit), არც ON CONFLICT (გამოიყენე MERGE ფრთხილად, ან ჯერ update და მერე insert), + სტრიქონებს აერთებს და მთელ შედეგს NULL-ად აქცევს, თუ რომელიმე ნაწილი NULL-ია, ნაგულისხმევ იზოლაციის დონეზე კი მკითხველებს შეუძლიათ მწერლების დაბლოკვა. ეს გზამკვლევი ამ განსხვავებებს SQL Server 2022-ისთვის მომუშავე მაგალითებით გადის, რომ SQL Server-ზე პირველი კვირა შენს აპლიკაციას დაუთმო და არა შეცდომის შეტყობინებებს.

შედეგების შეზღუდვა და გვერდებად დაყოფა#

LIMIT არ არსებობს. მისი ორი შემცვლელია TOP და OFFSET ... FETCH:

sql
-- The ten newest ordersSELECT TOP (10) OrderId, CustomerId, CreatedAtFROM dbo.OrdersORDER BY CreatedAt DESC;-- Page 3 at 20 rows per pageSELECT OrderId, CustomerId, CreatedAtFROM dbo.OrdersORDER BY CreatedAt DESC, OrderId DESCOFFSET 40 ROWS FETCH NEXT 20 ROWS ONLY;

TOP ფრჩხილებში იღებს ცვლადს ან პარამეტრს, TOP (10) WITH TIES კი მოიცავს ყველა დამატებით row-ს, რომელიც ORDER BY სვეტებით მეათეს უტოლდება. TOP ORDER BY-ის გარეშე დასაშვებია და აბრუნებს იმ row-ებს, რომელთა პოვნაც ყველაზე სწრაფია, რაც იშვიათად არის ის, რაც იგულისხმე.

OFFSET ... FETCH მოითხოვს ORDER BY-ს, და თანმიმდევრობა უნიკალური უნდა იყოს - დაამატე primary key თანაბრობის გასარღვევად, როგორც ზემოთ, თორემ ერთი და იმავე timestamp-ის მქონე row-ები შეიძლება ორ გვერდზე გამოჩნდეს ან არცერთზე. დიდი offset-ები აქაც ისეთივე ძვირია, როგორც ნებისმიერ ბაზაში, რადგან SQL Server ყოველ გამოტოვებულ row-ს კითხულობს და აგდებს. ღრმა გვერდებისთვის გამოიყენე keyset pagination: დაიმახსოვრე ბოლო row-ის დალაგების მნიშვნელობები და მოითხოვე ის, რაც მათ შემდეგ მოდის:

sql
SELECT TOP (20) OrderId, CustomerId, CreatedAtFROM dbo.OrdersWHERE CreatedAt < @lastCreatedAt   OR (CreatedAt = @lastCreatedAt AND OrderId < @lastOrderId)ORDER BY CreatedAt DESC, OrderId DESC;

(CreatedAt, OrderId)-ზე ინდექსით ყოველი გვერდი ერთნაირად ჯდება, რაც არ უნდა ღრმად იყოს. SQL Server-ის ინდექსები და execution plan-ები ხსნის, რატომ აქვს ინდექსის თანმიმდევრობას მნიშვნელობა.

Identity სვეტები და ახალი id-ის დაბრუნება#

AUTO_INCREMENT-ისა და SERIAL-ის ეკვივალენტია IDENTITY თვისება:

sql
CREATE TABLE dbo.Customers (    CustomerId  int IDENTITY(1,1) NOT NULL CONSTRAINT PK_Customers PRIMARY KEY,    Email       nvarchar(320) NOT NULL CONSTRAINT UQ_Customers_Email UNIQUE,    IsActive    bit NOT NULL CONSTRAINT DF_Customers_IsActive DEFAULT (1),    CreatedAt   datetime2(3) NOT NULL CONSTRAINT DF_Customers_CreatedAt DEFAULT (SYSUTCDATETIME()));

RETURNING-ის ნაცვლად T-SQL-ს აქვს OUTPUT, რომელიც აბრუნებს სვეტებს ჩასმული, განახლებული ან წაშლილი row-ებიდან:

sql
INSERT INTO dbo.Customers (Email)OUTPUT INSERTED.CustomerId, INSERTED.CreatedAtVALUES (N'ana@example.com');

ის მუშაობს UPDATE-ზეც (როგორც DELETED-ით ძველი მნიშვნელობებისთვის, ისე INSERTED-ით ახლებისთვის), DELETE-სა და MERGE-ზეც. ერთი შეზღუდვა: თუ ცხრილს ჩართული trigger აქვს, OUTPUT-მა უნდა ჩაწეროს INTO ცხრილის ცვლადში და არა პირდაპირ დააბრუნოს row-ები.

id-ის მიღების ძველი გზაა SCOPE_IDENTITY(), რომელიც აბრუნებს მიმდინარე scope-ში გენერირებულ ბოლო identity მნიშვნელობას. მოერიდე @@IDENTITY-ს, რომელიც აბრუნებს სესიაში სადმე გენერირებულ ბოლო მნიშვნელობას - მათ შორის trigger-ის შიგნით, რომელმაც audit ცხრილში ჩასვა - და IDENT_CURRENT('dbo.Customers')-ს, რომელიც ცხრილის ბოლო მნიშვნელობას ყველა სესიაში აბრუნებს და კონკურენტულობისას არასწორია.

Identity მნიშვნელობებს ხარვეზები აქვს და ეს ნორმალურია. უკან დაბრუნებული insert მნიშვნელობას ხარჯავს, SQL Server კი identity მნიშვნელობებს ბლოკებად ინახავს cache-ში, ამიტომ მოულოდნელმა restart-მა შეიძლება მიმდევრობა გადაახტუნოს - int-ისთვის 1,000-მდე. თუ ხარვეზებს მართლა აქვს მნიშვნელობა (თითქმის არასოდეს უნდა ჰქონდეს; ჩვეული შემთხვევა ინვოისების ნომრებია), ეს ნომრები თავად გააგენერირე ტრანზაქციის შიგნით და identity-ს ნუ დაეყრდნობი. identity სვეტში აშკარა მნიშვნელობების ჩასასმელად, მონაცემთა მიგრაციისთვის, insert მოათავსე SET IDENTITY_INSERT dbo.Customers ON;-სა და OFF-ს შორის.

ცხრილებს შორის გაზიარებული ან insert-მდე საჭირო მნიშვნელობებისთვის SEQUENCE ობიექტი PostgreSQL-ისას მსგავსად მუშაობს: CREATE SEQUENCE dbo.OrderNumbers START WITH 1000;, შემდეგ კი NEXT VALUE FOR dbo.OrderNumbers.

Upsert-ები: MERGE და უფრო უსაფრთხო ალტერნატივა#

T-SQL-ს არ აქვს ON CONFLICT ან ON DUPLICATE KEY UPDATE. მას აქვს MERGE:

sql
MERGE dbo.PageHits WITH (HOLDLOCK) AS targetUSING (SELECT @pageId AS PageId) AS source    ON target.PageId = source.PageIdWHEN MATCHED THEN    UPDATE SET Hits = target.Hits + 1WHEN NOT MATCHED THEN    INSERT (PageId, Hits) VALUES (source.PageId, 1);

MERGE-ში ორი რამ არჩევითი არ არის. ის წერტილმძიმით უნდა დასრულდეს, თორემ სინტაქსურ შეცდომას მიიღებ. და HOLDLOCK-ის (სამიზნეზე serializable lock-ის) გარეშე ორ სესიას შეუძლია ერთი და იმავე გასაღებისთვის ორივემ დაინახოს „not matched“ და ორივემ სცადოს ჩასმა, ამიტომ დატვირთვისას ერთი დუბლიკატი გასაღების შეცდომით ვარდება. MERGE-ს წლების განმავლობაში ბაგების გრძელი სიაც ჰქონდა, ძირითადად trigger-ებთან, ფილტრირებულ ინდექსებთან და ინდექსირებულ view-ებთან კომბინაციაში. ბევრი გამოცდილი SQL Server დეველოპერი ერთი row-ის upsert-ებისთვის მას თავს არიდებს და აშკარა ფორმას წერს:

sql
SET XACT_ABORT ON;BEGIN TRANSACTION;UPDATE dbo.PageHits WITH (UPDLOCK, SERIALIZABLE)SET Hits = Hits + 1WHERE PageId = @pageId;IF @@ROWCOUNT = 0    INSERT dbo.PageHits (PageId, Hits) VALUES (@pageId, 1);COMMIT TRANSACTION;

UPDLOCK, SERIALIZABLE hint-ები გასაღების დიაპაზონს მაშინაც ბლოკავს, როცა row ჯერ არ არსებობს, ამიტომ მეორე სესია ელოდება და არ ეჯიბრება. staging ცხრილიდან ბევრი row-ის bulk upsert-ებისთვის MERGE გონივრულია და გაცილებით მოკლე; გატესტე და HOLDLOCK დატოვე.

ტრანზაქციები და შეცდომების დამუშავება#

ყოველი ინსტრუქცია საკუთარ ტრანზაქციაში ეშვება, თუ თავად არ გახსნი, ისევე როგორც სხვა ძრავებში. განსხვავებული ნაწილი შეცდომების დამუშავებაა. ნაგულისხმევად T-SQL-ში ბევრი runtime შეცდომა მხოლოდ მიმდინარე ინსტრუქციას წყვეტს და არა ტრანზაქციას: batch გრძელდება, და შემდეგი COMMIT სამუშაოს ნახევარს commit-ს უკეთებს. SET XACT_ABORT ON ნებისმიერ runtime შეცდომაზე მთელ ტრანზაქციას უკან აბრუნებს, და ის უნდა იყოს ყოველი პროცედურის ან სკრიპტის პირველი ხაზი, რომელიც ტრანზაქციას ხსნის:

sql
CREATE OR ALTER PROCEDURE dbo.TransferCredit    @fromId int, @toId int, @amount decimal(12,2)ASBEGIN    SET NOCOUNT ON;    SET XACT_ABORT ON;    BEGIN TRY        BEGIN TRANSACTION;        UPDATE dbo.Accounts SET Balance = Balance - @amount WHERE AccountId = @fromId;        UPDATE dbo.Accounts SET Balance = Balance + @amount WHERE AccountId = @toId;        COMMIT TRANSACTION;    END TRY    BEGIN CATCH        IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION;        THROW;    END CATCH;END;

THROW არგუმენტების გარეშე თავდაპირველ შეცდომას მისი ნომრითა და შეტყობინებით ხელახლა აგდებს, ამიტომ აპლიკაცია ხედავს, რა მოხდა სინამდვილეში. SET NOCOUNT ON თიშავს „rows affected“ შეტყობინებებს, რომლებსაც ზოგიერთი დრაივერი სხვაგვარად შედეგების ნაკრებად აღიქვამს.

იზოლაცია PostgreSQL-ის დეველოპერებისთვის მეორე სიურპრიზია. SQL Server-ის ნაგულისხმევი READ COMMITTED დონე lock-ებს იყენებს, ამიტომ ხანგრძლივი update იმავე row-ების მკითხველებს ბლოკავს, სანამ commit არ მოხდება. read committed snapshot-ის ჩართვა მკითხველებს ნაცვლად ბოლო commit-ებულ ვერსიას აჩვენებს, ისე, როგორც PostgreSQL იქცევა:

sql
ALTER DATABASE [appdb] SET READ_COMMITTED_SNAPSHOT ON WITH ROLLBACK IMMEDIATE;

ის row-ების ვერსიებს tempdb-ში ინახავს, რაც ცოტა I/O-სა და ადგილს ჯდება, და ვებ-აპლიკაციების უმეტესობისთვის ბლოკირების მთელ კლასს აქრობს. ამის ნაცვლად ნუ მიმართავ WITH (NOLOCK)-ს: ის ჯერ commit-ის გარეშე დარჩენილ მონაცემებს კითხულობს და გვერდების გაყოფისას შეიძლება row-ები ორჯერ დააბრუნოს ან გამოტოვოს.

ტიპები, რომლებიც სხვაგვარად მუშაობს#

შეიძლება დაწეროT-SQL-შიშენიშვნა
boolean, TRUEbit, 1boolean ტიპი არ არსებობს; WHERE IsActive = 1
textnvarchar(max)text და ntext არსებობს, მაგრამ მოძველებულია
timestampdatetime2(3)timestamp T-SQL-ში row-ის ვერსიაა და არა თარიღი
timestamptzdatetimeoffsetმნიშვნელობასთან ერთად offset-ს ინახავს
uuiduniqueidentifierNEWID(); NEWSEQUENTIALID() მხოლოდ default-ებში
jsonbnvarchar(max) + JSON ფუნქციებიISJSON, JSON_VALUE, OPENJSON
მასივის ტიპებიშვილობილი ცხრილი, ან JSONმასივის სვეტები არ არსებობს

ძველ datetime-ს ამჯობინე datetime2, რადგან პირველი დაახლოებით 3 მილიწამის ნაბიჯებად ამრგვალებს და უფრო პატარა დიაპაზონი აქვს. მოერიდე money-ს, რომელსაც გაყოფისას მოულოდნელი დამრგვალება აქვს; გამოიყენე decimal. და გაითვალისწინე, რომ rowversion, რომელიც ასევე timestamp-ად იწერება, ავტომატურად ცვალებადი ბინარული მნიშვნელობაა ოპტიმისტური კონკურენტულობისთვის - სასარგებლოა, მაგრამ დროსთან კავშირი არ აქვს.

ტექსტისთვის nvarchar Unicode-ს ინახავს, varchar კი - არა, თუ სვეტს UTF-8 collation არ აქვს, და სტრიქონის ლიტერალებს N პრეფიქსი სჭირდებათ, რომ Unicode დარჩნენ. SQL Server-ის collation-ები და Unicode ამას სათანადოდ განიხილავს, რადგან ეს დაზიანებული ტექსტის ყველაზე ხშირი წყაროა.

სტრიქონები, NULL-ები და თარიღები#

სტრიქონების შეერთება +-ს იყენებს, და NULL + 'anything' არის NULL. CONCAT NULL-ს ცარიელ სტრიქონად თვლის, CONCAT_WS კი გამყოფს ამატებს და NULL-ებს გამოტოვებს:

sql
SELECT FirstName + ' ' + LastName              AS may_be_null,       CONCAT(FirstName, ' ', LastName)         AS never_null,       CONCAT_WS(', ', Street, City, Postcode)  AS addressFROM dbo.Customers;

სხვა ძრავების გავრცელებული ფუნქციების ეკვივალენტები:

სხვა ძრავებიT-SQL
string_agg, GROUP_CONCATSTRING_AGG(Name, ', ') WITHIN GROUP (ORDER BY Name)
ILIKELIKE რეგისტრისადმი არამგრძნობიარე collation-ით (ნაგულისხმევი)
IFNULL, NVLISNULL(a, b) ან COALESCE(a, b, c)
NOW()SYSDATETIME(), ან SYSUTCDATETIME() UTC-სთვის
date_trunc('month', d)DATETRUNC(month, d) (2022)
generate_seriesGENERATE_SERIES(1, 100) (2022)
GREATEST, LEASTGREATEST, LEAST (2022)
IS DISTINCT FROMIS DISTINCT FROM (2022)

მათგან რამდენიმე მხოლოდ SQL Server 2022-ში გამოჩნდა, ზოგიერთი კი, როგორიცაა GENERATE_SERIES, მოითხოვს, რომ ბაზა compatibility level 160-ზე იყოს. ძველი სერვერიდან აღდგენილი ბაზა თავის ძველ დონეს ინარჩუნებს, სანამ არ აწევ; ბაზის მიგრაცია SQL Server ჰოსტინგზე განიხილავს, როდის გააკეთო ეს.

ISNULL და COALESCE ისე განსხვავდება, რომ შეიძლება იკბინოს: ISNULL აბრუნებს თავისი პირველი არგუმენტის ტიპს, ამიტომ ISNULL(@shortVarchar, 'a much longer default') ნაგულისხმევ მნიშვნელობას წყვეტს. COALESCE ტიპების ჩვეულებრივ პრიორიტეტს მისდევს. და DATEDIFF ითვლის გადაკვეთილ საზღვრებს და არა გასულ დროს: DATEDIFF(year, '2025-12-31', '2026-01-01') არის 1.

GETDATE() აბრუნებს სერვერის ადგილობრივ დროს როგორც datetime. სერვერზე, რომელსაც შენ არ აკონტროლებ, ეს შეიძლება შენი დროის სარტყელი არ იყოს. შეინახე UTC SYSUTCDATETIME()-ით და საჩვენებლად გადაიყვანე, AT TIME ZONE-ით, თუ ეს აუცილებლად SQL-ში უნდა გააკეთო.

JSON, CTE-ები და სიმრავლეებით აზროვნება#

SQL Server 2022-ს მშობლიური JSON სვეტის ტიპი არ აქვს; JSON ცხოვრობს nvarchar(max)-ში და ფუნქციებით მუშავდება. ეს იმაზე ნაკლებად შემზღუდველია, ვიდრე ჟღერს. check შეზღუდვა არავალიდურ დოკუმენტებს არ უშვებს, JSON_VALUE სკალარს კითხულობს, JSON_QUERY ობიექტს ან მასივს, OPENJSON კი დოკუმენტს row-ებად აქცევს, რომლებთანაც join შეგიძლია:

sql
CREATE TABLE dbo.Events (    EventId  bigint IDENTITY PRIMARY KEY,    Payload  nvarchar(max) NOT NULL CONSTRAINT CK_Events_Json CHECK (ISJSON(Payload) = 1),    UserId   AS CAST(JSON_VALUE(Payload, '$.userId') AS int)   -- computed column);CREATE INDEX IX_Events_UserId ON dbo.Events (UserId);SELECT e.EventId, t.[value] AS TagFROM dbo.Events AS eCROSS APPLY OPENJSON(e.Payload, '$.tags') AS tWHERE e.UserId = 42;

გამოთვლადი სვეტი ის ხრიკია, რომელიც JSON query-ებს სწრაფს ხდის: SQL Server-ს დოკუმენტის შიგნით პირდაპირ ინდექსირება არ შეუძლია, მაგრამ შეუძლია გამოთვლადი სვეტის ინდექსირება, რომელიც მნიშვნელობას ამოიღებს, და ოპტიმიზატორი ამ ინდექსს იყენებს query-ებისთვის, რომლებიც იმავე გამოსახულებით ფილტრავენ. საპირისპირო მიმართულებით, FOR JSON PATH SELECT-ის ბოლოს შედეგს JSON დოკუმენტად აბრუნებს, და წერტილიანი სვეტის alias-ები, როგორიცაა [customer.email], ჩადგმულ ობიექტებს ქმნის. SQL Server 2022-მა ასევე დაამატა JSON_OBJECT და JSON_ARRAY დოკუმენტების ადგილზე ასაგებად, და JSON_PATH_EXISTS იმის შესამოწმებლად, არსებობს თუ არა გზა.

Common table expression-ები სხვა ძრავების მსგავსად მუშაობს, ორი დეტალით. WITH-მდე მდგომი ინსტრუქცია წერტილმძიმით უნდა დასრულდეს, სწორედ ამიტომ ხედავ T-SQL-ს ;WITH-ად დაწერილს. და რეკურსიული CTE-ები - რომლებიც RECURSIVE საკვანძო სიტყვის გარეშე იწერება, უბრალოდ საკუთარ თავზე მითითებით - ნაგულისხმევად 100 დონის შემდეგ შეცდომით ჩერდება; უფრო ღრმად წასასვლელად გარე query-ს დაუმატე OPTION (MAXRECURSION 1000), ან 0 ლიმიტის გარეშე, თუ დარწმუნებული ხარ, რომ რეკურსია დასრულდება.

უფრო ფართო ჩვევა, რომლის T-SQL-ში მოტანაც ღირს, სიმრავლეებით აზროვნებაა. პროცედურული კოდი cursor-ებითა და WHILE ციკლებით, რომელიც ერთდროულად ერთ row-ს ამუშავებს, SQL Server-ის წარმადობის კლასიკური პრობლემაა, რადგან ყოველი იტერაცია ცალკე ინსტრუქციაა საკუთარი დამატებითი ხარჯითა და lock-ებით. UPDATE join-ით ან INSERT ... SELECT იმავე სამუშაოს ერთ გავლაში აკეთებს. ციკლები სწორი ინსტრუმენტია ზუსტად ერთ გავრცელებულ შემთხვევაში: როცა უზარმაზარ ცვლილებას შეგნებულად ნაწილებად ყოფ, რომ lock-ები არ დაიკავოს და ტრანზაქციების ლოგი არ გაავსოს, როგორც ამას SQL Server-ის recovery მოდელები და ლოგის ზრდა აღწერს.

იდენტიფიკატორები, batch-ები და DDL-ის ჩვევები#

იდენტიფიკატორები, რომლებიც საკვანძო სიტყვებს ემთხვევა ან ჰარებს შეიცავს, კვადრატულ ფრჩხილებში იწერება: [Order], [User Name]. ორმაგი ბრჭყალებიც მუშაობს, როცა QUOTED_IDENTIFIER ჩართულია, რაც ყოველი თანამედროვე დრაივერისთვის ასეა; backtick-ები საერთოდ არ მუშაობს. ობიექტები სქემებში ცხოვრობენ, ნაგულისხმევად dbo-ში, და კარგი პრაქტიკაა, სქემა ყოველთვის დაწერო - dbo.Orders - რაც სახელის გარჩევის ნაბიჯსა და plan cache-ის სიურპრიზებს გაცილებს.

სკრიპტები batch-ებად იყოფა GO-ით, რომელიც კლიენტის მხარის გამყოფია sqlcmd-სა და SSMS-ში და არა T-SQL. CREATE PROCEDURE, CREATE VIEW და CREATE FUNCTION batch-ში პირველი ინსტრუქცია უნდა იყოს, ადგილობრივი ცვლადები კი GO-ს ვერ გადაურჩება. sqlcmd და bcp განიხილავს ასეთი სკრიპტების pipeline-იდან გაშვებას.

წერტილმძიმეები უმეტეს ინსტრუქციაზე არჩევითია, მაგრამ რამდენიმე ადგილას სავალდებულოა: MERGE მისით უნდა დასრულდეს, ხოლო WITH common table expression-ის ან THROW-ს წინა ინსტრუქცია უნდა დაიხუროს. ყოველი ინსტრუქციის წერტილმძიმით დასრულება მთელ ამ საკითხს აქრობს.

განმეორებადი DDL-ისთვის:

sql
CREATE OR ALTER VIEW dbo.ActiveCustomers ASSELECT CustomerId, Email FROM dbo.Customers WHERE IsActive = 1;GODROP TABLE IF EXISTS dbo.ImportStaging;IF OBJECT_ID(N'dbo.AuditLog', N'U') IS NULL    CREATE TABLE dbo.AuditLog (AuditId bigint IDENTITY PRIMARY KEY, Message nvarchar(4000));

CREATE OR ALTER მუშაობს view-ებზე, პროცედურებზე, ფუნქციებსა და trigger-ებზე, მაგრამ არა ცხრილებზე, და CREATE TABLE IF NOT EXISTS არ არსებობს, აქედან OBJECT_ID-ის შემოწმება. სვეტის დამატება არის ALTER TABLE dbo.Orders ADD Notes nvarchar(500) NULL;, COLUMN საკვანძო სიტყვის გარეშე. დროებითი ცხრილები #-ით იწყება და სესიის დასრულებისას ქრება; ## ქმნის გლობალურს, რომელიც ყველა სესიისთვის ხილულია, რაც იშვიათად არის ის, რაც გინდა.

პარამეტრები და დინამიკური SQL#

ყველაფერი ზემოთ პარამეტრებს გულისხმობს, და წესი იგივეა, რაც ყველგან: არასოდეს ააწყო SQL მომხმარებლის შეყვანის მიერთებით. როცა T-SQL-ის შიგნით მართლა გჭირდება დინამიკური SQL - ვთქვათ, დალაგების სვეტი, რომელიც გაშვებისას ირჩევა - გამოიყენე sp_executesql პარამეტრებით მნიშვნელობებისთვის და QUOTENAME იდენტიფიკატორებისთვის:

sql
DECLARE @sql nvarchar(max) =    N'SELECT TOP (@n) OrderId, CreatedAt FROM dbo.Orders ORDER BY '    + QUOTENAME(@sortColumn) + N' DESC;';EXEC sp_executesql @sql, N'@n int', @n = @pageSize;

QUOTENAME სახელს ფრჩხილებში სვამს და მის შიგნით ყოველ დამხურავ ფრჩხილს escape-ს უკეთებს, ამიტომ მავნე სვეტის სახელი ვერ გაარღვევს. დამატებით შეამოწმე ის დაშვებული სვეტების სიასთან. პარამეტრიზებული sp_executesql ზარები ასევე აძლევს SQL Server-ს საშუალებას, ყოველი მნიშვნელობისთვის ერთი plan ხელახლა გამოიყენოს, რასაც მიერთებული SQL არ აკეთებს. SQL Server-ის login-ები, მომხმარებლები და როლები განიხილავს, როგორ შეზღუდო, რისი გაკეთება შეუძლია აპლიკაციის login-ს, თუ რამე მაინც გაიპარება.

FAQ#

როგორ შევზღუდო row-ები SQL Server-ში LIMIT-ის გარეშე?

პირველი n row-ისთვის გამოიყენე SELECT TOP (n) ... ORDER BY ..., გვერდებად დაყოფისთვის კი ORDER BY ... OFFSET x ROWS FETCH NEXT n ROWS ONLY. OFFSET მოითხოვს ORDER BY-ს, რომელიც უნიკალურ სვეტს უნდა მოიცავდეს, რომ გვერდები სტაბილური იყოს.

რა არის RETURNING-ის ეკვივალენტი SQL Server-ში?

OUTPUT ნაწილი. INSERT ... OUTPUT INSERTED.Id VALUES (...) ახალ identity-ს აბრუნებს, და ის UPDATE-ზე, DELETE-სა და MERGE-ზეც მუშაობს, DELETED კი ძველ მნიშვნელობებს იძლევა. trigger-ების მქონე ცხრილზე output ცხრილის ცვლადში გაგზავნე.

აქვს SQL Server-ს boolean ტიპი?

არა. გამოიყენე bit, რომელიც ინახავს 0-ს, 1-ს ან NULL-ს, და შეადარე = 1-ით ან = 0-ით. ORM-ებისა და დრაივერების უმეტესობა თავის boolean ტიპს bit-ზე ავტომატურად აბამს.

რატომ იბლოკება ჩემი წაკითხვები, როცა სხვა სესია update-ს აკეთებს?

ნაგულისხმევი იზოლაციის დონე shared lock-ებს იყენებს, რომლებიც მწერლების commit-ს ელოდებიან. ბაზისთვის ჩართე READ_COMMITTED_SNAPSHOT, რომ მკითხველებმა ნაცვლად ბოლო commit-ებული ვერსია დაინახონ. სწორედ ამას ელის PostgreSQL-იდან მოსული დეველოპერების უმეტესობა.

უსაფრთხოა MERGE-ის გამოყენება upsert-ებისთვის?

მუშაობს, მაგრამ კონკურენტულობისას უსაფრთხოებისთვის HOLDLOCK სჭირდება, დამასრულებელი წერტილმძიმე და ტესტირება, თუ trigger-ები ან ფილტრირებული ინდექსებია ჩართული. ერთი row-ის upsert-ებისთვის UPDATE UPDLOCK, SERIALIZABLE-ით, რასაც პირობითი INSERT მოსდევს, უფრო მარტივი და პროგნოზირებადია.


კომენტარები

სრულიად ანონიმურად: ანგარიშის, ელფოსტის და cookie-ის გარეშე. ინახება მხოლოდ სახელი, ტექსტი და დრო - სხვა არაფერი. ბმულების რაოდენობა ლიმიტირებულია.

0/2000