|
SQL tips af Joakim Dalby SQL er et deklarativt forespørgsels
programmeringssprog, d.v.s. man angiver, hvad man ønsker at få ud af
databasen fremfor at beskrive, hvordan man finder det ønskede i databasen. Deklarerer
betyder angiver. »Declarative
language describes what should be done, not how it should be done.« Donald D. Chamberlin og Raymond F. Boyce er designere
af den originale SQL-sprogspecifikation i 1974, læs gerne deres artikel. Læs mere om historikken omkring SQL
i artiklen Remembering E.F.
Codd. Læs mere om database design og SQL i
Database-Håndbogen. Læs mere om SQL funktioner i SQL database functions og i nederst venstre hjørne klik download
pdf, og se gode illustrationer. Mange tips på nettet sqlshack Statisticsparser sqlperformance Sql tools Sommarskog Cathrine Wilhelmsen Ola
Hallengren Maintenance Solution David Schanzer Toolbox Brent Ozar blitz Microsoft How to maintain azure sql indexes and statistics Dynamisk
SQL Dynamic sql Dynamic
search conditions Arrays
in sql System
stored procedures Metadata queries Round function Sql server
execution plans for query Improve sql query performance Index usage The Data Loading Performance Guide Reduce deadlocks with a clustered index Undgå:
Warning: Null value is eliminated by an aggregate or other SET operation. Anvend: SET ANSI_WARNINGS OFF SQL Server eksempel databaser: pubs,
northwind, adventure works, wide world og et datamart eksempel polaris. T-SQL
står for Transact-SQL Structured Query Language til bl.a. @variabel, #tabel og
flow control i en stored procedure samt dynamisk sql sætninger der bliver udført
og execute on the fly. Types of SQL Commands · DDL – Data Definition Language e.x. CREATE TABLE,
ALTER TABLE, TRUNCATE TABLE. · DML – Data Manipulation Language e.x. INSERT INTO,
UPDATE, DELETE. · DCL – Data Control Language e.x. GRANT: Give user’s
access privileges to database. · DQL – Data Query Language e.x. SELECT. · TCL – Transaction Control Language e.x. SAVE
TRANSACTION @SavePoint, BEGIN TRANSACTION, COMMIT
TRANSACTION, ROLLBACK TRANSACTION. Anbefalet
options i SQL Server Management Studio
Se options. Får man denne fejlmelding ved design
af en tabel i SQL Server Management Studio: »Saving changes is not permitted.
The changes that you have made require the following tables to be dropped
and re-created. You have either made changes to a table that can't be
re-created or enabled the option Prevent saving changes that require the
table to be re-created.« så vælg i menuen Tools, Options, åben noden
Designers, klik på noden Table and Database Designers og fjern flueben i
Prevent saving changes that require the table re-creation. Inline Primary
key, Index, References and Default creation tabel eksempel CREATE TABLE dbo.Employee ( EmployeeId int
IDENTITY(1,1) NOT NULL CONSTRAINT PK_dbo_Employee PRIMARY
KEY CLUSTERED, Name nvarchar(100) NOT NULL INDEX
IX_dbo_Employee_Name NONCLUSTERED (Name), Title nvarchar(100) NULL CONSTRAINT
UK_Title UNIQUE NONCLUSTERED
(Title ASC), -- unique key Code nvarchar(10) NOT NULL CONSTRAINT
CK_Code CHECK (Code
IN('Good','Great')), BirthDate date
NULL CONSTRAINT CK_BirthDate CHECK (BirthDate <= cast(getdate() as date)), Age tinyint NULL CONSTRAINT CK_Age CHECK (Age IS NULL OR (Age IS NOT NULL AND Age >= 18)), DepartmentId INT
NOT NULL CONSTRAINT FK_dbo_Employee_Department FOREIGN KEY (DepartmentId) REFERENCES dbo.Department (DepartmentId), IsEmployed bit
NOT NULL CONSTRAINT DF_dbo_Employee_IsEmployed DEFAULT ((0)), InsertTime_UTC_meta datetime2(3) NOT NULL CONSTRAINT DF_dbo_Employee_InsertTime_UTC_meta DEFAULT (sysutcdatetime()) ) ON [PRIMARY] GO Ovenstående
contraint danner automatisk et indeks ellers kan det gøres: CREATE UNIQUE NONCLUSTERED INDEX UIX_Title_dbo_Employee_Title
ON dbo.Employee (Title ASC) WHERE (Title IS NOT NULL) -- accepts rows with duplicate null value ON [PRIMARY]
-- act as an unique key (UK) GO Tilføjelse
af en ny kolone/felt (der angives ikke add column): ALTER TABLE dbo.Employee ADD
UpdateTime_UTC_meta datetime2(3) NULL GO Tilføjelse
af en default værdi til en eksisterende kolonne/felt: ALTER TABLE dbo.Employee ADD CONSTRAINT DF_dbo_Employee_UpdateTime_UTC_meta DEFAULT (sysutcdatetime()) FOR UpdateTime_UTC_meta GO Tilføjelse
af en ny kolonne/felt med default der udfylder alle rækkerne automatisk: ALTER TABLE dbo.Employee ADD
UpdateTime_UTC_meta datetime2(3) NOT NULL CONSTRAINT
DF_dbo_Employee_UpdateTime_UTC_meta DEFAULT (sysutcdatetime()) GO Tilføjelse af en primærnøgle til en
tom tabel, feltet skal være udfyldt, not null: ALTER TABLE dbo.Employee ALTER COLUMN EmployeeId int
NOT NULL GO ALTER TABLE dbo.Employee ADD CONSTRAINT PK_dbo_Employee PRIMARY
KEY CLUSTERED
(EmployeeId) ON [PRIMARY] GO Tilføjelse af en primærnøgle til en
fyldt tabel, feltet skal være udfyldt, not null: ALTER TABLE [fact].[Salg] ADD
[FactSalgId] int IDENTITY(1,1) NOT NULL GO ALTER TABLE [fact].[Salg] ADD CONSTRAINT PK_fact_Salg PRIMARY
KEY CLUSTERED ([FactSalgId]) ON [PRIMARY] GO Alternativ
til unikt indeks er en constraint hvor NULL kravet kan ikke kan opsættes og UK_Title
bliver vist under Indexes under tabellen dbo.Employee i Management Studio,
fordi der uden et indeks vil blive udført en tablescan som tager tid for at
undersøge om indsatte titel findes i forvejen i tabellen: ALTER TABLE dbo.Employee ADD CONSTRAINT UK_Title UNIQUE
NONCLUSTERED(Title) -- unique key (UK) GO Slet primærnøgle reglen: ALTER TABLE [dbo].[Employee]
DROP CONSTRAINT [PK_dbo_Employee] WITH ( ONLINE = OFF ) GO Slet en kolone/felt: ALTER TABLE [dbo].[Employee]
DROP COLUMN EmployeeId Alternativ til constraint angivelse er
efter at tabellen er blevet kreeret og med et tjek af eksisterende rækker med
data i tabellen: ALTER TABLE [dbo].[Employee] WITH CHECK ADD CONSTRAINT
[CK_Code] CHECK
(([Code]='Great' OR
[Code]='Good')) GO ALTER TABLE [dbo].[Employee]
CHECK CONSTRAINT
[CK_Code] GO ALTER TABLE [dbo].[Employee] WITH CHECK ADD CONSTRAINT
[FK_dbo_Employee_Department] FOREIGN KEY([DepartmentId]) REFERENCES [dbo].[Department] ([DepartmentId]) GO ALTER TABLE [dbo].[Employee]
CHECK CONSTRAINT
[FK_dbo_Employee_Department] GO Klassisk
opstilling for kreering af en tabel med nogle af ekstra regler CREATE TABLE [dbo].[Employee] ( [EmployeeId]
[int] IDENTITY(1,1) NOT NULL, [Name]
[nvarchar](100)
NOT NULL, [Title]
[nvarchar](100)
NULL, [Code]
[nvarchar](10)
NOT NULL, [BirthDate]
[date] NULL, [Age]
[tinyint] NULL, [DepartmentId]
[int] NOT NULL, [IsEmployed]
[bit] NOT NULL
CONSTRAINT [DF_dbo_Employee_IsEmployed] DEFAULT ((0)), [InsertTime_UTC_meta]
[datetime2](3)
NOT NULL CONSTRAINT [DF_dbo_Employee_InsertTime_UTC_meta] DEFAULT (sysutcdatetime()), CONSTRAINT
[PK_dbo_Employee] PRIMARY KEY CLUSTERED ( [EmployeeId]
ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE =
OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS
= ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY], CONSTRAINT
[UK_Title] UNIQUE NONCLUSTERED
( [Title]
ASC -- unique key
(UK) )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE =
OFF, IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO Andet eksempel på navngivning med
prefix PK_, IX_, UIX_, DF_ og hvor schemaname og tablename og
columnname indgår i navngivningen: CREATE TABLE [base].[TradingPhases] ( [TradingPhases_Id]
[bigint] IDENTITY(1,1) NOT NULL, [DatetimeOfOrder_UTC]
[date] NOT NULL, [SequenceNumber]
[bigint] NOT NULL, [FinancialInstrument]
[varchar(12)] NOT NULL, [TradingVenueMIC]
[varchar(20)] NULL, [InsertTime_UTC_meta]
[datetime2](3)
NOT NULL, [RecordSource_meta_SourceFile_Log_Id]
[int] NOT NULL, CONSTRAINT [PK_base_TradingPhases] PRIMARY KEY CLUSTERED ( [TradingPhases_Id]
ASC ) ) GO CREATE NONCLUSTERED INDEX
[IX_base_TradingPhases_RecordSource_meta_SourceFile_Log_Id] ON [base].[TradingPhases] ( [RecordSource_meta_SourceFile_Log_Id]
ASC ) GO CREATE UNIQUE NONCLUSTERED INDEX
[UIX_base_TradingPhases_Multiple] ON
[base].[TradingPhases] ( [DatetimeOfOrder_UTC]
ASC, -- unique key (UK). [SequenceNumber]
ASC, [FinancialInstrument]
ASC ) INCLUDE([TradingVenueMIC]) -- to cover an extra column to avoid keylookup to table. GO ALTER TABLE [base].[TradingPhases]
ADD CONSTRAINT
[DF_base_TradingPhases_InsertTime_UTC_meta]
DEFAULT (sysutcdatetime())
FOR [InsertTime_UTC_meta] GO Alle
objekter så som tabel, felt/kolonne, primærnøgle, fremmednøgle reference,
default værdi og indeks skal have et sigende navn til senere opdatering via
et sql script. I SQL Server Management Studio under Object Explorer kan der
ud for en tabel klikkes på + tegnet og objekttyper folder sig ud. Eksempelvis
under tabellens Constraints vises default værdi objekter, og på et navn som DF__Employee__IsEmpl__2176D992
kan der højreklikkes og vælges Rename og indtaste et mere sigende navn på
objektet for en default værdi til et felt. Forslag til andre navngivning
Tabelnavn:
Sagstype som svarer til et forretningsbegreb. Tabelnavn:
SagType fordi Type er en egenskab/felt/kolonne til forretningsbegrebet Sag og
det er valgt at beholde kolonnenavnet Type i tabellen Sag, selvom Type er et
meget generisk og generelt udtryk i forhold til Sagstype. Nedenfor
ser jeg hurtigt, at det er en tabel OrderDetails der joins med en CTE der er
defineret ovenover select delen, ligeså er table alias holdt som forkortelse
med små bogstaver. Jeg forsøger at undgå brug af [] ved at undgå mellemrum
men omkring reserverede ord kan [] være praktisk så ordets farve er neutral: SELECT [Year] = Year(OrderDate) FROM dbo.OrderDetails od INNER JOIN
max_OrderID mx ON mx.OrderID
= od.OrderID Metadata
og dokumentation i hovedet af en stored procedure CREATE PROC [mart].[Customer_Mart_Dim_Customer_Fact_Sales_Load] AS /* ============================================================ Author: Joakim Dalby Create date: 2008-02-07 SSIS-package:Load_Customer_Mart SQL job: Load_Customer_Mart_ every_hour Description: Moving data for
customer dimension and sales fact from Sales_EDW
to Customer_Mart. Changelog: 2008-08-16 Joakim Dalby, add a
vat calculation. 2009-10-30 Charlotte Jensen,
add a CustomerType. 2014-06-05 Flemming Bohrs,
change the revenue calculation. ============================================================ */ SET NOCOUNT ON Changelog or Revision History. Password
- adgangskode Send
ikke er password fra en applikation til en database for at kontrollere rigtigheden,
fordi passwordet kan måske blive aflæst af en hacker. I
stedet applikationen UI vil hash password som sendes til databasen, og i
databasen bliver det gemte password også hashed, og de to hash værdier sammenlignes
i databasen f.eks. i en stored procedure. Password forlader aldrig databasen
og det er kun en dba med hans rettigheder som kan se passwordet. Dato
søgning Find
de ordrer som er efter 14 dage fra Ordredato, eller sagt på en anden måde, at
Ordredato er mere end 14 dage gammel i forhold til dagsdato: DATEDIFF(DAY,
Ordredato, GETDATE()) > 14 DATEDIFF(DAY,
Fradato, Tildato) hvor Fradato normalt kommer før Tildato. SELECT DATEDIFF_BIG(second, '0001-01-01
00:00:00.0000000', '2020-10-20 23:59:59.9999999'); giver 63.738.835.199
sekunder. SELECT DATEDIFF_BIG(day, '0001-01-01
00:00:00.0000000', '2020-10-20 23:59:59.9999999'); giver 737.717 dage. I
SQL 2000 er der kun datetime og afhængig
af gemning i et felt f.eks. bruges Getdate() så gemmes dato og klokkeslæt
eksempelvis: 2013-12-01 12:24:14.000 hvilket kræver en konvertering af
datofeltet ved udsøgning på en dato uden klokkeslæt eksempelvis: WHERE
CONVERT(char(10), Ordredato, 126) = '20131201' ååååmmdd eller dagsdato som også konverteres WHERE CONVERT(char(10), Ordredato,
126)=CONVERT(char(10), Getdate(), 126) Når
en bruger indtaster en dato i et felt af datetime sættes klokkeslæt til
midnat eksempelvis 2013-12-01 00:00:00.000 kan en dato direkte udsøges
eksempelvis WHERE Ordredato = '20131201' eller dagsdato som først konverteres
WHERE Ordredato = CONVERT(char(10),
Getdate(), 126) WHERE Ordredato BETWEEN '20131201'
AND '20131231' søger med klokkeslæt 00:00:00.000 d.v.s. en Ordredato
2013-12-31 12:24:14.000 medtages ikke, så WHERE
CONVERT(char(10),Ordredato,126) BETWEEN '20131201' AND '20131231' WHERE Ordredato >= '20131201' AND
Ordredato < '20140101' så medtages til og med 2013-12-31 23:59:59.999. Til
og med i går: WHERE Ordredato < Getdate(). Ordredato er et datetime felt i
databasen med klokkeslæt f.eks. 2017-12-03 13:32:45.123 som er
søndag eftermiddag. DECLARE @FraDato as date =
'20171127' DECLARE
@TilDato as date = '20171203' WHERE
Ordredato >= @FraDato AND Ordredato < DATEADD(d, 1, @TilDato)
-- d.v.s. < 20171204 00:00:00.000 WHERE Ordredato BETWEEN @FraDato AND
DATETIMEFROMPARTS(YEAR(@TilDato), MONTH(@TilDato),
DAY(@TilDato), 23, 59, 59, 999) I SQL 2008 kom date datatypen, og den indeholder ikke klokkeslæt, så udsøgning
bliver mere simpel: WHERE
Ordredato = '20131201' eller '2013-12-01' d.v.s. yyyymmdd Eller dagsdato som først konverteres,
da Getdate fortsat giver et klokkeslæt: WHERE Ordredato = CAST(Getdate() AS date) Når Ordredato er en datetime2(7) d.v.s. indeholder måske
et klokkeslæt som ikke er midnat, og jeg ønsker alle rækker med dagsdato: WHERE
CAST(Ordredato AS date) = CAST(Getdate() AS date) Eller et kendt interval, her
december 2013 uanset klokkeslæt i Ordredato: WHERE
CAST(Ordredato AS date) BETWEEN '20131201' AND '20131231' Bedst for svartiden / performance
fordi feltet Ordredato ikke bliver casted: WHERE Ordredato BETWEEN '2013-12-01
00:00:00.0000000' AND '2013-12-31 23:59:59.9999999' Første
dato i gældende måned
(1.): FirstDateOfMonth ·
DECLARE @dt date = DATEADD(day, 1-DAY(GETDATE()),
GETDATE()); SELECT @dt; ·
CAST(DATEADD(day, -DAY(GETDATE()) + 1, GETDATE()) AS
date) ·
CAST(GETDATE() - DAY(GETDATE()) + 1 AS date) ·
CAST(FORMAT(GETDATE(),'yyyyMM01') AS DATE) ·
DATEADD(month, DATEDIFF(month, 0, GETDATE()), 0) ·
DATEFROMPARTS(YEAR(GETDATE()), MONTH(GETDATE()), 1) Eller den 1. om fire måneder: CAST(DATEADD(month,
4, GETDATE() - DAY(GETDATE()) + 1) AS date) Sidste
dato i gældende måned
(28., 29., 30., 31.): LastDateOfMonth ·
CAST(DATEADD(d, -DAY(GETDATE()), DATEADD(m, 1,
GETDATE())) AS date) ·
CAST(DATEADD(m, 1, GETDATE()) - DAY(GETDATE()) AS
date) ·
DATEADD(day, -1,
DATEADD(month, DATEDIFF(month, 0, GETDATE()) + 1, 0)) ·
EOMONTH(GETDATE()) Første
dato i gældende kvartal:
FirstDateOfQuarter CONVERT(date,DATEADD(quarter,
DATEDIFF(quarter, 0, GETDATE()), 0)) Sidste
dato i gældende kvartal:
LastDateOfQuarter CONVERT(date,DATEADD(day,-1,DATEADD(quarter,DATEDIFF(quarter,0,GETDATE())+1,0))) Første
dato i gældende uge:
FirstDateOfWeek SELECT
DATEADD(wk, DATEDIFF(d, 0, GETDATE()) / 7, 0) eller SET
DATEFIRST 1 SELECT
CAST(GETDATE() - DATEPART(dw, GETDATE()) + 1 AS date) Sidste
dato i gældende uge:
LastDateOfWeek SELECT
DATEADD(wk, DATEDIFF(d, 0, GETDATE()) / 7, 6) eller SET
DATEFIRST 1 SELECT
CAST(GETDATE() + (7 - DATEPART(dw, GETDATE())) as date) Abbreviations:
d = day, m = month, wk = week, dw = weekday, qq = quarter. DATEDIFF bruger altid søndag som den
første dag i ugen for at sikre, at funktionen er deterministisk. Heltal
dato yyyymmdd konverteret til ægte dato yyyy-mm-dd SELECT
CONVERT(date, '20130207', 23) SELECT
CONVERT(date, '20130702', 23) SELECT
CONVERT(date, CAST(OrderDate AS char(10)), 23) Ægte
dato konverteret til heltal dato yyyymmdd SELECT CONVERT(char(8), Getdate(),
112) -- giver
streng SELECT CONVERT(int,CONVERT(char(8),
Getdate(), 112)) -- giver heltal Dato variabel eller felt som heltal
yyyymmdd til en streng: DECLARE
@Birthdate as date = '1966-02-07' SELECT
CONVERT(char(8), @Birthdate, 112), CONVERT(char(8), OrderDate, 112) Gældende
klokkeslæt hh:mm:ss fås SELECT
CONVERT (varchar, Getdate(), 8) SELECT
CONVERT (time(0), Getdate(), 24) Udregn en alder på en person (human
age): (yyyyMMdd - yyyyMMdd) / 10000 =
forskel i fulde år ved datoer som heltal. DECLARE
@Birthdate as date = '1966-02-07' SELECT (CONVERT(int,CONVERT(char(8),
Getdate(), 112)) - CONVERT(int,CONVERT(char(8), @Birthdate, 112))) / 10000 SQL Date Format
Examples using CONVERT Function Første
dato og Sidste dato ud fra et heltal på formen ååååmm / yyyymm DECLARE @yyyymm int = 202002 DECLARE @FirstDate AS date DECLARE @LastDate AS date -- Anvend en
Kalender tabel: SELECT @FirstDate = MIN([Dato]), @LastDate
= MAX([Dato]) FROM [dbo].[Kalender] WHERE ÅrMåned = @yyyymm SELECT FirstDate = @FirstDate,
LastDate = @LastDate -- Anvend en
algoritme: SET @FirstDate = CONVERT(date, CONCAT(@yyyymm,'01')) SET @LastDate = EOMONTH(CONVERT(date, CONCAT(@yyyymm,'01'))) SELECT FirstDate = @FirstDate,
LastDate = @LastDate
SET
@LastDate = DATEFROMPARTS(LEFT(@yyyymm,4), RIGHT(@yyyymm,2), DAY(EOMONTH(DATEFROMPARTS(LEFT(@yyyymm,4), RIGHT(@yyyymm,2),1)))) SQL Server har forskellig format for
en dato med Select eller Print: Få sekunderne med ved en
konvertering til en streng: SELECT
CONVERT(VARCHAR,GETDATE(),121) -- 2013-02-01 14:23:44.340 SELECT
CONVERT(VARCHAR,GETDATE(),126) -- 2013-02-01T14:23:44.340 Fordi denne her giver kun timer og
minutter: SELECT
CONCAT(GETDATE(),NULL) -- Feb 1
2018 2:23PM Så undgå at bruge den ved dannelse
af en hashbyte, gør i stedet dette: SELECT
CONCAT(CONVERT(VARCHAR,GETDATE(),126),';') Formatter
en dato og andre dato operationer declare @date datetime = getdate()
-- '2016-12-31 23:59:59.998' select @date -- giver 2016-12-31 23:59:59.998 print @date -- giver Dec 31 2016 11:59PM print
cast(format(@date,'yyyy0101') as date)
-- giver 2016-01-01 print
cast(format(@date,'yyyyMM01') as date)
-- giver 2016-12-01 print
cast(format(@date,'yyyy0101') as int)
-- giver 20160101 print
cast(format(@date,'yyyyMMdd') as int)
-- giver 20161231 print
cast(format(@date,'yyyyMM') as int)
-- giver 201612 print
format(@date,'yyyyMMddThh:mm:ss.fff','en-US') --20161231T11:59:59.997 print
format(@date,'yyyyMMddTHH:mm:ss.fff','en-US') --20161231T23:59:59.997 Find debitor rækken hvor datoen er i
perioden mellem FraDato og TilDato: WHERE
DebitorID = @Debitor AND @date >= FraDato AND @date <=
TilDato ELLER: FraDato
<= @date AND TilDato >= @date
ELLER: @date BETWEEN FraDato AND TilDato Ofte er perioden mellem FraDato og
TilDato ikke et lukket interval, fordi datoen i feltet TilDato ikke må
medtages, f.eks. ved Kimball type 2 dimension: WHERE DebitorID = @Debitor AND @date >= FraDato AND @date <
TilDato ELLER: FraDato <= @date AND TilDato >
@date d.v.s. between kan ikke anvendes
her. En nyttig funktion som giver en ægte
dato (date) til at arbejde videre med: DATEFROMPARTS (2015, 10, 11) giver
2015-10-11 d.v.s. 11. oktober 2015. DATEADD(day, 10, DATEFROMPARTS
(2015, 10, 11)) har lagt 10 dage til. DECLARE @yyyyddmm AS varchar(100) = '20223010' DECLARE @date AS date SET @date = DATEFROMPARTS(LEFT(@yyyyddmm,4),RIGHT(@yyyyddmm,2),SUBSTRING(@yyyyddmm,5,2)) SELECT @date Eller som datetime hvor du også
bestemmer klokkeslæt f.eks. dato for et år siden med klokkeslæt 00:00:00.000
fra dagens begyndelse: DATETIMEFROMPARTS(YEAR(GETDATE())-1,
MONTH(GETDATE()), DAY(GETDATE()), 0, 0, 0, 0) Ordredato er en datetime med mange
forskellige klokkeslæt f.eks. 10:12:34.521 og der ønskes alle rækker siden
dato for et år siden med klokkeslæt fra dagens begyndelse: WHERE Ordredato >= DATETIMEFROMPARTS(YEAR(GETDATE())-1, MONTH(GETDATE()), DAY(GETDATE()), 0,
0, 0, 0) Eller siden 1. januar sidste år: WHERE Ordredato >= DATETIMEFROMPARTS(YEAR(GETDATE())-1, 1, 1, 0, 0, 0, 0) Ønskes dato og klokkeslæt som
datetime findes denne funktion, hvis jeg har alle delene i int variable: DATETIMEFROMPARTS
(@Year, @Month, @Day, @Hour, @Minute, @Seconds, @Milliseconds) Eller kun som klokkeslæt til
datatypen time: TIMEFROMPARTS
(@Hour, @Minute, @Seconds, @Fractions, @Precision) SELECT CURRENT_TIMESTAMP giver samme
tidspunkt som Getdate(). En dato med date data typen ønskes
lavet om til første og sidste klokkeslæt: DECLARE
@FraDato date = '2010-02-04' DECLARE
@TilDato date = '2010-02-07' SELECT
CAST(@FraDato AS datetime) -- 2010-02-04
00:00:00.000 SELECT
CAST(@FraDato AS datetime2(7)) -- 2010-02-04
00:00:00.0000000 SELECT
DATEADD(millisecond,(-2), CAST(DATEADD(day,1,@TilDato) AS datetime)) -- 2010-02-07
23:59:59.997 SELECT
DATEADD(nanosecond,(-50), CAST(DATEADD(day,1,@TilDato) AS datetime2(7))) -- 2010-02-07 23:59:59.9999999 Altid
rart at kunne få fat i nogle bestemte datoer ud fra et felt, getdate eller en
variable, f.eks. første dato i året, første dato sidste år o.s.v.: DECLARE @dato
date = '2016-02-07' SELECT DATEADD(year,DATEDIFF(year,0,@dato),0) -- 2016-01-01 00:00:00.000 SELECT
DATEADD(year,DATEDIFF(year,0,@dato)-1,0)
-- 2015-01-01 00:00:00.000 SELECT
DATEADD(year,DATEDIFF(year,0,@dato),-1)
-- 2015-12-31 00:00:00.000 SELECT
DATEADD(year,DATEDIFF(year,0,@dato)+1,-1) -- 2016-12-31 00:00:00.000 SELECT
DATEADD(year,DATEDIFF(year,0,@dato)+1,0)
-- 2017-01-01 00:00:00.000 SELECT
DATEADD(year,DATEDIFF(year,0,@dato)+2,-1) -- 2017-12-31 00:00:00.000 Fordi
SQL Server har givet DateAdd disse betydninger: SELECT
DATEADD(year,0,0) -- 1900-01-01
00:00:00.000 SELECT
DATEADD(year,116,37)
-- 2016-02-07 00:00:00.000 Ugedag
navn og ugenummer, husk sproget: SELECT
@@LANGUAGE SET LANGUAGE
us_english; SET LANGUAGE
danish; SELECT
DATENAME(weekday, GETDATE()) -- ugedag navnet, name of day in week SELECT
@@DATEFIRST SET DATEFIRST
1 SELECT
DATENAME(week, GETDATE()) -- ugenummer, number of week En uge
kan starte i det forgangne år, lørdag 2. januar 2016 tilhører uge 53 i 2015
der starter mandag 28. december 2015, det kan udregnes med denne algoritme: DECLARE @dato date = '2016-01-02' SET DATEFIRST 1 -- gør mandag til første dag i
ugen, SELECT @@DATEFIRST SELECT DATEADD(day,1-DATEPART(weekday,@dato),@dato)
-- 2015-12-28 Eller
uden brug af datefirst 1 fås mandag i ugen ud fra en datetime: DECLARE @dato
datetime = '2016-01-02 15:36:21.553' SELECT DATEADD(wk,
DATEDIFF(d, 0, @dato) / 7, 0) -- 2015-12-28 Første
dato i en måned som date fra en datetime, derfor cast til date: DECLARE @dato
datetime = '2016-02-07 15:36:21.553' SELECT
CAST(@dato-DAY(@dato)+1 AS date) -- 2016-02-01 Reporting Services rapport: Mandag i
denne uge, angiver at uge starter mandag: =DateAdd(Dateinterval.Day,-DatePart(DateInterval.Weekday,Today(),FirstDayOfWeek.Monday)+1,Today()) Mandag i forrige uge: =DateAdd(Dateinterval.Day,-DatePart(DateInterval.Weekday,Today(),FirstDayOfWeek.Monday)-6,Today()) Søndag i forrige uge: =DateAdd(Dateinterval.Day,-DatePart(DateInterval.Weekday,Today(),FirstDayOfWeek.Monday),Today()) Aktuelle ugenummer: =Year(Today())*100+DatePart(DateInterval.WeekOfYear,Today(),FirstDayOfWeek.Monday) Sidste uges ugenummer: =Year(Dateadd("d",-7,Today()))*100+DatePart(DateInterval.WeekOfYear,Dateadd("d",
-7,Today()),FirstDayOfWeek.Monday) Programmeringen for FraDato default,
hvis dagsdato er mandag, så vis mandagen fra forrige uge ellers mandag fra
gældende uge: =IIF(DatePart(DateInterval.Weekday,Today(),FirstDayOfWeek.Monday)=1,
DateAdd(Dateinterval.Day,-DatePart(DateInterval.Weekday,Today(),FirstDayOfWeek.Monday)-6,Today()),
DateAdd(Dateinterval.Day,-DatePart(DateInterval.Weekday,Today(),FirstDayOfWeek.Monday)+1,Today())) Programmeringen for TilDato default: =dateadd(Dateinterval.Day,-1,Today()) Dagsdato er onsdag 19-12-2018 bliver
FraDato 17-12-2018, TilDato 18-12-2018. Dagsdato er mandag 17-12-2018 bliver
FraDato 10-12-2018 og TilDato 16-12-2018 d.v.s. fra mandagen i ugen før til
og med i går som er søndag i ugen før. En
rapport har flere kolonner men skal kun vise en kolonne hvis der er indhold i
den i en af rækkerne. Højreklik på kolonnen øverste grå firkant ovenover
kolonneoverskriften, vælg Column Visibility, marker Show or hide based on an
expression, klik på Fx symbolet og indtast expression for hidden, d.v.s. er
udtrykket sand så skjules kolonnen. Når max() funktionen finder en værdi så
giver isnothing() funktionen falsk og kolonnen vises i rapporten, fordi der
er indhold i kolonnen i en eller flere rækker i datasættet: =IsNothing(Max(Fields!Column8.Value,
"Dataset")) Bemærk, at nedenstående kun ser på
første række i rapporten: =IIf(IsNothing(Fields!Column8.Value),true,false) Er det en kolonne der skal skjules
ved eksport af rapportens viste data til et Excel regneark fil anvendes dette
expression: =Not Globals!RenderFormat.IsInteractive Reporting Services Reusable Code Blocks En sql sætning kunne også gøre
kolonnen null, men er ikke så brugbar for rap: Column8 =
CASE WHEN MAX(Column8) OVER(ORDER BY
(SELECT 1)) IS NOT NULL THEN ISNULL(CAST(Column8 AS varchar),'')
ELSE NULL END Første
dato og sidste dato i en uge via ugenummer og det årstal ugen er startet i: DECLARE @uge int = 52 DECLARE @år int = 2016 SET DATEFIRST 1 -- gør mandag
til første dag i ugen SELECT UgeStatdato = DATEADD(WEEK, @uge, CONCAT(@år,'0101')) - 4 – DATEPART(WEEKDAY, DATEADD(WEEK,
@uge, CONCAT(@år,'0101')) - 4) + 1 SELECT UgeSlutdato = DATEADD(WEEK, @uge, CONCAT(@år,'0101')) - 4 – DATEPART(WEEKDAY, DATEADD(WEEK,
@uge, CONCAT(@år,'0101')) - 4) + 7 Angivelse af SET DATEFIRST har ingen
effekt på DATEDIFF. DATEDIFF bruger altid søndag som den første dag i ugen for
at sikre, at funktionen er deterministisk. Udregning
af uge nummer og med et årstal for ugen fordi f.eks. søndag den 1. januar
2012 tilhører uge 52 som kommer fra året 2011 så ÅrUge = 201152, hvor der
bruges iso_week som svarer til danske uger hvor det er første torsdag i det
nye år som bestemmer at den uge bliver til uge 1 i året. (USA anvender første
mandag i året til at stemme uge 1, derfor er uge nummer i DK og USA ikke
altid samme værdi). DECLARE @dato
date = '20120101' -- '20160102' -- GETDATE() SELECT Uge = DATEPART(iso_week,@dato), ÅrUge = CASE WHEN
DATEPART(iso_week,@dato) = 1 AND YEAR(@dato) <
YEAR(DATEADD(day,7,@dato)) THEN
YEAR(DATEADD(day,7,@dato))*100+DATEPART(iso_week,@dato) WHEN DATEPART(iso_week,@dato) >= 52
AND YEAR(@dato) >
YEAR(DATEADD(day,-7,@dato)) THEN
YEAR(DATEADD(day,-7,@dato))*100+DATEPART(iso_week,@dato) ELSE YEAR(@dato)*100+DATEPART(iso_week,@dato) END UgeStartÅr = ÅrUge / 100. DECLARE @ÅrUge int = 201807 SELECT År
= @ÅrUge / 100 -- qoutient, kvotient SELECT Uge = @ÅrUge % 100 -- remainder, resten Ønskes
en dato klokkeslæt som et timestamp i en log historik audit tabel bør datatypen
datatime2 overvejes fordi den er mere præcis og det er uheldigt hvis to
rækker i tabellen får samme dato klokkeslæt, tidspunkt: DECLARE @d1
DATETIME = GETDATE() DECLARE @d2
DATETIME2(7) = SYSDATETIME() SELECT
@d1 -- 1966-02-07 15:36:21.553 SELECT @d2
-- 1966-02-07 15:36:21.5556335 Og som default værdi i et felt/kolonne
i en tabel: CREATE TABLE
[dbo].[CustomerLog] ( ... [CreatedDate] [datetime2](7) NOT NULL CONSTRAINT [DF_CustomerLog_CreatedDate]
DEFAULT (SYSDATETIME()) ... ) GO Eller udført efter Create Table: ALTER TABLE [dbo].[CustomerLog]
ADD CONSTRAINT
[DF_CustomerLog_CreatedDate] DEFAULT
(SYSDATETIME()) FOR [CreatedDate] GO En
periode er i formattet mmyy og jeg ønsker rækkerne sorteret efter år og måned,
derfor omdanner jeg mmyy til en dato som den første i måneden: -- Test eksempel: DECLARE @mmyy nchar(4) DECLARE
@dato date SET
@mmyy = '0315' SET @dato = Convert(date,Stuff(Stuff('01'+@mmyy,5,0,'.'),3,0,'.'),4) PRINT
@dato SELECT
Convert(date, Stuff(Stuff('01'+Periode,5,0,'.'),3,0,'.'), 4) FROM dbo.BESTILLING Performance problemet er, at nedenstående ikke
anvender indeks på Ordredato, og at DateDiff skal udregnes for hver række
post i tabellen: WHERE
DATEDIFF(DAY, Ordredato, GETDATE()) < 210 Bedre at gøre dette som anvender
indekset og hvor DateAdd udregnes én gang: WHERE Ordredato > DATEADD(DAY, -210 , GETDATE()) Dateadd fratrækker 210 dage fra
dagsdato men er gældende klokkeslæt f.eks. kl. 14:12:39 så vil en ordredato
for 210 dage siden med klokkeslæt kl. 10:00:00 ikke medtages, omvendt er
feltet Ordredato formentlig af data typen Date i tabellen. Datediff sammenligner kun på dagen. Alternativt
og fortsat brug af indeks: WHERE
Ordredata > DATEADD(DAY, -210 , GETDATE()) AND DATEDIFF(DAY, Ordredato, GETDATE()) < 210 Eksempel med brug af Year funktionen:
WHERE YEAR(Ordredato) = 2015 Hurtigere er: WHERE Ordredato
BETWEEN '2015-01-01' AND '2015-12-31' YEAR funktionen er bedst kun at
bruge i SELECT til at vise et årstal. Konvertere
en ægte dato og klokkeslæt til en tekst dato dd-mm-yyyy En ægte datetime Dato felt med
værdien 2013-08-06 07:57:27.327 bliver med: CAST(Dato
as varchar) til
"Aug 6 2013 7:57AM" CONVERT(varchar(10), Dato, 20) til "2013-08-06" CONVERT(varchar(10), Dato, 105) til "06-08-2013" Konvertere
en dato som tekst til en ægte dato f.eks. dd-mm-yyyy dmy Convert har en række formater til
parsing af en tekst-dato til en ægte dato f.eks.: SELECT Convert(datetime,
'23/07/2009', 103) SELECT
Convert(datetime, '02-07-2009', 105) SET
DATEFORMAT dmy SELECT
Cast('02-07-2009' AS date) Mere om Cast og Convert in SQL Server
2012 og frem Mere om Cast og
Convert in SQL Server 2000 SQL Date
Format Examples using CONVERT Function En nyttig dato funktion CREATE function [dbo].[SenesteBankDag]
(@Dato date) RETURNS
date AS /*
SELECT dbo.SenesteBankDag(getdate()) */ /*
SELECT dbo.SenesteBankDag('2019-11-25') */ BEGIN DECLARE @ForrigeDato date = DATEADD(day,
-1, @Dato) DECLARE @SenesteBankDag date SELECT @SenesteBankDag = MAX(Dato) FROM
dbo.Kalender WHERE
Dato <= @ForrigeDato AND ErBankdag = 1 RETURN
@SenesteBankDag END Noget der skal ske efter kl. 18 IF
CAST(SUBSTRING(CAST(CONVERT(time, GETDATE()) AS varchar),1,2) AS int) >=
18 Byg tidspunkt op som en streng og så cast
til datetime Tidspunkt data d.v.s. dato og klokkeslæt
kan bygges op som en streng, og så castes til datatype datetime: CAST
('20100917 08:40' AS datetime) d.v.s. ååååmmdd mellemrum tt:mm Den indbyggede funktion DATETIMEFROMPARTS blev vist
tidligere. Konvertere
et tal som tekst til et ægte tal Belob i et tekst felt 4.210.593,79
konverteres til decimaltal ved at fjerne punktum og erstatte det danske komma
med amerikansk punktum som decimaltegn: CAST(REPLACE(REPLACE(Belob,'.',''),',','.')
AS DECIMAL(19,4)) Procent SELECT p.Pris * 1.25 lægger 25% oveni,
p.Pris * 0.75 fratrækker 25%. Order
by med feltnavn eller feltnummer placering Normalt skriver man de felter der
skal sorteres på, f.eks.: SELECT Kundenummer, Kundenavn, Postnummer,
Rabatbeløb * 1.10 AS Ny FROM dbo.KUNDE -- eller Ny = Rabatbeløb
* 1.10 ORDER BY Postnummer, Ny, Kundenavn Når
der sorteres på felter der er medtaget i SELECT, kan man i stedet anvende deres
feltnummer placering hvor første felt i select er nummer 1: SELECT Kundenummer, Kundenavn, Postnummer,
Rabatbeløb * 1.10 AS Ny FROM
dbo.KUNDE ORDER BY
3, 4, 2 Tilfældig
sortering af rækker i en tabel der får et sorteringsnummer NewId() danner en GUID f.eks.:
C820BE93-2AC1-44CA-876F-D51D36777D22 SELECT KundeId, KundeNavn,
ROW_NUMBER() OVER(ORDER BY NewId()) AS Sorteringsnummer FROM dbo.Kunde Generering
af næste nummer til et Id felt med data fra anden tabel Tabel
Kunde har primærnøglefelt KundeId som ikke er Identity, fordi databasen er en
på en distribueret arkitektur (distributed architecture). Derfor skal næste
nummer til KundeId udregnes i en SQL sætning, når der indsættes data fra en
anden tabel, her Stage_Customers som har engelske feltnavne. Med Max findes
største værdi af KundeId i Kunde tabellen, og er tabellen tom, d.v.s. ingen
rækker, så sættes største værdi til default 0. Med Row_Number over et unikt
felt i den anden tabel, her feltet CustomerID, fås talrækken 1, 2, 3 som
lægges til Max værdien, hvorved der bliver udregnet unikke numre til
primærnøglefeltet KundeId. ;WITH
maxKundeId AS ( SELECT MaxKundeId = ISNULL(MAX(KundeId), 0) FROM dbo.Kunde WITH (TABLOCKX) ) INSERT INTO dbo.Kunde WITH (TABLOCKX) (KundeId,
Kundenavn) SELECT MaxKundeId + ROW_NUMBER() OVER(ORDER BY
CustomerID) AS KundeId, CompanyName AS Kundenavn FROM
dbo.Stage_Customers CROSS JOIN maxKundeId Forløbende
nummer tal fra 1 til ca. 14735 SELECT
ROW_NUMBER() OVER(ORDER BY (SELECT 1)) AS Id FROM
MASTER.dbo.syscolumns ;WITH
TallyTable(n) AS ( -- 8000 rows as in max length of the
VARCHAR string SELECT ROW_NUMBER() OVER (ORDER BY
(SELECT NULL)) FROM
(VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) a(n) CROSS JOIN
(VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) c(n) CROSS JOIN
(VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) d(n) CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0))
e(n) ) SELECT * FROM
TallyTable; ;WITH
TallyTable(n) AS ( SELECT TOP 100000 ROW_NUMBER() OVER(ORDER
BY (SELECT NULL)) FROM dbo.syscolumns tb1,dbo.syscolumns
tb2 ) SELECT * FROM
TallyTable; WITH
E00(N) AS (SELECT 1 UNION ALL SELECT 1) ,E02(N) AS (SELECT 1 FROM E00 a, E00 b) ,E04(N) AS (SELECT 1 FROM E02 a, E02 b) ,E08(N) AS (SELECT 1 FROM E04 a, E04 b) ,E16(N) AS (SELECT 1 FROM E08 a, E08 b) ,E32(N) AS (SELECT 1 FROM E16 a, E16 b) ,TallyTable(N) AS (SELECT ROW_NUMBER()
OVER ORDER BY (SELECT
NULL)) FROM E32) SELECT N FROM
TallyTable WHERE N
<= 1000000; Tilfældig
tal – randum number newid() giver for hver række en ny
guid 36 tegn kode f.eks.: EDD3D7A3-A11C-4577-BCBD-5C40FBB88708
som randum funktionen oversætter til et tilfældigt tal mellem 0 og 2: SELECT
table_name, floor(3 * rand(convert(varbinary,
newid()))) number, abs(checksum(newid())
% 3) number FROM INFORMATION_SCHEMA.TABLES WHERE
TABLE_TYPE = 'BASE TABLE' ORDER BY
2 Single-Row
Table / One-Row Table / tabellen kan tømmes til 0 rækker CREATE
TABLE dbo.SingleRow ( RestrictToOneRow char(1) NOT NULL CONSTRAINT DF_SingleRow_RestrictToOneRow
DEFAULT 'X', Season varchar(9) NOT NULL DEFAULT
'2022/2021', ImportFileFolder varchar(100) NULL, CONSTRAINT PK_SingleRow PRIMARY KEY
(RestrictToOneRow), CONSTRAINT CK_SingleRow_Lock CHECK
(RestrictToOneRow='X') ) INSERT
INTO dbo.SingleRow (ImportFileFolder) VALUES
('C:\ImportFile\2022\') What is the difference between Charindex and
Patindex? The charindex and patindex functions return the starting
position of a pattern you specify. Both take two arguments, but they work
slightly differently, since patindex can use wildcard characters, but
charindex cannot. SELECT
TOP 1 eller SELECT TOP 10 er langsom Et
view som indeholder flere joins til tabeller kan forårsage, at SELECT TOP(10)
har lang svartid, det kan undgås ved at anvende hash join type: INNER HASH JOIN eller LEFT OUTER HASH JOIN. Examples Et view kan ikke anvende OPTION (HASH
JOIN). Dynamic
SQL build on the fly, dynamisk sql sætning, fang fejl og vis sql Test eksempler: DECLARE @sql nvarchar(max) SET
@sql = N'SELECT A =
1' EXECUTE sys.sp_executesql @sql EXECUTE (@sql) Inde
fra en stored procedure: CREATE PROCEDURE dbo.CustomerDynamicSort
@Column NVARCHAR(128) = N'CustomerId',
@Direction NVARCHAR(4) = N'ASC' AS BEGIN SET NOCOUNT ON; DECLARE @sql NVARCHAR(MAX) SET @Column = QUOTENAME(@Column); -- set [] around the
column SET @sql = N'SELECT CustomerName, CustomerAddress
FROM dbo.Customer
ORDER BY ' + @Column + N' ' + @Direction BEGIN TRY EXEC [SalesSystem].sys.sp_executesql @sql
--databasename
if different from current END TRY BEGIN CATCH SELECT CONVERT(XML, @sql) --click Results, click sql
statement, copy-paste to new query PRINT CAST(@sql AS NTEXT) --click Messages to see sql
at text. ;THROW END CATCH END object_id=OBJECT_ID('HumanResources.Employee') Data fra en dynamisk sql udført
select from sætning kan indsættes i en tabel: IF
OBJECT_ID('tempdb..#tmp') IS NOT NULL DROP TABLE #tmp --from sql
server 2016 DROP TABLE IF EXISTS #tmp CREATE TABLE
#tmp ( Id int NOT NULL, Name nvarchar(256) NOT NULL, CreateDate datetime NOT NULL, PRIMARY KEY CLUSTERED (Id) -- nameless primary key WITH (PAD_INDEX = OFF,
STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS =
ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR =
100) ) INSERT INTO
#tmp (Id, Name, CreateDate) EXEC sys.sp_executesql
@sql, @params, @par1, ... Fetch the result of a stored procedure and insert it
into a table: INSERT
INTO dbo.Customers EXEC dbo.Fetch_Customers Dynamisk sql med output parametre Antal rækker i en tabel: DECLARE @Schema nvarchar(256) = 'dbo' DECLARE @Table nvarchar(256) =
'Employees' DECLARE @sql as nvarchar(max) DECLARE @params nvarchar(max) DECLARE @out_Count bigint SET @sql = 'SELECT @prm_Count = COUNT(1) FROM
[<Schema>].[<Table>]' SET @sql =
REPLACE(@sql,'<Table>',@Table) SET @sql = REPLACE(@sql,'<Schema>',@Schema) SET @params = N'@prm_Count bigint OUTPUT' EXEC sys.sp_executesql @sql, @params, @prm_Count = @out_Count OUTPUT SELECT @out_Count Andet eksempel med where kriterie og
værdier i to variable: DECLARE @LowerPrice INT = 3000, @HigherPrice INT = 6000 DECLARE @sql nvarchar(1000) DECLARE @params nvarchar(100) SET @sql = N'SELECT Id, Name, Price FROM Book WHERE Price > @paramLowerPrice
AND Price < @paramHigherPrice' SET @params = N'@paramLowerPrice INT, @paramHigherPrice INT' EXECUTE sys.sp_executesql @sql ,@params, @paramLowerPrice = @LowerPrice,
@paramHigherPrice = @HigherPrice Tredje eksempel med where kriterie
og parameter todate med default værdi DECLARE @tbl nvarchar(MAX), @sql nvarchar(MAX), @params nvarchar(MAX), @count int SELECT @sql = N'SELECT @cnt = COUNT(*) FROM
dbo.' + quotename(@tbl) + N' ' + N'WHERE LastUpdated >=
@fromdate AND LastUpdated < coalesce(@todate, ''99991231'')' SET @params = N'@fromdate date, @todate date = NULL, @cnt int OUTPUT' EXEC sys.sp_executesql @sql, @params, '20100101', @cnt = @count OUTPUT PRINT @tbl + ': ' + convert(varchar(10), @count) + ' rows.' Fra SQL 2005 blev TOP gjort dynamisk
eller parameter styret / variabel styret: DECLARE
@n int = 5 SELECT
TOP(@n) * FROM
dbo.Orders ORDER BY
Ordredato Finde
dubletter i en tabel De
varenummer som findes flere gange hos samme kunde i samme leveringsmåned,
derfor omdannes leveringsdatoen til en måned på formen ååååmm, hvor HAVING
COUNT(*) >= 2 er kriteriet, at der skal være mindst to rækker med samme
Varenummer, Kundenummer og Leveringsmåned. SELECT Varenummer FROM
dbo.ORDRE GROUP BY Varenummer, Kundenummer,
YEAR(Leveringsdato) * 100 + MONTH(Leveringsdato) HAVING COUNT(*) >= 2 -- kan også være HAVING COUNT(*) > 1. Et kunde arkiv har to eller flere
ens KundeId’er inden for samme leverance: SELECT
t.* FROM ( SELECT k.*, Antal = COUNT(*) OVER (PARTITION BY
k.LeveringId, k.KundeId) FROM [Arkiv].[Kunde] k ) t WHERE
t.Antal > 1 OVER
PARTITION clause allows the use of aggregation without using a Group By
clause. Frasortering
af dubletter i en tabel (Removing duplicates) Et
kunde arkiv indeholder dubletter inden for samme kunde, hvor kolonnen KundeId
indeholder samme værdi over flere rækker. For hver kunde (KundeId) opstilles
dubletterne sorteret efter seneste Oprettelsesdato inkl. klokkeslæt hvor
første række får RowNumber = 1, anden række får RowNumber = 2 inden for samme
værdi i kolonnen KundeId. Det kaldes at danne en partition for hver af værdier i kolonne KundeId. Når der er mange
rækker med forskellige værdier i KundeId, fås der mange partitioner, hvor én
partition består af en eller flere rækker med samme værdi i kolonnen KundeId
d.v.s. består af dubletter. Inden for hver partition d.v.s. inden for hver
værdi af KundeId, bliver partitionens dubletrækker sorteret efter seneste Oprettelsesdato
inkl. klokkeslæt og hver række inden for partitionen får hver deres RowNumber
fra 1 og fremefter. Det betyder, at hver partition har RowNumber fra 1 og
fremefter, d.v.s. inden for hver KundeId hvorved dubletrækkerne markeret med
RowNumber 1, 2, 3… Where delen medtager kun første dubletrække inden for hver
partition med kriteriet at RowNumber = 1 og derved bliver de andre dubletter
frasorteret. SELECT t.* FROM ( SELECT k.*,
RowNumber = ROW_NUMBER() OVER (PARTITION BY k.KundeId,
CAST(k.Oprettelsesdato AS
dato)
ORDER BY
k.Oprettelsesdato DESC) FROM [Arkiv].[Kunde]
k ) t WHERE t.RowNumber = 1 Sletning
af dubletter i en tabel Sletning
af dubletter i felt Id i tabel Friend uden primærnøgle eller Identity felt: CREATE TABLE dbo.Friend ( Id INT NOT NULL, Name VARCHAR (20) NOT NULL ) GO INSERT INTO dbo.Friend(Id, Name) VALUES (1,
'Ole'), (2,
'Ane'), (3,
'Per'), (4,
'Ida') GO 5 SELECT * FROM dbo.Friend ;WITH duplicate AS ( SELECT *,
RowNumber = ROW_NUMBER() OVER (PARTITION BY Id ORDER BY (SELECT NULL) ASC) FROM dbo.Friend ) DELETE FROM
duplicate WHERE RowNumber > 1 SELECT * FROM dbo.Friend Sletning
af dubletter i felt F1 og F2 i tabellen T, som har et RowNumber indsat som et
Identity felt: DELETE
dbo.T FROM dbo.T AS A WHERE RowNumber <> (SELECT MIN(B.RowNumber) FROM dbo.T AS B WHERE B.F1 = A.F1 AND B.F2 = A.F2) ;WITH
dublet AS -- keep the youngest row by SystemDateTime ( SELECT *, RowNumber = ROW_NUMBER() OVER (PARTITION BY F1, F2 ORDER
BY SystemDateTime DESC) FROM dbo.T ) DELETE
FROM dublet WHERE RowNumber > 1 --SELECT
* FROM dublet WHERE RowNumber > 1 Sletning af dubletter i felt LokalId
i en staging dimensionstabel i et datavarehus inden den indgår i en Merge med
mart dimensionstabellen af type 1. Først konstaterer vi, at der er
dubletter: SELECT
[LokalId], COUNT(*) FROM
[StagingDim].[Virksomhed] WHERE [LokalId] IS NOT NULL -- flere
virksomheder har ingen LokalId. GROUP BY
[LokalId] HAVING
COUNT(*) >= 2 Dernæst sletter vi dubletterne ud
fra en prioriteringsregel i Row_Number: ;WITH
dublet AS ( SELECT [VirksomhedId],[LokalId], RowNumber = ROW_NUMBER() OVER (PARTITION BY
[LokalId] ORDER BY CASE WHEN [Status] = 'Ophørt' THEN 1 WHEN [Status] = 'Aktiv' THEN 2 WHEN [Status] = 'Brexit' THEN 3 WHEN
[Status] = 'Fusioneret' THEN 4
ELSE 5 END ASC, -- Ophørt beholdes først.
ISNULL([Ophørsdato],'1900-01-01') DESC, -- seneste dato beholdes.
[Tilladelsesdato] DESC) -- datoen skal være udfyldt i tabellen. FROM [StagingDim].[Virksomhed] WHERE [LokalId] IS NOT NULL -- flere virksomheder har ingen LokalId. ) DELETE
FROM dublet WHERE RowNumber > 1 --SELECT
* FROM dublet WHERE RowNumber > 1 Indsæt
data uden dubletter i en tabel INSERT
INTO dbo.DB SELECT
n.F1, n.F2, n.F3 n.F4 FROM
dbo.NEW n INNER JOIN ( SELECT t.Cprnr, t.Dato,
MAX(t.RowNumber) AS RowNumber FROM
dbo.NEW t GROUP BY t.Cprnr, t.Dato ) AS maks ON maks.RowNumber =
n.RowNumber Find
seneste data registreret på en dato i år SELECT * FROM dbo.T WHERE Dato = (SELECT MAX(Dato) FROM dbo.T WHERE YEAR(Dato) = YEAR(GETDATE())) Find
seneste dags ordredata Med
brug af indlejret (nested) select sætning som finder max værdi som der joines
med til den ydre select sætning: SELECT Kundenummer, Ordredato, Antal, Beløb FROM dbo.ORDRE INNER
JOIN
(
SELECT MAX(Ordredato) AS SenesteOrdreDato FROM dbo.ORDRE
) Seneste ON Seneste.SenesteOrdreDato = dbo.ORDRE.OrdreDato Eller inden for hver vare som derved
ingår i indlejret select og i join: SELECT o.Kundenummer, o.Ordredato, o.Antal,
o.Beløb FROM
dbo.ORDRE o INNER JOIN (
SELECT VareId, MAX(Ordredato) AS SenesteOrdreDato
FROM dbo.ORDRE WHERE Antal >= 100 GROUP BY VareId ) s ON s.VareId = o.VareId AND
s.SenesteOrdreDato = o.OrdreDato Foranstillet nuller - Leading zeros – Left
padding zeros SELECT
RIGHT(CONCAT('00000', Kode), 5) AS Kode,
-- null giver 00000. REPLICATE('0', 5 - LEN(Kode)) +
CAST(Kode AS varchar) AS Kode
-- null giver null. RIGHT(STUFF(Kode, 1, 0, REPLICATE('0',
5)), 5) AS Kode FROM
dbo.T Kode
kolonnen har datatype int med værdien 8 eller varchar med værdien 8A. Kode
8 giver 00008 og 8A giver 0008A, mens negativ værdi -8 giver 000-8. Format
og Str kræver at det er tal, derfor giver tekstværdien 8A giver fejl: SELECT FORMAT(Kode, '00000') AS Kode, -- null giver null.
FORMAT(Kode, '00000;-00000;00000') AS Kode, -- pos, neg, nul.
FORMAT(ISNULL(Kode,''), '00000') AS Kode, -- null giver 00000.
FORMAT(Kode, 'D5') AS Kode, -- null giver null.
REPLACE(STR(Kode, 5),' ','0') AS Kode --STR foranstil mellemrum FROM
dbo.T Kode
8 giver 00008 og negativ værdi -8 giver -00008 ved Format. Løsning
for positiv tal, negativ tal og tekst værdi, undtagen -8A som giver 00-8A: SELECT
CASE WHEN ISNUMERIC(Kode) = 1 THEN FORMAT(TRY_CAST(Kode AS
int), '00000') ELSE REPLICATE('0', 5 - LEN(Kode))
+ CAST(Kode AS varchar) END AS Kode FROM dbo.T Leg med James Bond: DECLARE
@Number int = 7 SELECT
RIGHT(CONCAT('000', @Number), 3) --
007 SELECT
FORMAT(@Number, '000') --
007 DECLARE
@NumberN int = -7 SELECT
RIGHT(CONCAT('000', @NumberN), 3) -- 0-7 SELECT
FORMAT(@NumberN, '000') --
-007 DECLARE
@NumberA varchar(3) = '7X' SELECT
RIGHT(CONCAT('000', @NumberA), 3) -- 07X SELECT
FORMAT(@NumberA, '000') --
fejl / error. PostgreSQL
eksempel med en left padding funktion, MySQL har den også SELECT
p.name AS product_name, date_part('year', s.date) AS year, --
foranstillet 0 så altid på to cifre:
lpad(date_part('month', s.date)::text, 2, '0') AS month, --
konvertering til string med et format på to cifre/tvunget 0:
to_char(date_part('day', s.date), '00') AS day, SUM(p.price * sd.count)::text::money
AS totalrevenue, 1 AS sort FROM
sales AS s INNER JOIN sales_details AS sd ON
sd.sale_id = s.id INNER JOIN products AS p ON p.id =
product_id GROUP BY
p.name, date_part('year', s.date), date_part('month', s.date),
date_part('day', s.date) left
pad lpad foranstiller både tal og tekst uden fejl: lpad(8::text, 5, '0'), lpad('8'::text, 5, '0'), lpad('8A'::text,
5, '0') mens
lpad('-8'::text, 5, '0') giver 000-8. to_char(8,
'00000') giver 00008 og to_char(-8, '00000') giver -00008. mens
to_char('8A', '00000') giver fejl. Beløb
til summering i udskrift (to summarize) SELECT ROUND(Beløb,2) AS Beløb -- til
visning og summering i rapport. FROM
dbo.T Gennemsnit SELECT AVG(Antal) -– Antal er int, giver AVG
også heltal. FROM
dbo.T SELECT AVG(CAST(Antal AS float)) AS
Gennemsnitlig_Antal FROM
dbo.T Dividere
heltal med et kommatal, så resultatet bliver kommatal SELECT AntalDage / 365.0 -- adder .0 så er nævneren et kommatal. SELECT AntalDage / CAST(DageIMåned AS
float) – nævner er et kommatal. Produkt
af rækker, altså værdi ganget sammen fremfor summet sammen SELECT EXP(SUM(LOG(Værdi))) FROM
dbo.STAT SumProdukt Vi kender den fra Excel eksempelvis =SUMPRODUKT(A1:A32;B1:B32)/SUM(A1:A32) Som svarer til følgende beregninger: (A1*B1)+(A2*B2)+...+(An*Bn)/SUM(A1:An) SELECT
SUM(A*B)/SUM(A) FROM dbo.STAT SELECT
ISNULL(SUM(A*B)/NULLIF(SUM(A),0),0) FROM dbo.STAT select
sum(baseSeries.Actual * weightSeries.Actual) / sum(weightSeries.Actual) from ( select
RecordDate, Actual from
ProductionRecords where
KPI = 'Weighty' ) baseSeries inner
join ( select
RecordDate, Actual from
ProductionRecords where
KPI = 'Tons Milled' ) weightSeries on weightSeries.RecordDate
= baseSeries.RecordDate NULL
bliver til en værdi Med
funktion ISNULL får man anden parameter, når første parameter IS NULL, man
kan sige, at man erstatter en null værdi i en kolonne med en kendt værdi: ISNULL(dbo.KUNDE.SkyldnerBeløb, 0) Få
den værdi som er ikke NULL Med funktion COALESCE får man den
parameter som er IS NOT NULL: COALESCE(dbo.KUNDE.SkyldnerBeløb,
dbo.ORDRE.Beløb, dbo.VARE.StkBeløb) Hvilken SELECT returnerer ikke en
error? DECLARE
@i SMALLINT = NULL SELECT
ISNULL (@i, 40000) SELECT COALESCE (@i, 40000) SELECT COALESCE (@i, 'abc') ISNULL versus COALESCE For
the single replacements, ISNULL in SQL Server is a bit faster than COALESCE. With
ISNULL, the datatype of the second operand is also auto-magically converted
to the first. With
COALESCE, the datatype will be returned as which ever data type of the
finally selected operand is. COALESCE use the data type precedents rules. That
can lead to some major surprises: ISNULL
(SmallintValue, 499) – OK. ISNULL
(SmallintValue, 44499) – Error. COALESCE
(SmallintValue, 499) – OK. COALESCE
(SmallintValue, 44499) - OK, return data type is INT. COALESCE
(SmallintValue1, SmallintValue2, 67, 99999) - OK, will return 67 having the
data type INT, because 99999 is INT. ISNULL
(VARCHAR_5, 'NULL') - OK, returns the string constant 'NULL'. ISNULL
(VARCHAR_5, 'The column must have a value' ) - OK, returns 'The c'. COALESCE
(VARCHAR_5, 'NULL') - OK, returns 'NULL'. COALESCE
(VARCHAR_5, 'The column must have a value' ) - OK, returns 'The column must
have a value'. Få
en NULL når feltet har en bestemt værdi Med
funktion NullIf får man NULL værdi når de to parametre har samme værdi ellers
får man værdien af første parameter, er sammenlignes med den tomme streng: NULLIF(dbo.KUNDE.Navn, '') Når
Navn = 'Jensen' fås 'Jensen'. Når
Navn = '' fås NULL. Når
Navn = NULL fås NULL. SELECT
CASE WHEN NULLIF(dbo.KUNDE.Navn, '') IS NULL THEN 0
ELSE 1 END
AS UdfyldtNavn Det
kan ofte være en god idé at undgå at have NULL værdi i et felt i en tabel, og
i KUNDE tabellen kunne Navn feltet i stedet have en default værdi på '',
fordi på et skærmbilledet i en applikation vises NULL og den tomme streng på
samme måde, nemlig som ingenting klar til at udfyldt af brugeren og select
bliver lidt kortere: SELECT
CASE WHEN dbo.KUNDE.Navn = '' THEN 0 ELSE 1 END AS UdfyldtNavn SELECT
IIF(dbo.KUNDE.Navn = '', 0 ,1) AS UdfyldtNavn Andre eksempler: SELECT ISNULL(NULLIF(Navn,''),'¤') -- Når Navn er den tomme streng, fås Null,
der giver ¤ tegnet. SELECT IIF(LEN(Navn)=0,'¤',Navn) -- Jeg synes brug af Len er mere
selvforklarende. SELECT
ISNULL(Beløb / NULLIF(Antal,0), 0) -- Når nævner Antal er 0, fås Null, d.v.s.
ingen division, der fås 0. Strenge og null SELECT
'abc' + NULL -- giver NULL SET
CONCAT_NULL_YIELDS_NULL OFF SELECT
'abc' + NULL -- giver abc. CONCAT
funktion så concatenate NULL er uden problem i SQL Server 2012 SELECT
CONCAT('abc', NULL) -- giver abc. DECLARE
@a nvarchar(50), @b nvarchar(50), @c nvarchar(101) SET @a = 'abc' SET @b = 'æøå' SET @c = CONCAT(@a, @b) PRINT @c SET @b =
NULL SET @c =
CONCAT(@a, @b) PRINT @c CONCAT
konverterer selv parametrene til strenge: DECLARE
@a smallint, @b tinyint, @c nvarchar(50) SET @a = 12345 SET @b = 123 SET @c = CONCAT(@a, @b) PRINT @c SET @b =
NULL SET @c =
CONCAT(@a, @b) PRINT @c SELECT sætning der giver nyt felt
ved at sætte felter sammen med mellemrum: SELECT
Result = CONCAT(Region, ' ', Name, ' ', SalesYTD) FROM Customer Brug Concat fremfor SELECT Land +
'_F' , fordi den giver NULL når Land is NULL. IIF (Inline IF eller Immediately IF) i SQL Server
2012 Syntaks:
IIF( boolean_expression, true_value, false_value ) Før måtte man anvende CASE WHEN, men
IIF gør programeringen kønnere: SELECT
CASE WHEN Num1 > Num2 THEN Num1 ELSE Num2 END AS 'Max' FROM dbo.Numbers SELECT IIF(Num1
> Num2, Num1, Num2) AS 'Max' FROM
dbo.Numbers I tilfælde af, at felterne Num1 og
Num2 kan indeholde NULL, så gøres NULL til nul: SELECT IIF(ISNULL(Num1,
0) > ISNULL(Num2, 0), Num1, Num2) AS 'Max' IIF kan også bruges som indlejret
(nested): SELECT
IIF(Num1 > Num2, IIF (Num1 > Num3, Num1, Num3), Num2) AS 'Max' SELECT
PersonId = IIF(PersonId >= 0, PersonId, 0) SELECT
ErIdag=IIF(CAST(Ordredato AS date)=CAST(GETDATE() AS date),1,0) IIF kan indgå i alle udtryk, her sammen med
ISNUMERIC om streng-feltindholdet er en talværdi så kan den konverteres til
en ægte talværdi og indgå i en join: UPDATE new SET
KommuneId = db.KommuneId FROM
dbo.EDW_NEW_TILLADELSE new
INNER JOIN dbo.EDW_DB_Kommune db ON db.KommuneKode =
IIF(ISNUMERIC(new.KommuneKode) = 1, CAST(new.KommuneKode
AS int), 0) SELECT IIF([Antal Uger] = 0, NULL,
[Salgsbeløb] / [Antal Uger]) AS Gennemsnitssalgpruge IIF is simular to IF THEN ELSE. Find max værdi ud fra tre felter der
omdannes til en on-the-fly tabel med tre rækker ved brug af values kommandoen
vi normal ser ved en Insert Into: SELECT *, MaxBeløb = (SELECT
MAX(t.Beløb) FROM
(VALUES (AfgiftBeløb), (ForsendelseBeløb), (MomsBeløb)) AS
t(Beløb)) FROM VareSalg Join to tabeller A og B og udtag den
første værdiJOIN TO TABELLER
SAMMEN SELECT A.Id, B.Id, MaxBeløb = (SELECT MAX(t.Beløb) FROM (VALUES (A.Beløb),
(B.Beløb)) AS t(Beløb)) FROM A
INNER JOIN B ON B.Id = A.Id CHOOSE
udvælger en værdi via et indeks i SQL Server 2012 Syntaks: CHOOSE ( index, val_1,
val_2 [, val_n ] ) Giver værdien på indeks pladsen,
nedenfor gives 'Developer': SELECT
Type = CHOOSE(3, 'Manager', 'Director', 'Developer', 'Tester') Til gruppering af værdier, her
forudsat at ProductCategoryID er mellem 1 og 5: SELECT
ProductCategoryID, CHOOSE(ProductCategoryID, 'A','B','C','D','E')
AS Mode FROM
Production.ProductCategory; Måneder
til sæson: SELECT
JobTitle, HireDate, CHOOSE(MONTH(HireDate),'Winter','Winter', 'Spring','Spring','Spring','Summer','Summer','Summer','Autumn',
'Autumn','Autumn','Winter') AS Season FROM
HumanResources.Employee WHERE YEAR(HireDate)
> 2005 ORDER BY
YEAR(HireDate); Choose
is simular to Switch/Case When Then End. Formatteringer DECLARE @d DATETIME = GETDATE() SELECT FORMAT(@d, 'yyyy-MM-dd') AS 'år-måned-dag', -- 2015-01-10 FORMAT(@d, 'dd-MM-yyyy') AS 'dag-måned-år', -- 10-01-2015 FORMAT(@d,
'dd/MM/yyyy') AS 'dd/MM/yyyy', -- 10/01/2015 FORMAT(@d,
'dd/MMM/yyy') AS 'dd/MMM/yy', -- 10/Jan/2015 FORMAT(@d, 'd', 'da-DK') AS 'Danmark', -- 10-01-2015 FORMAT(@d, 'dd. MMMM yyyy', 'da-DK') AS
'Danmark', -- 10. januar 2015 FORMAT(@d, 'D', 'da-DK') AS 'Danmark', -- 10. januar 2015 FORMAT(123456789, 'F0', 'da-DK') AS
'Nummer', -- 123456789 FORMAT(123456789, 'F1', 'da-DK') AS
'Nummer', -- 123456789,0 FORMAT(123456789, 'F2', 'da-DK') AS
'Nummer', -- 123456789,00 FORMAT(123456789, 'F3', 'da-DK') AS
'Nummer', -- 123456789,000 FORMAT(123456789, 'N0', 'da-DK') AS
'Nummer', -- 123.456.789 FORMAT(123456789, 'N2', 'da-DK') AS
'Nummer', -- 123.456.789,00 FORMAT(1.23456789, 'N4', 'da-DK') AS
'Nummer', -- 1,2346 FORMAT(1.494, 'N2', 'da-DK') AS 'Nummer' -- 1,49 FORMAT(1.495, 'N2', 'da-DK') AS 'Nummer' -- 1,50 FORMAT(ROUND(1.495,0), 'N2', 'da-DK') AS
'Nummer' -- 1,00 FORMAT(ROUND(1.5,0), 'N2', 'da-DK') AS
'Nummer' -- 2,00 FORMAT(123456789, 'C', 'da-DK') AS 'Beløb',
-- kr.
123.456.789,00 FORMAT(123456789,
'###-##-####') AS 'Format', -- 123-45-6789 FORMAT(0.3333, 'P', 'da-DK') AS 'Procent' --
33,33 % Format
'N0' viser ingen decimaler, 'N1' viser en decimal, 'N2' viser 2 decimaler, 'N4'
viser 4 decimaler, og der afrundes på normalvis så 1.495, 'N2' giver 1,50.
Round 0 afrunder på normalvis til uden decimaler og sammen med 'N2' fås ,00. Jeg
bruger tit Format på tal kolonner, inden jeg copy-paste resultatet til Excel,
så bliver det til ægte kommatal og kan summeres i Excel. Kontrol
inden der udføres en cast til en bestemt data type Det kan være, at data ikke kan castes
til den ønskede data type, hvorved CAST vil give fejl: Conversion failed when
converting the nvarchar value '123a' to data type int, så kan TRY_CAST
anvendes fordi den ikke giver fejl men i stedet giver NULL: DECLARE
@s nvarchar(100) = N'123a' SELECT
Tal = CAST(@s AS int)
-– giver fejl SELECT
Tal = TRY_CAST(@s AS int)
-– giver null SELECT
Tal = ISNULL(TRY_CAST(@s AS int), -1)
-– giver -1 SET @s =
N'123' SELECT
Tal = CAST(@s AS int)
-– giver 123 SELECT
Tal = TRY_CAST(@s AS int)
-– giver 123 SELECT
Tal = ISNULL(TRY_CONVERT(int, @s), -1) -– giver 123 Nedenstående
sætter Tal til Null når SET @s = NULL: SELECT
Tal = CASE WHEN @s IS NOT NULL THEN ISNULL(TRY_CAST(@s AS
int), -1) END Dato er altid et klassisk tilfælde,
hvordan er formatet for en dato: SET
DATEFORMAT mdy IF
TRY_CAST('12/31/2010' AS date) IS NOT NULL PRINT 'OK' ELSE PRINT 'ERROR' SET
DATEFORMAT dmy IF
TRY_CAST('31/12/2010' AS date) IS NOT NULL PRINT 'OK' ELSE PRINT 'ERROR' SET
DATEFORMAT dmy IF
TRY_CAST('31-12-2010' AS date) IS NOT NULL PRINT
'OK' ELSE
PRINT 'ERROR' Det
er ikke altid, at et Nummer felt indeholder et tal nummer, så her undersøges
om Nummer feltet kan castes til et helttal og går det godt, så castes til en
streng ellers giver TRY_CAST Null og så forsøger jeg med feltets indhold for at
se om det er lig med Kunde.Kode feltet:
TRY_CAST(t.Nummer AS int) AS varchar(50)), t.Nummer) = Kunde.Kode En
slags konverteringsfunktioner PARSE() and TRY_PARSE() i SQL 2012 SELECT TRY_PARSE('27
juli 2011' AS DATE) AS 'English', -- giver null TRY_PARSE('27
july 2011' AS DATE USING 'en-US') AS 'English', TRY_PARSE('27
juli 2011' AS DATE USING 'da-DK') AS 'Danish', TRY_PARSE('27
juillet 2011' AS DATE USING 'fr-FR') AS 'French' Videnskabelig notation konverter til
kommatal DECLARE @QuantityScientificNotation varchar(100) = '1.0E7' -- '7.890123456E8'
'5.68579E+17' '7.54001E+006' -- E means exponent DECLARE @QuantityDecimal decimal(28, 5) SELECT CASE WHEN (@QuantityScientificNotation
IS NULL OR
(@QuantityScientificNotation
<> '' AND (TRY_CAST(@QuantityScientificNotation AS decimal(28, 5)) IS NOT NULL) OR (TRY_CAST(@QuantityScientificNotation AS float) IS NOT NULL))) THEN 'Success' ELSE 'Failure' END SET
@QuantityDecimal = CASE WHEN (TRY_CAST(@QuantityScientificNotation
AS decimal(28, 5)) IS NOT NULL) THEN CAST(@QuantityScientificNotation AS decimal(28, 5)) WHEN (TRY_CAST(@QuantityScientificNotation AS float) IS NOT NULL) THEN CAST(CAST(@QuantityScientificNotation AS float) AS decimal(28, 5)) END SELECT @QuantityDecimal 3,12977952358254E+16
= 31297795235825400
men var i virkeligheden lidt mere præcist som 31297795235825420
så E+ skal undgås i float værdi. SELECT CONVERT(decimal(28,2), CAST('3.12977952358254E16'
AS FLOAT)) SELECT CONVERT(decimal(28,2), CAST('5E16' AS FLOAT)) SELECT CONVERT(decimal(28,16), CAST('5E-16' AS FLOAT)) 31297795235825400.00 50000000000000000.00 0.0000000000000005 Bedst
at finde store værdier ved at udtrykke det i en SQL Where del: SELECT * FROM
dbo.RapportData WHERE
FloatValue >= 1000000000000000 Linieskift
og email DECLARE @tekst nvarchar(1024) SET @tekst = 'ETL udført.' + CHAR(10) +
'Kuber processeret.' EXEC msdb.dbo.sp_send_dbmail -- Database
Mail opsat smtp ip-adresse
@profile_name = 'Drift',
@recipients = 'bla@bla.dk;blu@bla.dk',
@body = @tekst,
@subject = 'ETL status' Data
fra en tabel bliver konverteret til en html tabel og sendt på email DECLARE @xml NVARCHAR(MAX) DECLARE @body VARCHAR(MAX) SET @xml = CAST(( SELECT [CustomerID] AS 'td','',[CompanyName] AS 'td','', [ContactName] AS 'td','',[ContactTitle] AS 'td' FROM Customers ORDER BY CustomerID FOR XML PATH('tr'), ELEMENTS ) AS
NVARCHAR(MAX)) SET @body = '<html><body><table
border=1><tr><th>CustomerID</th><th>CompanyName</th> <th>ContactName</th><th>ContactTitle</th></tr>' + CAST(CAST(@xml AS XML) AS NVARCHAR(MAX)) + '</table></body></html>' --SELECT
@body -- output kopieres til noteblok og gem som ansi test.htm fil. EXEC msdb.dbo.sp_send_dbmail @recipients =
@recipients, --
variablene er tildelt med ønskede værdier. @subject = @subject, @body =
@body, @body_format ='HTML'; <html><body><table
border=1> <tr><th>CustomerID</th><th>CompanyName</th><th>ContactName</th> <th>ContactTitle</th></tr> <tr><td>ALFKI</td><td>Alfreds
Futterkiste</td><td>Maria Anders</td> <td>Sales
Representative</td></tr> </table></body></html> Username without domain SELECT
SUSER_SNAME() AS [Domain\User], STUFF(SUSER_SNAME(), 1, charindex('\',
SUSER_SNAME()), '') AS [User] SELECT
ORIGINAL_LOGIN(), SUSER_SNAME() EXECUTE
AS LOGIN = 'XXX' GO SELECT
ORIGINAL_LOGIN(), SUSER_SNAME() -- SUSER_SNAME = 'XXX'. The
Current_User will return the name of the user in the database whereas
SUSER_NAME() will return the user name on the server. Foreningsmængde SELECT F1, F2 FROM
dbo.T1 UNION -- dropper dubletter, UNION ALL
medtager dubletter fra T1 og T2. SELECT
F1, F2 FROM dbo.T2 Hvilke medarbejdere har enten C++
eller C# som programmeringsfærdigheder: SELECT e.EmployeeId,
e.EmployeeName FROM dbo.Employee e INNER JOIN dbo.SkillGroup s ON
s.SkillGroupId = e.SkillGroupId WHERE
s.Skill = 'C++' UNION SELECT e.EmployeeId,
e.EmployeeName FROM dbo.Employee e INNER JOIN dbo.SkillGroup s ON
s.SkillGroupId = e.SkillGroupId WHERE s.Skill = 'C#' Se senere hvilke medarbejdere har både
C++ og C# programmeringsfærdigheder. Fællesmængde (eksempler fra SQL 2005’s
AdventureWorks database) A
INTERSECT B. Rækker som er i A er også i B. A
og B har fælles rækker for angivet felter i SELECT og der fås unikke rækker
retur (indbygget distinct), d.v.s. der fås de VendorID,ModifiedDate som
både er i tabellen VendorContact og i tabellen VendorAddress: SELECT
VendorID,ModifiedDate FROM
Purchasing.VendorContact INTERSECT SELECT
VendorID,ModifiedDate FROM Purchasing.VendorAddress ORDER BY VendorID,ModifiedDate -- sikring af sortering. Giver 104 unikke række. Brug af INNER JOIN i stedet for
INTERSECT og sortering efter clustered: SELECT
vc.VendorID,vc.ModifiedDate FROM
Purchasing.VendorContact vc INNER JOIN Purchasing.VendorAddress va ON va.VendorID = vc.VendorID AND
va.ModifiedDate = vc.ModifiedDate Giver 156 rækker med gentagelser
(dubletter). Brug af DISTINCT for at undgå
gentagelser og sortering efter clustered: SELECT
DISTINCT vc.VendorID,vc.ModifiedDate FROM
Purchasing.VendorContact vc INNER JOIN Purchasing.VendorAddress va ON va.VendorID = vc.VendorID AND
va.ModifiedDate = vc.ModifiedDate Giver 104 unikke række. Alternativ til INTERSECT og INNER
JOIN: SELECT
VendorID,ModifiedDate FROM ( SELECT 1 AS dummy,VendorID,ModifiedDate FROM Purchasing.VendorContact UNION ALL -- medtager dubletter i
modsætning til SELECT 2 AS dummy,VendorID,ModifiedDate FROM Purchasing.VendorAddress )AS T GROUP BY
VendorID,ModifiedDate HAVING
COUNT(*) >= 2 ORDER BY
VendorID,ModifiedDate Giver 104 unikke række. Hvilke medarbejdere har både C++ og
C# som programmeringsfærdigheder: SELECT e.EmployeeId,
e.EmployeeName FROM dbo.Employee e INNER JOIN dbo.SkillGroup s ON
s.SkillGroupId = e.SkillGroupId WHERE
s.Skill = 'C++' INTERSECT SELECT e.EmployeeId,
e.EmployeeName FROM dbo.Employee e INNER JOIN dbo.SkillGroup s ON
s.SkillGroupId = e.SkillGroupId WHERE
s.Skill = 'C#' ORDER BY
EmployeeName, EmployeeId Alternativ til INTERSECT og INNER
JOIN er String_Split og en variabel: DECLARE
@SkillList varchar(1000) = 'C++,C#' SELECT e.EmployeeId,
e.EmployeeName FROM dbo.Employee e INNER JOIN dbo.SkillGroup s ON
s.SkillGroupId = e.SkillGroupId WHERE
s.Skill IN(SELECT value FROM STRING_SPLIT(@SkillList, ',')) GROUP BY
e.EmployeeId, e.EmployeeName HAVING
COUNT(*) = (SELECT COUNT(*) FROM STRING_SPLIT(@SkillList, ',')) ORDER BY
e.EmployeeName, e.EmployeeId Har en medarbejder kun et af de to
programmeringsfærdigheder, så vil COUNT(*) give 1 og det er ikke lig med de
to fag som er angivet i variablen. En medarbejder medtages kun, når COUNT(*)
giver 2 d.v.s. at begge programmeringsfærdigheder er opfyldt. Hvorvidt
medarbejderen har andre programmeringsfærdigheder er ligegyldigt i for denne
forespørgsels formål. Brug af GROUP BY betyder, at samme medarbejder ikke
medtages to gange, d.v.s. svarende til SELECT DISTINCT. Differensmængde (eksempler fra SQL 2005’s
AdventureWorks database) A
MINUS B. Rækker som er i A men ikke i B. A
er fratrukket B for angivet felter i SELECT og der fås unikke rækker retur
(indbygget distinct). Kaldes EXCEPT fremfor MINUS: SELECT
VendorID,ModifiedDate FROM
Purchasing.VendorContact EXCEPT SELECT
VendorID,ModifiedDate FROM
Purchasing.VendorAddress ORDER BY VendorID,ModifiedDate -- sikring af sortering. Giver
en række, fordi Vendor 105 er blevet indsat med en kontaktperson uden
adresse. Sammenligne indholdet mellem to
tabeller for at se, om indholdet er ens. SELECT
F1, F2, F3 FROM
T1 EXCEPT SELECT
F1, F2, F3 FROM
T2 Og
vice versa. 0
rows is good for a equal comparison test. Brug
af NOT EXISTS i stedet for EXCEPT: SELECT
vc.VendorID,vc.ModifiedDate FROM
Purchasing.VendorContact vc WHERE
NOT EXISTS(SELECT TOP(1) 1 FROM
Purchasing.VendorAddress va WHERE va.VendorID =
vc.VendorID AND va.ModifiedDate = vc.ModifiedDate) Giver en række. A
logical left semi join is a query with an EXISTS() clause. A
logical left anti semi join is a query with an NOT EXISTS() clause. Brug af LEFT OUTER JOIN i stedet for
NOT EXISTS er ofte hurtigere på store data mængder: SELECT
vc.VendorID,vc.ModifiedDate FROM
Purchasing.VendorContact vc LEFT OUTER JOIN Purchasing.VendorAddress
va ON va.VendorID = vc.VendorID AND
va.ModifiedDate = vc.ModifiedDate WHERE va.VendorID IS NULL AND
va.ModifiedDate IS NULL Giver en række. Når left outer join indeholder et
kriterie betyder det, at en række fra A medtages når enten dens Id ikke
findes i B eller når Id findes i B så skal B rækken også have IsCurrent = 1
for at A rækken medtages: FROM
A LEFT OUTER JOIN B ON B.Id = A.Id AND B.IsCurrent = 1 Alternativ
til EXCEPT og NOT EXISTS og LEFT OUTER JOIN: SELECT
VendorID,ModifiedDate FROM ( SELECT 1 AS dummy,VendorID,ModifiedDate FROM Purchasing.VendorContact UNION ALL SELECT 2 AS dummy,VendorID,ModifiedDate FROM Purchasing.VendorAddress )AS T GROUP BY
VendorID,ModifiedDate HAVING MAX(dummy) = 1 Giver
en række. Hvad
tror du disse to sql’er giver: SELECT * FROM t INNER JOIN s ON s.pk = t.pk WHERE NOT EXISTS (SELECT s.* INTERSECT SELECT t.*) SELECT * FROM t INNER JOIN s ON s.pk = t.pk WHERE COALESCE(s.id, -2147483648) <>
COALESCE(t.id, -2147483648) OR COALESCE(s.txt,
'*') <> COALESCE(t.txt, '*') OR COALESCE(s.val,
-922337203685477.5808) <> COALESCE(t.val,
-922337203685477.5808) Hvilke
måneder er komplete fra Deltagerlisten, d.v.s. hvor der er data på alle
ugerne i måneden: SELECT DISTINCT a.ÅrMåned -- Finder alle de måneder der ikke FROM
dbo.DW_KALENDER_ÅrUge a --
er i mængden af den indre select, WHERE
NOT EXISTS( -- d.v.s.
mængde difference. SELECT DISTINCT b.ÅrMåned
-- Finder alle de måneder der
FROM dbo.DW_KALENDER_ÅrUge b --
ikke er uger på.
WHERE NOT EXISTS( SELECT TOP(1) 1 FROM dbo.Deltagerliste c WHERE c.ÅrUge = b.ÅrUge)
AND b.ÅrMåned = a.ÅrMåned) ORDER BY a.ÅrMåned Hvilke medarbejdere har C++ og har ikke
C# som programmeringsfærdighed: SELECT e.EmployeeName FROM dbo.Employee e INNER JOIN dbo.SkillGroup s ON
s.SkillGroupId = e.SkillGroupId WHERE
s.Skill = 'C++' EXCEPT SELECT e.EmployeeName FROM dbo.Employee e INNER JOIN dbo.SkillGroup s ON
s.SkillGroupId = e.SkillGroupId WHERE s.Skill = 'C#' Forrige række, næsten, med self
referencing join og theta join Ordre tabellen har dette indhold:
Der ønskes de ordrer med antal 20+
og den forrige ordre som har antal 10+:
Findes med denne sql sætning: SELECT cur.Kundenummer, cur.Ordredato, -- current
MAX(pre.Ordredato) AS Forrige_Ordredato -- previous FROM
dbo.ORDRE cur
INNER JOIN dbo.ORDRE pre
ON pre.Kundenummer = cur.Kundenummer AND -- selfreferencingjoin pre.Ordredato <
cur.Ordredato -- thetajoin WHERE cur.Antal >= 20 AND pre.Antal >= 10 -- filtercriteria GROUP BY
cur.Kundenummer, cur.Ordredato ORDER BY
cur.Kundenummer, cur.Ordredato Forventet
indlæsningsdatoer og indlæsningsdatoer SELECT Dato FROM [Kalender].[dbo].[Kalender] WHERE DagIUge BETWEEN 1 AND 5 AND (Helligdag IS NULL
OR Helligdag NOT IN('Langfredag','2.
Påskedag', 'Kristi
Himmelfartsdag','Juledag','2. Juledag') ) AND År
BETWEEN 2018 AND 2023 AND Dato != '2020-12-31' -- undtagelsesvis ikke forventet dato. EXCEPT SELECT Load_Dato FROM [Salgsmart].[audit].[Load_Log] WHERE [Status] = 'Success' ORDER BY 1 Tom
tabel eller tabel med mindst en række Count giver antal rækker i en tabel: IF
(SELECT COUNT(*) FROM dbo.Kunde) = 0 PRINT
'TOM' ELSE
PRINT 'MINDST EN RÆKKE' IF
(SELECT COUNT(*) FROM dbo.Kunde) >= 1 PRINT
'MINDST EN RÆKKE' ELSE
PRINT 'TOM' Exists
kan bruges til en formulering, hvis der ikke findes… TOP(1) for at begrænse
antallet af rækker til kun én række for det er nok med én række til at lave
en mængde med værdien 1 d.v.s. det andet 1 i SELECT, og det 1 står for sand): IF NOT
EXISTS (SELECT TOP(1) 1 FROM dbo.Kunde) PRINT
'TOM' ELSE
PRINT 'MINDST EN RÆKKE' IF
EXISTS (SELECT TOP(1) 1 FROM dbo.Kunde) PRINT
'MINDST EN RÆKKE' ELSE
PRINT 'TOM' Table
Value Constructor – lav en tabel med data til en sql sætning Values
kan danne on-the-fly data med flere rækker og kolonner, så det bliver til en
tabel med navngivne kolonner, tabellen får navnet t med kolonnerne a og b: SELECT t.a,
t.b FROM ( VALUES
(1, 2), (3, 4), (5, 6), (7, 8), (9, 10) ) AS t(a, b) Eller
on-the-fly til en Cross join: SELECT
DISTINCT a.ProductName, t.Name FROM
dbo.Products a CROSS JOIN (VALUES ('Blade'), ('Race'),
('Cap')) AS t(Name) Max fun fact Max() always give a row and it can have NULL in the
column, like this: CREATE
TABLE #t(i INT) SELECT
MAX(i) FROM #t -- return
null row SELECT
ISNULL(MAX(i),0) FROM #t -- convert
null to zero SELECT
MAX(i) FROM #t WHERE 0 = 1 -- return
null row SELECT
MAX(i) FROM #t HAVING 0 = 1 -- here no row DROP
TABLE #t Return two rows with 1 and 2 values: SELECT * FROM
(VALUES (1), (2)) AS x(i) WHERE
EXISTS (SELECT MAX(y.i) FROM
(VALUES (1)) AS y(i) WHERE
y.i = x.i) Return one row with 1 value: SELECT * FROM
(VALUES (1), (2)) AS x(i) WHERE
EXISTS (SELECT MAX(y.i) FROM
(VALUES (1)) AS y(i) WHERE
y.i = x.i HAVING
MAX(y.i)>=1) Statistik med CASE WHEN Antal
gode kunder i de enkelte lande hvorfra kunderne kommer i forhold til det samlet
antal kunder per land. Der kan være lande, som ikke har gode kunder, så
bliver forholdet 0 for de lande. Gange med 1.0 (kommatal i USA) for at få
brøkens resultat som decimaltal: SELECT
KundeLand,
ForholdsvisGodeKunder = SUM(CASE WHEN KundeStatus IN('Bedre','God')
THEN 1 ELSE 0 END) * 1.0 / COUNT(*) FROM dbo.Kunde GROUP BY KundeLand -- per land, d.v.s. FOR COUNT(*) og SUM. Krydstabulering med CASE WHEN d.v.s uden Pivot Pivotering
af rækker, så kolonnen Træart der indeholder flere forskellige data bliver
til flere kolonner navnet efter dataindholdet: CREATE PROCEDURE
[dbo].[EffektPrTræart_Salgsliste] @År smallint, @Halvår
smallint AS SET
NOCOUNT ON SELECT
CAST(@Halvår AS nvarchar) + '. halvår ' + CAST(@År AS nvarchar) AS Periode, EffektNavn, SUM(CASE WHEN Træart = 'ALØ' THEN Salgspris
END) AS ALØ, SUM(CASE WHEN Træart = 'ANÅ' THEN Salgspris
END) AS ANÅ, SUM(CASE WHEN Træart = 'ASK' THEN Salgspris
END) AS ASK, SUM(CASE WHEN Træart = 'BØG' THEN Salgspris
END) AS BØG, SUM(CASE WHEN Træart = 'EG' THEN Salgspris END) AS EG FROM ( SELECT e.EffektNavn, p.Træart, CASE @Halvår WHEN 1 THEN
NULLIF(p.Salgspris1halvår, 0) WHEN 2 THEN NULLIF(p.Salgspris2halvår, 0)
END AS Salgspris FROM dbo.EFFEKT e INNER JOIN dbo.EFFEKTPRIS p ON p.EffektId
= e.EffektId WHERE p.År
= @År ) t GROUP BY
EffektNavn ORDER BY EffektNavn Krydstabulering med PIVOT Kundesalget
opgjort pr. kundegruppe og måneder fra tabellen Kundesalg, hvor oplysninger
står som rækker, men med pivot vendes månedernes om til kolonner:
PIVOT angiver Antal kolonnen
summeres og fordeles ud på kolonneoverskrifterne: SELECT * FROM ( SELECT ÅrMåned, Kundegruppe, COUNT(*) Antal FROM
dbo.Kundesalg GROUP BY ÅrMåned, Kundegruppe ) T PIVOT (SUM (Antal) FOR ÅrMåned IN
([201101],[201102],[201103], [201104],[201105],[201106],[201107],[201108],[201109], [201110],[201111],[201112])) P Medarbejdernes
tre fraværstyper med hvert sit Id nummer, ønskes vist som kolonner med
sigende fraværstypenavne. Det gøres med brug af PIVOT: CREATE VIEW dbo.Oversigt AS SELECT
TOP 99.99 PERCENT M.Cprnr, M.Navn, -- TOP i View med ORDER BY [1]
AS [Sygdom], [2] AS [Barsel], [3] AS [Ferie] –-tre kolonner FROM dbo.EDW_Medarbejder AS M -- tabel med medarbejder og PK Cprnr LEFT OUTER JOIN –- medtager også medarbejder der ikke har
fravær (SELECT * FROM
dbo.EDW_Medarbejder_Fravær -- tabel
med fravær og FK Cprnr PIVOT
(SUM (AntalDage) FOR FraværTypeId IN ([1],[2],[3]) ) AS
PVT) AS T ON T.Cprnr = M.Cprnr ORDER BY M.Cprnr
Kunder har op til tre telefonnumre,
som ønskes vist som kolonner:
SELECT KundeId, Telefonnummer1 = MAX([1]),
Telefonnummer2 = MAX([2]), Telefonnummer3 = MAX([3]) FROM dbo.KUNDE_TELEFONNUMMER PIVOT (MAX(Telefonnummer) FOR TelefonId IN
([1],[2],[3])) p GROUP BY KundeId Eller uden MAX fordi Telefonnummer er af datatypen bigint: SELECT KundeId, Telefonnummer1 = [1],
Telefonnummer2 = [2], Telefonnummer3 = [3] FROM dbo.KUNDE_TELEFONNUMMER PIVOT (MAX(Telefonnummer) FOR TelefonId IN
([1],[2],[3])) p ORDER BY KundeId Eller uden PIVOT hvor MAX klapper rækker
sammen i én række (smash, collaps): SELECT KundeId, [1] = MAX(CASE WHEN TelefonId = 1 THEN
Telefonnummer END), [2] = MAX(CASE WHEN TelefonId = 2 THEN
Telefonnummer END), [3] = MAX(CASE WHEN TelefonId = 3 THEN
Telefonnummer END) FROM dbo.KUNDE_TELEFONNUMMER GROUP BY KundeId ORDER BY KundeId Brug
af Pivot ved en rapport forespørgsel, men idet der indgår kriterier som
Startdato og Slutdato, anvendes funktion (Inline Table-Valued Function TVF) til at udsøge data, fordi Pivot
har nogle begrænset i brug af WHERE: Funktion
med parameter som kriterier for rapporten, der returnerer en tabel: CREATE
FUNCTION [dbo].[Rapport_StedStatistikBasis] (@StartDato date, @SlutDato date) RETURNS TABLE AS RETURN ( SELECT BrugerId, HandlingTypeNavn,
HandlingTidspunkt FROM dbo.BRUGERLOG WHERE Handlingstidspunkt BETWEEN @StartDato
AND @SlutDato ) GO Funktionen kaldes eller anvendes som
om det var en tabel: SELECT * FROM
dbo.Rapport_StedStatistikBasis('2004-03-01', '2004-05-31') Udvidet udgave som indeholder tabel
definition og en opdatering kaldet Multi-Statement Table-Valued Function TVF: CREATE
FUNCTION [dbo].[Rapport_StedStatistikBasis] (@StartDato date, @SlutDato date) RETURNS
@Resultet TABLE (BrugerId int NOT NULL, HandlingTypeNavn varchar(50) NOT NULL, HandlingTidspunkt date NOT NULL, Status varchar(5) NOT NULL, PRIMARY KEY CLUSTERED(BrugerId, HandlingTidspunkt) ) AS BEGIN INSERT
INTO @Resultet SELECT BrugerId, HandlingTypeNavn,
HandlingTidspunkt, NULL FROM
dbo.BRUGERLOG WHERE Handlingstidspunkt BETWEEN @StartDato
AND @SlutDato UPDATE @Resultet SET
Status =
CASE WHEN HandlingTidspunkt = @SlutDato THEN 'End'
ELSE 'Okay' END
RETURN
-- returnerer output tabellen fra variablen @Resultet. END GO En
anden funktion som danner Pivot på basis af første der bliver kaldt i FROM: CREATE
FUNCTION [dbo].[Rapport_StedStatistikPivot] (@StartDato date, @SlutDato date) RETURNS
TABLE AS RETURN ( SELECT
BrugerId, Sted, SUM(Login) AS AntalLogin, SUM(Søgning)
AS AntalSøg FROM
dbo.Rapport_StedStatistikBasis(@StartDato, @SlutDato) A INNER JOIN dbo.BRUGERSTED AS B ON
B.BrugerId = A.BrugerId AND A.HandlingTidspunkt BETWEEN B.FraDato AND
B.TilDato PIVOT (COUNT(HandlingTidspunkt) FOR
HandlingTypeNavn IN
([Login],[Søgning])) AS PVT GROUP BY BrugerId, Sted ) GO Data skal anvendes i en rapport f.eks. i
Access eller Reporting Services, hvor stederne skal sorteres i faldende
orden med antal login. En stored procedure kan kaldes fra rapportens dataset
med de kriterier brugerens har udfyldt som parameter. Hvis rapporten kun kan
stigende sortering (og ikke faldende), sikres faldende sortering ved at
datasættet gør sorteringsværdien negativ ved at gange med minus 1: CREATE
PROCEDURE [dbo].[Rapport_StedStatistik] @StartDato date, @SlutDato date AS BEGIN SET NOCOUNT ON SELECT A.Sted, A.BrugerId, A.AntalLogin,
A.AntalSøg, C.AntalStedLogin * (-1) AS
AntalStedLogin FROM
dbo.Rapport_StedStatistikPivot(@StartDato, @SlutDato) A INNER
JOIN –- indlejret forespørgsel giver tabel kaldet B der ( --finder antal login pr. sted så der
kan sorteres på værdien. SELECT T.Sted, T.SUM(AntalLogin) AS
AntalStedLogin
FROM dbo.Rapport_StedStatistikPivot(@StartDato, @SlutDato) T GROUP BY T.Sted ) B ON B.Sted = A.Sted ORDER
BY B.AntalStedLogin DESC, A.Sted ASC, -- sortering ikke nødven
A.AntalLogin DESC, A.BrugerId ASC -- dig fordi rap. gør det END GO Modkrydstabulering med UNPIVOT Tabellen
Salgspris har kolonner for hver træart med hver deres salgspris per
år:
I
en database ønskes træarterne som rækker, så der kun er en kolonne at søge i.
Ovenstående tabel kan krydstabuleres, så træarterne bliver til rækker, og værdierne
der repræsenterer salgspriser kommer i sin egen kolonne, så der fås nedenstående
tabel, som en unpivot af ovenstående tabel:
UNPIVOT
med faste værdier fra kolonnerne med træarter: SELECT SalgsprisÅrstal, Træart, Salgspris FROM dbo.Salgspris UNPIVOT (Salgspris FOR Træart IN
(ALØ, ANÅ, ASK, BØG, EG) ) U Ønsker
man ikke de oprindelige kolonner i en Træart kolonne, skriv Nothing eller
[Type] i UNPIVOT:
SELECT SalgsprisÅrstal, Salgspris FROM dbo.Salgspris UNPIVOT
(Salgspris FOR Nothing IN
(ALØ, ANÅ, ASK, BØG, EG) )
U CROSS
APPLY med faste værdier fra kolonnerne med træarter: SELECT s.SalgsprisÅrstal, a.Træart,
a.Salgspris FROM
dbo.Salgspris s
CROSS APPLY (VALUES ('ALØ', ALØ),('ANÅ', ANÅ),('ASK', ASK), ('BØG', BØG),('EG', EG)) a(Træart,
Salgspris) CROSS
JOIN med faste værdier fra kolonnerne med træarter: SELECT
s.SalgsprisÅrstal, a.Træart, CASE a.Træart WHEN 'ALØ' THEN s.ALØ WHEN
'ANÅ' THEN s.ANÅ WHEN 'ASK' THEN s.ASK WHEN 'BØG' THEN s.BØG WHEN 'EG' THEN s.EG END AS Salgspris FROM
dbo.Salgspris s CROSS JOIN (VALUES ('ALØ'), ('ANÅ'),
('ASK'), ('BØG'), ('EG')) AS a(Træart) UNION
ALL med faste værdier fra kolonnerne med træarter: SELECT SalgsprisÅrstal, Træart='ALØ',
Salgspris = ALØ FROM dbo.Salgspris UNION ALL SELECT SalgsprisÅrstal, Træart='ANÅ',
Salgspris = ANÅ FROM dbo.Salgspris UNION ALL SELECT SalgsprisÅrstal, Træart='ASK',
Salgspris = ASK FROM dbo.Salgspris UNION ALL SELECT SalgsprisÅrstal, Træart='BØG',
Salgspris = BØG FROM dbo.Salgspris UNION ALL SELECT SalgsprisÅrstal, Træart='EG',
Salgspris = EG FROM dbo.Salgspris ORDER BY SalgsprisÅrstal, Træart Når brugere indtaster data i et
regneark, lader de altid tiden være kolonner:
I
en database ønskes tiden som rækker, så der kun er en kolonne at søge i.
Ovenstående tabel kan krydstabuleres, så tiden bliver til rækker, og
værdierne der repræsenterer henvendelser til en help desk support funktion
kommer i sin egen kolonne, så der fås nedenstående tabel, hvor en ÅrMåned
kolonne er medtaget via join til en anden tabel:
Månedsnavnene knyttes til en ÅrMåned
Kalender tabel:
SELECT Id, Kundenavn, K.ÅrMåned, K.Måned,
Henvendelser FROM
(SELECT Id, Kundenavn, Januar, Februar, Marts, April, Maj, Juni,
Juli, August, September, Oktober, November, December
FROM dbo.IMPORT) T UNPIVOT
(Henvendelser FOR Måned IN
(Januar, Februar, Marts, April, Maj, Juni,
Juli, August, September, Oktober, November, December) ) U INNER JOIN dbo.KALENDER_MÅNED K ON K.Måned
= U.Måned Union all kan også lave unpivot SELECT Id, Kundenavn,
ÅrMåned=200901, Måned='Januar', Henvendelser = Januar FROM dbo.IMPORT UNION ALL SELECT Id, Kundenavn,
ÅrMåned=200902, Måned='Februar', Henvendelser = Februar FROM dbo.IMPORT UNION ALL SELECT Id, Kundenavn,
ÅrMåned=200903, Måned='Marts', Henvendelser = Marts FROM dbo.IMPORT UNION ALL ... Cross apply kan
også lave unpivot
SELECT
BookingId, AntalPersoner FROM
dbo.Booking CROSS APPLY(VALUES
(AntalVoksne),(AntalBørn)) COLUMNNAMES(AntalPersoner)
Funktion
– Scalar-valued functions, giver en værdi retur Nyttig funktion der klarer division
også når nævneren er 0: CREATE
FUNCTION dbo.Div (@t float, @n float) RETURNS
float AS BEGIN DECLARE
@div float IF (@t
IS NULL) OR (@n IS NULL) SET @div = NULL ELSE IF @n <> 0 SET
@div = (@t / @n)
ELSE
SET @div = NULL RETURN (@div) END GO Kald en funktion: SELECT
dbo.Div(25,5) as x, dbo.Div(25,4) as y, dbo.Div(25,0) as z, Funktion
– Table-valued functions, giver en tabel
retur Funktion der beregner og returnerer
en tabel tilbage: CREATE
FUNCTION dbo.GetKundeSumMængde(@KundeTypeId int, @Dato date) RETURNS
TABLE AS RETURN ( SELECT
dbo.VAREMÆNGDE.VareId, SUM(dbo.VAREMÆNGDE.Mængde)
AS Mængde,
SUM(dbo.Div(dbo.Nz(dbo.VAREMÆNGDE.Mængde), dbo.GetPræstation(dbo.KUNDE.KundeTypeId,
@Dato))) AS Tidsforbrug FROM dbo.KUNDE
INNER JOIN dbo.VAREMÆNGDE
ON dbo.VAREMÆNGDE.KundeId = dbo.KUNDE.KundeId WHERE dbo.KUNDE.KundeTypeId = @KundeTypeId GROUP BY dbo.VAREMÆNGDE.VareId ) GO En stored procedure der anvender
funktionen: CREATE
PROCEDURE dbo.Rapport_KundeMængde @Dato date AS BEGIN SET NOCOUNT ON SELECT dbo.VARE.VareNavn, GetKundeSumMængde.* FROM dbo.KUNDE INNER JOIN dbo.VARE ON dbo.VARE.KundeTypeId =
dbo.KUNDE.KundeTypeId INNER
JOIN dbo.GetKundeSumMængde(dbo.KUNDE.KundeTypeId, @Dato)
GetKundeSumMængde
ON GetKundeSumMængde.VareId = dbo.VARE.VareId END GO STRING_SPLIT
funktion returnerer tabel til kriterie for stored procedure En
funktion til at opstille en liste til en tabel med listens elementer som rækker: CREATE
FUNCTION dbo.STRING_SPLIT (@string varchar(4000), @delimiter
varchar(10)) RETURNS
@table TABLE (Value varchar (4000)) AS BEGIN DECLARE @pos int DECLARE @piece varchar(4000) IF RIGHT(RTRIM(@string),1) <>
@delimiter
SET @string = @string +
@delimiter SET
@pos = PATINDEX('%'+@delimiter+'%' ,
@string) WHILE
@pos <> 0 BEGIN SET @piece = left(@string, @pos - 1) INSERT INTO @table VALUES(@piece) SET
@string = STUFF(@string, 1, @pos, '') SET @pos =
PATINDEX('%'+@delimiter+'%' , @string) END RETURN END GO Eller med brug af en opbygget XML
struktur og uendelig lang streng: CREATE
FUNCTION dbo.STRING_SPLIT (@string nvarchar(MAX), @delimiter
varchar(10)) RETURNS @table TABLE
(Value nvarchar(MAX)) AS BEGIN DECLARE @xml XML SET @XML = N'<rt><r>' +
REPLACE(@string, @delimiter , '</r><r>')+ '</r></rt>' INSERT INTO @table(Value) SELECT n.value('.',' nvarchar(MAX)') AS
Value FROM
@xml.nodes('//rt/r') m(n) -- tabel m med
kolonne n RETURN END
GO Eller med brug af Cross Apply og en opbygget
XML struktur uden tabel variabel: CREATE
FUNCTION dbo.STRING_SPLIT (@string varchar(4000), @delimiter
varchar(10)) RETURNS
TABLE AS RETURN ( SELECT
LTRIM(RTRIM(m.n.value('.[1]','varchar(4000)'))) AS Value FROM ( SELECT
CAST('<XMLRoot><RowData>' + REPLACE(@string,@delimiter,'</RowData><RowData>')
+ '</RowData></XMLRoot>'
AS XML) AS x ) t -- tabel t kolonne x CROSS APPLY t.x.nodes('/XMLRoot/RowData') m(n)
-- tabel m kolonne n ) SELECT value
FROM dbo.STRING_SPLIT('200905,200906,200907', ',') giver en tabel med tre rækker,
d.v.s. strengen er blevet splittet op i rækker:
Når
en parameter til en stored procedure indeholder en tekst streng med værdier
adskilt med komma, så kan strengen vendes om til en tabel og derved kan der
søges på værdierne i en anden tabel, her LEVERANCE tabel: CREATE
PROCEDURE dbo.List_Leverance @ÅrMåneder varchar(4000) = '' AS -- EXEC dbo.List_Leverance
'200905,200906,200907' BEGIN SET NOCOUNT ON SELECT * FROM dbo.LEVERANCE WHERE
ÅrMåned IN (SELECT Value FROM dbo. STRING_SPLIT(@ÅrMåneder, ',')) END GO DECLARE
@list VARCHAR(MAX) = ' 8C5A7AEF-03FB-4E42-98CE-EB06672B3967 E847BBDE-5324-42BA-B1C4-866453561A6F E78BC645-96CD-4DEB-8333-0AA596F383C9 1B0EA60A-3BD3-447A-A2D6-2DF80785FC30 ' SELECT
o.* FROM dbo.Orders
o INNER JOIN dbo. STRING_SPLIT(@list, CHAR(13)+CHAR(10))
l ON
l.Value = o.Id ORDER BY
o.Id -- Here
is a TABs in front of table names: DECLARE
@TableList nvarchar(max) = N' customer order invoice ' SELECT
DISTINCT TABLE_NAME FROM ERP.INFORMATION_SCHEMA.TABLES
t INNER JOIN dbo. STRING_SPLIT(@TableList,
CHAR(13)+CHAR(10)) l ON REPLACE(l.Value, CHAR(9), '') =
t.TABLE_NAME WHERE TABLE_TYPE
= 'BASE TABLE' AND TABLE_SCHEMA = 'commerce' Andet eksempel på split for
heltalsværdier CREATE
FUNCTION Slice_CSV_to_Integers(@csv_list varchar(256)) RETURNS
@table TABLE (Slice INT NOT NULL) AS BEGIN DECLARE @current_pos INT = 0, @next_pos INT = 1, @list_len INT = 0 WHILE @next_pos > 0 BEGIN SET @next_pos = CHARINDEX(',', @csv_list
, @current_pos + 1) SET @list_len = CASE WHEN @next_pos >
0 THEN @next_pos ELSE LEN(@csv_list)
+ 1 END INSERT INTO @table VALUES(CAST(SUBSTRING(@csv_list,
@current_pos + 1, @list_len) AS INT)) SET @current_pos = @next_pos END END SELECT *
FROM Slice_CSV_to_Integers('123,456,789') An Improved SQL 8K CSV Splitter Function Eksempel
med en tabel (dannet on the fly) hvis kolonne vriddes om til rækker DECLARE
@t TABLE ( Id INT, Titel VARCHAR(8000) ) INSERT @t VALUES (1,'IT-Konsulent,Datalog,HD,HH,Gymnasium'),
(2,'Direktør,cand.scient')
SELECT
Id, LTRIM(RTRIM(m.n.value('.[1]','varchar(8000)')))
AS Titel FROM ( SELECT Id,
CAST('<XMLRoot><RowData>' + REPLACE(Titel,',','</RowData><RowData>')
+
'</RowData></XMLRoot>'
AS XML) AS x FROM @t ) t CROSS APPLY t.x.nodes('/XMLRoot/RowData')
m(n) En kommasepareret værdi splittes via
en xml til tre kolonner kaldet string-split-to-columns: WITH
xmldata AS ( SELECT CONVERT(XML,'<Data><Tag>'
+ REPLACE(v,';', '</Tag><Tag>') + '</Tag></Data>')
AS Dataset FROM ( VALUES ('123;Godt;2016-02-12') ) AS
t(v) ) SELECT Id =
Dataset.value('/Data[1]/Tag[1]','int'), Status =
Dataset.value('/Data[1]/Tag[2]','varchar(10)'), Dato = Dataset.value('/Data[1]/Tag[3]','date') FROM
xmldata SQL Server 2016 med STRING_SPLIT STRING_SPLIT konverterer en string
indhold til tabel rækker a la SplitToTable. DECLARE
@tags NVARCHAR(400) = 'clothing,road,touring,bike' SELECT
value FROM
STRING_SPLIT(@tags, ',') WHERE RTRIM(value) <> '' Eller
at søge efter produkt-id’er som er enten 1, 2 eller 3: SELECT p.ProduktId, p.ProduktNavn FROM dbo.Produkt p INNER JOIN
STRING_SPLIT('1,2,3', ',') s ON s.value = p.ProduktId SELECT ProduktId, ProduktNavn FROM dbo.Produkt WHERE ProduktId
IN(SELECT CONVERT(int, value) FROM STRING_SPLIT('1,2,3',
',')) Der
kan opnås forbedret performance ved at lægge søgekriterierne i en temporær
tabel og sikre at sql server query optimizer kender antallet af dens rækker: DROP TABLE
IF EXISTS #Liste_ProduktId CREATE
TABLE #Liste_ProduktId (ProduktId int NOT NULL PRIMARY KEY) INSERT #Liste_ProduktId(ProduktId) SELECT value FROM STRING_SPLIT('1,2,3',
',') UPDATE STATISTICS #Liste_ProduktId SELECT ProduktId, ProduktNavn FROM dbo.Produkt WHERE
ProduktId IN (SELECT ProduktId FROM #Liste_ProduktId) Ovenstående
anvender Clustered Index Seek i Produkt tabel. Sikring
af sorteringsorden fra string_split inkl. dubletter: DECLARE @s nvarchar(max)= N'Mit navn er
Anders And, husk mit navn' ;WITH
MyCTE AS ( SELECT value , CHARINDEX (value,@s,0)
MyOrder FROM STRING_SPLIT(@s,' ') ) SELECT
value , MyOrder, RN = ROW_NUMBER() OVER (PARTITION BY
MyOrder ORDER BY MyOrder) - 1, FinalOrder = CHARINDEX (value,@s,MyOrder +
ROW_NUMBER() OVER (PARTITION BY
MyOrder ORDER BY MyOrder) - 1) FROM MyCTE ORDER BY FinalOrder SQL
Server 2022 med STRING_SPLIT
rækkefølgenummer, parameter 1 DECLARE @s varchar(50) = 'abe,kat,hund' SELECT * FROM STRING_SPLIT(@s,',')
Før SQL
Server 2022 måtte man selv udregne et rækkefølgenummer for at sikre sorteringsorden
med Order By: SELECT *, Number = ROW_NUMBER() OVER(ORDER BY (SELECT 1)), TotalNumberOf
= COUNT(1) OVER() FROM STRING_SPLIT(@s,',') ORDER BY 2
Fra SQL
Server 2022 angiver en parameter med værdien 1 at der ønskes et
rækkefølgenummer kolonne kaldet ordinal, som der kan sorteres efter: SELECT * FROM STRING_SPLIT(@s,',',1) ORDER BY ordinal
Kun det
andet element i listen d.v.s. når et rækkefølgenummer er 2. Der fås Kat: SELECT value FROM STRING_SPLIT(@s,',',1) WHERE ordinal = 2 Multiple-character
delimiter erstattes med et tegn så String_Split virker DROP TABLE IF EXISTS dbo.Pets; CREATE TABLE dbo.Pets ( StudentID int PRIMARY KEY,
PetNames nvarchar(4000) ); INSERT dbo.Pets(StudentID, PetNames) VALUES (1, N'Furface~|~Snowball~|~Max'), (2, N'Kirby~|~Quigley'), (3, N'Bond'), (4, N'Dax~|~Spike~|~Bowser~|~Rosco'); SELECT p.StudentID, s.Value FROM dbo.Pets AS p OUTER APPLY STRING_SPLIT(REPLACE(p.PetNames, N'~|~', NCHAR(9999)), NCHAR(9999)) AS s;
Fjernelse
af mellemrum char(32) i to løsningsforslag DECLARE @text VARCHAR(100) = '
Hello World ' SELECT TRIM(REPLACE(REPLACE(REPLACE(@text, CHAR(32), '()'), ')(', ''), '()', CHAR(32))) AS CleanedText SELECT STRING_AGG(value, CHAR(32)) WITHIN GROUP (ORDER BY CHARINDEX(value, @text)) AS CleanedText FROM STRING_SPLIT(TRIM(@text), CHAR(32)) -- gets two lines Hello and World WHERE value <> ''
-- and skip
emtpy rows -- @text becomes 'Hello World' Udsøgning
af produkter fra en liste af produktnavne til en rapport STRING_SPLIT
for at søge efter udvalgte produkter fra en rapport i Reporting Services,
hvor STRING_SPLIT laver
strengen af produkter om til en tabel med produkter der indgår i en INNER
JOIN: CREATE PROCEDURE
dbo.ProduktListePrUge
@ÅrUge int, -- brugeren indtaster 201740 for uge 40 i 2017.
@Produktnavne varchar(max) -- hvor flere produkter er valgt i en
dropdown AS BEGIN DECLARE @Uge int = @ÅrUge -
((@ÅrUge / 100) * 100) DECLARE @År int = (@ÅrUge / 100) DECLARE @UgeStartdato datetime -- mandag kl. 00:00:00.000 som default. DECLARE @UgeSlutdato datetime -- søndag kl. 23:59:59.997 sidste sekund. SET DATEFIRST 1 -- gør mandag til første dag
i ugen SET @UgeStartdato =
DATEADD(WEEK, @Uge, CONCAT(@År,'0101')) - 4 – DATEPART(WEEKDAY,
DATEADD(WEEK, @Uge, CONCAT(@År,'0101')) - 4) + 1 SET @UgeSlutdato =
DATEADD(MILLISECOND, -3, DATEADD(WEEK, @Uge, CONCAT(@År,'0101')) - 4 - DATEPART(WEEKDAY, DATEADD(WEEK,
@Uge, CONCAT(@År,'0101')) - 4) + 8) SET NOCOUNT ON SELECT p.ProduktId,
p.ProduktNavn, ÅrUge = @ÅrUge FROM dbo.Produkt p INNER JOIN
STRING_SPLIT(@Produktnavne, ',') s ON s.value = p.ProduktNavn WHERE p.IsCurrent = 1 AND p.ProduktTidspunkt
BETWEEN @UgeStartdato AND @UgeSlutdato END -- ProduktTidspunkt er en
datetime med forskellige klokkeslæt. Søge i et Kimball star schema i en stored
procedure med parametre, hvor en multivalue drop down i en Reporting Services
rapport giver en streng som indeholder de valgte kriterier, her er de i
parameteren @KontotypeKodeList og STRING_SPLIT laver strengen om til en tabel
med kriterierne som rækker til IN: CREATE PROCEDURE [dbo].[Bankpostering] -- Default value indicates a parameter
must not be null in the call. @FraDato date = '2018-01-01', @TilDato date = '2018-12-31', @Kundenummer int, -- Kundenummer er business key. @KontotypeKodeList nvarchar(MAX) =
N'-1,STANDARD,PENSION,HUSLÅN,BILLÅN,INDLÅN' AS BEGIN SET NOCOUNT ON DECLARE @Kunde_dkey int --
durable key for current value to search fast in fact. SELECT
@Kunde_dkey = Kunde_dkey FROM Datamart.dbo.Dim_Kunde_Current WHERE Kundenummer_bkey = @Kundenummer SELECT Kundenavn = k.Kundenavn, Dato = fact.fact.Bankdato,
Kontonr = fact.Kontonummer, Kontonavn = p.Produktnavn FROM Datamart.dbo.Fact_Bankpostering fact
INNER JOIN Datamart.dbo.Dim_Kunde_Current k ON k.Kunde_dkey = fact.Kunde_dkey
INNER JOIN Datamart.dbo.Dim_Produkt_Registered p ON
p.Produkt_key = fact.Produkt_key WHERE fact.Bankdato
BETWEEN @FraDato AND @TilDato AND ( (@Kundenummer IS NULL)
OR (fact.Kunde_dkey = @Kunde_dkey) ) AND (fact.Kontotype_dkey IN
(SELECT Kontotype_dkey FROM Datamart.dbo.Dim_Kontotype_Current WHERE KontotypeKode
IN(SELECT value
FROM STRING_SPLIT(@KontotypeKodeList, ',')))) ORDER BY 1,2,3 OPTION(RECOMPILE) -- bygger query execution
plan på aktuelle parameter værdier. END OPTION (RECOMPILE) for a new plan for every
execution. OPTION (RECOMPILE) can kill your instance immediately.
That's fine for an analytic-like query that is executed occasionally. Still,
if your most important query runs several times a minute or even a second,
the CPU overload from RECOMPILE will bring the system to a halt. OPTIMIZE FOR UNKNOWN for a optimizer uses average
distribution. Flere
String split eksempler Produkt tabel kolonne Tags
indeholder en streng 'clothing,road,touring,bike' som bliver splittet til
rækker, så samme produkt gentager for hvert tag i Tags: SELECT
ProduktId, ProduktNavn, value as Tag FROM
dbo.Produkt CROSS
APPLY STRING_SPLIT(Tags, ',') CROSS
APPLY kan også kalde din egen kodet funktion: SELECT * FROM
dbo.Kunde CROSS APPLY dbo. Scalar_Valued_Function(KundeNavn); Et k-nummer med bindestreg opdeles i
enkeltstående talværdier som rækker: DECLARE
@k varchar(50) = '546-13090-074-3595-88505122' SELECT
value as Delnummer FROM
STRING_SPLIT(@k, '-') WHERE TRIM(value) <> '' Måske kan Pivot lave de fem rækker
om til kolonner, ellers kan det gøres sådan, a string-split-to-columns giver
disse fem kolonner: D1 = 546 D2 = 13090 D3 = 074 D4 = 3595 D5 = 88505122 /* Udregn start position
for hvert tegn efter bindestreg for hvert af de fem delnumre: select 1 select charindex('-',@k)+1 select charindex('-',@k,charindex('-',@k)+1)+1 select charindex('-',@k,charindex('-',@k,charindex('-',@k)+1)+1)+1 select
charindex('-',@k,charindex('-',@k,charindex('-',@k,charindex('-',@k)+1)+1)+1)+1 Udregning af længden (antal
tegn) for hvert delnummer så de kan udtages af strengen: (næste delnummers start
position) minus (gældende delnummers start position) minus 1. */ select -- parse value of @k Count = coalesce(len(@k) - len(replace(@k,'-','')), len(@k), 0), Valid = case when @k like
'[0-9][0-9][0-9]-[0-9][0-9][0-9][0-9][0-9]-[0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]-[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]'
then 1 else 0 end, D1 = substring(@k, 1, charindex('-',@k)-1), D2 = substring(@k, charindex('-',@k)+1, (charindex('-',@k,charindex('-',@k)+1)+1)-(charindex('-',@k)+1)-1), D3 = substring(@k, charindex('-',@k,charindex('-',@k)+1)+1, (charindex('-',@k,charindex('-',@k,charindex('-',@k)+1)+1)+1)- (charindex('-',@k,charindex('-',@k)+1)+1)-1), D4 = substring(@k,
charindex('-',@k,charindex('-',@k,charindex('-',@k)+1)+1)+1, (charindex('-',@k,charindex('-',@k,charindex('-',@k,charindex('-',@k)+1)+1)+1)+1)- (charindex('-',@k,charindex('-',@k,charindex('-',@k)+1)+1)+1)-1), D5 = substring(@k, charindex('-',@k,charindex('-',@k,charindex('-',@k, charindex('-',@k)+1)+1)+1)+1,
len(@k)) -– len tager resten. LIKE
kan bruges i join til udsøgning af data rækker, eksempelvis en tabel Navn
indeholder en masse navne, og tabel Del indeholder navne f.eks. »Mette« og »Lotte«
som kan være en del af et længere navn f.eks. »Mette Louise« eller »Charlotte«
og dette sql udtryk finder de navne som indeholder delnavnene (substring): SELECT n.* FROM Navn n
INNER JOIN Del d ON n.Navn Like '%' + d.Delnavn + '%' Alternativ med kendte værdier som
ikke er i en tabel: WHERE
Navn LIKE '%Mette%' OR Navn LIKE '%Lotte%' SELECT n.*
FROM
Navn n JOIN (values ('Mette'),('Lotte')) d(Delnavn)
ON n.Navn Like '%' + d.Delnavn +
'%' Søge
efter email adresser: values
('hotmail'),('gmail'),('yahoo'),('Outlook.com')). Men nedenstående sql udtryk lader
sig ikke gøre: WHERE
Navn LIKE ('%Mette%','%Lotte%') WHERE
Navn LIKE IN ('%Mette%','%Lotte%') WHERE Navn LIKE_IN ('%Mette%','%Lotte%') som en ny kommando eller INLIKE. Hvis
du anvender full-text search
on full-text indexed column, så prøv dette: WHERE
CONTAINS(Navn, '"*Mette*" OR "*Lotte*"') Oracle har en kommando REGEXP_LIKE: WHERE REGEXP_LIKE
(Navn,'^Mette^|^Lotte^') Postgresql
har en kommando LIKE ANY: SELECT *
FROM
database.Navn WHERE
LOWER(Navn) LIKE ANY ('{%mette%,%lotte%}') Feltet
TransactionIdentification starter med et ReceiptNumber, find rækkerne: fact.TransactionIdentification
LIKE CONCAT(@ReceiptNumber,'%') UTC
til Dansk tid fra SQL Server 2016 Datatypen er datetime eller datetime2 og
indholdet af feltet OrderDateTime_UTC er i tiden UTC Universal Time
Coordinated d.v.s. uden sommertid Daylight Savings Time DST. Gældende dato og klokkeslæt fås med GETUTCDATE()
eller SYSUTCDATETIME(). UTC tid til Dansk tid ved at fortælle, at
felt-indholdet er UTC tid og dernæst at konvertere det til centraleuropæisk
tid CET som Danmark DK hører ind under: SELECT
GETDATE(), GETUTCDATE(), GETCETDATE = GETUTCDATE() AT TIME ZONE
'UTC' AT TIME ZONE 'Central European Standard Time' SELECT
OrderDateTime_UTC, OrderDateTime_DK = OrderDateTime_UTC
AT TIME ZONE 'UTC' AT TIME ZONE 'Central
European Standard Time' FROM dbo.Order Når
datatypen er datetimeoffset, ved indholdet, at det er i UTC tid: SELECT
OrderDateTime_UTC, OrderDateTime_DK = OrderDateTime_UTC
AT TIME ZONE 'Central
European Standard Time' FROM
dbo.Order SELECT
CONVERT(datetime2(0), '2015-03-29T01:01:00', 126) AT TIME ZONE 'Central
European Standard Time' Når datatypen er streng med T og Z hvor T
adskiller dato og klokkeslæt og Z angiver Zero timezone d.v.s. at
klokkeslættet er i UTC tid, så kan der udregnes en ægte UTC dato og UTC klokkeslæt
og en ægte CET dato og CET klokkeslæt som time(6) fordi der seks sekund-cifre
i strengen i viste sql @variabel: DECLARE @dateandtime varchar(100) = '2022-01-03T23:06:56.090591Z' SELECT DateOfOrder_UTC = CAST(REPLACE(REPLACE(@dateandtime,'T',' '),'Z','')
AS date), TimeOfOrder_UTC = CAST(REPLACE(REPLACE(@dateandtime,'T',' '),'Z','')
AS time(6)), DatetimeOfOrder_UTC =
CAST(REPLACE(REPLACE(@dateandtime,'T',' '),'Z','') AS datetime2(6)), DateOfOrder_CET = CAST(CAST(REPLACE(REPLACE(@dateandtime,'T','
'),'Z','') AS datetime2(6)) AT TIME
ZONE 'UTC' AT TIME ZONE 'Central European Standard Time' AS date), TimeOfOrder_CET = CAST(CAST(REPLACE(REPLACE(@dateandtime,'T','
'),'Z','') AS datetime2(6)) AT TIME
ZONE 'UTC' AT TIME ZONE 'Central European Standard Time' AS time(6)), DatetimeOfOrder_CET = CAST(CAST(REPLACE(REPLACE(@dateandtime,'T','
'),'Z','') AS datetime2(6)) AT TIME
ZONE 'UTC' AT TIME ZONE 'Central European Standard Time' AS datetime2(6))
Dansk
tid til UTC tid, ved at fortælle, at felt-indholdet er centraleuropæisk tid
og dernæst at konvertere det til UTC tid: SELECT
OrderDateTime_UTC = OrderDateTime_DK AT TIME ZONE 'Central European Standard
Time' AT TIME ZONE 'UTC' FROM
dbo.Order WITH
PeopleAndTZs AS ( SELECT * FROM (VALUES ('Rob',
'Cen. Australia Standard Time'), ('Paul',
'New Zealand Standard Time'), ('Phia',
'US Eastern Standard Time') ) t (person, tz) ) SELECT ptz.person,
o.SalesOrderID, o.OrderDate AT TIME ZONE 'UTC' AT TIME ZONE ptz.tz FROM
PeopleAndTZs ptz CROSS
JOIN Sales.SalesOrderHeader o WHERE
o.SalesOrderID BETWEEN 44001 AND 44010 Rækker
til en streng med Id adskilt med komma, en kommalist (STRING_AGG) »Horizontal join or Grouped concatenation is when
you want to take multiple rows of data and compress them into a single string
is called string aggregate« Løbende
tildeling af felter til en streng variabel kaldet aggregeret streng: DECLARE
@KundeList varchar(8000) SELECT
@KundeList = CONCAT(ISNULL(@KundeList + ', ',
''), KundeId) FROM
dbo.KUNDE WHERE
KundeId IS NOT NULL ORDER BY
KundeId PRINT
@KundeList -- 123, 124,
125, 126 Aggregeret
streng eller string aggregation via xml til en streng variabel: DECLARE
@KundeList varchar(8000) SET
@KundeList = SUBSTRING( (SELECT CONCAT(', ', KundeId) FROM dbo.KUNDE WHERE KundeId IS NOT NULL ORDER BY KundeId FOR XML PATH('') ),
3, 99999999) PRINT @KundeList -- 123, 124, 125, 126 Aggregeret
streng eller string aggregation via json til en streng variabel: DECLARE
@KundeList varchar(8000) SET
@KundeList = JSON_VALUE(REPLACE( (SELECT _ = KundeId FROM dbo.KUNDE WHERE KundeId IS NOT NULL ORDER BY KundeId FOR JSON
PATH),'"},{"_":"',', '),'$[0]._') PRINT @KundeList -- 123, 124, 125, 126 Aggregeret streng eller string
aggregation via string aggregate funktion: DECLARE
@KundeList varchar(8000) SELECT
@KundeList = STRING_AGG(KundeId, ', ') FROM
dbo.KUNDE WHERE
KundeId IS NOT NULL ORDER BY
KundeId PRINT
@KundeList -- 123, 124, 125, 126 Se også afsnittet om: Felter i en
tabel eller kolonner i en tabel. En
kunde med KundeId har flere rækker i tabellen KUNDE_TELEFONNUMMER, fordi en
kunde har flere telefonnumre, og for hver KundeId ønskes kun én række som har
smeltet telefonnumrene sammen til en sorteret string adskilt med komma,
kaldet en aggregeret streng eller string aggregate: SELECT KundeId,
TelefonnummerListe = STRING_AGG(Telefonnummer,
', ') WITHIN
GROUP (ORDER BY Telefonnummer ASC) FROM dbo.KUNDE_TELEFONNUMMER T2 GROUP BY KundeId WITHIN GROUP clause fordi der ønskes
en sorteret liste med ORDER BY og det har ikke noget at gøre med GROUP BY,
fordi den er for at feltet KundeId kan gruppere de enkelte værdier i hver sin
række:
Før
SQL Server 2017: SELECT DISTINCT T2.KundeId,
TelefonnummerListe = SUBSTRING( ( SELECT ', ' + CAST(T1.Telefonnummer AS
varchar) AS [text()] FROM
dbo.KUNDE_TELEFONNUMMER T1
WHERE T1.KundeId = T2.KundeId ORDER BY
T1.KundeId FOR XML PATH ('') ),
3, 99999999) FROM dbo.KUNDE_TELEFONNUMMER T2 Aggregeret
streng ud fra flere rækker i en en-til-mange relation (STRING_AGG) SELECT dc.ClientKey, dc.ClientName, ClientRoleNameList =
cr.ClientRoleNameList FROM dim.Client dc OUTER APPLY (SELECT(SUBSTRING((SELECT ', ' + dcr.ClientRoleName FROM fact.ClientRole fcr WITH(NOLOCK,
INDEX(IX_Fact_ClientRole_Keys)) INNER JOIN
dim.ClientRole dcr WITH(NOLOCK) ON
dcr.ClientRoleKey = fcr.ClientRoleKey WHERE
fcr.ClientKey = dc.ClientKey ORDER BY dcr.ClientRoleName FOR XML
PATH('')), 3, 99999999) ) AS ClientRoleNameList) cr -- eller bruge Json fra version
2016 OUTER
APPLY (SELECT(JSON_VALUE(REPLACE((SELECT
_ = dcr.ClientRoleName FROM
fact.ClientRole fcr WITH(NOLOCK, INDEX(IX_Fact_ClientRole_Keys)) INNER JOIN
dim.ClientRole dcr WITH(NOLOCK) ON
dcr.ClientRoleKey = fcr.ClientRoleKey WHERE
fcr.ClientKey = dc.ClientKey ORDER BY dcr.ClientRoleName FOR JSON
PATH),'"},{"_":"',', '),'$[0]._') ) AS ClientRoleNameList) cr SQL
Server 2017 aggregeret streng En aggregeret streng hvor data
rækker lægges sammen til én række i output til en csv liste hvor person navne
er blevet adskilt med komma: SELECT STRING_AGG(ISNULL(Navn,'N/A'), ',') AS csv FROM dbo.Person Giver
csv: Peter,N/A,Yan,Michael,Pia,Jens Sikring af sortering i listen og
mellemrum mellem navnene: SELECT STRING_AGG(ISNULL(Navn,'N/A'), ', ') WITHIN
GROUP (ORDER BY Navn ASC) AS csv FROM dbo.Person Giver
csv: N/A, Jens, Michael, Peter, Pia, Yan Med gruppering og to kolonner, hvor
anden kolonne er en aggregeret streng: SELECT Bynavn, STRING_AGG(Navn, ',') WITHIN GROUP (ORDER BY Navn ASC) AS Beboer FROM dbo.Person WHERE Bynavn Is Not Null AND Navn Is Not Null GROUP BY Bynavn WITHIN GROUP clause fordi der ønskes
en sorteret liste med ORDER BY og det har ikke noget at gøre med GROUP BY,
fordi den er for at feltet Bynavn kan gruppere de enkelte værdier i hver sin
række:
Trimming af mellemrum både før og
efter eller left and right i en samlet funktion: SELECT TRIM(CustomerName) FROM dbo.Customer SELECT CONCAT(',', '1 Microsoft Way', NULL, NULL, 'Redmond', 'WA',
98052) AS Add Giver: ,1 Microsoft WayRedmondWA98052 Concat
with separator: CONCAT_WS ( separator,
argument1, argument1 [, argumentN]… ) SELECT CONCAT_WS(',', '1 Microsoft Way', NULL, NULL, 'Redmond', 'WA',
98052) AS Add Giver: 1 Microsoft Way,Redmond,WA,98052 SELECT STRING_AGG(CONCAT_WS( ',', database_id, recovery_model_desc,
ISNULL(containment_desc,'N/A')), char(13)) AS Info FROM sys.databases Giver: 1,SIMPLE,NONE 2,SIMPLE,NONE 3,FULL,NONE 4,SIMPLE,NONE SELECT CONCAT_WS(',', CustomerId, CustomerName, CustomerStatus) FROM
dbo.Customer SQL
Server 2022 aggregeret streng Fjernelse
af dubletter i en streng (Removing duplicates in a string) ved først at opdele
en streng til rækker, og dernæst aggregere rækkerne tilbage til en streng,
fordi Group By er det samme som Distinct i en Select, hvor ordinal er den nye
funktion (new feature) i 2022. DECLARE @List nvarchar(max), @Delim nchar(1) = N'/' SET @List = N'Bravo/Alpha/Bravo/Tango/Delta/Bravo/Alpha/Delta' SELECT STRING_AGG(t.value, @Delim) WITHIN GROUP (ORDER BY t.ordinal) FROM ( SELECT value, ordinal =
MIN(ordinal) FROM STRING_SPLIT(@List,
@Delim, 1) GROUP BY value ) t Giver
strengen: Bravo/Alpha/Tango/Delta. Ovenstående
GROUP BY value fjerner dubletter a la SELECT DISTINCT. mySQL kalder det GROUP_CONCAT. En SQLCLR routine giver group_concat
til tidligere sql server versioner: http://groupconcat.codeplex.com Drop de mange Replace inde i
hinanden når flere tegn skal erstattet en for en: SELECT
TRANSLATE('2*[3+4]/{7-2}', '[]{}', '()()') giver 2*(3+4)/(7-2) SELECT
TRANSLATE([DataField], '#[]{}~~', '1111111') FROM
[TableName] The
second and third arguments must contain an equal number of characters. Value {~~[##Dalby##]~~} becomes:
111111Dalby111111. Tabel
type som variabel og parameter men ikke en tabel i databasen CREATE
TYPE customer_table AS TABLE (CustomerId INT NOT NULL PRIMARY KEY, Name
NVARCHAR(50) NULL) DECLARE
@t customer_table INSERT
INTO @t(CustomerId, Name) VALUES
(1,'Jensen'), (2,'Nielsen') CREATE
PROCEDURE DoCustomer @p customer_table READONLY AS BEGIN SELECT * FROM dbo.Loans WHERE CustomerId IN (SELECT CustomerId FROM
@p) END EXEC DoCustomer @p = @t Kaldes Table-valued parameter men
kun som read-only og er ikke optional. Findes en værdi i en tabel CREATE
PROCEDURE dbo.Exist_ÅrDato @ÅrDato int AS BEGIN SET NOCOUNT ON SET ROWCOUNT 1 SELECT 1 FROM dbo.KALENDER WITH (NOLOCK) WHERE
(ÅrDato = @ÅrDato) RETURN
@@ROWCOUNT -- ROWCOUNT_BIG() kæmpe
antal rækker. END GO Få fat i en værdi i en tabel CREATE
PROCEDURE dbo.Get_Bruger_Email @BrugerLogin nvarchar(20), @BrugerEmail nvarchar(50) OUTPUT AS BEGIN SET NOCOUNT ON SELECT @BrugerEmail = ISNULL(BrugerEmail,
'Mangler') FROM
dbo.BRUGER WITH (NOLOCK) WHERE
BrugerLogin = @BrugerLogin END GO Få
vist en nyttig kolonne Hvilke kunder har i år afgivet en
eller flere ordrer: SELECT k.Kundenummer, k.Kundenavn,
CASE WHEN o.Ordrenummer IS NOT NULL THEN 1
ELSE 0
END AS HarOrdrer FROM dbo.KUNDE k LEFT
OUTER JOIN dbo.ORDRE o ON o.Kundenummer = k.Kundenummer AND
YEAR(o.Ordredato) = YEAR(Getdate())
Jensen har ordrer, men det var
sidste år. Andersen har endnu ikke afgivet ordre. Opdatering
af rækker med største Id Find seneste ordre som har det
største OrderID nummer og giv en rabat på 40%: DECLARE
@MaxOrderID int SELECT
@MaxOrderID = MAX(OrderID) FROM dbo.[Order Details] UPDATE
OD SET
Discount = 0.4 FROM
dbo.[Order Details] OD WHERE OrderID = @MaxOrderID Men
mellem de to sql sætninger kunne en anden proces indsætte en ny ordre, der så
går glip af 40% rabatten, derfor skal select og update udføres i én sql
sætning og output husker på hvordan rækkerne så ud inden opdateringen, så jeg
fortsat kan få fat i det OrderID der får rabatten. Der anvendes indlejret forespørgsel
(nested query) select i en join: DECLARE
@MaxOrderID int DECLARE
@UpdatedRows TABLE (OrderID int) UPDATE
OD SET
Discount = 0.4 OUTPUT
inserted.OrderID INTO @UpdatedRows FROM
dbo.[Order Details] OD INNER JOIN ( SELECT MAX(OrderID) AS OrderID FROM dbo.[Order Details] ) MX ON MX.OrderID = OD.OrderID SELECT
DISTINCT @MaxOrderID = OrderID FROM @UpdatedRows OUTPUT
inserted indeholder ny værdi og de kolonner der ikke er blev ændret. OUTPUT
deleted indeholder gamle værdi d.v.s.
før opdateringen fandt sted: DECLARE
@ChangedRows TABLE (Id int NOT NULL PRIMARY KEY CLUSTERED, Navn
nvarchar(50) NOT NULL) UPDATE dbo.Kunde SET Navn = 'Jensen' OUTPUT
deleted.Id, deleted.Navn INTO @ChangedRows WHERE Id = 876 SELECT * FROM @ChangedRows -- viser at
kunde 876 før hed Nielsen. Mange
kan ikke lide indlejret select fordi det kan gøre programmeringen overskuelig,
derfor man man i stedet lave et view og anvende det: CREATE
VIEW [dbo].[Max_OrderID] AS SELECT
MAX(OrderID) AS OrderID FROM dbo.[Order Details] GO DECLARE
@MaxOrderID int DECLARE
@UpdatedRows TABLE (OrderID int) UPDATE
OD SET
Discount = 0.4 OUTPUT
inserted.OrderID INTO @UpdatedRows FROM
dbo.[Order Details] OD INNER JOIN dbo.Max_OrderID MX ON
MX.OrderID = OD.OrderID SELECT
DISTINCT @MaxOrderID = OrderID FROM @UpdatedRows Eller
man kan i stedet lave en table function og anvende den, to slags funktioner. Scalar-valued
function der returnerer en enkelt værdi: CREATE
FUNCTION [dbo].[Max_OrderID]() RETURNS
int AS BEGIN DECLARE @OrderID int SELECT @OrderID = MAX(OrderID) FROM dbo.[Order Details] RETURN
@OrderID END Funktion indgår i Where fordi der
kun gives en værdi: DECLARE
@MaxOrderID int DECLARE
@UpdatedRows TABLE (OrderID int) UPDATE
OD SET
Discount = 0.4 OUTPUT
inserted.OrderID INTO @UpdatedRows FROM
dbo.[Order Details] OD WHERE
OrderID = dbo.Max_OrderID() SELECT
DISTINCT @MaxOrderID = OrderID FROM @UpdatedRows Table-valued
function der returnerer en eller flere rækker og kolonner som der joines til: CREATE
FUNCTION [dbo].[Max_OrderID]() RETURNS
TABLE AS RETURN ( SELECT MAX(OrderID) AS OrderID FROM dbo.[Order Details] ) DECLARE
@MaxOrderID int DECLARE
@UpdatedRows TABLE (OrderID int) UPDATE
OD SET
Discount = 0.4 OUTPUT
inserted.OrderID INTO @UpdatedRows FROM
dbo.[Order Details] OD INNER JOIN dbo.Max_OrderID() MX ON
MX.OrderID = OD.OrderID SELECT
DISTINCT @MaxOrderID = OrderID FROM @UpdatedRows Table-valued function kan også
anvendes i en Where sammen med In: IF
(SELECT COUNT(1) FROM dbo.Max_OrderID()) = 1 UPDATE
OD SET
Discount = 0.4 OUTPUT
inserted.OrderID INTO @UpdatedRows FROM
dbo.[Order Details] OD WHERE
OrderID IN(SELECT OrderID FROM dbo.Max_OrderID()) Mange
kan ikke lide indlejret select eller brug af view eller function fordi det
kan gøre programmeringen uoverskuelig, derfor man kan i stedet opstille et common table expression CTE med brug
af With, så al kodningen fortsat er samlet og nedenstående bliver udført som
en transaktion: DECLARE
@MaxOrderID int DECLARE
@UpdatedRows TABLE (OrderID int) ;WITH max_OrderID
(OrderID) AS ( SELECT MAX(OrderID) AS OrderID FROM dbo.[Order Details] ) UPDATE
OD SET
Discount = 0.4 OUTPUT
inserted.OrderID INTO @UpdatedRows FROM
dbo.[Order Details] od INNER JOIN max_OrderID mx ON mx.OrderID
= od.OrderID SELECT
DISTINCT @MaxOrderID = OrderID FROM @UpdatedRows With
er et nyt reserveret ord og kræver enten semikolon i Declare sætningen eller som
vist her i starten af With, men det er ikke altid at ; er nødvendigt, men SQL
Server lægger op til at alle sætninger afsluttes med ; så det ligner
almindelig programmering. With er ikke en #temporær tabel, fordi With
sætningen bliver først udført sammen med den Select, Insert, Update eller Delete
den er knyttet til eller har virkefelt i (execution scope), derved minder With
meget om et view men står nu samlet med Update, men With kan ikke genbruges i
andre sql sætninger. Flere
With kan kombineres som en slags sql-udtryk-mellemberegninger selvom alle sql
sætningerne bliver udført i samme transaktion så kan denne opstilling give et
bedre overblik end at have indlejret (nested) sql sætninger inde i hinanden
eller view der i From anvender et andet view: ;WITH senesteOrdrer AS ( SELECT VareId, MAX(Ordredato) AS
SenesteOrdreDato FROM
dbo.ORDRE WHERE Antal >= 100 GROUP BY VareId ), senesteKunder AS ( SELECT Kundenummer FROM
dbo.ORDRE o
INNER JOIN senesteOrdrer s ON
s.VareId = o.VareId AND s.SenesteOrdreDato = o.OrdreDato ) SELECT k.Kundenummer, k.Kundenavn FROM dbo.KUNDE k
INNER JOIN senesteKunder s ON s.Kundenummer = k.Kundenummer Læs om
forskellige anvendelser Recursive
update with et common table expression CTE a kind of iteration CTE
giver mulighed for at formulere et rekursivt udtryk, der minder om at gennemløbe
eller at iterere mange rækker som om det var en loop (løkke, typisk med while
og en cursor), men med with og union all opstilles blot et udtryk. Fibonaccis talrække: ; WITH rcte (c, n1, n2) AS ( -- Starting
values SELECT 1 AS
c, CAST(0
AS numeric(38, 0)), CAST(1
AS numeric(38, 0)) UNION ALL -- The
recursion SELECT c+1, n2, n1+n2 FROM rcte WHERE c <
183 ) SELECT n2 FROM rcte OPTION(MAXRECURSION 0); En
kunde tabel mangler brancherkoder hist og her og derfor ønskes disse null
værdier udfyldt efter to principper i prioriteret rækkefølge:
1. Tilbageføring af branchekoder til kundens
forrige år f.eks. branchekode 1566 kopieres til år 2004, 2003 og 2002, og
1543 kopieres til 2000. 2. Fremføring af branchekoder til kundens næste
år f.eks. branchekode 4432 kopieres til år 2011 og 2012. Når
samme branchekode skal kopieres til flere år kan det opfattes som en loop
eller som rekursion i den forstand at der kopieres en værdi ad gangen og at
det faktisk er 1566 fra 2005 til 2004 og derefter er det kopiering fra 2004
til 2003 o.s.v. De to føringer udtrykkes i hver sin CTE med en efterfølgende
UPDATE: ;WITH KUNDE_BRANCHE_TILBAGEFØRT AS ( SELECT a.Kundenr, a.År, b.Branchekode FROM dbo.KUNDE a
INNER JOIN KUNDE b ON b.Kundenr = a.Kundenr WHERE
b.Branchekode IS NOT NULL AND a.Branchekode IS NULL AND
(b.År - a.År) = 1 -- d.v.s forrige år. UNION ALL SELECT a.Kundenr, a.År, b.Branchekode -- I
join indgår CTE udtrykket FROM dbo.KUNDE a
INNER JOIN KUNDE_BRANCHE_TILBAGEFØRT b ON b.Kundenr = a.Kundenr WHERE
b.Branchekode IS NOT NULL AND a.Branchekode IS NULL AND
(b.År - a.År) = 1 -- d.v.s forrige år. ) UPDATE a SET
Branchekode = b.Branchekode FROM dbo.KUNDE a
INNER JOIN KUNDE_BRANCHE_TILBAGEFØRT b ON b.Kundenr = a.Kundenr AND b.År
= a.År WHERE
a.Branchekode IS NULL /* I tilfælde af at man vil se data før man
udfører UPDATE. SELECT Kundenr, År, Branchekode FROM KUNDE_BRANCHE_TILBAGEFØRT UNION SELECT Kundenr, År, Branchekode FROM dbo.KUNDE WHERE
Branchekode IS NOT NULL ORDER BY
Kundenr, År */ Kunde 1 og 2 fik deres branchekoder
tilbageført til forrige år:
;WITH KUNDE_BRANCHE_FREMFØRT AS ( SELECT a.Kundenr, a.År, b.Branchekode -- I
join indgår CTE udtrykket FROM dbo.KUNDE a
INNER JOIN KUNDE b ON b.Kundenr = a.Kundenr WHERE
b.Branchekode IS NOT NULL AND a.Branchekode IS NULL AND
(b.År - a.År) = -1 -- d.v.s næste år. UNION
ALL SELECT
a.Kundenr, a.År, b.Branchekode FROM dbo.KUNDE a
INNER JOIN KUNDE_BRANCHE_FREMFØRT b ON b.Kundenr = a.Kundenr WHERE
b.Branchekode IS NOT NULL AND a.Branchekode IS NULL AND (b.År - a.År) = -1 -- d.v.s næste år. ) UPDATE a SET
Branchekode = b.Branchekode FROM dbo.KUNDE_ a
INNER JOIN KUNDE_BRANCHE_FREMFØRT b ON b.Kundenr = a.Kundenr AND b.År
= a.År WHERE
a.Branchekode IS NULL Kunde 3 fik sin branchekode fremført
til næste år:
Indsættelser til to tabeller, går
det galt, så drop alle indsættelser Der
anvendes try-catch til at fange når indsættelserne fejler og transaction til
at rullle data tilbage f.eks. når anden insert into fejler, så vil første
insert blive tilbageført og derved ikke udført. CREATE
PROCEDURE [dbo].[Depot_Insert] AS BEGIN SET NOCOUNT ON BEGIN TRY BEGIN TRANSACTION TRUNCATE TABLE dbo.DepotPrivat INSERT INTO dbo.DepotPrivat (DepotKode,
DepotNavn, InsertTime_meta) SELECT DepotKode = DCode, DepotNavn =
DText, InsertTime_meta = GETDATE() FROM DSA.DP.Depot WHERE DType = 'Private' ORDER BY DCode TRUNCATE
TABLE dbo.DepotErhverv INSERT
INTO dbo.DepotErhverv (DepotKode, DepotNavn,InsertTime_meta) SELECT
DepotKode = DCode, DepotNavn = DText, InsertTime_meta = GETDATE() FROM DSA.DP.Depot WHERE DType = 'Business' ORDER BY DCode IF
@@TRANCOUNT > 0 COMMIT TRANSACTION END TRY BEGIN CATCH IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION ;THROW -- fejlmelding med præcis kodelinienummer
end raiseerror END
CATCH END Eksempel på fejlmelding som også
siver op til den SSIS pakke som kalder sp.: Msg 2627, Level 14, State 1, Procedure
Depot_Insert, Line 18 Violation of PRIMARY KEY constraint
'PK_DepotErhverv'. Cannot insert duplicate key in object 'dbo.DepotErhverv'. The
duplicate key value is (3). Ønskes
navn på stored procedure in Try delen og i Catch delen: PRINT CONCAT('COMMIT AT SP: ', OBJECT_SCHEMA_NAME(@@PROCID),
'.', OBJECT_NAME(@@PROCID)) PRINT CONCAT('ROLLBACK AT SP: ', ERROR_PROCEDURE(),
'. ERRORMESSAGE: ', ERROR_MESSAGE()) Indsæt
ny post i en tabel og få Id retur (no
deadlock, blocking, disruption or duplicate ID) Tabel KUBE primærnøgle er
fortløbende nummer KubeId INT IDENTITY (1, 1). CREATE
PROCEDURE dbo.New_Kube
@KubeNavn nvarchar(50), @KubeForklaring
nvarchar(255) = NULL,
@TemaId int -- fremmednøgle. AS BEGIN DECLARE @KubeId int = 0 -- Returneres der 0
er rækken ikke indsat. SET
NOCOUNT ON BEGIN
TRY DECLARE @TranCounter INT = @@TRANCOUNT, --
Antal gældende trans. @SavePoint NVARCHAR(32) =
CAST(@@PROCID AS NVARCHAR(20)) + N'_' + CAST(@@NESTLEVEL AS
NVARCHAR(2)) --
Join eksisterende transaktion eller start en ny transaktion. IF
@TranCounter > 0 SAVE TRANSACTION @SavePoint ELSE BEGIN TRANSACTION INSERT INTO dbo.KUBE WITH (ROWLOCK) (KubeNavn,
KubeForklaring, TemaId) VALUES
(@KubeNavn, @KubeForklaring, @TemaId) SELECT
@KubeId = SCOPE_IDENTITY() -- Får nye dannet
KubeId nummer. IF
@TranCounter = 0 -- Transaktion er startet i proceduren. COMMIT TRANSACTION END TRY BEGIN
CATCH IF
@TranCounter = 0 -- Transaktion er startet i proceduren. ROLLBACK
TRANSACTION ELSE IF XACT_STATE() = 1 -- Transaktion er
startet før proceduren. ROLLBACK TRANSACTION @SavePoint ELSE IF XACT_STATE() = -1 ROLLBACK TRANSACTION THROW END
CATCH RETURN
@KubeId END GO Få fat i sidste anvendte Id nummer,
selvom dens række måtte være blevet slettet i tabellen, fordi et Identity
nummer må ikke blive genbrugt af senere indsatte rækker: SELECT
IDENT_CURRENT('dbo.KUBE') Men ovenstående giver ikke altid det
korrekte svar f.eks. efter nedbrud. Nedenstående giver aktuelle maksimum og
minimum værdier der findes som rækker i tabellen, d.v.s. slettede rækkers
Identity nummer medtages ikke: SELECT MAX($IDENTITY), MIN($IDENTITY) FROM
dbo.KUBE Bedst i kombination for at finde den
største værdi der ikke bliver genbrugt: SELECT GREATEST(ISNULL(IDENT_CURRENT('dbo.KUBE'), 0),
ISNULL(MAX($IDENTITY), 0)) FROM dbo.KUBE Fra SQL Server 2005 har INSERT fået en
OUTPUT mulighed, hvor Id kan aflæses: CREATE
PROCEDURE dbo.New_Kube @KubeNavn nvarchar(50), @KubeForklaring
nvarchar(255) = NULL,
@TemaId int -- fremmednøgle. AS BEGIN DECLARE
@KubeId int SET
NOCOUNT ON DECLARE @InsertedRow TABLE (KubeId int) -–
en #TEMP tabel som variabel INSERT
INTO dbo.KUBE WITH (ROWLOCK) (KubeNavn, KubeForklaring, TemaId) OUTPUT
inserted.KubeId INTO @InsertedRow -- får fat i dannet KubeId VALUES
(@KubeNavn, @KubeForklaring, @TemaId) SELECT
@KubeId = KubeId FROM @InsertedRow RETURN
@KubeId END GO Fra SQL Server 2012 fået et Sequence
object som en global nummertæller for hele databasen, læs mere om den i
indlægget om SQL Server 2014. OUTPUT er praktisk i et ETL forløb, hvor
en række kildedata skal indsættes i et datawarehouse tabel hvor de nye rækker
også får et Id nummer, der ønskes tildelt de nye data fra NEW tabel til DIM
tabellen, eksempelvis: CREATE
PROCEDURE [dbo].[DW_DIM_KUNDE_New] AS BEGIN SET NOCOUNT ON -- KundeId er PK for DW og KundeNummer er kildens PR. DECLARE @InsertedRows TABLE (KundeId int,
KundeNummer int) INSERT
INTO dbo.DW_DIM_KUNDE WITH(TABLOCK) (KundeNummer, KundeNavn) OUTPUT
inserted.KundeId, inserted.KundeNummer INTO @InsertedRows SELECT
KundeNummer, KundeNavn FROM DW_NEW_KUNDE new WITH(NOLOCK) WHERE NOT EXISTS (SELECT 1 FROM dbo.DW_DIM_KUNDE dim
WITH(NOLOCK) WHERE dim.KundeNummer = new.KundeNummer); -- Evt. opdatere NEW tabellen med de
nyoprettede Id numre. UPDATE
dbo.DW_NEW_KUNDE WITH(TABLOCK) SET KundeId = r.KundeId FROM
dbo.DW_NEW_KUNDE n
INNER JOIN @InsertedRows AS r ON r.KundeNummer = n.KundeNummer; -- Opdatere eksisterende kunder med nyt
navn fra NEW tabellen. UPDATE
dbo.DW_DIM_KUNDE WITH(TABLOCK) SET
KundeNavn = new.KundeNavn FROM
dbo.DW_DIM_KUNDE dim
INNER JOIN dbo.DW_NEW_KUNDE new WITH(NOLOCK)
ON new.KundeNummer =
dim.KundeNummer WHERE
dim.KundeNavn <> new.KundeNavn; -- Forberedelse af nye segmenter til nye
kunders KundeId WITH CTE(KundeId, KundeNummer, Segment) AS ( SELECT r.KundeId, new.KundeNummer,
new.Segment FROM DW_NEW_KUNDE_SEGMENT
new WITH(NOLOCK) INNER
JOIN @InsertedRows r ON r.KundeNummer = new.KundeNummer ) -- Indsæt nye segmenter for de nye kunder INSERT
INTO dbo.DW_DIM_KUNDE_SEGMENT WITH(TABLOCK) (KundeId, Segment) SELECT
new.KundeId, new.Segment FROM CTE new WHERE NOT EXISTS (SELECT 1 FROM
dbo.DW_DIM_KUNDE_SEGMENT dim WHERE
dim.KundeId = new.KundeId AND dim.Segment = new.Segment); END Har
en eksisterende kunde fået nyt segment kommer det ikke med i ovenstående. Nedenstående
viser indsættelse af fire kundenumre og hvordan man får fat i hver deres Globally
unique identifier GUID værdier via en temporær tabel: CREATE
TABLE dbo.Customer ( CustomerGuid uniqueidentifier NOT NULL
DEFAULT NewID(),
--NewSequentialID() CustomerNumber int NOT NULL ) GO DECLARE
@Guid TABLE (Guid uniqueidentifier) -- temporær tabel INSERT
INTO dbo.Customer(CustomerNumber) OUTPUT
inserted.CustomerGuid INTO @Guid VALUES
(1), (2), (3), (4) --
indsætter fire rækker. SELECT *
FROM @Guid SELECT *
FROM dbo.Customer NewID()
giver:
NewSequentialID()
giver og er derved god for et clustered
index på Guid felt:
Opdater
en post i en tabel via primærnøglen CREATE
PROCEDURE dbo.Update_Kube @KubeId int, @KubeNavn nvarchar(50), @KubeForklaring nvarchar(255), @TemaId int AS BEGIN SET
NOCOUNT ON UPDATE
dbo.KUBE WITH (ROWLOCK) SET
KubeNavn = @KubeNavn,
KubeForklaring = @KubeForklaring,
TemaId = @TemaId FROM dbo.KUBE WHERE KubeId
= @KubeId END GO Slet
en post i en tabel via primærnøglen CREATE
PROCEDURE dbo.Delete_Kube @KubeId int AS BEGIN SET
NOCOUNT ON DELETE
dbo.KUBE WITH (ROWLOCK) FROM dbo.KUBE WHERE
KubeId = @KubeId END GO Indsættelse
af ny værdi husk at undersøge om den findes i forvejen I
stedet for VALUES anvendes SELECT sammen med NOT EXISTS så bliver undersøgelse/tjek
og indsættelse samtidig i en transaktion: CREATE
PROCEDURE dbo.Ny_Kunde @KundeNavn nvarchar(50), @KundeAdresse
nvarchar(100) AS BEGIN INSERT INTO dbo.KUNDE (KundeNavn,
KundeAdresse) SELECT @KundeNavn, @KundeAdresse WHERE NOT EXISTS ( SELECT TOP(1) 1 FROM dbo.KUNDE WHERE KundeNavn = @KundeNavn ) DECLARE @KundeId INT; SET @KundeId = SCOPE_IDENTITY(); IF @KundeId > 0 BEGIN ... END RETURN @KundeId END GO Kopiering
af en-til-mange forekomst i to tabeller CREATE PROCEDURE dbo.KundeOrdre_Kopiering
@KundeId int, @NytKundeNavn
nvarchar(50) AS BEGIN DECLARE
@NytKundeId int SET
NOCOUNT ON INSERT
INTO dbo.KUNDE WITH (ROWLOCK) (KundeNavn, Adresse, Postnummer) SELECT
@NytKundeNavn, Adresse, Postnummer FROM dbo.KUNDE WITH (NOLOCK) WHERE KundeId = @KundeId SELECT
@NytKundeId = SCOPE_IDENTITY() INSERT INTO dbo.KUNDEORDRE WITH (ROWLOCK)
(KundeId, Ordrenummer, Pris) SELECT @NytKundeId, Ordrenummer, Pris FROM
dbo.KUNDEORDRE WHERE
KundeId = @KundeId RETURN @NytKundeId -- retur til applikation der viser den nye
kunde. END GO Opdatering og indlejret select – kan du
gætte hvad der ser her UPDATE
dbo.EDW_NEW_BerørteBorger WITH (TABLOCK) SET
FørsteFalddato = kal1.SidsteArbejdsdato FROM
dbo.EDW_NEW_BerørteBorger new INNER JOIN dbo.EDW_RULE_KALENDER kal
WITH (NOLOCK) ON kal.Dato =
DateAdd(day,56,new.FørsteFraværsdato) INNER JOIN ( SELECT ÅrUge, MAX(Dato) AS
SidsteArbejdsdato FROM dbo.EDW_RULE_KALENDER WITH (NOLOCK) WHERE
Arbejdsdag = 1
GROUP BY ÅrUge
) kal1 ON kal1.ÅrUge = kal.ÅrUge Fordel
dage i uger 1-5 dage placeres i uge 1, 6-10 dage
i uge 2, 11-15 i 3, 16-20 i 4 o.s.v.: SELECT
CASE WHEN (Dage % 5) = 0 THEN (Dage / 5) ELSE
((Dage + (5 - (Dage % 5))) / 5) END AS AntalUger Reset
et Identity felt, typisk efter en Delete og før en Insert til fact tabel DELETE dbo.FACT -- slet rækker med
ÅrDato'er som findes i STAGING. FROM dbo.FACT WHERE ÅrDato IN (SELECT DISTINCT ÅrDato FROM dbo.STAGING) DECLARE
@maxId int SELECT
@maxId = ISNULL(MAX(Id),0) -- tom tabel giver null derfor 0. FROM dbo.FACT WITH(TABLOCK) DBCC CHECKIDENT ('dbo.FACT', RESEED,
@maxId) -- reset for genbrug Id. INSERT INTO dbo.FACT(ÅrDato, StedId, ArtId,
Beløb) -- -1=Mangler værdi SELECT stg.ÅrDato,
ISNULL(st.StedId,-1), ISNULL(ar.ArtId,-1), ISNULL(SUM(Beløb),0) FROM dbo.STAGING stg LEFT OUTER JOIN dbo.DIM_STED st ON
st.Stedkode = stg.Stedkode LEFT OUTER JOIN dbo.DIM_ART ar ON ar.Artkode = stg.Artkode GROUP BY
stg.ÅrDato, ISNULL(st.StedId,-1), ISNULL(ar.ArtId,-1) -- left outer join og isnull sikrer at indeholder
staging en kode som -- ikke findes i dimensionen så medtages
staging rækken med -1 i id. SET
IDENTITY_INSERT dbo.FACT ON --
tilsidesætter Identity. INSERT
INTO dbo.FACT(Id, ÅrDato, StedId, ArtId, Beløb) SELECT
Id+@maxId, ÅrDato, StedId, ArtId, Beløb FROM dbo.TMP SET
IDENTITY_INSERT dbo.FACT OFF -- genetablerer Identity. TRUNCATE
TABLE dbo.FACT tømmer en tabel og sætter Identity til 0,
men tabellen må ikke indgå i nogen relation (reference integrity) til andre
tabeller. Select Max kan tage performance for en
tabel med mange millioner rækker, ligeså kan Select Top 1 Id From dbo.Fact
Order By Id Desc, mens nedenstående giver et hurtigt svar: DECLARE @maxId bigint SET NOCOUNT ON --SELECT @maxId = ISNULL(MAX(Orders_Id),
0) + 1 --FROM base.Orders --SELECT TOP 1 @maxId =
ISNULL(Orders_Id), 0) + 1 --FROM base.Orders --ORDER BY Orders_Id DESC SELECT @maxId = GREATEST(ISNULL(IDENT_CURRENT('base.Orders'), 0) + 1, ISNULL(MAX($IDENTITY), 0) + 1) FROM base.Orders DBCC CHECKIDENT ('base.Orders', RESEED, @maxId) IDENT_CURRENT
kan give forkert resultat ved nedbrud, derfor er MAX($IDENTITY) også anvendt,
hvor den største værdi bliver valgt. Index
på et view til hurtigere svartid (performance) View
oprettes med et skemabinding, eksempelvis: CREATE
VIEW Dim.Customer_CityState WITH
SCHEMABINDING AS SELECT
City, State, COUNT_BIG(*) AS NumRows -- stort antal rækker FROM
Dim.Customer GROUP BY
City, State GO CREATE
UNIQUE CLUSTERED INDEX IX_Customer_CityState ON Dim.Customer_CityState (City, State) GO Schemabinding
forhindrer også ændring (alter) af tabellen. Få vist logical reads and physical reads
som helst skal være så lille som muligt, og det kan et indeks (index IX)
hjælpe med til og give en bedre performance: SET
STATISTICS IO ON; GO SET
STATISTICS TIME ON; GO SELECT FROM WHERE SET
STATISTICS TIME OFF; GO SET
STATISTICS IO OFF; GO Kik på: Logical reads 2930, physical reads 2, read-ahead
reads 2910, lob logical reads 0, lob physical reads 0, lob read-ahead reads
0. SQL Server Execution Times: CPU time = 26498 ms,
elapsed time = 3520 ms. Tilføj
et felt til tabel IF NOT
EXISTS (select 1 from syscolumns where id=object_id('KUNDE')
and name = 'Aktiv') BEGIN ALTER TABLE dbo.KUNDE ADD
Aktiv bit NOT NULL CONSTRAINT
DF_KUNDE_Aktiv DEFAULT 0 END GO Ændre felt fra tillad null til ikke
tillad null IF
EXISTS (select 1 from syscolumns where id=object_id('KUNDE') and name = 'Antal') BEGIN ALTER TABLE dbo.KUNDE ALTER COLUMN Antal smallint NOT NULL END Slet
felt i en tabel ALTER TABLE dbo.KUNDE DROP
CONSTRAINT DF_KUNDE_Aktiv GO ALTER
TABLE dbo.KUNDE DROP
COLUMN Aktiv GO Lidt mere DDL Tilføj en kolonne eller et felt til
en tabel. IF NOT
EXISTS(SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'Orders' AND
COLUMN_NAME = 'Pickup') BEGIN ALTER TABLE dbo.Orders ADD Pickup bit NOT NULL CONSTRAINT
DF_Orders_Pickup DEFAULT 0 END GO Kan
ikke summere på en bit data type, derfor ændres den til int. ALTER
TABLE dbo.Orders ALTER
COLUMN Pickup int NOT NULL GO Tjek
om et felt findes i en tabel. DECLARE
@Schema nvarchar(256) = 'dbo' DECLARE
@Table nvarchar(256) = 'Employees' DECLARE
@Column nvarchar(256) = 'City' IF
EXISTS(SELECT TOP(1) 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = @Schema AND TABLE_NAME = @Table AND COLUMN_NAME = @Column) BEGIN PRINT 'FINDES' END ELSE BEGIN
PRINT 'FINDES IKKE' END Gør en kolonne not null og tildel en
primærnøgle med GO som batch separator ellers fås fejlmeldinger: Cannot define
PRIMARY KEY constraint on nullable column in table 'Depot'. Could not create
constraint or index. See previous errors. IF (SELECT IS_NULLABLE FROM
INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA =
'dp' AND
TABLE_NAME = 'Depot'
AND
COLUMN_NAME = 'DType') = 'YES' BEGIN ALTER TABLE dp.Depot ALTER COLUMN
[DType] nvarchar(50) NOT NULL END GO IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
WHERE TABLE_SCHEMA ='dp' AND TABLE_NAME = 'Depot' AND CONSTRAINT_TYPE = 'PRIMARY KEY') BEGIN ALTER TABLE dp.Depot ADD CONSTRAINT PK_Depot PRIMARY
KEY CLUSTERED
(DCode, DText, DType) ON [PRIMARY] END GO T-SQL har ikke batch separator, fordi det
er en applikation der separerer eller adskiller batches og sender dem
enkeltvis fra applikation til databasen. Vi kan emulate eller efterligne GO
operator ved at pakke hver batch ind i en Execute kommando som en streng, så
bliver de udført IF (SELECT IS_NULLABLE FROM
INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA =
'dp' AND
TABLE_NAME = 'Depot'
AND
COLUMN_NAME = 'DType') = 'YES' EXECUTE (' ALTER TABLE dp.Depot ALTER COLUMN [DType] nvarchar(50) NOT NULL ') IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
WHERE TABLE_SCHEMA ='dp' AND TABLE_NAME = 'Depot' AND CONSTRAINT_TYPE = 'PRIMARY KEY') EXECUTE (' ALTER TABLE dp.Depot ADD CONSTRAINT PK_Depot PRIMARY KEY CLUSTERED
(DCode, DText,
DType) ON [PRIMARY] ') Shrink database CREATE
PROCEDURE dbo.Shrink2000_2005 AS BEGIN BACKUP LOG <databasenavn> WITH
TRUNCATE_ONLY DBCC
SHRINKDATABASE (<databasenavn>, 0) DBCC
UPDATEUSAGE (<databasenavn>) END GO CREATE
PROCEDURE dbo.Shrink2008 AS BEGIN BEGIN TRY DBCC SHRINKFILE
(<databasenavn>_LOG, 1) DBCC
SHRINKDATABASE (<databasenavn>, 0) DBCC
UPDATEUSAGE (<databasenavn>) END TRY BEGIN CATCH SELECT ERROR_NUMBER() AS ErrorNumber END
CATCH END GO Genereret sql sætning fra en Select
sætning En
tabel UDSKIFT indeholder gamle værdier fra 2018 og nye værdier for 2019 som
skal bruges til at udskifter værdier med en OPGAVE tabel på den måde, at der
genereres mange UPDATE sql sætninger. CREATE TABLE [dbo].[UDSKIFT]( [SKOVNUMMER 2018] [float] NOT NULL, [LITRA
2018] [nvarchar](255) NOT NULL, [SKOVNUMMER 2019] [float] NOT NULL, [LITRA 2019] [nvarchar](255) NOT NULL ) ON
[PRIMARY] Select
sætning danner UPDATE sætninger en for en for hver række fra UDSKIFT: SELECT 'UPDATE
dbo.OPGAVE SET [Skovnummer] = ' + CAST([SKOVNUMMER 2019] AS nvarchar) + ',
[Litra] = ''' + CAST([LITRA 2019] AS nvarchar) + ''' WHERE [Skovnummer] = ' +
CAST([SKOVNUMMER 2018] AS nvarchar) + ' AND [Litra] = ''' + CAST([Litra 2018]
AS nvarchar) + '''' FROM dbo.[UDSKIFT] Herefter
kan update sql sætningerne udføres i SQL Server Management Studio. Udførsel af genereret sql sætning CREATE PROCEDURE dbo.EXEC_SQL @Statement
nvarchar(MAX) -- ntext in SQL Server
7.0 and 2000. AS BEGIN EXEC sys.sp_executesql @Statement END GO Ovenstående giver mulighed for SQL Injection, lad os tage et konkret
eksempel på en stored procdure, der kan tømme en tabel uanset hvilken
rettighed brugeren måtte have: CREATE PROCEDURE [dbo].[TruncateTable] @SchemaTablename varchar(200) WITH EXECUTE AS OWNER AS BEGIN DECLARE @sqlCommand nvarchar(220) = ''; SET @sqlCommand = N'TRUNCATE
TABLE ' + @SchemaTablename + ';' EXECUTE sp_executesql @sqlCommand; END Kald
af stored procedure for at tømme en tabel: EXEC
[dbo].[TruncateTable] 'Dim.Customer'; Hvad
med at slette tabellen, klassisk eksempel på SQL Injection: EXEC
[dbo].[TruncateTable] 'Dim.Customer; DROP TABLE Dim.Customer;'; Måske
kunne databasen også droppes? En
forbedret udgave af stored proceduren, der tjekker om tabellen findes. CREATE PROCEDURE [dbo].[TruncateTable] @SchemaTablename varchar(200) WITH EXECUTE AS OWNER AS BEGIN DECLARE @sqlCommand nvarchar(220) = ''; IF EXISTS(SELECT 1 FROM INFORMATION_SCHEMA.TABLES WHERE CONCAT(TABLE_SCHEMA,'.',TABLE_NAME) = @SchemaTablename) BEGIN SET @sqlCommand = N'TRUNCATE
TABLE ' + @SchemaTablename + ';'
EXECUTE sp_executesql @sqlCommand; END END Adgang og rettigheder Giver execute kørsel rettighed til en
stored procedure enten til en Windows AD gruppe, en AD bruger eller en SQL
login, eksempelvis til en login med public adgang: USE [KUNA]
-- Kunde administration databasen GO GRANT
EXECUTE ON [dbo].[EXEC_SQL] TO [NT AUTHORITY\authenticated users] GO Adgang
til en stored procedure til en bestemt bruger: GRANT
EXECUTE ON [dbo].[GetFinance] TO
[username] GO Til
alle stored procedures til en bestemt bruger: GRANT
EXECUTE TO [username] GO Til alle stored procedures til alle brugere: GRANT
EXECUTE TO public GO Et
schema knyttes til en AD-gruppe LAN\AnalyseBrugere der er tilføjet i Security
med user mapping til databasen og schema Analyse er tilføjet til databasen,
og der tildeles læseadgang (kaldes Select) til objekter f.eks. views i
skemaet: GRANT
CONNECT TO [LAN\AnalyseBrugere] GRANT
SELECT ON SCHEMA::Analyse TO [LAN\AnalyseBrugere] PostDeployment.sql: DECLARE
@dbName nvarchar(255) SELECT
@dbName = DB_NAME() IF
@dbName = 'ERP' BEGIN RAISERROR ('Adding access rights to ERP.',
0, 1) WITH NOWAIT END Automatisk genereret view fra et arkiv
til opdagelse hos brugere CREATE
PROCEDURE [dbo].[GenerateViewsToDataDiscovery] AS BEGIN SET
NOCOUNT ON DECLARE
@schema nvarchar(50) = N'customer' DECLARE
@table nvarchar(255) DECLARE
@sqldrop nvarchar(300) DECLARE
@sqlview nvarchar(MAX) DECLARE
@Scan CURSOR SET @Scan
= CURSOR LOCAL FORWARD_ONLY DYNAMIC FOR SELECT
TABLE_NAME FROM
Archive.INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_SCHEMA
= @schema OPEN @Scan
FETCH NEXT FROM @Scan INTO @table WHILE
@@FETCH_STATUS = 0 BEGIN SELECT
@sqlview = SUBSTRING( ( SELECT
',' + QUOTENAME(COLUMN_NAME) FROM
Archive.INFORMATION_SCHEMA.COLUMNS WHERE
TABLE_SCHEMA = @schema AND TABLE_NAME = @table AND COLUMN_NAME
!= 'LogId' AND COLUMN_NAME NOT LIKE '%_meta' FOR
XML PATH ('') ),
2, 99999999) SET
@sqlview = N'CREATE VIEW ' + @schema + '.' + @table + N' AS SELECT ' + @sqlview + N' FROM
Archive.' + @schema + N'.' + @table SET @sqldrop = 'IF OBJECT_ID(''' + @schema +
'.' + @table + ''') IS NOT NULL DROP VIEW ' +
@schema + '.' + @table --PRINT @sqldrop --PRINT
@sqlview EXECUTE sp_executesql @sqldrop EXECUTE
sp_executesql @sqlview FETCH
NEXT FROM @Scan INTO @table END CLOSE
@Scan DEALLOCATE
@Scan END GO CREATE
PROCEDURE [dbo].[MaterializeViewsToTables] AS BEGIN SET NOCOUNT ON DECLARE @schemaview nvarchar(50) =
N'customer_vw' DECLARE @schematable nvarchar(50) =
N'customer' DECLARE @view nvarchar(255) DECLARE @sqldrop nvarchar(300) DECLARE @sqltable nvarchar(300) DECLARE @Scan CURSOR SET @Scan = CURSOR LOCAL FORWARD_ONLY
DYNAMIC FOR SELECT
DISTINCT TABLE_NAME FROM DataDiscovery.INFORMATION_SCHEMA.VIEWS WHERE
TABLE_SCHEMA = @schemaview OPEN @Scan FETCH NEXT FROM @Scan INTO @view WHILE @@FETCH_STATUS = 0 BEGIN SET
@sqldrop = 'IF OBJECT_ID(''DataDiscovery.' + @schematable + '.' + @view + ''') IS NOT NULL DROP
TABLE DataDiscovery.' + @schematable + '.' + @view SET
@sqltable = 'SELECT * INTO DataDiscovery.' + @schematable + '.' + @view + ' FROM DataDiscovery.' +
@schemaview + '.' + @view --PRINT @sqldrop --PRINT @sqltable EXECUTE sp_executesql @sqldrop EXECUTE sp_executesql @sqltable FETCH NEXT FROM @Scan INTO @view END CLOSE @Scan DEALLOCATE @Scan END GO Undgå subquery i Select for at op bedre
performance, forespørgselstid KundeOrdre tabellen har felterne Kundenr
int , Ordredato datetime, Beløb money. Det er langsomt at indsætte en subquery
select i hoved select delen: SELECT kun.Kundenr, (SELECT ISNULL(SUM(ko.Beløb), 0) FROM dbo.KundeOrdre
ko WITH (NOLOCK) WHERE
ko.Kundenr = kun.Kundenr AND Ordredato >= '20090101'
) AS SamletBeløb FROM
dbo.Kunde kun WITH (NOLOCK) Det
er hurtigere at have subquery i from delen via en left outer join: SELECT kun.Kundenr, SamletBeløb =
ISNULL(T.SamletBeløb,0) FROM dbo.Kunde kun WITH (NOLOCK) LEFT
OUTER JOIN -- fælles mængde med INNER
JOIN
(SELECT ko.Kundenr, SamletBeløb = SUM(ko.Beløb)
FROM dbo.KundeOrdre ko WITH (NOLOCK) WHERE Ordredato >= '20090101' GROUP BY ko.Kundenr )
AS T ON T.Kundenr = kun.Kundenr -- alias kun er udenfor. Outer
apply er en indlejret sql sætning, der angiver join-delen i Where-delen
ligesom i første eksempel ovenfor d.v.s. inklusiv left outer join: SELECT kun.Kundenr, SamletBeløb =
ISNULL(T.SamletBeløb,0) FROM dbo.Kunde kun WITH (NOLOCK) OUTER APPLY (SELECT SamletBeløb = SUM(ko.Beløb) FROM dbo.KundeOrdre
ko WITH (NOLOCK) WHERE ko.Kundenr = kun.Kundenr AND Ordredato >=
'20090101' )
AS T -- alias kun er
indenfor. UPDATE kun SET
kun.SamletBeløb = ISNULL(T.SamletBeløb,0) FROM dbo.Kunde kun
OUTER APPLY
(SELECT SamletBeløb = SUM(ko.Beløb)
FROM dbo.KundeOrdre ko WITH (NOLOCK) WHERE
ko.Kundenr = kun.Kundenr AND
Ordredato >= '20090101'
) AS T -- alias kun
er indenfor. Cross
apply er en indlejret sql sætning, der angiver join-delen i Where-delen
inklusiv inner join (at en kunde har mindst en ordre for at kunden bliver
medtaget), men kræver, at primærnøglen indgår i select delen, derfor medtages
her ko.Kundenr. Kaldes også en »lateral join using a query's output as input
to another query in the same Select statement.«: SELECT kun.Kundenr, SamletBeløb =
ISNULL(T.SamletBeløb,0) FROM dbo.Kunde kun WITH (NOLOCK) CROSS APPLY (SELECT ko.Kundenr, SamletBeløb =
SUM(ko.Beløb) FROM dbo.KundeOrdre
ko WITH (NOLOCK) WHERE
ko.Kundenr = kun.Kundenr AND
Ordredato >= '20090101'
GROUP BY ko.Kundenr ) AS T Kan
også løses med Partition i Windowed function: SELECT DISTINCT Kundenr, SamletBeløb = SUM(Beløb)
OVER(PARTITION BY Kundenr) FROM
dbo.KundeOrdre WITH (NOLOCK) WHERE Ordredato >= '20090101' Her sikrer DISTINCT at kunden kun vises
en gang selvom samme Kundenr står i flere rækker, det samme gør GROUP BY
ovenfor også som her er erstattet af PARTITION BY, men vi opnår at få samme
resultat uden join hvilket er godt. Inderste
From-Where finder største ordredato per kunde fra den ydre From: SELECT k.Kundenavn, o.Ordredato, o.Beløb FROM dbo.KUNDE k INNER JOIN dbo.ORDRE o ON
o.KundeId = k.KundeId WHERE o.Ordredato = (SELECT MAX(oo.Ordredato) -– finder største
ordredato i Ordre tabel. FROM
dbo.ORDRE oo WHERE oo.KundeId = k.KundeId ) Ulempen er, at Ordre tabellen indgår to
gange fordi ordrens beløb ønskes vist. Det kan undgås med Cross apply og jeg
prøver her TOP(1) med DESC som finder én ordre-række i tabellen med største
ordredato per kunde, og fra den ordre-række kan dato og beløb udtages og
vises (overvej selv hvordan med WITH TIES): SELECT k.Kundenavn, o.Ordredato, o.Beløb FROM
dbo.KUNDE k CROSS
APPLY ( SELECT TOP(1) oo.Ordredato, oo.Beløb -–
finder én række i Ordre tbl. FROM
dbo.ORDRE oo WHERE oo.KundeId = k.KundeId -- for den k.KundeId der søges på. ORDER BY
oo.Ordredato DESC ) o Brug af OR logisk udtryk kan nogen gange
drille forespørgselsplanen når der indgår mange tabeller, derfor kan en OR
med fordel opdeles. Fra: SELECT
T1.FF FROM T1 LEFT OUTER JOIN T2 ON T2.F1 = T1.F1 LEFT OUTER JOIN T3 ON T3.F1 = T2.F1 WHERE
T1.F3 = 'OK' AND (T1.FINISHDATE >= DATEADD(month, -3, GETDATE()) OR T3.F2 = 'Finish') Til dette med brug af common table expression cte: ;WITH
T1_OK_WITHIN_3_MONTHS AS (SELECT
T1.FF FROM T1 WHERE T1.F3 = 'OK' AND T1.FINISHDATE >=
DATEADD(month, -3, GETDATE()) ), T1_OK_WITH_FINISH
AS ( SELECT T1.FF FROM T1 INNER JOIN T2 ON T2.F1 = T1.F1 INNER JOIN T3 ON T3.F1 = T2.F1 WHERE T1.F3 = 'OK' AND T3.F2 = 'Finish' ), T1_OK_TOTAL
AS (SELECT
* FROM T1_OK_WITHIN_3_MONTHS UNION SELECT * FROM T1_OK_WITH_FINISH ), .... many more cte...der slutter af med en
række joins mellem cte’s: SELECT FROM
T1_OK_TOTAL INNER JOIN...... Procentvis
andel af beløb ud af det samlede beløb og løsning af division med 0: SELECT Kundenr, Beløb, ProcentAndel = ISNULL(Beløb / NULLIF(SUM(Beløb) OVER(PARTITION BY Kundenr),0),0) * 100, TotalAntalRækker = COUNT(1) OVER(), KundevisAntalRækker = COUNT(1)
OVER(PARTITION BY Kundenr), ProcentAndelRækker = COUNT(1) OVER(PARTITION BY Kundenr) / 1.0 *
COUNT(1) OVER() FROM
dbo.KundeOrdre WITH (NOLOCK) WHERE Ordredato >= '20090101' For divide-by-zero, how to avoid Divide
by zero error encountered jeg konverterer sum(beløb) fra 0 til null med
NULLIF funktionen og hvis jeg ikke ønsker null værdi i resultatet så kan jeg
konvertere null til 0 med ISNULL funktionen. Ved at gange med 1.0 får jeg
brøkens tæller som kommatal, kunne også gøres med en cast as float. I
tilfælde af opdatering af andele felter i tabellen: ;WITH beregn
AS ( SELECT Kundenr, ProcentAndel
= ISNULL(Beløb / NULLIF(SUM(Beløb)
OVER(PARTITION BY Kundenr),0),0) * 100, TotalAntalRækker = COUNT(1) OVER(), KundevisAntalRækker = COUNT(1)
OVER(PARTITION BY Kundenr), ProcentAndelRækker = COUNT(1) OVER(PARTITION BY Kundenr) / 1.0 * COUNT(1) OVER() FROM
dbo.KundeOrdre WITH (NOLOCK) WHERE Ordredato >= '20090101' ) UPDATE k
WITH (TABLOCK) SET ProcentAndel = Beregn.ProcentAndel, TotalAntalRækker = Beregn.TotalAntalRækker, KundevisAntalRækker
= Beregn.KundevisAntalRækker, ProcentAndelRækker
= Beregn.ProcentAndelRækker FROM
dbo.KundeOrdre k
INNER JOIN beregn ON Beregn.Kundenr = k.Kundenr WHERE k.Ordredato >= '20090101' Løbende
sum eller løbende total, running sum or running total: SELECT Ordredato, Beløb, LøbendeSamletBeløb
= SUM(Beløb) OVER (ORDER BY Ordredato) FROM
dbo.KundeOrdre WITH (NOLOCK) ORDER BY Ordredato Løbende
sum pr. kunde: SELECT Kundenr, Ordredato, Beløb, LøbendeSamletBeløb
= SUM(Beløb) OVER (PARTITION BY Kundenr ORDER
BY Ordredato) FROM
dbo.KundeOrdre WITH (NOLOCK) ORDER BY Ordredato Tilføjelse
af Window frame or range efter Ordredato ROWS UNBOUNDED PRECEDING giver ingen
forskel her da det definerer vinduet, hvorpå resultatet beregnes. Hvordan beregnes Resterende Sum Remaining Sum? ROWS uses an in-memory spool and RANGE uses an
on-disk spool. Window function Partition By og Group By minder en del om
hinanden, nedenfor sker summeringen over hver kunde, d.v.s. sum pr. kunde,
men samme sum vises i alle rækkerne fra tabellen. Uden brug af Partition fås
en Total sum eller en I alt sum: SELECT Kundenr,
SumPrKunde = SUM(Beløb) OVER(PARTITION BY Kundenr),
SumIalt = SUM(Beløb) OVER() FROM
dbo.KundeOrdre WITH (NOLOCK) Group By viser hver kunde én gang som en
sammentræk af rækkerne fra tabellen og ved at pakke SUM(Beløb) ind i en endnu
en SUM, så er Group By uændret, d.v.s. det er en sammenlægning af de enkelte
kunders SumPrKunde beregning: SELECT Kundenr,
SumPrKunde = SUM(Beløb),
SumIalt = (SUM(SUM(Beløb))
OVER()) FROM
dbo.KundeOrdre WITH (NOLOCK) GROUP BY KundeId Group By collapses rows into summaries. Window Function preserves all rows and enrich them
with contextual calculations. Et
klassisk eksempel på at finde den største værdi inden for en kolonne: SELECT Kundenr, MAX(Beløb) AS StørsteBeløb FROM
dbo.KundeOrdre WITH (NOLOCK) GROUP BY Kundenr Som
Window function hvor Distinct sikrer, at samme kunde ikke gentages: SELECT DISTINCT Kundenr,
MAX(Beløb) OVER(PARTITION BY Kundenr) AS StørsteBeløb FROM
dbo.KundeOrdre WITH (NOLOCK) Kriterie
på det største beløb: SELECT Kundenr, MAX(Beløb) AS StørsteBeløb FROM
dbo.KundeOrdre GROUP BY
Kundenr WITH (NOLOCK) HAVING MAX(Beløb) >= 100 Som
Window function: SELECT DISTINCT ko.Kundenr, ko.StørsteBeløb FROM ( SELECT Kundenr, MAX(Beløb) OVER(PARTITION BY
Kundenr) AS StørsteBeløb
FROM dbo.KundeOrdre WITH (NOLOCK) )
AS ko WHERE ko.StørsteBeløb >= 100 Den klassiske self-join når en største
værdi skal findes og den har et kriterie tilknyttet, her større end eller
lig med 100 men en join koster altid performance: SELECT DISTINCT ko.Kundenr, ko.Ordredato,
ko.Beløb FROM dbo.KundeOrdre ko WITH (NOLOCK) INNER
JOIN
(SELECT Kundenr, MAX(Beløb) AS StørsteBeløb
FROM dbo.KundeOrdre
GROUP BY Kundenr
HAVING MAX(Beløb) >= 100
) AS T ON T.Kundenr = ko.Kundenr AND T.StørsteBeløb = ko.Beløb Et
klassisk eksempel på at finde antal poster inden for en kolonne: SELECT
Kundenr, COUNT(*) AS AntalOrdre FROM
dbo.KundeOrdre WITH (NOLOCK) GROUP BY
Kundenr Som
Window function: SELECT
DISTINCT Kundenr, COUNT(*) OVER(PARTITION BY Kundenr) AS
AntalOrdre FROM
dbo.KundeOrdre WITH (NOLOCK) Antal
unikke datoer ud fra tidsstemplet Ordredato: SELECT
Kundenr, COUNT(DISTINCT CAST(Ordredato AS date)) AS AntalOrdre FROM
dbo.KundeOrdre WITH (NOLOCK) GROUP BY
Kundenr Windows function tillader ikke COUNT(DISTINCT) så må
CROSS APPLY anvendes: COUNT(DISTINCT
CAST(Ordredato AS date)) OVER(PARTITION BY Kundenr) SELECT
a.Kundenr, t.AntalOrdre FROM
dbo.KundeOrdre a WITH (NOLOCK) CROSS APPLY ( SELECT COUNT(DISTINCT CAST(Ordredato AS
date)) AS AntalOrdre FROM dbo.KundeOrdre b WITH (NOLOCK)
WHERE b.Kundenr = a.Kundenr )
t How to find the second largest value or second
highest or second best or rank #2: Using max: select
max(col) from
[table] where
col < (select max(col) from [table]) Using sort top two and then bottom one because of
top(1) with order by desc: select
top(1) col from ( select top(2) col from [table] order by col ) topTwo order by
col desc Using row number and fetch the second row: select
col from ( select row_number() over (order by col
asc) as rowNum, col from [table] ) withRowNum where
rowNum = 2 Using offset and fetch: select
top(2) col from
[table] group by
col order by
col desc offset 1
rows fetch
next 1 rows only Or with mysql: select
col from
[table] group by
col order by
col desc limit 1,1 De
10 første rækker fra en sorteret liste af rækker: SET
ROWCOUNT 10 SELECT
p.ProductName AS TenMostExpensiveProducts, p.UnitPrice FROM
Products p ORDER BY
p.UnitPrice DESC Find data rækker ud fra en on-the-fly
defineret prioritetsliste, eksempelvis en UserSystem tabel hvor der for hvert
UserId ønskes den række hvis SystemId har højeste prioritet, vist med fed
tekst:
;WITH
systemPriority AS -- On-the-fly
defined priority list of systems ( SELECT sp.* FROM (VALUES
(42, 1), (57, 2), (19, 3), (28, 4), (NULL, 5) ) sp(SystemId, Priority) ) SELECT
DISTINCT us.UserId, SystemId = FIRST_VALUE(us.SystemId)
OVER(PARTITION BY us.UserId ORDER BY sp.Priority ROWS BETWEEN UNBOUNDED PRECEDING
AND UNBOUNDED FOLLOWING) FROM
dbo.UserSystem us LEFT OUTER JOIN systemPriority sp ON
sp.SystemId = us.SystemId WHERE us.SystemId
Is Not Null FIRST_VALUE udsøger den første række
inden for hver partition/gruppe af UserId hvor rækkerne er blevet sorteret
efter Priority. Starting and
ending rows might be fixed or relative to the current row based on the
following keywords: · CURRENT ROW, the current row · UNBOUNDED PRECEDING, all rows before the current row
-> fixed · UNBOUNDED FOLLOWING, all rows after the current row
-> fixed · x PRECEDING, x rows before the current row ->
relative · y FOLLOWING, y rows after the current row ->
relative Window functions rows clause SELECT - OVER Clause STRING_AGG enten i en subquery eller i en
join: SELECT p.ProjectName, (SELECT Customers = STRING_AGG(c.CustomerName, ',
') WITHIN GROUP (ORDER BY i.InvoiceAmount DESC, c.CustomerName ASC) FROM dbo.Invoice i INNER JOIN dbo.Customer c ON c.CustomerId = i.CustomerId WHERE i.ProjectId = p.ProductId ) AS Customers FROM dbo.Project p SELECT p.ProjectName, l.Customers FROM dbo.Project p OUTER APPLY (SELECT Customers = STRING_AGG(c.CustomerName, ',
') WITHIN GROUP (ORDER BY i.InvoiceAmount DESC, c.CustomerName ASC) FROM dbo.Invoice i INNER JOIN dbo.Customer c ON c.CustomerId = i.CustomerId WHERE i.ProjectId = p.ProductId ) l I en liste ønskes udvalgte kæder vist
først, dog skal »Alle« vises allerførst og dernæst skal alle de andre kæder
vises i alfabetisk orden derfor anvender »jeg full outer join« for både at få
medtaget »Alle« som ikke er en kæde og alle de andre kæder som ikke er
angivet i den udvalge og prioriteret sorteret liste: CREATE VIEW
[report].[Chain] AS WITH Priority
AS ( SELECT t.* FROM ( VALUES (0,'Alle'), (1,'Circle K'),
(2,'COOP Brugsen'),
(3,'Dagli'' Brugsen'), (4,'Irma'), (5,'Kvickly'), (6,'LokalBrugsen'),
(7,'SuperBrugsen') ) AS
t(Sortorder, ChainName) ), Chain AS ( SELECT
ChainName FROM
Datamart.SalesForce.Dim_Chain WHERE
ChainName NOT IN('Mangler', 'Uoplyst') ) SELECT TOP
99.99 PERCENT ISNULL(c.ChainName, p.ChainName) AS ChainName FROM Chain c FULL OUTER
JOIN Priority p ON p.ChainName = c.ChainName ORDER BY
ISNULL(p.Sortorder,99), ISNULL(c.ChainName, p.ChainName) GO Nedenfor skal organisationsnavn prioriteres før gruppens navn som
igen skal anvendes før brugerens navn, d.v.s. bruger navn vises kun hvis
ejeren hverken er organisation eller group: SELECT
e.Name, Name = Coalesce(o.Name, g.Name, u.Name) FROM
dbo.Event e LEFT OUTER JOIN dbo.Organisation o ON o.Id
= e.OwnerId LEFT OUTER JOIN dbo.Group g ON g.Id = e.OwnerId LEFT OUTER JOIN dbo.User u ON u.Id = e.OwnerId År
til dato ÅTD (Year-to-date YTD) er løbende sum reset
pr. år. Her ønskes det fordelt pr. måned repræsenteret som 1. dato i hver
måned, d.v.s. ordredato f.eks. 21-02-2015 omdannes til 01-02-2015 og ordrer
pr. måned summeres sammen pr. kunde så rækkerne sammentrækkes til en række
pr. kunde pr. måned ved brug af Group By og Partition By pr. kundenr og år
fra ordredato, derfor skal Year(Ordredato) indgå i Group By, men årstallet
skader ikke fordi der allerede grupperes på månedsniveau. Ingen self-join så
performance er meget hurtig: SELECT Kundenr, MånedsvisDato
= CAST(Ordredato - DAY(Ordredato) + 1 AS date), Beløb = SUM(Beløb), Beløb_ÅTD
= SUM(SUM(Beløb)) OVER ( PARTITION BY Kundenr,
YEAR(Ordredato) ORDER BY CAST(Ordredato -
DAY(Ordredato) + 1 AS date)), Rækker
= COUNT(*), Rækker_ÅTD
= SUM(COUNT(*)) OVER ( PARTITION BY Kundenr,
YEAR(Ordredato) ORDER BY CAST(Ordredato -
DAY(Ordredato) + 1 AS date)) FROM
dbo.KundeOrdre WITH(NOLOCK) GROUP BY Kundenr, YEAR(Ordredato), CAST(Ordredato - DAY(Ordredato) + 1 AS
date)
Fordelt pr. uge repræsenteret som mandag
datoen, d.v.s. ordredato f.eks. 21-02-2015 omdannes til 16-02-2015, så
forespørgslen ligner næsten den månedsvise: SET DATEFIRST 1 -- sætter Mandag som første dag
i ugen. SELECT Kundenr, UgevisDato
= CAST(Ordredato - DATEPART(weekday,Ordredato) + 1 AS date), Beløb = SUM(Beløb), Beløb_ÅTD
= SUM(SUM(Beløb)) OVER ( PARTITION BY
Kundenr, YEAR(Ordredato - DATEPART(weekday,Ordredato) + 1) ORDER BY CAST(Ordredato -
DATEPART(weekday,Ordredato) + 1 AS date)), Rækker
= COUNT(*), Rækker_ÅTD
= SUM(COUNT(*)) OVER ( PARTITION BY Kundenr, YEAR(Ordredato -
DATEPART(weekday,Ordredato) + 1) ORDER BY CAST(Ordredato -
DATEPART(weekday,Ordredato) + 1 AS date)) FROM
dbo.KundeOrdre WITH(NOLOCK) GROUP BY Kundenr, YEAR(Ordredato -
DATEPART(weekday,Ordredato) + 1), CAST(Ordredato -
DATEPART(weekday,Ordredato) + 1 AS date) ÅTD kan også udføres frem til en bestemt
dato, her repræsenteret som en måned hvor der både ønskes vist beløbet for oktober
2016 pr. kunde og beløbets ÅTD d.v.s. fra januar 2016 til og med oktober
2016: DECLARE @dato
date = '2016-10-01' SELECT Kundenr, Beløb = SUM(CASE WHEN CAST(Ordredato - DAY(Ordredato)
+ 1 AS date) = @dato THEN Beløb ELSE 0 END),
Beløb_ÅTD = SUM(Beløb) FROM
dbo.KundeOrdre WITH(NOLOCK) WHERE
CAST(Ordredato - DAY(Ordredato) + 1 AS date) BETWEEN
DATEADD(year,DATEDIFF(year,0,@dato),0) AND @dato GROUP BY Kundenr Perioden fra januar til og med oktober er
angivet i WHERE betingelsen. Den almindelige SUM(Beløb) er summen for hele
perioden d.v.s. netop år-til-dato grupperet pr. kunde. For at få beløbet kun
for den udsøgte oktober måned anvendes en CASE WHEN betingelse og for at
Ordredato ikke skal indgå i Group By så sættes en SUM udenom (gør ingen
skade, men her er der faktisk flere ordredatoer inden for en måned hvis beløb
naturligvis skal summeres sammen). Der fås:
For oktober 2015 fås:
Indeks
i forhold til sidste år som har basisværdien 100 (procentvise stigning) for
en givet måned og tilsvarende måned sidste år. Ordredato omdannes igen til måned
og det ville være oplagt med en self-join på samme Kundenr og måned = måned
-1 men husk at alle join koster performance på mange rækker, derfor anvendes
en Union All mellem gældende måned og sidste års måned som sammentrækkes
via en SUM, så begge datasæt fås i sammen række pr. kunde: DECLARE @dato date = '2016-02-01' SELECT Kundenr, Beløb = SUM(Beløb),
SidsteÅr_Beløb = SUM(SidsteÅr_Beløb),
Indeks = 100.0 * SUM(Beløb) / NULLIF(SUM(SidsteÅr_Beløb),0) FROM ( SELECT
Kundenr, Beløb = SUM(Beløb), SidsteÅr_Beløb = NULL FROM
dbo.KundeOrdre WHERE CAST(Ordredato - DAY(Ordredato) + 1 AS
date) = @dato GROUP
BY Kundenr UNION ALL SELECT
Kundenr, Beløb = NULL, SidsteÅr_Beløb = SUM(Beløb) FROM
dbo.KundeOrdre WHERE CAST(Ordredato - DAY(Ordredato) + 1 AS
date) = DATEADD(year,-1,@dato) GROUP BY Kundenr ) t GROUP BY
Kundenr Foreningsmængde
af disse to mængder for gældende måned og sidste års måned:
Bliver
sammentrukket via SUM til to rækker og Indeks kan udregnes:
Performance ved hjælp af et indeks Store data mængder kræver gode indeks, et
eksempel på et indeks som query planen vil anvende og få svartiden ned på få
sekunder fremfor minutter fordi indekset er skræddersyet til OUTER APPLY
WHERE sætningen: CREATE
NONCLUSTERED INDEX [IX_Fact_Sale_Concat] ON [dbo].[Fact_Sale] ( [DimTransactionType] ASC, [DimProduct] ASC, [SpecialNumber] ASC, [DimTransactionDate] ASC ) INCLUDE
([DimDebitor]) WHERE
([IsDeleted]=(0)) WITH
(PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF,
DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS =
ON, DATA_COMPRESSION = PAGE) ON [Primary] SELECT TransactionDate = fact.DimTransactionDate, DepositDebitor = ISNULL(rd.Debitor,
r.Debitor) FROM [dbo].[Fact_Sale]
fact INNER JOIN [dbo].[DimDebitor] r ON r.DimDebitor = fact.DimDebitor OUTER APPLY (SELECT TOP 1 DimDebitor FROM [dbo].[Fact_Sale] factd WHERE factd.DimTransactionType IN (SELECT DimTransactionType FROM [dbo].[DimTransactionType]
WHERE Code ='DEPOSIT') AND factd.DimProduct =
fact.DimProduct AND factd.SpecialNumber =
fact.SpecialNumber AND factd.DimTransactionDate <=
fact.DimTransactionDate ORDER BY factd.DimTransactionDate ) factd LEFT OUTER JOIN [dbo].[DimDebitor] rd ON rd.DimDebitor = factd.DimDebitor WHERE
IsDeleted=0 Where betingelsen er vigtig for at
anvende indekset IX_Fact_Sale_Concat, fordi indekset har en tilsvarende where
betingelse ([IsDeleted]=(0)). Include er et covered index der indeholder
alle nødvendige felter/kolonner til at løse forespørgslen, hvorved det
clustered index ikke tages i anvendelse. A covered index
is an index which »cover« all columns needed from a specific table, removing
the need to access the physical table at all for a given query/ operation. While covering indexes can often provide good
benefit for retrieval, they do add somewhat to insert/ update overhead; due
to the need to write extra or larger index rows on every update. Performance ved hjælp af option SELECT t.Field1, x.Field2 FROM dbo.T t INNER JOIN dbo.X x ON x.Id = t.Id WHERE t.Field3 = 'Maling' ORDER BY
x.SortOrder ASC OPTION(FORCE ORDER) eller OPTION(MAXDOP 3) OPTION(FORCE ORDER) bruges til at
fortælle SQL Server at den skal joine tabeller i den rækkefølge der er angivet
i sql sætningen og derved kan SQL Serveren hurtigere lave en query plan, især
ved rigtig mange tabeller der skal joines. Læs mere om Option force order OPTION(MAXDOP 3) eller OPTION(MAXDOP 1) max
degree of parallelism angiver antallet af kerner som sql sætningen må blive
udført på eller med, fordi nogen gange kan for mange kerne ende i deadlock. Læs mere om MAXDOP
Læs mere om table hints. SELECT
name, value_in_use FROM
sys.configurations WHERE
description LIKE '%max%%parallelism%' Har du en forespørgel med mange joins,
kan dannelsen af query plan give en fejlmelding a la: Query processor
could not produce a query plan because of the hints defined in this query.
Resubmit the query without specifying any hints and without using SET
FORCEPLAN. The query
processor ran out of internal resources and could not produce a query plan.
This is a rare event and only expected for extremely complex queries or
queries that reference a very large number of tables or partitions. Please
simplify the query. If you believe you have received this message in error,
contact Customer Support Services for more information. Løsningen
er: OPTION (HASH JOIN), OPTION (LOOP JOIN) eller OPTION (MERGE JOIN). OPTION (FORCE ORDER, HASH JOIN) ved rigtig mange joins. Læs mere
derom Query hints Joins En table-valued function kaldes oftest
fra enten et view med felt som parameter værdi eller fra en stored procedure
med declared @variabel som parameter, og sidst nævnte kan give en ekstra lang
udførselstid som kan undgås ved brug af: INSERT
INTO #temp SELECT 'CRM'
AS Source, t.* FROM dbo.CRMdatafn( DATETIMEFROMPARTS(YEAR(@DateTimeFrom),
MONTH(@DateTimeFrom), DAY(@DateTimeFrom), 0, 0,
0, 000), DATETIMEFROMPARTS(YEAR(@DateTimeTo),
MONTH(@DateTimeTo), DAY(@DateTimeTo), 23, 59,
59, 999), @KundeId, @BrandId, @Weekno, @Amount) AS
t OPTION(RECOMPILE) Recompile tager nogle millisekunder for at
lave en ny query plan, men der undgås de problemer som kaldes parameter sniffing og om ulemper. Når en stored procedure har en parameter, kan
det nogen gange være en fordel at lave en lokal variabel i stored proceduren
og udføre SET lokalvariabel = parametervariabel og anvende den lokale
variabel i where delen af forespørgslen. En tabel har over 1 milliard rækker eller
flere milliarder rækker og der kun søges på primærnøgle clustered indeks og
der ikke er andre indeks i tabellen. Sql server opsamler optimizer
statistikker og det koster når der løbende indsættes nye millioner rækker i
tabellen, fordi Trace Flag 2371 er default fra SQL Server 2016, og når der
udføres en select from where på primærnøglen er det en del af parsingen at trigge
en ajourføring af statistikker, som kan tage mange minutter, hvorved den
samlede svartid bliver lang (update statistics is triggered). Det kan undgås
ved at bruge: OPTION(KEEPFIXED PLAN). En anden løsning er at enable, at ajourføringen
sker asynkront, læs mere: ALTER DATABASE [databasename] SET
AUTO_UPDATE_STATISTICS_ASYNC ON; Kan også sættes per tabel, læs mere. Måske
kan en genkompilering af sp give en hurtigere svartid: CREATE
PROCEDURE dbo.MySP @FilterExpr varchar(100) WITH RECOMPILE AS Denne
anvendes også for at speede læsning op og se bort fra låsning SET
TRANSACTION ISOLATION LEVEL READ UNCOMMITTED At
studere query plan og se efter task med stort antal rækker, memory forbrug
eller høj estimated time kan give inspiration til omskrivning af sql
sætningen, f.eks. for at undgå lazy table spool. Flere gode råd på
https://sqlperformance.com f.eks. om table expressions. Slet index før stor indsættelse og opret
index bagefter for hurtig søgning IF
EXISTS (SELECT 1 FROM sysindexes WHERE name =
'IX_KUNDE_Postnummer') BEGIN
DROP INDEX dbo.KUNDE.IX_KUNDE_Postnummer END DROP
INDEX IF EXISTS dbo.KUNDE.IX_KUNDE_Postnummer IF NOT
EXISTS (SELECT 1 FROM sysindexes WHERE name = 'IX_KUNDE_Postnummer') BEGIN CREATE NONCLUSTERED INDEX
IX_KUNDE_Postnummer ON
dbo.KUNDE(Postnummer) WITH FILLFACTOR = 100 ON [PRIMARY] END forudsætning, at der er et clustered
index typisk primærnøglen. Sæt
et index midlertidig ud af kraft ALTER INDEX IX_KUNDE_Postnummer ON dbo.KUNDE
DISABLE -- indeks i hvile ... indsæt data ... ALTER INDEX IX_KUNDE_Postnummer ON dbo.KUNDE
REBUILD -- indeks enabled Sæt
alle index midlertidig ud af kraft TRUNCATE TABLE dbo.KUNDE -- når indsæt er full
load. ALTER INDEX ALL ON dbo.KUNDE DISABLE -- tabel kan ikke åbnes. ALTER INDEX PK_KUNDE ON dbo.KUNDE REBUILD
-- tabel kan åbnes igen. .. indsæt data .. -- ALTER INDEX
IX_KUNDE_Postnummer ON dbo.KUNDE DISABLE ALTER INDEX ALL ON dbo.KUNDE REBUILD -- indeks er enabled igen. ALTER
INDEX ALL ON dbo.KUNDE REBUILD WITH (ONLINE = ON, RESUMABLE = ON,
MAX_DURATION = 10) Se
hvilke index er sat midlertidig ud af kraft Select sys.objects.name as table_name, sys.indexes.name as index_name From sys.indexes inner join sys.objects on
sys.objects.object_id = sys.indexes.object_id Where sys.objects.name = 'KUNDE' and sys.indexes.is_disabled
= 1 Order by sys.objects.name, sys.indexes.name Create Index Alter Index Transaction log space
Read more Management views Online Rebuild
functionality only supported in Enterprise edition of SQL Server. MAX_DURATION
are in minutes. Rebuild Indexes
(online) - An online index rebuild does not require object-level locks until
the end of the operation, when a lock must be held for a short duration to
complete the rebuild. Single transaction vil kræve meget plads
i tempdb i modsætning til enkelvis rebuild af indekserne. DECLARE
@TableName VARCHAR(255) DECLARE
@sql NVARCHAR(500) DECLARE
@fillfactor INT SET
@fillfactor = 80 DECLARE
TableCursor CURSOR FOR SELECT QUOTENAME(OBJECT_SCHEMA_NAME([object_id]))+'.'
+
QUOTENAME(name) AS TableName FROM
sys.tables OPEN
TableCursor FETCH
NEXT FROM TableCursor INTO @TableName WHILE
@@FETCH_STATUS = 0 BEGIN SET @sql
= 'ALTER INDEX ALL ON ' + @TableName + ' REBUILD WITH (FILLFACTOR = ' +
CONVERT(VARCHAR(3),@fillfactor) + ')' EXEC
(@sql) FETCH
NEXT FROM TableCursor INTO @TableName END CLOSE
TableCursor DEALLOCATE
TableCursor GO Dan ikke index på en kolonne med få
forskellige værdier som Frøken, Frue, Herre. Det gælder også en bit kolonne. SELECT dbschemas.[name] as 'Schema', dbtables.[name] as 'Table', dbindexes.[name] as 'Index', indexstats.avg_fragmentation_in_percent, indexstats.page_count FROM sys.dm_db_index_physical_stats (DB_ID(), NULL, NULL, NULL, NULL)
AS indexstats INNER JOIN sys.tables dbtables on dbtables.[object_id] =
indexstats.[object_id] INNER JOIN sys.schemas dbschemas on dbtables.[schema_id] =
dbschemas.[schema_id] INNER JOIN sys.indexes AS dbindexes ON dbindexes.[object_id] =
indexstats.[object_id] AND indexstats.index_id = dbindexes.index_id WHERE indexstats.database_id = DB_ID() AND dbtables.[name] like '%%' ORDER BY indexstats.avg_fragmentation_in_percent desc Slet alle index undtagen primærnøglen DECLARE @qry nvarchar(4000) IF EXISTS(select 1 from sys.indexes i join
sys.objects o on i.object_id=o.object_id join sys.schemas s on
o.schema_id=s.schema_id where o.name = 'KUNDE' and o.type<>'S' and
is_primary_key<>1 and index_id>0 and s.name<>'sys') BEGIN select @qry = (select 'drop index ['+s.name+'].['+o.name+'].['+i.name+'];' from sys.indexes i join sys.objects o on
i.object_id=o.object_id join sys.schemas s on o.schema_id=s.schema_id where o.name = 'KUNDE' and o.type<>'S' and
is_primary_key<>1 and index_id>0 and s.name<>'sys' for xml path('')) select @qry EXEC sp_executesql @qry END GO Påtving
brug af indeks (force index) Her dannes et komprimeret indeks: CREATE
NONCLUSTERED INDEX [IX_Order_InsertDate] ON
[dbo].[Order] ( [InsertedDate] ASC )WITH (DATA_COMPRESSION = PAGE) GO Det er ikke altid, at SQL Serveren kan
finde ud af anvende et indeks, hvilket betyder, at der bliver udført en Clustered
Index Scan som medfører en Table Scan der tager mange minutter på den store
data mængde. Men i sql sætningen kan vi tvinge den til at bruge indekset, så
der bliver udført en Index Seek og Key Lookup som er meget hurtig, f.eks. ved
en inkremental udlæsning (delta data): DECLARE
@d as datetime2(7) = '2017-12-13 02:36:08.6333333' SELECT * FROM
dbo.Order WITH(nolock, index(IX_Order_InsertDate)) WHERE InsertedDate > @d Key Lookup er langsom når sql sætningen
returnerer mange rækker. Pas på med forceseek
Query hints NOLOCK - KAN VÆRE FARLIG VED ROLLBACK. This table hint, also known as READUNCOMMITTED, is
applicable to SELECT statements only. NOLOCK indicates that no shared locks
are issued against the table that would prohibit other transactions from
modifying the data in the table. The benefit of the statement is that it
allows you to keep the database engine from issuing locks against the tables
in your queries; this increases concurrency and performance because the
database engine does not have to maintain the shared locks involved. The
downside is that, because the statement does not issue any locks against the
tables being read, some "dirty," uncommitted data could potentially
be read. A "dirty" read is one in which the data being read is
involved in a transaction from another connection. If that transaction rolls
back its work, the data read from the connection using NOLOCK will have read
uncommitted data. This type of read makes processing inconsistent and can
lead to problems. Tilføjelse
af et fortløbende nummer med indeks ALTER
TABLE [archive].[Order] ADD
ArcRecordId bigint NOT NULL IDENTITY (1, 1) GO CREATE
NONCLUSTERED INDEX [IX_Order_ArcRecordId] ON [archive].[Order] ( [ArcRecordId] ASC )WITH
(PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF,
DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS =
ON) ON [PRIMARY] GO Slet eller opret
fremmednøgle (Foreign Key Constraint) IF
EXISTS (SELECT 1 FROM
INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE WHERE TABLE_NAME = 'KUNDE' AND CONSTRAINT_NAME =
'FK_KUNDE_POSTNUMMER') BEGIN
ALTER TABLE dbo.KUNDE DROP
CONSTRAINT FK_KUNDE_POSTNUMMER END IF NOT
EXISTS (SELECT 1 FROM
INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE WHERE TABLE_NAME = 'KUNDE' AND
CONSTRAINT_NAME =
'FK_KUNDE_POSTNUMMER') BEGIN
ALTER TABLE dbo.KUNDE ADD
CONSTRAINT FK_KUNDE_POSTNUMMER
FOREIGN KEY (Postnummer) REFERENCES dbo.POSTNUMMER (Postnummer) END giver fejl når data i fremmednøglen
(barn-tabel) ikke findes i primærnøglen (mor). De data findes via en NOT EXISTS: SELECT * FROM
dbo.KUNDE k WHERE
NOT EXISTS(SELECT TOP(1) 1 FROM dbo.POSTNUMMER p WHERE p.Postnummer = k.Postnummer) Midlertidig slå alle fremmednøgle check
fra før stor indsættelse ALTER
TABLE dbo.KUNDE NOCHECK CONSTRAINT ALL Men
når den slås til igen fås der ikke fejlmelding ved forkerte data i
fremmednøgle ALTER
TABLE dbo.KUNDE CHECK CONSTRAINT ALL --
Disable single constraint ALTER
TABLE MyTable NOCHECK CONSTRAINT MyConstraint --
Enable single constraint ALTER
TABLE MyTable CHECK CONSTRAINT MyConstraint Udfør check af fremmednøgler efter stor
indsættelse Via
ad hoc query som returnerer tabel der viser fejlene: DBCC
CHECKCONSTRAINTS ('[TEST]') Men
upraktisk i et ETL job, derfor noget a la: TRUNCATE
TABLE dbo.TEST_ERROR INSERT
INTO dbo.TEST_ERROR(Nr) WITH (TABLOCK) SELECT
Nr FROM
dbo.TEST WITH(NOLOCK) LEFT OUTER JOIN dbo.KALENDER_MÅNED
WITH(NOLOCK) ON dbo.KALENDER_MÅNED.ÅrMåned = dbo.TEST.ÅrMåned LEFT OUTER
JOIN dbo.ARBEJDE WITH(NOLOCK) ON dbo.ARBEJDE.ArbejdeId = dbo.TEST.ArbejdeId WHERE dbo.KALENDER_MÅNED.ÅrMåned IS NULL OR
dbo.ARBEJDE.ArbejdeId IS NULL Der
kan så udføres en test af om tabellen ikke er tom og sendes en email til
driften. Vis alle constraints for en tabel EXEC
sp_helpconstraint @objname = 'dbo.KUNDE', @nomsg = NULL; Full dump Indlæsning til et enterprise
datawarehouse EDW for alle data først slettes og genindlæses fra
kildedatasystemet og er behandlet i en NEW tabel via DSA. BorgerId er et Identity (auto incremental)
fortløbende nummer. CREATE
PROCEDURE [dbo].[EDW_DB_Borger_Insert] AS BEGIN SET NOCOUNT ON DECLARE @Today datetime = GETDATE() TRUNCATE TABLE dbo.EDW_DB_Borger SET IDENTITY_INSERT dbo.EDW_DB_Borger ON INSERT INTO dbo.EDW_DB_Borger (
BorgerId, BorgerKode, BorgertypeId, Fornavn, Efternavn, Fødselsdato, KønId, CivilstatusId, DataKilde, BorgerOprettet ) VALUES(-1,
NULL, -1, 'Mangler', 'Mangler', NULL,-1,-1,'EDW',@Today) SET IDENTITY_INSERT dbo.EDW_DB_Borger OFF INSERT INTO dbo.EDW_DB_Borger ( BorgerKode, BorgertypeId, Fornavn, Efternavn, Fødselsdato, KønId, CivilstatusId, DataKilde, BorgerOprettet ) SELECT
BorgerKode, BorgertypeId, Fornavn, Efternavn, Fødselsdato, KønId, CivilstatusId, DataKilde, BorgerOprettet
= @Today FROM
dbo.EDW_NEW_Borger ORDER BY
BorgerKode END Incremental
update Indlæsning til et enterprise
datawarehouse EDW hvor CivilstatusId = -1 står for en Mangler værdi der ikke
findes i kildedatasystemet og i DSA databasen og CivilstatusKode som er primærnøglen
i kilden får værdien 0 der en repræsentation for null. CivilstatusId er et Identity (auto incremental,
surrogate) fortløbende nummer. CREATE
PROCEDURE [dbo].[EDW_DB_Civilstatus_InsertUpdate] AS BEGIN SET NOCOUNT ON DECLARE @Today datetime = GETDATE() IF NOT EXISTS(SELECT TOP(1) 1 FROM
dbo.EDW_DB_Civilstatus WHERE CivilstatusId = -1) BEGIN SET IDENTITY_INSERT
dbo.EDW_DB_Civilstatus ON INSERT INTO dbo.EDW_DB_Civilstatus (CivilstatusId,
CivilstatusKode, CivilstatusNavn, CivilstatusType,
CivilstatusOprettet, CivilstatusOpdateret) VALUES(-1,
0, 'Mangler', NULL, @Today, @Today) SET IDENTITY_INSERT
dbo.EDW_DB_Civilstatus OFF END INSERT INTO dbo.EDW_DB_Civilstatus (CivilstatusKode,
CivilstatusNavn, CivilstatusType, CivilstatusOprettet, CivilstatusOpdateret) SELECT
dsa.CivilstatusKode, dsa.CivilstatusNavn,
dsa.CivilstatusType, @Today, @Today FROM dbo.DSA_BASISDATA_CIVILSTATUS dsa WHERE NOT EXISTS(SELECT TOP(1) 1 FROM
dbo.EDW_DB_Civilstatus db WHERE db.CivilstatusKode =
dsa.CivilstatusKode) ORDER BY
dsa.CivilstatusKode UPDATE db SET CivilstatusNavn = dsa.CivilstatusNavn, CivilstatusType = dsa.CivilstatusType,
CivilstatusOpdateret = @Today FROM dbo.EDW_DB_Civilstatus db INNER JOIN dbo.DSA_BASISDATA_Civilstatus
dsa
ON dsa.CivilstatusKode = db.CivilstatusKode WHERE
db.CivilstatusNavn <> dsa.CivilstatusNavn OR
ISNULL(db.CivilstatusType,'*') <>
ISNULL(dsa.CivilstatusType,'*') END MySql
anvender en anden UPDATE syntaks/notation: DECLARE
varToday date; SET
varToday := CURDATE(); UPDATE
EDW_DB_Civilstatus db INNER
JOIN DSA_BASISDATA_CIVILSTATUS dsa ON
dsa.CivilstatusKode = db.CivilstatusKode SET
db.CivilstatusNavn = dsa.CivilstatusNavn,
db.CivilstatusType = dsa.CivilstatusType,
db.CivilstatusOpdateret = varToday WHERE db.CivilstatusNavn <>
dsa.CivilstatusNavn OR
IFNULL(db.CivilstatusType,'*') <>
IFNULL(dsa.CivilstatusType,'*'); Merge kommando slår insert og update sammen,
men mangler order by MERGE dbo.EDW_DB_Civilstatus
AS db -- target USING dbo.DSA_BASISDATA_CIVILSTATUS
AS dsa -- source ON (dsa.CivilstatusKode = db.CivilstatusKode) WHEN NOT
MATCHED THEN INSERT
(CivilstatusKode, CivilstatusNavn, CivilstatusType,
CivilstatusOprettet, CivilstatusOpdateret) VALUES (dsa.CivilstatusKode,
dsa.CivilstatusNavn,
dsa.CivilstatusType, @Today, @Today) WHEN
MATCHED AND db.CivilstatusNavn <> dsa.CivilstatusNavn OR ISNULL(db.CivilstatusType,'*') <>
ISNULL(dsa.CivilstatusType,'*') THEN UPDATE SET db.CivilstatusNavn =
dsa.CivilstatusNavn, db.CivilstatusType =
dsa.CivilstatusType, db.CivilstatusOpdateret =
@Today; Alternativ: WHEN
MATCHED AND (ISNULL(NULLIF(db.CivilstatusNavn,
dsa.CivilstatusNavn), NULLIF(dsa.CivilstatusNavn,
db.CivilstatusNavn)) IS NULL Optimizing MERGE Statement Performance with OPTION (LOOP JOIN) må
ikke bruges sammen med String_Agg, fordi det ender i en freezing kørsel som
ikke bliver færdig. Alt i alt må brug af query hint testes ekstra gange og
kun anvendes, hvor det er nødvendigt for at en SQL sætning kan køres eller
med ekstra hurtig kørselstid. Med flere millioner rækker kan Merge
fylde transactionsloggen (Merge is filling up the transaction log). Der er
heller ikke nogen order by og dette link skriver om fejl: Use Caution with SQL Server’s MERGE Statement Merge issues Merge hvor en række ikke findes mere og
bliver markeret som slettet og med et overblik af antallet af ændrede rækker.
Kimball type 1 dimension CREATE TABLE [dim].[ClientState]( [ClientState_key] [int] IDENTITY(1,1) NOT NULL, [ClientState_bkey] [varchar](100) NOT NULL, [ClientStateName] [varchar](100) NOT NULL, [ClientStateDescription] [varchar](255) NOT NULL, [Comparison_bkey_meta] [binary](32) NOT NULL, [Comparison_data_meta] [binary](32) NOT NULL, [IsDeleted_meta] [bit] NOT NULL CONSTRAINT [DF_dim_ClientState_IsDeleted_meta] DEFAULT ((0)), [IsInferred_meta] [bit] NOT NULL CONSTRAINT [DF_dim_ClientState_IsInferred_meta] DEFAULT ((0)), [InsertTime_meta] [datetime2](3) NOT NULL CONSTRAINT [DF_dim_ClientState_InsertTime_meta] DEFAULT (sysdatetime()), [UpdateTime_meta] [datetime2](3) NOT NULL CONSTRAINT [DF_dim_ClientState_UpdateTime_meta] DEFAULT (sysdatetime()), [RecordSource_meta] [varchar](100) NOT NULL, CONSTRAINT [PK_dim_ClientState] PRIMARY KEY CLUSTERED ( [ClientStateKey]
ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE =
OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO CREATE UNIQUE NONCLUSTERED INDEX [IX_dim_ClientState_ClientState_bkey]
ON [dim].[ClientState] ( [ClientState_bkey] ASC
-- unique key (UK). )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE =
OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] GO CREATE UNIQUE NONCLUSTERED INDEX
[IX_dim_ClientState_Comparison_bkey_meta] ON
[dim].[ClientState] ( [Comparison_bkey_meta] ASC -- unique key (UK). ) INCLUDE ([Comparison_data_meta], [IsDeleted_meta])
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE =
OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] GO DECLARE @Inserted int = 0, @Updated int = 0, @Deleted int = 0; DECLARE @NumberOfChanges TABLE(Change nvarchar(10)); MERGE
dim.ClientState AS
tgt USING --USING staging.ClientState AS src (SELECT TOP 99.99 PERCENT * FROM staging.ClientState ORDER BY
ClientState_bkey) src ON tgt.Comparison_bkey_meta
= src.Comparison_bkey_meta WHEN NOT MATCHED BY TARGET THEN INSERT (ClientState_bkey, ClientStateName, ClientStateDescription,
Comparison_bkey_meta, Comparison_data_meta, IsDeleted_meta,IsInferred_meta) VALUES (src.ClientState_bkey, src.ClientStateName, src.ClientStateDescription,
src.Comparison_bkey_meta, src.Comparison_data_meta, 0, 0) WHEN NOT MATCHED BY SOURCE AND tgt.IsDeleted_meta
= 0 AND tgt.IsInferred_meta =0
THEN UPDATE SET tgt.IsDeleted_meta = 1,
tgt.UpdateTime_meta = sysdatetime() WHEN MATCHED AND (tgt.Comparison_data_meta <>
src.Comparison_data_meta OR tgt.IsDeleted_meta
= 1) THEN --or is deleted
before and now is back UPDATE SET tgt.ClientStateName =
src.ClientStateName,
tgt.ClientStateDescription = src.ClientStateDescription
tgt.IsDeleted_meta = 0,
tgt.Comparison_data_meta = src.Comparison_data_meta,
tgt.UpdateTime_meta = sysdatetime(); SELECT @Inserted = COUNT(*) FROM @NumberOfChanges WHERE
Change = 'INSERT'; SELECT @Updated = COUNT(*) FROM
@NumberOfChanges WHERE Change = 'UPDATE'; SELECT @Deleted = COUNT(*) FROM
@NumberOfChanges WHERE Change = 'DELETE'; Incremental update med nye data
repræsenteret i ny AnsættelsesMånedDato. I tilfælde af omkørsel af ETL,
indledes der med en sletning af tidligere leveret data med samme
AnsættelsesMånedDato som findes i NEW tabellen inden indsættelse i DB
tabellen. Men der sker ikke nogen tømning af DB tabellen, for så mistes alle
data der hidtidig er leveret fra kilesystemet. DELETE FROM
dbo.EDW_DB_MedarbejderAnsættelse WITH(TABLOCK) WHERE AnsættelsesMånedDato IN (SELECT
DISTINCT AnsættelsesMånedDato FROM dbo.EDW_NEW_MedarbejderAnsættelse
WITH (NOLOCK)) INSERT
INTO dbo.EDW_DB_MedarbejderAnsættelse WITH (TABLOCK) SELECT * FROM dbo.EDW_NEW_MedarbejderAnsættelse WITH
(NOLOCK) Nedenstående stored procedure anvender
Merge til enten at indsætte ny værdi eller slette eksisterende værdi baseret
på en operation indikator bit 1 eller 0. Indsæt
nyt telefonnummer til bestemt kunde hvis nummeret ikke findes i forvejen: EXEC dbo.Kunde_Telefonnummer_New_Delete
124, 88001022, 1 Slet
eksisterende telefonnummer til bestemt kunde EXEC dbo.Kunde_Telefonnummer_New_Delete
124, 88001021, 0 CREATE
PROCEDURE dbo.Kunde_Telefonnummer_New_Delete @KundeId int, @Telefonnummer as bigint, @Operation bit -- 1 = Indsæt, 0 = Slet. AS BEGIN SET NOCOUNT ON; MERGE dbo.KUNDE_TELEFONNUMMER AS t USING
(VALUES(@KundeId, @Telefonnummer)) s(KundeId, Telefonnummer) ON
s.KundeId = t.KundeId AND s.Telefonnummer = t.Telefonnummer WHEN
MATCHED AND @Operation = 0 THEN DELETE WHEN NOT MATCHED BY TARGET AND @Operation =
1 THEN INSERT(KundeId,
Telefonnummer)
VALUES(@KundeId, @Telefonnummer); END GO (VALUES(@KundeId, @Telefonnummer)) er en Table Value Constructor der danner
source table til Merge. Findes
kundens telefonnummer allerede i tabellen og operator er 1, så er de to When betingelser
falske og der sker hverken sletning eller indsættelse. Primærnøglen i tabellen består af KundeId
og TelefonId hvor TelefonId er et fortløbende
nummer inden for samme KundeId, eksempelvis:
TelefonId styres af trigger på tabellen
der tilsidesætter den normale indsættelse: CREATE TRIGGER
[dbo].[KUNDE_TELEFONNUMMER_Trigger_InsteadOfInsert] ON [dbo].[KUNDE_TELEFONNUMMER] INSTEAD
OF INSERT AS BEGIN SET NOCOUNT ON INSERT
INTO dbo.KUNDE_TELEFONNUMMER (KundeId,
Telefonnummer, TelefonId)
SELECT KundeId, Telefonnummer,
TelefonId = (SELECT ISNULL(MAX(TelefonId),0) + 1 FROM dbo.KUNDE_TELEFONNUMMER
WHERE KundeId =
INSERTED.KundeId)
FROM INSERTED END GO Merge
kræver at der også er en trigger for DELETE operationen: CREATE TRIGGER
[dbo].[KUNDE_TELEFONNUMMER_Trigger_InsteadOfDelete] ON [dbo].[KUNDE_TELEFONNUMMER] INSTEAD
OF DELETE AS BEGIN SET NOCOUNT ON DELETE t FROM dbo.KUNDE_TELEFONNUMMER t INNER JOIN DELETED d ON d.KundeId =
t.KundeId AND d.TelefonId = t.TelefonId -- PK END GO Trigger for insert kan ikke håndtere bulk
insert af flere rækker samtidig fordi de alle får samme TelefonId nummer og
så bliver der primærnøgle brud, a la: INSERT INTO dbo.KUNDE_TELEFONNUMMER
(KundeId, Telefonnummer) VALUES (125,50100000), (125, 50100001),
(125, 50100002) Eller: INSERT INTO dbo.KUNDE_TELEFONNUMMER
(KundeId, Telefonnummer) SELECT KundeId, Telefonnummer FROM dbo.IMPORT_TELEFONNUMMER Men
det kan denne trigger fordi den først udregner de nye TelefonId numre: CREATE TRIGGER
[dbo].[KUNDE_TELEFONNUMMER_Trigger_InsteadOfInsert] ON [dbo].[KUNDE_TELEFONNUMMER] INSTEAD
OF INSERT AS BEGIN SET NOCOUNT ON INSERT
INTO dbo.KUNDE_TELEFONNUMMER (KundeId,
Telefonnummer, TelefonId)
SELECT i.KundeId, i.Telefonnummer,
TelefonId = ISNULL(t.TelefonId,0) + ROW_NUMBER() OVER (PARTITION BY i.KundeId ORDER
BY i.Telefonnummer) FROM INSERTED i OUTER
APPLY
(SELECT t.KundeId, TelefonId = MAX(t.TelefonId)
FROM dbo.KUNDE_TELEFONNUMMER t
WHERE t.KundeId = i.KundeId GROUP BY t.KundeId ) AS t END GO Når en række er blevet opdateret, angiv et
tidspunkt (mark a row) Kolonnen
UpdateTime får et nyt tidspunkt, timestamp, når en anden kolonne bliver
opdateret. Den har allerede en default værdi: [UpdateTime] [datetime2](0) NOT NULL CONSTRAINT
[DF_Department_UpdateTime] DEFAULT (sysdatetime()) Det
løses med en trigger after update. CREATE
TRIGGER [dbo].[trgAfterUpdate] ON [dbo].[Department] AFTER
UPDATE AS UPDATE t SET UpdateTime = sysdatetime() FROM [dbo].[Department] t INNER JOIN inserted i ON i.DepartmentId
= t.DepartmentId GO Undgå at slette en række. To prevent
deletion of the records in a table CREATE
TRIGGER [dbo].[Customer_Delete] ON
[dbo].[Customer] INSTEAD OF DELETE AS DELETE [dbo].[Customer] WHERE 1=2; Trigger til at lave en Change Data
Capture CDC log på en Kunde tabel Implementeret som
after-insert/update/delete triggers på Customer tabel og gem ændringer i data
i en CustomerLog tabel med metadata kolonner. Jeg har her indskrænket opdateringsdata
til kun at logge når feltet CustomerType ændres. CREATE TRIGGER [dbo].[Customer_Delete]
ON [dbo].[Customer] AFTER DELETE AS SET NOCOUNT
ON INSERT INTO dbo.CustomerLog ( LogEntryAction_metadata, LogEntryDateTime_metadata, LogEntryUser_metadata, LogEntryHost_metadata, CustomerId, CustomerName, CustomerType ) SELECT 'Deleted', SYSDATETIME(), STUFF(SUSER_SNAME(), 1, charindex('\', SUSER_SNAME()), ''), HOST_NAME(), CustomerId, CustomerName, CustomerType FROM [Deleted] GO CREATE TRIGGER [dbo].[Customer_Insert]
ON [dbo].[Customer] AFTER INSERT AS SET NOCOUNT
ON INSERT INTO dbo.CustomerLog ( LogEntryAction_metadata, LogEntryDateTime_metadata, LogEntryUser_metadata, LogEntryHost_metadata, CustomerId, CustomerName, CustomerType ) SELECT 'Inserted', SYSDATETIME(), STUFF(SUSER_SNAME(), 1, charindex('\', SUSER_SNAME()), ''), HOST_NAME(), CustomerId, CustomerName, CustomerType FROM [Inserted] GO CREATE TRIGGER [dbo].[Customer_Update]
ON [dbo].[Customer] AFTER UPDATE AS SET NOCOUNT
ON IF UPDATE (CustomerType) -- only when
CustomerType is updated by a user BEGIN INSERT INTO dbo.CustomerLog ( LogEntryAction_metadata, LogEntryDateTime_metadata, LogEntryUser_metadata, LogEntryHost_metadata, CustomerId, CustomerName, CustomerType, [CustomerTypePrevious] ) SELECT 'Updated', SYSDATETIME(), STUFF(SUSER_SNAME(), 1, charindex('\', SUSER_SNAME()), ''), HOST_NAME(), i.CustomerId, i.CustomerName, i.CustomerType, d.CustomerType FROM [Inserted] i INNER JOIN
[Deleted] d ON i.CustomerId
= d.CustomerId
AND i.CustomerType
<> d.CustomerType -- Deleted table contains old values and Inserted
contains updated values. END GO Når Customer tabellen udvides med nye
kolonner eller ændring af data type på eksisterende kolonner, skal
tilsvarende gøres i tabellen CustomerLog og de tre triggers skal ligeså udvides.
Lad det ske samtidig i en idriftsættelse (deployment). MySql trick Not
exists til subquery giver problem. Virker
med insert: INSERT INTO DimKunde(KundeKode, KundeNavn) SELECT c.uid,
c.name FROM source.Customer
c WHERE NOT
EXISTS(SELECT 1 FROM DimKunde k WHERE k.Kundekode = c.uid); Virker
ikke med update: UPDATE DataSumBeløb Cur SET Cur.Beløb = Cur.HensatBeløb WHERE
NOT EXISTS(SELECT 1 FROM DataSumBeløb Pre WHERE Pre.Månedsdato = Cur.ForrigeMånedsdato
AND Pre.Kode = Cur.Kode); Giver
fejlmelding: ERROR 1093 [HY000]: You can't specify target table 'Table' for
update in FROM clause. I Mysql kan man ikke opdatere en tabel
når samme tabel indgår i en subquery, men så kan tabellen i subquery
erstattes af en select, der returnerer et dataset og så kan tabellen blive
opdateret, man siger på engelsk, at MySql materializes subqueries in the FROM
clause (»derived tables«): WHERE
NOT EXISTS(SELECT 1 FROM (SELECT * FROM DataSumBeløb) Pre En
alternativ løsning er som er meget hurtig når der er index på join felterne: UPDATE DataSumBeløb Cur LEFT OUTER JOIN DataSumBeløb Pre
ON Pre.Månedsdato = Cur.ForrigeMånedsdato AND
Pre.Kode = Cur.Kode SET Cur.Beløb = Cur.HensatBeløb WHERE Pre.Månedsdato IS NULL; Endnu
et par mysql 5.7 eksempler, der viser ValidTo og opdaterer ValidTo: CREATE VIEW DimCustomer_ValidTo AS SELECT Customer_key, CustomerName, ValidFrom, (SELECT IFNULL(MIN(b.ValidFrom), '9999-12-31
00:00:00') AS ValidFrom FROM DimCustomer b WHERE b.Business_key = a.Business_key AND
b.Customer_key > a.Customer_key ) AS ValidTo FROM DimCustomer a UPDATE DimCustomer a SET a.ValidTo = (SELECT
IFNULL(MIN(b.ValidFrom), '9999-12-31 00:00:00') AS ValidFrom FROM (SELECT * FROM
DimCustomer) b WHERE b.Business_key = a.Business_key AND b.Customer_key >
a.Customer_key ) Et nummer pr. række Med ROW_NUMBER og en sorteringsorden får
man et fortløbende rækkenummer pr. række i udtrækket med SELECT eksempelvis
fra AdventureWorks hvor der fås en liste i faldende orden af salget år-til-dato
med største salg som rækkenr 1: SELECT
ROW_NUMBER() OVER(ORDER BY SalesYTD DESC) AS Row, FirstName,
LastName, ROUND(SalesYTD,2,1) AS [Sales YTD] FROM
Sales.vSalesPerson WHERE
SalesYTD <> 0
Med
brug af Partition (slags Group By opdeling) får man opdelt rækkenumrene, så
de kan starte forfra igen med nummer 1 som her pr. år dannet via Year(): SELECT
BusinessEntityID, YEAR(QuotaDate) AS SalesYear, SalesQuota AS CurrentQuota, ROW_NUMBER() OVER (PARTITION BY
YEAR(QuotaDate) ORDER BY SalesQuota DESC) AS Row FROM
Sales.SalesPersonQuotaHistory
En rangering betyder, at ens data (her samme
SalesQuota) får samme nummer: SELECT
BusinessEntityID, YEAR(QuotaDate) AS SalesYear, SalesQuota AS CurrentQuota, RANK() OVER (PARTITION BY
YEAR(QuotaDate) ORDER BY SalesQuota DESC)
AS Row FROM Sales.SalesPersonQuotaHistory
Rangering
uden at næste nummer springer som ovenfor fra 4 til 7: SELECT
BusinessEntityID, YEAR(QuotaDate) AS SalesYear, SalesQuota AS CurrentQuota, DENSE_RANK() OVER (PARTITION BY
YEAR(QuotaDate) ORDER BY SalesQuota
DESC) AS Row FROM Sales.SalesPersonQuotaHistory
Inddeling
i grupper med hvert sit nummer, her ønskes 8 grupper pr. år: SELECT
BusinessEntityID, YEAR(QuotaDate) AS SalesYear, SalesQuota AS CurrentQuota, NTILE(8) OVER (PARTITION BY
YEAR(QuotaDate) ORDER BY SalesQuota
DESC) AS Row FROM
Sales.SalesPersonQuotaHistory
Forrige rækkes
værdi Med
LAG() funktion får man forrige række værdi uden brug af self-join: SELECT
BusinessEntityID, YEAR(QuotaDate) AS SalesYear, SalesQuota
AS CurrentQuota, LAG(SalesQuota,1,0)
OVER (ORDER BY YEAR(QuotaDate)) AS PreviousQuota FROM
Sales.SalesPersonQuotaHistory
Næste rækkes
værdi Med
LEAD() funktion får man næste række værdi uden brug af self-join: SELECT
BusinessEntityID, YEAR(QuotaDate) AS SalesYear, SalesQuota
AS CurrentQuota, LEAD(SalesQuota,1,0)
OVER (ORDER BY YEAR(QuotaDate)) AS NextQuota FROM Sales.SalesPersonQuotaHistory
I
nedenstående tabel indsættes en ny række hver gang en sats ændrer beløb:
Men
hvordan findes beløbet for sats A den 10-10-2010: SELECT
s.Sats, s.Beløb FROM
dbo.SATS s INNER JOIN ( SELECT Sats, Dato = MAX(Dato) FROM dbo.SATS WHERE Sats = 'A' AND Dato <=
'20101010' GROUP BY Sats ) t ON t.Sats = s.Sats AND t.Dato =
s.Dato Men skal der laves mange opslag, så kan
det betale sig at have en ekstra udgave af tabellen med FraDato og TilDato
interval perioder, som LEAD kan hjælpe med, fordi den går til næste række (1)
og fra den rækkes dato fratrækkes 1 dag (-1) og findes næste række ikke
anvendes en default dato som 9999-12-31: SELECT
Sats, FraDato = Dato, TilDato = LEAD(DATEADD(DAY, -1,
Dato), 1, '9999-12-31') OVER(PARTITION BY Sats
ORDER BY Dato), Beløb FROM dbo.SATS
Og
så kan der udføres en Where betingelse: FraDato
<= '2010-10-10' AND TilDato >= '2010-10-10' eller '2010-10-10'
>= FraDato AND '2010-10-10' <= TilDato
eller '2010-10-10' BETWEEN FraDato AND TilDato Overskuelig sql kode med brug af tabel
variabel I
en sql sætning ser man tit CASE WHEN der forestår oversættelser/mapninger: SELECT
Bynr, CASE WHEN Bynr = 1 THEN 'København' WHEN Bynr = 2 THEN 'Aarhus' WHEN Bynr = 3 THEN 'Odense' ELSE 'Øvrige byer'
END AS Bynavn FROM Byliste Oversættelserne
kan i stedet placeres i en tabel variabel som der joines til, når man er i en
stored procedure: DECLARE
@bynavn TABLE (Bynr int, Bynavn varchar(50)) INSERT INTO @bynavn VALUES
(1,'København'),(2,'Aarhus'),(3,'Odense') SELECT
b.Bynr, ISNULL(bn.Bynavn,'Øvrige byer') AS Bynavn FROM
Bylist b LEFT OUTER JOIN @bynavn bn ON bn.Bynr = b.Bynr Tilsvarende
kan et kriterie: WHERE Bynr IN (2,3,8) laves om til en tabel variabel: DECLARE
@bynr TABLE (Bynr int) INSERT
INTO @bynr VALUES
(2),(3),(8) SELECT
Bynavn FROM
Bylist WHERE
Bynr IN (SELECT Bynr FROM @bynr) Udsøgning og undgå dubletter Udsøgning af historikdata baseret på en
regel og brug af ROW_NUMBER til at fjerne dubletter. Hvis samme kombination
af KundeId + ProduktId + Oprettelsesdato (inkl. klokkeslæt) + Ændringsdato
(uden klokkeslæt) findes flere gange, så udsøges denne kombination: KundeId + ProduktId + Oprettelsesdato
(inkl. klokkeslæt) + Ændringsdato (seneste klokkeslæt). SELECT DISTINCT o.KundeId, o.ProduktId, o.Oprettelsesdato, o.Ændringsdato FROM ( SELECT his.*, ROW_NUMBER()
OVER (PARTITION BY his.KundeId, his.ProduktId, his.Oprettelsesdato, CAST(his.Ændringsdato
AS date) ORDER BY his.Ændringsdato DESC) AS
RowNumber FROM dbo.KundeOrdre his ) o WHERE
o.RowNumber = 1 Sletning af dubletter: ;WITH dublet
AS ( SELECT ROW_NUMBER()
OVER (PARTITION BY his.KundeId, his.ProduktId, his.Oprettelsesdato, CAST(his.Ændringsdato
AS date) ORDER BY his.Ændringsdato DESC) AS
RowNumber FROM dbo.KundeOrdre ) DELETE
FROM dublet WHERE RowNumber > 1 Eller
i små bidder: DECLARE @Antal int DECLARE
@Fornavn nvarchar(50) DECLARE
@Efternavn nvarchar(50) DECLARE @Scan
CURSOR FAST_FORWARD FOR SELECT Fornavn, Efternavn, Count(*) – 1 AS
Antal FROM
Kunde
GROUP BY Fornavn, Efternavn HAVING Count(*)
> 1 OPEN @Scan
FETCH
NEXT FROM @Scan INTO @Fornavn, @Efternavn, @Antal WHILE
@@FETCH_STATUS = 0 BEGIN DELETE TOP(@Antal) FROM
Kunde WHERE
Fornavn = @Fornavn AND Efternavn = @Efternavn FETCH
NEXT FROM @Scan INTO @Fornavn, @Efternavn, @Antal END CLOSE @Scan DEALLOCATE @Scan Andet
eksempel på sletning i små bidder (chunking the deletes further): SET
NOCOUNT ON; DECLARE
@r INT; SET @r =
1; WHILE @r
> 0 BEGIN BEGIN TRANSACTION; DELETE TOP (100000) FROM dbo.SalesOrderDetail WHERE ProductID IN (712, 870, 873); SET @r = @@ROWCOUNT; COMMIT TRANSACTION; END DECLARE
@done bit = 0; WHILE
(@done = 0) BEGIN DELETE TOP(1000) FROM LogMessages WHERE LogDate <
'20020102'; IF @@rowcount < 1000 SET @done = 1; END; I
hvor mange rækker optræder dublet SELECT
his.*, COUNT(*) OVER (PARTITION BY his.KundeId,
his.ProduktId, his.Oprettelsesdato,
CAST(his.Ændringsdato AS date) ORDER BY his.Ændringsdato DESC) AS Antal FROM dbo.KundeOrdre his Mere udsøgning
Jeg ønsker for hvert sagsnr at få seneste
behandlingsdato og dens seneste sagstrin og de andre tilhørende oplysninger:
Den
klassiske metode med indlejret sql sætninger og self-joins: SELECT s1.* FROM Sagsbehandling s1 INNER JOIN ( SELECT s2.Sagsnr, s2.Behandlingsdato,
MAX(s2.Sagstrin) AS Sagstrin FROM Sagsbehandling s2 INNER JOIN ( SELECT s3.Sagsnr,
MAX(s3.Behandlingsdato) AS Behandlingsdato FROM Sagsbehandling
s3 GROUP BY s3.Person,
s3.Sagsnr ) m2 ON m2.Sagsnr =
s2.Sagsnr AND m2.Behandlingsdato = s2.Behandlingsdato GROUP BY s2.Sagsnr,
s2.Behandlingsdato ) m1 ON m1.Sagsnr =
s1.Sagsnr AND m1.Behandlingsdato = s1.Behandlingsdato AND m1.Sagstrin = s1.Sagstrin Med ROW_NUMBER og en faldende sorteringsorden
får man et fortløbende rækkenummer pr. række i udtrækket med SELECT og
udtager den første række 1: SELECT
s.* FROM ( SELECT *, ROW_NUMBER() OVER (PARTITION BY Sagsnr ORDER BY Behandlingsdato DESC,
Sagstrin DESC) AS RowNumber FROM Sagsbehandling )
s WHERE
s.RowNumber = 1 Streng gymnastik Et
Navn angivet som "Efternavn, Fornavn" ændres til "Fornavn Efternavn": SELECT
CASE WHEN Navn = '-' OR Navn = '- -' OR Navn = ',' THEN '-'
WHEN CHARINDEX(',', Navn) + 1
> 1 THEN CAST(LTRIM(SUBSTRING(Navn,
CHARINDEX(',', Navn) + 1, LEN(Navn)))
+ ' ' + SUBSTRING(Navn,1,CHARINDEX(',', Navn) - 1)
AS nvarchar(100)) ELSE Navn END AS Navn, FROM dbo.PERSON CAST
til nvarchar(100) er vigtig for at sikre at den samlede string er på 100
tegn. Fjernelse
af ekstra 0 i en adresse, så Plantevej
001, 03 tv bliver til Plantevej 1, 3 tv Plantevej
031, 03 tv bliver til Plantevej 31, 3 tv SELECT
REPLACE(REPLACE(REPLACE([Adresse], ' 0', ' '), ' 0', ' '), '
0', ' ') AS Adresse FROM dbo.PERSON En
Litra værdi består af en række bogstaver og nogen gange efterfulgt at et tal
eksempelvis a, abc, a1, abc18, hvor talværdien kaldes et løbenummer. Litraens
bogstaver og talværdi ønskes skilt ad så der kan bestemmes det største løbenummer
inden for en litra eksempelvis for tre litraer: a1, a2, a3 bliver det største
løbenummer 3 inden for litra a: CREATE VIEW
[dbo].[Tilmelding_Løbenummer] AS WITH
SkovpartAfdelingLitraLøbenummer (Skovpart, Afdeling, Litra, Løbenummer) AS ( SELECT Skovpart, Afdeling, Litra, Løbenummer =
CAST(ISNULL(NULLIF(RIGHT([Litra]+' ',PATINDEX('%[^0-9]%', REVERSE([Litra]))),''),0) AS int) -– eksempel:
a1 = 1, abc18 = 18, a = 0. FROM dbo.OPGAVE WITH(NOLOCK) ), SkovpartAfdelingLitraLøbenummer_LitraUdenLøbenummer
(Skovpart, Afdeling, Litra, Løbenummer,
LitraUdenLøbenummer) AS ( SELECT Skovpart, Afdeling,
Litra, Løbenummer, CASE WHEN Løbenummer >= 1 THEN LEFT(Litra,
LEN(Litra)-LEN(Løbenummer)) ELSE
Litra END
AS LitraUdenLøbenummer -- eksempel:
a1 = a, abc18 = abc, a = a FROM
SkovpartAfdelingLitraLøbenummer ) SELECT Skovpart, Afdeling,
LitraUdenLøbenummer, MAX(Løbenummer) AS Løbenummer FROM
SkovpartAfdelingLitraLøbenummer_LitraUdenLøbenummer GROUP BY Skovpart,
Afdeling, LitraUdenLøbenummer Kriteriet:
WHERE LEFT(TransactionIdentification,17) = @ReceiptNo er
langsom fordi funktionen LEFT kaldes for hver række i tabellen. WHERE TransactionIdentification LIKE @ReceiptNo +
'%' er
hurtig. Men er indholdet af @ReceiptNo ikke 17 cifre men kun to cifre '26%'
fås der rigtig mange rækker retur, derfor bedst at kombinere dem for at undgå
et slags sql injection agtig problem: WHERE LEFT(TransactionIdentification,17) =
@ReceiptNo AND TransactionIdentification LIKE
@ReceiptNo + '%' Left funktionen bliver kun kaldt af query
planen, når Like giver et sandt resultat, derved begrænses kaldet af Left
funktionen til de rækker sql sætningen returnerer. Udtage
en bestemt værdi mellem to -> og ikke medtage | tegnet: DECLARE @s as varchar(100) = '2018-03-06 20:45:00 : |Paris Saint-Germain|
|v| |Real Madrid| -> |Match Winner| -> |Paris Saint-Germain|' SELECT REPLACE(SUBSTRING(@s, CHARINDEX('->', @s)+3, CHARINDEX('->', @s, CHARINDEX('->', @s)+1)-CHARINDEX('->', @s)-4),'|','') Funktion som udtager første bogstav per ord i en streng,
d.v.s. laver en forkortelse CREATE
FUNCTION [dbo].[FørsteBogstavOgHeleTal] (@streng varchar(8000)) RETURNS
varchar(8000) AS BEGIN DECLARE
@anvendTegn varchar(100) = 'A-Å0-9' -- de tegn der skal håndteres DECLARE
@resultat varchar(8000) = '' DECLARE
@i int = 0 --
Når @streng indeholder cifre indsættes et mellemrum foran hvert ciffer. SET
@i = 48 -- hvor ciffer 0 starter fra. WHILE
@i <= 57 -- til og med 9. BEGIN SET @streng
= REPLACE(@streng COLLATE Danish_Norwegian_CS_AS, CHAR(@i),
CONCAT(' ',CHAR(@i))) SET @i += 1 END --
Når @streng anvender CamelCase indsættes et mellemrum foran hvert store
bogstav. SET
@i = 65 -- hvor bogstav A starter fra. WHILE
@i <= 90 -- til og med Z. BEGIN SET @streng
= REPLACE(@streng COLLATE Danish_Norwegian_CS_AS, CHAR(@i),
CONCAT(' ',CHAR(@i))) SET @i += 1 END --
Håndtering af Æ Ø Å bogstaverne, så de også får et mellemrum foran sig. SET
@i = 198 -- Æ ASCII ('Æ') giver 198 SET @streng = REPLACE(@streng COLLATE
Danish_Norwegian_CS_AS, CHAR(@i), CONCAT('
',CHAR(@i))) SET @i = 216 -- Ø SET @streng = REPLACE(@streng COLLATE
Danish_Norwegian_CS_AS, CHAR(@i), CONCAT('
',CHAR(@i))) SET @i = 197 -- Å SET @streng = REPLACE(@streng COLLATE
Danish_Norwegian_CS_AS, CHAR(@i), CONCAT('
',CHAR(@i))) --
Lave slut resultatet hvor bogstaver efter et mellemrum udtages og hele tallet. SET @i = 0 WHILE @i IS NOT NULL BEGIN SET
@resultat += ISNULL(SUBSTRING(@streng,@i+1,1),'') SET @i +=
NULLIF(PATINDEX('%[^('+@anvendTegn+')]['+@anvendTegn+']%', SUBSTRING(@streng,@i+1,8000)),0) END RETURN
RTRIM(LTRIM(UPPER(@resultat))) /* SELECT
dbo.FørsteBogstavOgHeleTal(null); SELECT
dbo.FørsteBogstavOgHeleTal(''); SELECT
dbo.FørsteBogstavOgHeleTal('J'); SELECT
dbo.FørsteBogstavOgHeleTal('Joakim'); SELECT
dbo.FørsteBogstavOgHeleTal('Joakim Dalby'); SELECT
dbo.FørsteBogstavOgHeleTal('Joakim von and Dalby'); SELECT
dbo.FørsteBogstavOgHeleTal('JoakimVonAndDalby'); SELECT
dbo.FørsteBogstavOgHeleTal('
Ingen ko på isen 1966 æ ø
å '); SELECT
dbo.FørsteBogstavOgHeleTal('
Ingen ko på isen 1 9 6 6
æ ø
å '); */ END GO COLLATE
Danish_Norwegian_CS_AS kan også være COLLATE DATABASE_DEFAULT hvis SQL Server
instansen er opsat til at være Danish_Norwegian_CS_AS. White space trailing space tegn Mystiske tegn kan komme i slutningen af
tekst strenge og gemme sig i slutningen af et varchar eller nvarchar tekst
felt eksempelvis et Adresse felt med tegn som: CHAR(0), CHAR(254), Horizontal
Tab CHAR(9), Line Feed CHAR(10), Vertical Tab CHAR(11), Form Feed CHAR(12), Carriage
Return CHAR(13), Column Break CHAR(14), Undo Ctrl+Z CHAR(26), Escape CHAR(27)
eller Non-breaking space CHAR(160). Carriage Return (\r) and Line Feed (\n): Char(13)+Char(10). Alt+0160 at
numeric keyboard giver et Non-breaking
space (No-Break Space) som kan erstattes til en tom streng:
Replace(<StringColumn>,CHAR(160),''). De mystiske tegn tæller ikke med Len
funktionen på et felt men er med i DATALENGTH funktionen der tæller antal
byte og kan ses med Cast til varbinary(max) og data vaskes væk med Substring,
eksempelvis: 0x4D0065007200630075007200760065006A002000200020002000 M e
r c u
r v e
j White spaces starter 4D
i hex = 77 i dec = M i ascii tabellen. TRIM([Adresse])
fjerner white space i nye sql server versioner ligesom C# Trim. Tidligere i Sql
kunne det gøres: SELECT CAST(Adresse AS varbinary(max)) AS
Indhold,
SUBSTRING(Adresse,1,LEN(Adresse)) AS Adresse_Renset FROM dbo.Kunde WHERE (LEN(Adresse) * 2) <>
DATALENGTH(Adresse) Trim
til den tomme streng (empty string) og lav den om til null: nullif(rtrim(ltrim(ADDRESS_LINE)),'') as
ADDRESS_LINE og
brug det afledte felt efter dens data rens og data vask på normal måde f.eks.: where ADDRESS_LINE is not null. TRIM(REPLACE(Adresse, CHAR(0x00), '')) WHERE column LIKE CONCAT("%", CHAR(0x00
using utf8), "%"); Brugerdefineret valideringskrav Constraints eller valideringskrav for
felter i en tabel i databasen, eksempelvis at når SlutDato feltet er udfyldt,
så skal StartDato feltet også være udfyldt: [SlutDato] IS NULL OR ([SlutDato] IS NOT NULL AND
[StartDato] IS NOT NULL) Slutdatoen
skal være større end eller lig med statdatoen: [SlutDato] IS NULL OR ([SlutDato] IS NOT NULL AND
[StartDato]<=[SlutDato]) Er email adressen gyldig eller ugyldig
syntaks mæssigt declare
@email nvarchar(255) = 'hej.med@dig.dk' select
case when @email like '%_@_%.__%' then cast(1 as bit) else cast(0 as bit) end
as ValidEmail Cast er nødvendig for ellers er data
typen int og går data gennem en SSIS pipeline til en destination tabel med en
bit kolonne, så kan SSIS vise gul advarselstrekant om at truncate værdien.
Det gælder især for en streng efter en concat m.fl. Udtrække et c/o navn fra tre adresse
felter med flere WHEN sætninger I
en af tre adresse felter kan der stå 'c/o Jens Jensen' og navnet ønskes ved
at erstatte c/o med tom string og så efterfølgende trimme værdien: select case when left(adresse1,3) = 'c/o' then
rtrim(ltrim(replace(adresse1,'c/o',''))) when left(adresse2,3) =
'c/o' then rtrim(ltrim(replace(adresse2,'c/o',''))) when left(adresse3,3) = 'c/o' then
rtrim(ltrim(replace(adresse3,'c/o',''))) end as CoNavn Når en when betingelse er opfyldt, så udføres
then delen og derefter hoppes ud (break) af case sætningen, d.v.s. de
andre when betingelser springes over. Cannot resolve
collation conflict for equal to operation (Temporary Table) Når tempdb og en normal database ikke
anvender samme sprog collation, normalt i Danmark: Danish Norwegian CI AS og
en stored procedure opretter en temporær # tabel og anvender tabellen sammen
med en tabel i den normale database, så får man fejlmelding, hvis man glemmer
at angive Danish_Norwegian_CI_AS ud for et tekst felt: CREATE
TABLE #TEMP ( [KundeNavn] [nvarchar](50) COLLATE
Danish_Norwegian_CI_AS NOT NULL , [Projektnummer] [int] NOT NULL ) ON
[PRIMARY] UPDATE dbo.KUNDE SET
Projektnummer = #TEMP.Projektnummer FROM
dbo.KUNDE
INNER JOIN #TEMP ON #TEMP.KundeNavn = dbo.KUNDE.KundeNavn Normalt sættes der ikke dbo. foran #
tabeller, og de behøver heller ikke at blive slettet før oprettelsen på
denne måde men det kan være en god idé: IF
OBJECT_ID('tempdb..#TEMP') IS NOT NULL DROP TABLE #TEMP I SQL Server 2016 brug DIE: DROP TABLE IF
EXISTS #TEMP SQL Server sætter en masse understreger
og nummer efter #TEMP så den er unik per session d.v.s. to forespørgsler
laver deres egne temp tabeller, ses således: SELECT * FROM tempdb..sysobjects Tabeller med hver sin collation Når man ønsker at joine to tabeller
sammen fra hver sin database med hver sin collation, kan man i ON delen tvinge
en collation igennem således: ON
A.Navn = B.Navn COLLATE Danish_Norwegian_CI_AS ON
A.Navn = B.Navn COLLATE
DATABASE_DEFAULT COLLATE Danish_Norwegian_CS_AS kan også være COLLATE
DATABASE_DEFAULT hvis SQL Server instansen er opsat til at være Danish_Norwegian_CS_AS. Husk,
at man godt kan anvende funktioner i en ON del af en INNER JOIN: ON
A.Navn = CAST(SUBSTRING(B.Navn, 5, 8) AS nvarchar(8)) COLLATE
Danish_Norwegian_CI_AS Anvend
samme collation fra sql server default på begge felter fra to databaser: ON IMP.dbo.KUNDE.Status
COLLATE DATABASE_DEFAULT = ERP.dbo.KUNDE.Status
COLLATE DATABASE_DEFAULT Collations
anvendes til sortering (ordering) og sammenligning (comparing) af strenge
(strings), tekster. Tal er ikke omfattet en collation. Datarække sortering via et clustered
indeks som er non-unique Et
clustered indeks bestemmer lagringen af data rækkerne i en tabel, det kan
være praktisk når en tabel har ikke unikke rækker og derfor ikke en
primærnøgle, men for at sikre en sorteringsorden af rækkerne, får tabellen et
clustered index som er non-unique: create clustered index
[CIX_Fil_Nummer_Navn] on [dbo].[Fil] ([Nummer] asc, [Navn] asc )with (pad_index = off, statistics_norecompute
= off, sort_in_tempdb = off, drop_existing = off, online = off, allow_row_locks = on, allow_page_locks = on) on [PRIMARY] Reindeksering løste en gang et problem DBCC DBREINDEX ('dbo.DIMENSIONDATA') Harddisk
plads forbrug af tempdb SELECT
[Source] = 'database_files', [TEMPDB_max_size_MB] = SUM(max_size) * 8
/ 1027.0, [TEMPDB_current_size_MB] = SUM(size) * 8 /
1027.0, [FileCount] = COUNT(FILE_ID) FROM
tempdb.sys.database_files WHERE type = 0 SELECT [FileSizeMB] =
convert(numeric(10,2),round(a.size/128.,2)), [UsedSpaceMB] = convert(numeric(10,2),round(fileproperty(
a.name,'SpaceUsed')/128.,2)) , [UnusedSpaceMB] =
convert(numeric(10,2),round((a.size-fileproperty(
a.name,'SpaceUsed'))/128.,2)) , [DBFileName] = a.name FROM
tempdb..sysfiles a SELECT
* FROM
tempdb.sys.tables t Flytning
af tempdb USE
master GO ALTER
DATABASE tempdb MODIFY FILE (NAME = tempdev, FILENAME = 'D:\SQLData\tempdb.mdf') GO ALTER
DATABASE tempdb MODIFY FILE (NAME = templog, FILENAME = 'E:\SQLLogs\tempdb.ldf') GO Der er en række tiltag man bør tage for
at optimere anvendelsen af tempdb afhængig af antal CPU: http://msdn.microsoft.com/en-us/library/ms175527.aspx http://searchsqlserver.techtarget.com/tip/0,289483,sid87_gci1307255,00.html Tøm tempdb DBCC
FREEPROCCACHE DBCC Shinkfile (tempdb) DBCC UPDATEUSAGE (tempdb) Oprettelse
af schema IF NOT
EXISTS( SELECT
schema_name FROM
INFORMATION_SCHEMA.SCHEMATA WHERE
schema_name = 'MySchema') BEGIN CREATE SCHEMA MySchema AUTHORIZATION dbo END Grouping
eksempler GROUPING
SETS TO MAKE GRAND TOTAL SELECT
c.CustomerName, CONVERT(CHAR(7), l.Loan_date, 126) AS YM, SUM(l.Loan_amount) AS Loans FROM
Customers AS c INNER JOIN Loans AS l ON l.CustomerId =
c.CustomerId GROUP BY GROUPING
SETS ((c.CustomerName,
CONVERT(CHAR(7), l.Loan_date, 126)),(c.CustomerName),(CONVERT(CHAR(7),
l.Loan_date, 126)),()) Laver sum per customerId og en grand total: SELECT
COALESCHE(CAST(CustomerId AS varchar(10)), 'Total'), COALESCHE(CAST(YEAR(loan_date) AS
varchar(10)), 'Subtotal'), SUM(Loan_amount) FROM
Loans GROUP BY
CustomerId, YEAR(loan_date) WITH ROLLUP Men
ovenstående problem hvis der optræder NULL i data, derfor: SELECT
CASE WHEN GROUPING(CustomerId) = 1 THEN 'Total' ELSE CAST(CustomerId AS
varchar(10)) END AS Customer CASE WHEN GROUPING(YEAR(loan_date)) =
1 THEN 'Subtotal' ELSE CAST(YEAR(loan_date) AS varchar(10)) END AS Loan_year SUM(Loan_amount) as total_loans FROM
Loans GROUP BY
CustomerId, YEAR(loan_date) WITH
ROLLUP Sum per CustomerId eller sum per Year: SELECT
CustomerId, YEAR(loan_date), SUM(Loan_amount) FROM
Loans GROUP BY
CustomerId, YEAR(loan_date) WITH
CUBE SELECT
CustomerId, Year(loan_ate), sum(loan_amount) FROM
loans GROUP BY
ROLLUP(CustomerId, year(loan_date)) SELECT
CustomerId, Year(loan_ate), sum(loan_amount) FROM
loans GROUP BY
CUBE(CustomerId, year(loan_date)) SELECT
CustomerId, Year(loan_ate), sum(loan_amount) FROM
loans GROUP BY
GROUPING SETS((CustomerId, year(loan_date)), (CustomerId),()) SELECT
CustomerId, Year(loan_ate), sum(loan_amount) FROM
loans GROUP BY
GROUPING SETS((CustomerId, year(loan_date)), ROLLUP(YEAR(loan_date)))) SELECT
CustomerId, CASE
GROUPING_ID(CustomerId, year(loan_amount)) WHEN 0 THEN CAST(YEAR(Loan_date) as
varchar(4)) -- binary 00 WHEN 1 THEN 'Subtotal' -- 01 WHEN 3 THEN 'Grand total' -- 11 END AS
Loan_year, SUM(Loan_amount) as total_loan GROUP BY
ROLLUP(CustomerId, year(loan_date)) Kontrol
af modtaget fil, her en backup database fil fra en anden sql server: CREATE PROCEDURE [dbo].[Backup_Fil] AS BEGIN BEGIN TRY SET NOCOUNT ON DECLARE @Now datetime =
GETDATE() IF EXISTS (SELECT 1 FROM
tempdb.INFORMATION_SCHEMA.TABLES WHERE CHARINDEX
('#DirFileInfo', TABLE_NAME) = 1) DROP TABLE #DirFileInfo CREATE TABLE #DirFileInfo
(DirInfo NVARCHAR(256)) INSERT INTO #DirFileInfo EXEC master.dbo.xp_cmdshell
'dir Z:\Backup\*.bak | findstr .bak' INSERT INTO dbo.LOG_BACKUP_FIL(DatabaseId,
Modtagelsestidspunkt, Filnavn, Filtidspunkt,
Filstørrelse) SELECT DatabaseId, Modtagelsestidspunkt =
@Now, Filnavn = REVERSE(SUBSTRING(REVERSE(DirInfo), 0, CHARINDEX(' ',
REVERSE(DirInfo)))), Filtidspunkt = CAST(LEFT(DirInfo,20) AS datetime), Filstørrelse = CAST(REPLACE(REVERSE(SUBSTRING(SUBSTRING(REVERSE(DirInfo),
CHARINDEX(' ', REVERSE(DirInfo))+1, LEN(DirInfo)),1,CHARINDEX(' ',
SUBSTRING(REVERSE(DirInfo), CHARINDEX(' ', REVERSE(DirInfo))+1,
LEN(DirInfo))))),',','') AS bigint) FROM #DirFileInfo INNER JOIN dbo.[DATABASE] db ON db.Filnavn =
REVERSE(SUBSTRING(REVERSE(DirInfo), 0,
CHARINDEX(' ', REVERSE(DirInfo)))) WHERE DirInfo IS NOT NULL AND
db.BackupIndlæs = 1 END TRY BEGIN CATCH EXEC dbo.Status_Fejl
'Registrering af dagens backup filer fejlede (sp Backup_Fil).' RAISERROR('Registrering af
dagens backup filer fejlede (sp Backup_Fil).', 2, 1) END CATCH END Efter
restore af backup database filen hentes data om databasen: CREATE PROCEDURE [dbo].[DatabaseLastBackup] AS BEGIN SET NOCOUNT ON SELECT bs.database_name AS
DatabaseName, MAX(bms.physical_device_name) AS FullBackupName,
bs.backup_finish_date as BackupTime FROM msdb.dbo.backupset bs INNER JOIN
msdb.dbo.backupmediafamily bms ON bs.media_set_id = bms.media_set_id INNER JOIN
master.dbo.sysdatabases s ON bs.database_name = s.name WHERE CONVERT(VARCHAR(20),
bs.backup_finish_date, 101) = CONVERT(VARCHAR(20), GETDATE(), 101) AND s.name NOT
IN ('master', 'msdb', 'model', 'pubs', 'Northwind') AND bs.type = 'D' GROUP BY bs.database_name,
bs.backup_finish_date END Linked server Smart
til at overføre data fra andre databasesystemer, men kræver en del i opsætningen,
hvor Microsoft bl.a. skriver, når forbindelsen ikke virker fra klient PC via SQL Server Management Studio: When connecting to another data source, SQL Server
impersonates the login appropriately for Windows authenticated logins;
however, SQL Server cannot impersonate SQL Server authenticated logins.
Therefore, for SQL Server authenticated logins, SQL Server can access another
data source, such as files, nonrelational data sources like Active Directory,
by using the security context of the Windows account under which the SQL
Server service is running. Doing this can potentially give such logins access
to another data source for which they do not have permissions, but the
account under which the SQL Server service is running does have permissions.
This possibility should be considered when you are using SQL Server
authenticated logins. Instead of having to use sp_addlinkedsrvlogin to create
a predetermined login mapping, SQL Server can automatically use the Windows
security credentials (Windows login name and password) of a user issuing
the query to connect to a linked server when all the following conditions
exist: • A user is connected to SQL Server by using Windows
Authentication Mode. • Security account delegation is available on the
client and sending server. • The provider supports Windows Authentication Mode;
for example, SQL Server running on Windows. Delegation does not have to be enabled for single-hop
scenarios, but it is required for multiple-hop scenarios. Ovenstående
kræver en del for at få fjernet eksempelvis følgende fejlmelding fra linked
server til en IBM DB2 database: Msg 7399, Level 16, State 1, Line 2 The OLE DB
provider "IBMDADB2" for linked server "<linked server
name>" reported an error. Access denied. Msg 7350, Level 16, State 2, Line 2 Cannot get the
column information from OLE DB provider "IBMDADB2" for linked
server "<linked server name>". En
metode er at anvende Local System som bruger af servicen SQL
Server (MSSQLSERVER) (Database Engine service) og kun have en ægte service
account på servicen for SQL Agent Jobs, hvilket er nødvendigt, hvis
indlæsningen af data sker på en DSA server og senere skal overføres til en
EDW server, hvis overførslen ikke skal ske via en SQL User account i
IS-pakken, men det anbefales normalt ikke at anvende fordi kontoens password
skal indtastes og gemmes. Eksempel
på en database server kaldet smartserver med en database kaldet Kalender
hvori der er en scalar-valued function kaldet AntalHverdage som har to dato indparametre
der bliver kaldt via linked servers på en anden database server: IF OBJECT_ID('tempdb..#output') IS NOT NULL DROP TABLE #output DECLARE @Fradato nvarchar(10) = '20170207' DECLARE @Tildato nvarchar(10) = '20170816' DECLARE @AntalHverdage as int DECLARE @sql as nvarchar(255) CREATE TABLE #output (Værdi nvarchar(50) NULL) SET @sql = 'SELECT
Kalender.dbo.AntalHverdage('''''+
@Fradato +''''',
'''''+ @Tildato +''''')' SET
@sql = 'SELECT * FROM
OPENQUERY([SMARTSERVER], '''+ @sql +''')' INSERT INTO #output EXECUTE (@sql) SELECT @AntalHverdage =
Værdi FROM #output SELECT @AntalHverdage Overførsel af data fra views på en linked server til en modtager server database kaldet AnalysisArea
der automatisk opretter en tabel med primærnøgle for hvert et view. --
============================================= -- Author:
Joakim Dalby -- Create date: 2022-10-19 in the
database AnalysisArea -- SQL job: Transfer_CustomerMart -- Description: Transfer all dimension
and fact data from views in a schema in parameter -- @SourceSchema into several on-the-fly
created tables in schemas dim and fact. -- Changelog: -- yyyy-mm-dd Developer
name..... when there is a change. -- ============================================= CREATE PROCEDURE [etl].[Views_to_Tables] @SourceSchema nvarchar(255) = 'CustomerMart', @PrimaryKeySuffix nvarchar(255) = '_key' AS BEGIN SET NOCOUNT ON; DECLARE @Scan CURSOR DECLARE @SourceTable nvarchar(255) DECLARE @TargetSchema nvarchar(255) DECLARE @TargetTable nvarchar(255) DECLARE @TargetTablePrimaryKey nvarchar(255) DECLARE @sqlTempleteTable nvarchar(MAX) DECLARE @sqlTempletePrimaryKey nvarchar(MAX) DECLARE @sqlExecute nvarchar(MAX) BEGIN TRY SET @sqlTempleteTable = N' DROP TABLE IF
EXISTS [<TargetSchema>].[<TargetTable>] SELECT *,
InsertTime_UTC_meta = sysutcdatetime() INTO
[<TargetSchema>].[<TargetTable>] FROM [<linkedserver>].[SalesDatawarehouse].[<SourceSchema>].[<SourceTable>]' SET @sqlTempletePrimaryKey = N' ALTER TABLE
[<TargetSchema>].[<TargetTable>] ADD CONSTRAINT
PK_<TargetSchema>_<TargetTable> PRIMARY KEY CLUSTERED
(<TargetTablePrimaryKey>)' SET @Scan = CURSOR LOCAL FORWARD_ONLY DYNAMIC FOR --SELECT
SourceSchema = QUOTENAME(TABLE_SCHEMA), SourceTable = QUOTENAME(TABLE_NAME)
-- because there are [ ] in the template. SELECT SourceSchema = TABLE_SCHEMA, SourceTable = TABLE_NAME
FROM [<linkedserver>].[SalesDatawarehouse].INFORMATION_SCHEMA.VIEWS
WHERE TABLE_SCHEMA = @SourceSchema
ORDER BY TABLE_NAME OPEN @Scan FETCH NEXT FROM @Scan INTO @SourceSchema, @SourceTable WHILE @@FETCH_STATUS = 0 BEGIN IF CHARINDEX('v_Dim_', @SourceTable) != 0 -- example of @SourceTable: [v_Dim_Branche] BEGIN SET @TargetSchema = 'dim' SET @TargetTable = REPLACE(@SourceTable, 'v_Dim_', '') END IF CHARINDEX('v_Fact_', @SourceTable) != 0 -- example of @SourceTable: [v_Fact_Sales] BEGIN SET @TargetSchema = 'fact' SET @TargetTable = REPLACE(@SourceTable, 'v_Fact_', '') END SET @sqlExecute = @sqlTempleteTable SET @sqlExecute = REPLACE(@sqlExecute, '<TargetSchema>', @TargetSchema) SET @sqlExecute = REPLACE(@sqlExecute, '<TargetTable>' , @TargetTable) SET @sqlExecute = REPLACE(@sqlExecute, '<SourceSchema>', @SourceSchema) SET @sqlExecute = REPLACE(@sqlExecute, '<SourceTable>' , @SourceTable) --PRINT @sqlExecute EXECUTE (@sqlExecute) --EXECUTE sp_executesql @sqlExecute SET @TargetTablePrimaryKey = NULL SELECT @TargetTablePrimaryKey = STRING_AGG(QUOTENAME(COLUMN_NAME), ',') WITHIN GROUP (ORDER BY QUOTENAME(COLUMN_NAME) ASC) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = @TargetSchema AND TABLE_NAME = @TargetTable AND
RIGHT(COLUMN_NAME, LEN(@PrimaryKeySuffix)) = @PrimaryKeySuffix IF @TargetTablePrimaryKey IS NOT NULL BEGIN SET @sqlExecute = @sqlTempletePrimaryKey SET @sqlExecute = REPLACE(@sqlExecute, '<TargetSchema>', @TargetSchema) SET @sqlExecute = REPLACE(@sqlExecute, '<TargetTable>' , @TargetTable)
SET @sqlExecute = REPLACE(@sqlExecute, '<TargetTablePrimaryKey>', @TargetTablePrimaryKey) --PRINT @sqlExecute EXECUTE (@sqlExecute) --EXECUTE sp_executesql @sqlExecute END FETCH NEXT FROM @Scan INTO @SourceSchema, @SourceTable END CLOSE @Scan DEALLOCATE @Scan END TRY BEGIN CATCH DECLARE @ErrorMessage nvarchar(4000), @ErrorSeverity int, @ErrorState int SELECT @ErrorMessage = ERROR_MESSAGE(), @ErrorSeverity = ERROR_SEVERITY(),
@ErrorState = ERROR_STATE() RAISERROR(@ErrorMessage, @ErrorSeverity, @ErrorState) WITH NOWAIT END CATCH END Stored proceduren etl.Views_to_Tables ønskes kaldt
fra en SQL Server Agent job Transfer_CustomerMart og når der anvendes en
linked server skal det opsættes på følgende måde: 1. Etablering af AD servicekonto der får adgang til at
læse data fra views på linked server databasen og skrive data på modtager
server databasen AnalysisArea. 2. På modtager serveren under Security oprettes en
Credential for servicekontoen. 3. Under SQL Server Agent under Proxies oprettes en Proxy som Operating System (CmdExec) der anvender den oprettede Credential. 4. Der oprettes et SQL Server Agent job med et step med
Type Operating System (CmdExec) og Run as den oprettede Proxy og i Command
angives: SQLCMD -I -b -S $(ESCAPE_DQUOTE(SRVR)) -d AnalysisArea
-Q"EXEC etl.Views_to_Tables" 5. En Schedules oprettes til at køre jobbet en gang om
dagen. Overførsel af mange
millioner rækker ved at tage en dato ad gangen CREATE TABLE [audit].[CCI_Log]( [CCI_Log_Id] [int] IDENTITY(1,1) NOT NULL, [DateOfOrder_UTC] [date] NOT NULL, [InsertTime_UTC]
[datetime2](3) NOT NULL, [UpdateTime_UTC]
[datetime2](3) NULL, [Duration] AS (CONVERT([time](0),dateadd(millisecond, datediff(millisecond,[InsertTime_UTC], [UpdateTime_UTC]),(0)))) PERSISTED, [Status] [varchar](10) NULL, CONSTRAINT [PK_audit_CCI_Log] PRIMARY KEY CLUSTERED ( [CCI_Log_Id] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [AUDIT] ) ON [AUDIT] GO CREATE UNIQUE NONCLUSTERED INDEX [UIX_audit_CCI_Log_DateOfOrder_UTC] ON [audit].[CCI_Log] ( [DateOfOrder_UTC] ASC -- unique key
(UK). )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF, DATA_COMPRESSION = PAGE) ON [AUDIT] GO ALTER TABLE [audit].[CCI_Log] ADD CONSTRAINT [DF_audit_CCI_Log_InsertTime_UTC] DEFAULT (sysutcdatetime()) FOR [InsertTime_UTC] GO CREATE PROCEDURE [audit].[CCI_Insert] AS BEGIN DECLARE @CCI_Log_Id int DECLARE @DateOfOrder_UTC date DECLARE @Scan CURSOR DECLARE @TranCounter int DECLARE @SavePoint NVARCHAR(32) = CAST(@@PROCID AS NVARCHAR(20)) + N'_' + CAST(@@NESTLEVEL AS NVARCHAR(2)) SET NOCOUNT ON BEGIN TRY INSERT INTO [audit].[CCI_Log] WITH(TABLOCK) ([DateOfOrder_UTC]) SELECT DISTINCT DateOfOrder_UTC FROM base.Orders b WITH(NOLOCK) WHERE NOT EXISTS(SELECT 1 FROM [audit].[CCI_Log] a WHERE a.DateOfOrder_UTC = b.DateOfOrder_UTC) ORDER BY DateOfOrder_UTC
SET @Scan = CURSOR LOCAL FORWARD_ONLY DYNAMIC FOR SELECT CCI_Log_Id, DateOfOrder_UTC FROM [audit].[CCI_Log] WITH(NOLOCK) WHERE [Status] IS NULL ORDER BY DateOfOrder_UTC OPEN @Scan FETCH NEXT FROM @Scan INTO @CCI_Log_Id, @DateOfOrder_UTC WHILE @@FETCH_STATUS = 0 BEGIN SET @TranCounter = @@TRANCOUNT SET @SavePoint = CAST(@@PROCID AS NVARCHAR(20)) + N'_' + CAST(@@NESTLEVEL AS NVARCHAR(2)) IF @TranCounter > 0
SAVE TRANSACTION @SavePoint ELSE
BEGIN TRANSACTION SET IDENTITY_INSERT [base].[OrdersCCI] ON INSERT INTO [base].[OrdersCCI] WITH(TABLOCK)
([Orders_Id]
,[InsertTime_UTC_meta] ,[RecordSource_meta_SourceFile_Log_Id]
,[Area_meta]
,[DateOfBusiness_meta]
,[DateOfOrder_UTC]
,[SubmittingEntity]
,[Client]
,[SequenceNumber]
,[FinancialInstrument] ,[OrderEvent]
,[TransactionPrice]
,[TransactionPrice_NOAP]) SELECT
[Orders_Id] ,[InsertTime_UTC_meta]
,[RecordSource_meta_SourceFile_Log_Id]
,[Area_meta]
,[DateOfBusiness_meta]
,[DateOfOrder_UTC]
,[SubmittingEntity]
,[Client]
,[SequenceNumber]
,[FinancialInstrument] ,[OrderEvent]
,[TransactionPrice]
,[TransactionPrice_NOAP] FROM base.Orders WITH(NOLOCK) WHERE DateOfOrder_UTC = @DateOfOrder_UTC
SET IDENTITY_INSERT [base].[OrdersCCI] OFF UPDATE [audit].[CCI_Log] SET [Status] = 'Success',
[UpdateTime_UTC] = sysutcdatetime() WHERE CCI_Log_Id = @CCI_Log_Id IF @TranCounter = 0
COMMIT TRANSACTION
FETCH NEXT FROM @Scan INTO @CCI_Log_Id, @DateOfOrder_UTC END CLOSE @Scan DEALLOCATE @Scan END TRY BEGIN CATCH IF @TranCounter = 0
ROLLBACK TRANSACTION ELSE IF XACT_STATE() = 1
ROLLBACK TRANSACTION @SavePoint ELSE IF XACT_STATE() = -1
ROLLBACK TRANSACTION ;THROW END CATCH END GO Sammenligne tabeller
fra to databaser til afstemning – Compare tables De to databaser kan
repræsenterer gammel produktion og ny produktion for at afstemme, om data er
ens. I hver database for dens alle tabellens rækker summerer jeg alle dens
tal og bit kolonner sammen, så hver tabel får en sum værdi, som jeg gemmer i
en tabel, og de to tabeller fra gammel og ny produktion kan jeg sammenligne i
Excel for at finde ud, om en eller flere tabeller har forskellige summer.
Hver tal og bit kolonne bliver konverteret til data type numeric(38,10) og er
summen null laves værdien om til 0, så kolonnerne kan lægges sammen + : CREATE PROCEDURE [dbo].[Reconciliation_Make] AS BEGIN SET NOCOUNT ON DECLARE @TableSchema nvarchar(255) = 'dbo' DECLARE @TableName nvarchar(255) DECLARE @ColumnList nvarchar(max) DECLARE @sqlTempleteTable nvarchar(MAX) DECLARE @sqlExecute nvarchar(MAX) DECLARE @Scan CURSOR TRUNCATE TABLE [dbo].[ReconciliationCheck] SET @sqlTempleteTable =
N' INSERT
INTO [dbo].[ReconciliationCheck] (TableName, Sum) SELECT
TableName = ''<Table>'', <ColumnList> FROM
[ERP].[<Schema>].[<Table>]' SET @Scan = CURSOR LOCAL FORWARD_ONLY DYNAMIC
FOR SELECT
TABLE_NAME FROM ERP.INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_SCHEMA
= @TableSchema OPEN @Scan FETCH NEXT FROM @Scan INTO
@TableName WHILE @@FETCH_STATUS
= 0 BEGIN SET @ColumnList = NULL SET @ColumnList = SUBSTRING( (SELECT CONCAT('+','ISNULL(SUM(','CONVERT(numeric(38,10),', QUOTENAME(COLUMN_NAME),')' ,'),0)') FROM ERP.INFORMATION_SCHEMA.COLUMNS WHERE DATA_TYPE IN('decimal','numeric','float', 'real','smallmoney','money', 'tinyint','smallint','int','bigint','bit') AND TABLE_SCHEMA =
@TableSchema AND TABLE_NAME = @TableName ORDER BY
ORDINAL_POSITION FOR XML PATH('') ), 2, 99999999) -- STRING_AGG came in SQL SERVER 2017 SELECT @ColumnList =
STRING_AGG(CONVERT(varchar(max), CONCAT('ISNULL(SUM(', CASE WHEN DATA_TYPE
= 'bit' THEN CONCAT('CONVERT(numeric(38,10),', QUOTENAME(COLUMN_NAME),')') ELSE CONCAT('CONVERT(numeric(38,10),', QUOTENAME(COLUMN_NAME),')') END ,'),0)')),'+') WITHIN GROUP (ORDER BY ORDINAL_POSITION ASC) FROM ERP.INFORMATION_SCHEMA.COLUMNS WHERE DATA_TYPE IN('decimal','numeric','float', 'real','smallmoney','money', 'tinyint','smallint','int','bigint','bit') AND TABLE_SCHEMA =
@TableSchema AND TABLE_NAME = @TableName SET @sqlExecute =
@sqlTempleteTable SET @sqlExecute = REPLACE(@sqlExecute, '<Schema>' , @TableSchema) SET @sqlExecute = REPLACE(@sqlExecute, '<Table>' ,
@TableName) SET @sqlExecute = REPLACE(@sqlExecute, '<ColumnList>'
, @ColumnList) --PRINT @sqlExecute EXECUTE (@sqlExecute) FETCH NEXT FROM @Scan INTO
@TableName END CLOSE @Scan DEALLOCATE @Scan END Tabel ReconciliationCheck
eksempelvis:
Transactions and Concurrency nogle spændende links https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/transactions-and-concurrency https://docs.microsoft.com/en-us/sql/relational-databases/sql-server-transaction-locking-and-row-versioning-guide?view=sql-server-2017 https://sqlperformance.com/2014/04/t-sql-queries/the-serializable-isolation-level https://docs.microsoft.com/en-us/sql/t-sql/queries/hints-transact-sql-table?view=sql-server-2017 Parallel sql
nogle spændende links https://stackoverflow.com/questions/4571823/sql-server-tsql-is-it-possible-to-exec-statements-in-parallel http://rusanu.com/2009/08/05/asynchronous-procedure-execution/ https://www.sqlservercentral.com/Forums/Topic1809449-392-2.aspx http://www.sqlnotes.info/2015/12/03/run-t-sql-in-parallel/ SQL
Server 2022 nyheder STRING_SPLIT rækkefølgenummer med parameter 1, se eksempler ovenfor. IS [NOT]
DISTINCT FROM Kunde tabel
hvor kategori kan være NULL eller tom d.v.s. ikke udfyldt grundet ukendt
kategori, eksempelvis:
Udsøgning
via en variabel med en tildelt værdi: DECLARE @Kategori varchar(50) = 'God' SELECT * FROM dbo.Kunde WHERE KundeKategori = @Kategori finder de
Gode. Er
variablens værdi NULL eller tom lader ovenstående sig ikke gøre: DECLARE @Kategori varchar(50) = NULL fordi det
kræver en anden sætningsopbygning for at finde de tomme: WHERE KundeKategori IS NULL eller en
tredje sætningsopbygning for at finde de udfyldte, d.v.s. ikke tom: WHERE KundeKategori IS NOT NULL I kombination
d.v.s. null eller en søgeværdi: DECLARE @Kategori varchar(50) = NULL --'God' SELECT * FROM dbo.Kunde WHERE KundeKategori = @Kategori OR (@Kategori IS NULL AND KundeKategori IS NULL) Med et nyt
udtryk opnår vi samme sætningsopbygning uanset variablens værdi, Two Value
Logic IS NOT DISTINCT, er ikke forskellig fra som håndterer NULL. DECLARE @Kategori varchar(50) = 'God' SELECT * FROM dbo.Kunde WHERE KundeKategori IS NOT DISTINCT FROM @Kategori -- NOT DISTINCT er lig med God finder de Gode. DECLARE @Kategori varchar(50) = NULL SELECT * FROM dbo.Kunde WHERE KundeKategori IS NOT DISTINCT FROM @Kategori -- NOT DISTINCT er lig med NULL og
finder de tomme/NULL fordi NULL behandles som lig med. DECLARE @Kategori varchar(50) = 'God' SELECT * FROM dbo.Kunde WHERE KundeKategori IS DISTINCT FROM @Kategori -- DISTINCT er
forskellig fra God finder også
de tomme/NULL. DECLARE @Kategori varchar(50) = NULL SELECT * FROM dbo.Kunde WHERE KundeKategori IS DISTINCT FROM @Kategori -- DISTINCT er
forskellig fra NULL finder de
udfyldte (not null). I
kombination d.v.s. NULL eller en søgeværdi: DECLARE @Kategori varchar(50) = NULL --'God' SELECT * FROM dbo.Kunde k WHERE KundeKategori IS NOT DISTINCT FROM @Kategori Ved
sammenligning af to kolonner uden at tænke på om der er NULL værdier: SELECT * FROM dbo.Kunde k WHERE KundeKategori IS NOT DISTINCT FROM KundeType Left join
giver alle rækker fra Kunde tabel selvom KundeKategori er NULL i Kunde og der
ikke er en tilsvarende række i Ordre tabel: SELECT k.Kundenavn, o.Ordretekst FROM dbo.Kunde k LEFT OUTER JOIN dbo.Ordre o ON k.KundeKategori IS NOT DISTINCT FROM o.KundeKategori DATE_BUCKET
funktion Finder
første (1) dato i ugen, måned, år og tilsvarende for klokkeslæt: DECLARE @date datetime = '2023-05-10
16:06:50.989' SELECT DATE_BUCKET(day, 1, @date) -- 2023-05-10 00:00:00.000 SELECT DATE_BUCKET(week, 1, @date) -- 2023-05-08 00:00:00.000 SELECT DATE_BUCKET(month, 1, @date) -- 2023-05-01 00:00:00.000 SELECT DATE_BUCKET(quarter, 1, @date) -- 2023-04-01 00:00:00.000 SELECT DATE_BUCKET(year, 1, @date) -- 2023-01-01 00:00:00.000 SELECT DATE_BUCKET(millisecond, 1, @date) -- 2023-05-10 16:06:50.990 SELECT DATE_BUCKET(second, 1, @date) -- 2023-05-10 16:06:50.000 SELECT DATE_BUCKET(minute, 1, @date) -- 2023-05-10 16:06:00.000 SELECT DATE_BUCKET(hour, 1, @date)
-- 2023-05-10 16:00:00.000 Bucket size er 1, hvorved funktionen returnerer startdatoen
for angivet periode f.eks. week giver ugens første dato. Næste eksempel vil vise: Group dates into buckets
called Date_Bucket by
identify the first day of the week (or month, quarter, year) that a given date occurred in. Fordeling
af fakturaer ud fra deres fakturadato:
Funktionen
tager forskellige fakturadatoer inden for 1 uge og samler dem i en spand med
en bucket size på 1 week, d.v.s. fakturadatoerne bliver omdannet til datoen
for mandagen i pågældende uge, her kaldet FakturaUge og der tælles antal
fakturaer og unikke kunder inden for hver FakturaUge, hvorved der fås en
statistik per uge: SELECT FakturaUge = DATE_BUCKET(week, 1, FakturaDato), AntalUgerFrem
= 1,
AntalFaktura = COUNT(*), AntalKunder
= COUNT(DISTINCT KundeId) FROM dbo.Faktura GROUP BY DATE_BUCKET(week, 1, FakturaDato) -- alle datoer i en uge
bliver grupperet sammen. ORDER BY FakturaUge
Funktionen
tager forskellige fakturadatoer inden for 2 uger og samler dem i en spand med
en bucket size på 2 week, d.v.s. en større spand af datoer som giver en længere
periode og derved flere antal fakturarækker at lave statistik på: SELECT FakturaUge = DATE_BUCKET(week, 2, FakturaDato), AntalUgerFrem
= 2,
AntalFaktura = COUNT(*), AntalKunder
= COUNT(DISTINCT KundeId) FROM dbo.Faktura GROUP BY DATE_BUCKET(week, 1, FakturaDato) -- alle datoer i to
uger bliver grupperet sammen. ORDER BY FakturaUge
Window
function OVER clause angivet kun
en gang Summering
af fakturabeløb per uge og mange andre beregninger: SELECT DISTINCT FakturaUge = DATE_BUCKET(week, 1, FakturaDato), AntalUgerFrem = 1, UgentligtFakturaBeløb
= SUM(FakturaBeløb) OVER win, SamletFakturaBeløb
= SUM(FakturaBeløb) OVER (), ProcentAndel
= ISNULL(SUM(FakturaBeløb) OVER win / NULLIF(SUM(FakturaBeløb) OVER (),0),0) * 100, UgentligtAntalFaktura
= COUNT(1) OVER win, SamletAntalFaktura
= COUNT(1) OVER() FROM dbo.Faktura WINDOW win AS (PARTITION BY DATE_BUCKET(week, 1, FakturaDato)) ORDER BY FakturaUge
Forskellige
OVER clauses kan angives: WINDOW winasc
AS (PARTITION BY KundeSegment ORDER BY KundeNavn ASC), windesc
AS (PARTITION BY KundeSegment ORDER BY KundeNavn DESC) Windows Referencing Other Windows: WINDOW base
AS (PARTITION BY KundeSegment), was AS (base ORDER BY KundeNavn ASC),
windesc AS (base ORDER BY KundeNavn DESC) Eksempel på
en stored procedure med samme sætningsopbygning uanset parameterens værdi og
brug en fælles window function Over clause: CREATE PROCEDURE dbo.KundeStatistik (@Kategori varchar(50) = NULL) AS BEGIN SELECT DISTINCT FakturaUge
= DATE_BUCKET(week, 1, FakturaDato), AntalUgerFrem = 1, UgentligtFakturaBeløb = SUM(FakturaBeløb) OVER win, SamletFakturaBeløb = SUM(FakturaBeløb) OVER (), ProcentAndel = ISNULL(SUM(FakturaBeløb) OVER win / NULLIF(SUM(FakturaBeløb) OVER (),0),0) * 100, UgentligtAntalFaktura = COUNT(1) OVER win, SamletAntalFaktura = COUNT(1) OVER(), GennemsnitligFakturaBeløb = AVG(FakturaBeløb) OVER win, MindsteFakturaBeløb = MIN(FakturaBeløb) OVER win, StørsteFakturaBeløb = MAX(FakturaBeløb) OVER win FROM dbo.Faktura f INNER JOIN dbo.Kunde k ON k.KundeId = f.KundeId WHERE k.KundeKategori IS NOT DISTINCT FROM @Kategori WINDOW win AS (PARTITION BY DATE_BUCKET(week, 1, FakturaDato)) ORDER BY FakturaUge END EXEC dbo.KundeStatistik -- ingen
parameterværdi, så anvendes default som er NULL EXEC dbo.KundeStatistik 'God' Lave
numre SELECT value FROM GENERATE_SERIES(1, 5) -- START = 1, STOP = 5 SELECT value FROM GENERATE_SERIES(1, 32, 7) -- START = 1, STOP
= 32, STEP = 7 ORDER BY value DESC DECLARE @Start datetime = '20220501
09:00',
@End datetime = '20220501
17:00' SELECT [Hour] = DATE_BUCKET(HOUR, 1, DATEADD(HOUR, gs.value, @Start)), Number = gs.value FROM GENERATE_SERIES(0, DATEDIFF(HOUR, @Start, @End) - 1) AS gs
Minutterne
inden for et klokkelæt timetal: SELECT DATEADD(minute, value, '2023-04-01
10:00') AS [Interval] FROM GENERATE_SERIES(0, 59, 1) SELECT value FROM GENERATE_SERIES(0.0, 1.0, 0.01) Største
og Mindst værdi SELECT CASE WHEN 1 > 5 THEN 1 ELSE 5 END, GREATEST(1, 5), GREATEST(6, 2), GREATEST(NULL, 2), LEAST (1, 5), LEAST (6, 2), LEAST (6, NULL), CASE WHEN 1 > 5 THEN CASE WHEN 1 > 3 THEN 1 ELSE 3 END ELSE CASE WHEN 5 > 3 THEN 5 ELSE 3 END END, GREATEST(1, 5, 3), LEAST (6, 2, 1, 3)
SELECT GREATEST('B','A','C','N','M') SELECT GREATEST('2001-09-11', '2001-11-09') SELECT GREATEST('Michael', 'Mikael') SELECT GREATEST('a', 'å') Efter en
pivotering, hvor første kvartals måneder er blevet til kolonner: SELECT Year,
BestMonth = GREATEST([Jan],[Feb],[Mar]), WorstMonth = LEAST ([Jan],[Feb],[Mar]) FROM dbo.SummarizedSales DATETRUNC funktion – truncate/fetch from datetime
into a datetime format DECLARE @d datetime2(3) = Sysdatetime() SELECT DATETRUNC(year, @d), DATEPART(year, @d)
-- yyyy-01-01 00:00:00.000 eller yyyy SELECT DATETRUNC(month, @d), DATEPART(month, @d) -- yyyy-mm-01 00:00:00.000 eller mm SELECT DATETRUNC(iso_week, @d), DATEPART(iso_week, @d) -- yyyy-mm-dd 00:00:00.000 eller week SELECT DATETRUNC(day, @d), DATEPART(day, @d)
-- yyyy-mm-dd 00:00:00.000 eller dd SELECT DATETRUNC(hour, @d), DATEPART(hour, @d) -- yyyy-mm-dd hh:00:00.000 eller hh Jeg synes en trunkering til en
dag/day ikke burde inde et klokkeslæt for midnat, men det er nok for at
bevare datatype datetime fremfor nedenfor at lave det om til en ægte dato: DECLARE @d datetime2(3) = Sysdatetime() SELECT CAST(@d AS date) FIRST_VALUE,
LAST_VALUE, LAG, LEAD med RESPECT NULLS og IGNORE NULLS A NULL treatment clause. ·
RESPECT NULLS er default og funktionen kan returnere
NULL. ·
IGNORE NULLS får funktionen til at springe NULL over og
altid returnere non-NULL, men kan der ikke findes en non-NULL returneres en
NULL. SELECT bucket, bucketorder, bucketvalue, firstvalueX = FIRST_VALUE(bucketvalue) RESPECT
NULLS OVER (PARTITION BY T.bucket ORDER BY T.bucketorder ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING), lastvalueX = LAST_VALUE(bucketvalue) RESPECT
NULLS OVER (PARTITION BY T.bucket ORDER BY T.bucketorder ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING), firstvalueY =
FIRST_VALUE(bucketvalue) IGNORE NULLS OVER (PARTITION BY T.bucket ORDER BY T.bucketorder ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING), lastvalueY = LAST_VALUE(bucketvalue) IGNORE
NULLS OVER (PARTITION BY T.bucket ORDER BY T.bucketorder ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) FROM (VALUES ('A', 1, 'first'), ('A', 2, 'middle'), ('A', 3, 'last'), ('B', 1, NULL), ('B', 2, 'middle'), ('B', 3, 'last'), ('C', 1, 'first'), ('C', 2, 'middle'), ('C', 3, NULL), ('D', 1, NULL), ('D', 2, '2'), ('D', 3, '1'), ('D', 4, '3'), ('D', 5, NULL) ) T(bucket, bucketorder, bucketvalue)
TRIM udvidelse sammen med Left og Right TRIM ( [ LEADING | TRAILING | BOTH ] [characters
FROM ] string ) Angivelse
af en eller flere tegn/karakterer der skal fjernes/trimmes væk. SELECT A = LTRIM('The
Boat', 'The'), B = LTRIM('TheBoat', 'The'), C = LTRIM('Thethe
Boat', 'The'), D = LTRIM('Thy
Boat', 'The'), E = RTRIM('Boat
The', 'The'), F = RTRIM('BoatThe', 'The'), G = TRIM(BOTH 'The' FROM 'The Edge'), H = TRIM(BOTH 'The' FROM 'TheEdge'), I = LTRIM('The
Boat', 'The '), J = LTRIM('Mr. Michael', 'Mr.
')
A, C, E, G bibeholder
mellemrum, fordi det tegn var ikke angivet, men det er i I. H går helt
galt, fordi fjernelse af hver tre tegn t h e også gør det i Edge så det
bliver til dg. Det kunne være smart at fjerne Mr. men går galt i Michael i J. Statistical Window Functions PERCENTILE_CONT og APPROX_COUNT_DISTINCT. Bit Operations LEFT_SHIFT (), RIGHT_SHIFT (), BIT_COUNT (), GET_BIT
(), SET_BIT (). JSON
Features SQL
Server 2025 nyheder STRING_SPLIT udvidelse med REGEXP more SELECT * FROM REGEXP_SPLIT_TO_TABLE
('the quick brown fox', '\s+'); Value Ordinal the 1 quick 2 brown 3 fox 4 --------------------------- Tabelstruktur fra landingzone til
datavarehus med metadata felter Et kildesystem kan indeholde mange
tabeller f.eks. et kildesystem som allerede har udført en Dimensionel
modellering og opstillet data i dim og fact tabeller i et dbo schema. I et datavarehus
ønskes tabeller i et staging lag/område og i et mart lag/område fordelt på
forskellige schemas og tilføjelse af metadata felter. Her beskrives en metode til at få kreeret
tabellerne i datavarehuset. Jeg har oprettet et Utility schema til at
indeholde tabeller og stored procedures. De to tabeller er metadata om tabeller og
kolonner fra en landingzone database. CREATE TABLE [Utility].[LandingZone_INFORMATION_SCHEMA_COLUMNS]( [TABLE_CATALOG]
[nvarchar](128)
NULL, [TABLE_SCHEMA]
[nvarchar](128)
NULL, [TABLE_NAME]
[nvarchar](128)
NULL, [COLUMN_NAME]
[nvarchar](128)
NULL, [ORDINAL_POSITION]
[int] NULL, [COLUMN_DEFAULT]
[nvarchar](4000)
NULL, [IS_NULLABLE]
[varchar](3) NULL, [DATA_TYPE]
[nvarchar](128)
NULL, [CHARACTER_MAXIMUM_LENGTH]
[int] NULL, [CHARACTER_OCTET_LENGTH]
[int] NULL, [NUMERIC_PRECISION]
[tinyint] NULL, [NUMERIC_PRECISION_RADIX]
[smallint] NULL, [NUMERIC_SCALE]
[int] NULL, [DATETIME_PRECISION]
[smallint] NULL, [CHARACTER_SET_CATALOG]
[nvarchar](128)
NULL, [CHARACTER_SET_SCHEMA]
[nvarchar](128)
NULL, [CHARACTER_SET_NAME]
[nvarchar](128)
NULL, [COLLATION_CATALOG]
[nvarchar](128)
NULL, [COLLATION_SCHEMA]
[nvarchar](128)
NULL, [COLLATION_NAME]
[nvarchar](128)
NULL, [DOMAIN_CATALOG]
[nvarchar](128)
NULL, [DOMAIN_SCHEMA]
[nvarchar](128)
NULL, [DOMAIN_NAME]
[nvarchar](128)
NULL ) ON [PRIMARY] GO CREATE TABLE [Utility].[LandingZone_INFORMATION_SCHEMA_TABLES]( [TABLE_CATALOG]
[nvarchar](128)
NULL, [TABLE_SCHEMA]
[nvarchar](128)
NULL, [TABLE_NAME]
[nvarchar](128)
NULL, [TABLE_TYPE]
[varchar](10)
NULL ) ON [PRIMARY] GO Jeg opretter en SSIS pakke til at udlæse
fra landingzone kildedatabasens metadata fra INFORMATION_SCHEMA og indlæse
til de to ovenstående tabeller i datavarehuset. Jeg programmerer en stored procedure i
datavarehuset, der opstiller en tabelstruktur kolonneliste baseret på metadata
fra tabellen LandingZone_INFORMATION_SCHEMA_COLUMNS. Kolonnelisten sendes ud
af stored proceduren og vil indgå i en Create Table opstilling i næste stored
procedure: CREATE PROCEDURE [Utility].[Table_ColumnList_Build] @TableSchema
nvarchar(255), @TableName
nvarchar(255), @ColumnList
nvarchar(MAX) OUTPUT AS BEGIN SET NOCOUNT ON ;WITH
TableColumnList AS ( SELECT TableSchema
= t.TABLE_SCHEMA, TableName = t.TABLE_NAME,
ColumnName = c.COLUMN_NAME, ColumnDataType
= QUOTENAME(c.COLUMN_NAME) + CASE WHEN c.DATA_TYPE IN ('binary','varbinary','char','nchar','varchar','nvarchar') THEN CONCAT(QUOTENAME(c.DATA_TYPE),'(',IIF(c.CHARACTER_MAXIMUM_LENGTH =
-1, 'MAX',CAST(c.CHARACTER_MAXIMUM_LENGTH AS
varchar)),')') WHEN c.DATA_TYPE IN ('datetime2','time','datetimeoffset') THEN CONCAT(QUOTENAME(c.DATA_TYPE),'(',CAST(c.DATETIME_PRECISION
AS varchar),')') WHEN c.DATA_TYPE IN ('decimal','numeric') THEN CONCAT(QUOTENAME(c.DATA_TYPE),'(',CAST(c.NUMERIC_PRECISION AS
varchar),',', CAST(c.NUMERIC_SCALE AS varchar),')') ELSE QUOTENAME(c.DATA_TYPE) END + IIF(c.IS_NULLABLE = 'YES', 'NULL', 'NOT NULL') + ',', ColumnOrder
= ORDINAL_POSITION FROM Utility.LandingZone_INFORMATION_SCHEMA_TABLES
t INNER JOIN Utility.LandingZone_INFORMATION_SCHEMA_COLUMNS
c ON c.TABLE_SCHEMA = t.TABLE_SCHEMA AND c.TABLE_NAME = t.TABLE_NAME WHERE t.TABLE_TYPE = 'BASE TABLE' AND t.TABLE_SCHEMA =
@TableSchema AND t.TABLE_NAME
= @TableName ) SELECT @ColumnList =
STRING_AGG(ColumnDataType, '') WITHIN GROUP (ORDER BY
ColumnOrder ASC) FROM TableColumnList END Jeg programmerer en stored procedure i
datavarehuset, der for hver fact tabel i landingzone opstiller en Create Table
sql sætning for to schemas StagingFact og Fact via dynamisk SQL. StagingFact
og Fact tabel vil anvende clustered columnstore index i filegroup MART, og
Fact tabel vil anvende en partition. Jeg opstiller en skabelon sql sætningen
med < > der løbende bliver erstattet af ægte sql delsætninger, f.eks. <ColumnList>
bliver erstattet med output fra ovenstående stored procedure: CREATE PROCEDURE [Utility].[Staging_Mart_Fact_Build] AS BEGIN SET NOCOUNT ON DECLARE @sqlStagingFact_Template nvarchar(MAX) DECLARE @sqlExecute nvarchar(MAX) DECLARE @ColumnList nvarchar(MAX) DECLARE @Filegroup nvarchar(500) DECLARE @SourceSchema nvarchar(255) DECLARE @SourceTable nvarchar(255) DECLARE @TargetSchema nvarchar(255) DECLARE @TargetTable nvarchar(255) DECLARE @Scan CURSOR SET @sqlStagingFact_Template = N' DROP
TABLE IF EXISTS [<TargetSchema>].[<TargetTable>] CREATE
TABLE [<TargetSchema>].[<TargetTable>]( InsertTime_UTC_meta
datetime2(3) NOT NULL CONSTRAINT DF_<TargetSchema>_<TargetTable>_InsertTime_UTC_meta
DEFAULT (sysutcdatetime()), InsertExecutionLogId_meta
int NOT NULL, Referenceperiode_meta
int NOT NULL, <ColumnList>
) ON
<Filegroup> CREATE
CLUSTERED COLUMNSTORE INDEX CCI_<TargetSchema>_<TargetTable> ON
[<TargetSchema>].[<TargetTable>] WITH
(DROP_EXISTING = OFF, COMPRESSION_DELAY = 0, DATA_COMPRESSION = COLUMNSTORE) ON <Filegroup> ' -- Udsøger fact tabeller fra landingzone opstillet to gange med hvert sit
schema. SET
@Scan = CURSOR
LOCAL FORWARD_ONLY
DYNAMIC FOR SELECT TABLE_SCHEMA,
TABLE_NAME, t.TargetSchema FROM [Utility].[LandingZone_INFORMATION_SCHEMA_TABLES] CROSS JOIN (VALUES ('StagingFact'), ('Fact') ) t(TargetSchema) WHERE TABLE_TYPE = 'BASE TABLE' AND
TABLE_SCHEMA = 'dbo'
AND LEFT([TABLE_NAME],5) = 'Fact_' OPEN @Scan FETCH NEXT FROM @Scan INTO
@SourceSchema, @SourceTable, @TargetSchema WHILE @@FETCH_STATUS
= 0 BEGIN SET @ColumnList =
NULL EXEC Utility.Table_ColumnList_Build @SourceSchema,
@SourceTable, @ColumnList OUTPUT SET @TargetTable = REPLACE(@SourceTable,'Fact_','') -- Fact tabel skal have
partition. StagingFact tabel skal ikke have partition. IF
@TargetSchema = 'Fact' BEGIN SET @Filegroup = '[PartitionScheme_Referenceperiode]([Referenceperiode_meta])' END ELSE -- @TargetSchema = 'StagingFact' BEGIN SET
@Filegroup = '[MART]' END -- De fleste tekstkolonne er
nvarchar, derfor ændrer jeg de få tekstkolonner som er -- varchar til nvarchar, så er
datatypen ens i datavarehuset. SET @ColumnList = REPLACE(@ColumnList, '[varchar]', '[nvarchar]') SET @ColumnList = REPLACE(@ColumnList, '[char]', '[nchar]') SET
@sqlExecute = @sqlStagingFact_Template SET @sqlExecute =
REPLACE(@sqlExecute, '<TargetSchema>', @TargetSchema) SET @sqlExecute =
REPLACE(@sqlExecute, '<TargetTable>'
, @TargetTable) SET @sqlExecute =
REPLACE(@sqlExecute, '<ColumnList>', @ColumnList) SET @sqlExecute =
REPLACE(@sqlExecute, '<Filegroup>', @Filegroup) --PRINT @sqlExecute EXECUTE
(@sqlExecute) --EXECUTE sp_executesql @sqlExecute FETCH NEXT FROM @Scan INTO
@SourceSchema, @SourceTable, @TargetSchema END CLOSE @Scan DEALLOCATE @Scan END Eksempel på en automatisk dannet Periodic
snapshot fact tabel via feltet Referenceperiode fra ovenstående stored
procedure: CREATE TABLE [Fact].[Instrument]( [InsertTime_UTC_meta]
[datetime2](3)
NOT NULL CONSTRAINT DF_Fact_Instrument_InsertTime_UTC_meta DEFAULT (sysutcdatetime()), [InsertExecutionLogId_meta]
[int] NOT NULL, [Referenceperiode_meta]
[int] NOT NULL, [DW_SK_Indberetter]
[int] NOT NULL, [DW_SK_Instrument]
[bigint] NOT NULL, [DW_SK_Valuta]
[int] NOT NULL, [DW_SK_JaNej_VariabelLøbetid]
[int] NOT NULL, [DW_SK_Dato_Optagelse]
[int] NOT NULL, [Bruttoudlån]
[decimal](28,
10) NULL, [Renteloft]
[decimal](28,
10) NULL, [Rentegulv]
[decimal](28,
10) NULL, [Restance]
[decimal](28,
10) NULL, ) ON [PartitionScheme_Referenceperiode]([Referenceperiode_meta]) GO CREATE CLUSTERED COLUMNSTORE INDEX
[CCI_Fact_Instrument] ON [Fact].[Instrument] WITH (DROP_EXISTING = OFF,
COMPRESSION_DELAY = 0, DATA_COMPRESSION = COLUMNSTORE) ON
[PartitionScheme_Referenceperiode]([Referenceperiode_meta]) GO Tilsvarende stored procedure kan
programmeres for en dimension tabel. Fjernskrivebord adgang til SQL server Når
de to standard brugere er optaget, så skriv i Start, Kør: mstsc /admin SQL
Server harddisk ledig plads EXEC master..xp_fixeddrives SQL
Server harddisk liste over filer i en mappe EXEC
master.dbo.xp_DirTree 'F:\Input\Csv', 1, 1 SQL
Server restore a backupfile --Set Singleuserrmode for SalesSystem ALTER DATABASE [SalesSystem] SET SINGLE_USER WITH ROLLBACK IMMEDIATE ALTER DATABASE [SalesSystem] SET MULTI_USER -- Restore Newest Database DECLARE @FileList TABLE ([FileName] varchar(300), [Depth] int, [IsFile] int) DECLARE @BackupFile varchar(300) INSERT INTO @FileList EXEC Master.dbo.xp_DirTree 'F:\Landingzone\BackupFolder',1,1 SELECT TOP 1 @BackupFile = [FileName] FROM
@FileList WHERE
[FileName] like'X_Full_20%' ORDER BY [FileName] DESC SET @BackupFile = 'F:\Landingzone\BackupFolder\'
+ @BackupFile RESTORE DATABASE
[SalesSystem] FROM DISK =
@BackupFile WITH MOVE 'SalesSystem' TO 'D:\MSSQL\Data\SalesSystem.mdf', MOVE 'SalesSystem_log' TO
'L:\MSSQL\Log\SalesSystem_log.ldf', REPLACE --Add Permissions for
SalesSystem CREATE USER [LAN\<servicekonto>]
FOR LOGIN
[LAN\<servicekonto>] GO ALTER ROLE [db_owner] ADD
MEMBER [LAN\<servicekonto>] GO Aflæse SSISDB SSIS project environment variable med
T-SQL CREATE FUNCTION [dbo].[LandingzoneServername]() RETURNS nvarchar(50) AS BEGIN DECLARE @Landingzone nvarchar(50) SELECT @Landingzone = CAST(ev.[value] AS nvarchar(50)) FROM [SSISDB].[internal].[environment_variables] ev INNER JOIN [SSISDB].[internal].[environments] e
ON e.environment_id = ev.environment_id WHERE e.environment_name = 'SalesSystem' AND
ev.[name] = 'LandingZone_ServerName' RETURN @Landingzone END GO Databasereferencen SSISDB giver ikke
build problem i et Visual Studio DB Project, fordi SSISDB er angivet i en
funktion fremfor i et view eller i en stored procedure. Eksempel på kald af funktionen f.eks.
inde fra en stored procedure: DECLARE @LandingzoneServername AS nvarchar(50); SELECT @LandingzoneServername = dbo.LandingzoneServername() SELECT @LandingzoneServername SQL
Server versionsnummer som indirekte fortæller om service pack SELECT
@@VERSION Database server version og service pack SELECT
ServerProperty('Edition') AS SQLEdition, ServerProperty('ProductVersion') AS
Version, Serverproperty('ProductLevel') AS
ServicePack Server
oplysninger bl.a. konto som services kører under SELECT * FROM sys.dm_server_services SELECT * FROM sys.database_files SELECT * FROM
sys.dm_server_registry Tabeller
i en database SELECT
TABLE_NAME FROM
INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' Antal
rækker i tabeller og views SET
NOCOUNT ON IF
EXISTS (SELECT 1 FROM tempdb.INFORMATION_SCHEMA.TABLES WHERE CHARINDEX ('#TableViewRows',
TABLE_NAME) = 1) DROP TABLE #TableViewRows SELECT
IDENTITY(int, 1,1) AS Id, TABLE_CATALOG AS [Database], TABLE_TYPE AS [Type], TABLE_SCHEMA AS
[Schema], TABLE_NAME AS [Name], 0 AS [Rows] INTO
#TableViewRows FROM
INFORMATION_SCHEMA.TABLES WHERE
TABLE_NAME NOT IN('dtproperties', 'sysdiagrams') DECLARE
@id int DECLARE
@object nvarchar(512) DECLARE
@sql nvarchar(255) DECLARE
@rows int DECLARE @Scan
CURSOR LOCAL FORWARD_ONLY DYNAMIC FOR SELECT
Id, '[' + [Database] + '].[' + [Schema] + '].[' + [Name] + ']' AS [Object] FROM #TableViewRows OPEN @Scan
FETCH
NEXT FROM @Scan INTO @id, @object WHILE
@@FETCH_STATUS = 0 BEGIN -- Dynamic SQL SET @sql = 'SELECT @prmrows = COUNT(*) FROM
'+ @object BEGIN TRY EXECUTE sp_executesql @sql, N'@prmrows
int OUTPUT', @prmrows =
@rows OUTPUT UPDATE #TableViewRows SET [Rows] = @rows WHERE Id = @id END TRY BEGIN CATCH END CATCH FETCH NEXT FROM @Scan INTO @id, @object END CLOSE @Scan DEALLOCATE
@Scan SELECT
[Database], [Type], [Schema], [Name], [Rows] FROM
#TableViewRows ORDER BY
[Database], [Type], [Schema], [Name] Eller
via rekursion danner en samlet dynamisk sql og udfører den til sidst DECLARE
@sql nvarchar(MAX) SET
NOCOUNT ON SELECT @sql = COALESCE(@sql + ' UNION ALL ', '') + 'SELECT ''' + t.type + ''' AS ''Type'', ''' + s.name + ''' AS ''Schema'', ''' + t.name + ''' AS ''Name'', COUNT(*) AS Rows FROM ' + QUOTENAME(s.name) + '.' +
QUOTENAME(t.name) FROM
sys.schemas s INNER JOIN (
SELECT 'Table' AS type, name, schema_id
FROM sys.tables
WHERE name NOT IN('dtproperties', 'sysdiagrams')
UNION
SELECT 'View' AS type, name, schema_id
FROM sys.views )
t ON t.schema_id = s.schema_id ORDER BY
t.type, s.name, t.name EXECUTE (@sql) Antal
rækker i tabeller med indlejret sql SELECT st.name AS TableName, (SELECT SUM(max_length) FROM sys.columns WHERE object_id=st.object_id) AS RowLength, si.rowcnt AS
[RowCount], CAST(si.rowcnt*(SELECT SUM(max_length) FROM sys.columns WHERE object_id=st.object_id)/1024.0/1024.0 AS INT) AS MaxTableSizeMB FROM sys.tables as st LEFT JOIN sysindexes si
on st.object_id=si.id and si.indid < 2 ORDER BY st.name Antal
rækker i schema og tabeller – bedst at bruge SELECT
TableSchemaName = SCHEMA_NAME(t.schema_id),
TableName = t.name,
TableRowCount = SUM(p.rows) FROM sys.tables t INNER JOIN sys.partitions p ON t.object_id = p.object_id AND p.index_id IN(0, 1) WHERE SCHEMA_Name(t.schema_id) = 'base' AND t.name = 'Transaction' GROUP BY t.schema_id, t.name Eller på en anden måde med samme
resultat: SELECT
TableSchemaName = SCHEMA_NAME(o.schema_id),
TableName = o.name,
TableRowCount = SUM(p.row_count) FROM sys.objects o INNER JOIN sys.dm_db_partition_stats p ON
o.object_id
= p.object_id WHERE
o.type = 'U' AND o.is_ms_shipped
= 0x0 AND p.index_id IN(0, 1) AND SCHEMA_Name(o.schema_id) = 'base' AND o.name = 'Transaction' GROUP BY o.schema_id, o.name Ikke behov for kald af DBCC UPDATEUSAGE. Har man lige indsat flere millioner rækker
i en clustered columnstore index tabel, så kan ovenstående finde på at vise enten
1.048.576 eller 2.097.152 rækker for meget (kaldes en bucket count). Man kan
enten vente WAITFOR 00:10 minutter og prøve igen, eller udføre en SELECT
COUNT_BIG(1) som ofte er hurtig på en clustered columnstore index tabel. A rowgroup has exactly 1.048.576 rows. Et kald af en system stored procedure som bl.a.
returnerer Rows: EXEC sp_spaceused N'base.Tranction' Oversigt
over hvilke staging tabeller der har 0 rækker: DECLARE @NumberOfTableRowCount0 int = 0 DECLARE @StagingTableList varchar(1024) ;WITH dataset AS ( SELECT TableSchemaName = SCHEMA_NAME(o.schema_id),
TableName = o.name, TableRowCount = SUM(p.row_count) FROM sys.objects o
INNER JOIN sys.dm_db_partition_stats p ON o.object_id = p.object_id WHERE o.type = 'U' AND o.is_ms_shipped = 0x0 AND p.index_id IN(0, 1) AND SCHEMA_Name(o.schema_id) IN('StagingDim','StagingFact') GROUP BY o.schema_id, o.name ) SELECT @NumberOfTableRowCount0 = COUNT(*),
@StagingTableList = STRING_AGG(CONCAT(TableSchemaName,'.',TableName), ',
') FROM dataset WHERE TableRowCount = 0 SELECT @NumberOfTableRowCount0, @StagingTableList IF @NumberOfTableRowCount0 >= 1 Uventet, at mindst en staging tabel med 0
rækker, skriv det i en Log tabel for ETL forløbet, anden Log tabel eller send
en email til DataDevops, så de kan se på data leverancen fra et kildesystem. Tabellers total space og used space på harddisk SELECT t.NAME AS TableName, s.Name AS SchemaName, p.rows AS RowCounts, SUM(a.total_pages) * 8 AS TotalSpaceKB, CAST(ROUND(((SUM(a.total_pages) * 8) / 1024.00), 2) AS NUMERIC(36, 2))
AS TotalSpaceMB, SUM(a.used_pages) * 8 AS UsedSpaceKB, CAST(ROUND(((SUM(a.used_pages) * 8) / 1024.00), 2) AS NUMERIC(36, 2))
AS UsedSpaceMB, (SUM(a.total_pages) - SUM(a.used_pages)) * 8 AS UnusedSpaceKB, CAST(ROUND(((SUM(a.total_pages)
- SUM(a.used_pages)) * 8) / 1024.00, 2) AS NUMERIC(36, 2)) AS
UnusedSpaceMB FROM sys.tables t INNER JOIN sys.indexes i ON t.OBJECT_ID =
i.object_id INNER JOIN sys.partitions p ON i.object_id
= p.OBJECT_ID AND i.index_id = p.index_id INNER JOIN sys.allocation_units a ON
p.partition_id = a.container_id LEFT OUTER JOIN sys.schemas s ON t.schema_id =
s.schema_id WHERE t.NAME NOT LIKE 'dt%' AND t.is_ms_shipped = 0 AND i.OBJECT_ID > 255 GROUP BY t.Name, s.Name, p.Rows ORDER BY t.Name Hukommelse, momery, RAM SELECT
object_name, counter_name, cntr_value FROM
sys.dm_os_performance_counters WHERE
counter_name IN ('Total Server Memory (KB)', 'Target Server Memory (KB)') SELECT
physical_memory_kb FROM sys.dm_os_sys_info SELECT (physical_memory_in_use_kb/1024)
AS Memory_usedby_Sqlserver_MB, (locked_page_allocations_kb/1024)
AS Locked_pages_used_Sqlserver_MB, (total_virtual_address_space_kb/1024)
AS Total_VAS_in_MB, process_physical_memory_low, process_virtual_memory_low FROM
sys.dm_os_process_memory SELECT
object_name,counter_name,cntr_value / 1024 "MB" FROM
sys.dm_os_performance_counters WHERE
counter_name IN ('Free Memory (KB)','Total Server Memory (KB)','Target Server
Memory (KB)') SQL koden bag et view SELECT
VIEW_DEFINITION FROM
INFORMATION_SCHEMA.VIEWS WHERE TABLE_NAME = 'Customer_List' -- Ovenstående medtager kun de første 4000
tegn af viewets indhold. SELECT
MODS.DEFINITION FROM
SYS.OBJECTS OBJ INNER JOIN SYS.SQL_MODULES MODS ON OBJ.OBJECT_ID = MODS.OBJECT_ID WHERE
OBJ.TYPE = 'V' AND OBJ.NAME = 'Customer_List' SELECT
MODS.DEFINITION FROM
SYS.OBJECTS OBJ INNER JOIN SYS.SQL_MODULES MODS ON OBJ.OBJECT_ID = MODS.OBJECT_ID INNER JOIN SYS.SCHEMAS SCHS ON OBJ.SCHEMA_ID
= SCHS.SCHEMA_ID WHERE
OBJ.TYPE = 'V' AND SCHS.NAME = 'dbo' AND OBJ.NAME
= 'Customer_List' Opdatere
et view efter en tabel ændring – refresh view Der
anvendes EXECUTE sp_refreshview vist på tre måder. Den
lange version med en cursor: CREATE PROCEDURE [dbo].[Refresh_Views] @param_Schemaname
nvarchar(128) = NULL WITH EXEC AS CALLER AS /* To refresh all the views on the current database e.g.: EXEC dbo.Refresh_Views
<refresh all views> EXEC dbo.Refresh_Views 'dbo' EXEC dbo.Refresh_Views
'customer' */ BEGIN SET NOCOUNT ON DECLARE @Scan CURSOR DECLARE @Schemaname
nvarchar(128) DECLARE @Viewname
nvarchar(128) DECLARE @SchemaViewname
nvarchar(257) DECLARE @Number int SET @Scan = CURSOR LOCAL
FORWARD_ONLY DYNAMIC FOR SELECT
QUOTENAME(TABLE_SCHEMA) AS Schemaname, QUOTENAME(TABLE_NAME) AS Viewname, ROW_NUMBER()
OVER(ORDER BY (SELECT 1)) AS Number FROM
INFORMATION_SCHEMA.VIEWS WHERE @param_Schemaname IS
NULL OR TABLE_SCHEMA = @param_Schemaname ORDER BY TABLE_NAME,
TABLE_SCHEMA /* SELECT QUOTENAME(s.name)
AS Schemaname, QUOTENAME(v.name) AS Viewname, ROW_NUMBER()
OVER(ORDER BY (SELECT 1)) AS Number FROM sys.views v INNER
JOIN sys.schemas s ON s.schema_id = v.schema_id WHERE @param_Schemaname IS
NULL OR s.name = @param_Schemaname ORDER BY v.name, s.name */ OPEN @Scan FETCH NEXT FROM
@Scan INTO @Schemaname, @Viewname, @Number WHILE @@FETCH_STATUS = 0 BEGIN SET @SchemaViewname =
@Schemaname + '.' + @Viewname BEGIN TRY EXECUTE sp_refreshview @SchemaViewname PRINT CONCAT('Refreshed: ', FORMAT(@Number,
'00000'), ' ', @SchemaViewname) END TRY BEGIN CATCH PRINT CONCAT('Failed: ', FORMAT(@Number,
'00000'), ' ', @SchemaViewname, ', Error: ',
ERROR_MESSAGE()) END CATCH FETCH NEXT FROM @Scan INTO
@Schemaname, @Viewname, @Number END CLOSE @Scan DEALLOCATE @Scan SELECT @Number AS 'Number of
views, see them at tab Messages' END Den
korte version: DECLARE @sqlcmd
NVARCHAR(MAX) = '' SELECT @sqlcmd = @sqlcmd +
'EXEC sp_refreshview ''' + CONCAT(QUOTENAME(TABLE_SCHEMA),'.',QUOTENAME(TABLE_NAME))
+ ''';' FROM INFORMATION_SCHEMA.VIEWS ORDER BY TABLE_NAME, TABLE_SCHEMA EXEC(@sqlcmd) --SELECT @sqlcmd Eller
genere sql sætninger og manuelt afvikle dem: SELECT DISTINCT 'EXEC sp_refreshview ''' + name + '''' FROM sys.objects AS so INNER JOIN sys.sql_expression_dependencies AS sed ON so.object_id = sed.referencing_id WHERE so.type = 'V' AND sed.referenced_id =
OBJECT_ID('Person.Person'); Værdier
fra en række i en tabel En
række udvalgt med primærnøgleværdi kriterie og dens felter/kolonner og deres
værdier vises som rækker: select
T2.N.value('local-name(.)', 'nvarchar(128)')+': '+ T2.N.value('.', 'nvarchar(max)') from
(select * from Customers Where CustomerID='ALFKI' for xml path(''), type) as T1(X) cross apply T1.X.nodes('/*') as T2(N) Hvis
der er flere collation i samme database SELECT TABLE_SCHEMA,
TABLE_NAME, COLUMN_NAME, DATA_TYPE, COLLATION_NAME, CAST(DATABASEPROPERTYEX(TABLE_CATALOG,
'Collation') AS NVARCHAR(MAX)) AS DatabaseCollation FROM
INFORMATION_SCHEMA.COLUMNS C WHERE
EXISTS (SELECT
* FROM INFORMATION_SCHEMA.TABLES T WHERE T.TABLE_TYPE = 'BASE TABLE' AND T.TABLE_NAME
= C.TABLE_NAME) AND
COLLATION_NAME <> CAST(DATABASEPROPERTYEX(DB_NAME(), 'Collation') AS
NVARCHAR(128)) Udlæs datapunkter fra en XML fil En XML fil indeholder tre namespaces (ns)
som identificeres i en SQL sætning og i en sti (path) til et datapunkt (data
point). ESMAsample.xml.
Over tid har namespace versionsnummeret ændret sig, derfor foretages et tjek
for at bestemme rette versionsnummer, så der ikke fås fejl ved udlæsning af
datapunkterne. DECLARE @Xml xml DECLARE @Check bit DECLARE @Doc int DECLARE @RootXmlns varchar(255) SELECT @Xml = BulkColumn FROM OPENROWSET(BULK 'M:\XML\ESMAsample.xml', SINGLE_BLOB) AS XMLDATA -- Version from 2010: ;WITH XMLNAMESPACES ('urn:iso:std:iso:20022:tech:xsd:head.003.001.01' AS rns, -- rns = root namespace 'urn:iso:std:iso:20022:tech:xsd:head.001.001.01'
AS hns, -- hns = header namespace 'urn:iso:std:iso:20022:tech:xsd:auth.017.001.00'
AS dns) -- dns = document namespace -- BizData element root with
own namespace has two children: a header (Hdr) element with own namespace -- and a payload (Pyld)
element with root namespace. -- The Pyld element has a
child called Document with own namespace. SELECT @Check = @Xml.exist('rns:BizData/rns:Pyld/dns:Document') IF
@Check = 1 SET @RootXmlns = '<root
xmlns:rns="urn:iso:std:iso:20022:tech:xsd:head.003.001.01"
xmlns:hns="urn:iso:std:iso:20022:tech:xsd:head.001.001.01"
xmlns:dns="urn:iso:std:iso:20022:tech:xsd:auth.017.001.00"/>' -- Version from 2012: ;WITH XMLNAMESPACES ('urn:iso:std:iso:20022:tech:xsd:head.003.001.01' AS rns, -- rns = root namespace
'urn:iso:std:iso:20022:tech:xsd:head.001.001.01'
AS hns, -- hns = header namespace 'urn:iso:std:iso:20022:tech:xsd:auth.017.001.01'
AS dns) -- dns = document namespace SELECT @Check = @Xml.exist('rns:BizData/rns:Pyld/dns:Document') IF
@Check = 1 SET @RootXmlns = '<root
xmlns:rns="urn:iso:std:iso:20022:tech:xsd:head.003.001.01"
xmlns:hns="urn:iso:std:iso:20022:tech:xsd:head.001.001.01"
xmlns:dns="urn:iso:std:iso:20022:tech:xsd:auth.017.001.01"/>' -- Version from 2015: ;WITH XMLNAMESPACES ('urn:iso:std:iso:20022:tech:xsd:head.003.001.01' AS rns, -- rns = root namespace 'urn:iso:std:iso:20022:tech:xsd:head.001.001.01'
AS hns, -- hns = header namespace 'urn:iso:std:iso:20022:tech:xsd:auth.017.001.02'
AS dns) -- dns = document namespace SELECT @Check = @Xml.exist('rns:BizData/rns:Pyld/dns:Document') IF
@Check = 1 SET @RootXmlns = '<root
xmlns:rns="urn:iso:std:iso:20022:tech:xsd:head.003.001.01"
xmlns:hns="urn:iso:std:iso:20022:tech:xsd:head.001.001.01"
xmlns:dns="urn:iso:std:iso:20022:tech:xsd:auth.017.001.02"/>' EXEC sp_xml_preparedocument @Doc
OUTPUT, @Xml, @RootXmlns SELECT * FROM OPENXML(@Doc, '//rns:BizData/rns:Hdr/hns:AppHdr') -- root namespace and
header namespace WITH (
MsgDefIdr varchar(50) 'hns:MsgDefIdr' -- fetch
an element from header namespace ) SELECT InstrumentId,
ShortName, Amount,
Currency, Issr,
TradeId, FirstTradeDatetime = CAST(FirstTradeDatetime AS datetime2(0)), RefData_XML FROM OPENXML(@Doc, '//rns:BizData/rns:Pyld/dns:Document/dns:FinInstrmRptgRefDataRpt/dns:RefData') WITH -- RefData represents a record out of many records. (
InstrumentId varchar(12) 'dns:FinInstrmGnlAttrbts/dns:Id', -- fetch an element.
ShortName varchar(50) 'dns:FinInstrmGnlAttrbts/dns:ShrtNm', Amount
decimal(19,4)'dns:FinInstrmGnlAttrbts/dns:NtnlCcy',
Currency varchar(3) 'dns:FinInstrmGnlAttrbts/dns:NtnlCcy/@ccy', -- fetch an attribute. Issr varchar(20) 'dns:Issr',
TradeId varchar(50) 'dns:TradgVnRltdAttrbts/dns:Id', FirstTradeDatetime
varchar(50) 'dns:TradgVnRltdAttrbts/dns:FrstTradDt',
RefData_XML xml '.' -- fetch a xml structure for the RefData sub xml. ) EXEC sp_xml_removedocument @Doc Når
der er flere elementer for RefData, så fås der flere rækker lige til at
indsætte i en tabel med INSERT INTO foran eller INTO før FROM.
OPENROWSET
and OPENXML OPENXML OPENXML examples WITH XMLNAMESPACES
Add namespaces Siden 2008 har man kunne anvende xml Data Type Methods og xquery, men koden bliver ikke pæn for at håndtere flere
namespace versionsnumre, så det undlades her. Jeg anvender termen List for
at angive, at der kan være mellem 0 og mange elementer som jeg termer Element
og sætter xml-elementnavnet RefData foran. DECLARE @XmlTable TABLE (Id
int identity primary key,
XmlData xml,
LoadedDateTime datetime default getdate()); INSERT INTO @XmlTable (XmlData) SELECT CAST(BulkColumn AS XML) FROM OPENROWSET(BULK 'M:\XML\ESMAsample.xml', SINGLE_BLOB) AS XMLDATA ;WITH XMLNAMESPACES ('urn:iso:std:iso:20022:tech:xsd:head.003.001.01' AS rns, -- rns = root namespace 'urn:iso:std:iso:20022:tech:xsd:head.001.001.01'
AS hns, -- hns = header namespace 'urn:iso:std:iso:20022:tech:xsd:auth.017.001.02'
AS dns) -- dns = document namespace -- BizData element root with
own namespace has two children: a header (Hdr) element with own namespace -- and a payload (Pyld)
element with root namespace. -- The Pyld element has a
child called Document with own namespace. SELECT InstrumentId
= RefDataList.RefDataElement.value('(dns:FinInstrmGnlAttrbts/dns:Id/text())[1]','varchar(12)'),-- an element
ShortName = RefDataList.RefDataElement.value('(dns:FinInstrmGnlAttrbts/dns:ShrtNm/text())[1]','varchar(50)'),
Amount = RefDataList.RefDataElement.value('(dns:FinInstrmGnlAttrbts/dns:NtnlCcy/text())[1]','decimal(19,4)'),
Currency = RefDataList.RefDataElement.value('(dns:FinInstrmGnlAttrbts/dns:NtnlCcy/@ccy)[1]','varchar(3)'),-- an attribute
Issr = RefDataList.RefDataElement.value('(dns:Issr/text())[1]','varchar(20)'),
TradeId = RefDataList.RefDataElement.value('(dns:TradgVnRltdAttrbts/dns:Id/text())[1]','varchar(50)'),
FirstTradeDatetime = RefDataList.RefDataElement.value('(dns:TradgVnRltdAttrbts/dns:FrstTradDt/text())[1]','varchar(50)') FROM
@XmlTable AS t OUTER APPLY t.XmlData.nodes('//rns:BizData/rns:Pyld/dns:Document/dns:FinInstrmRptgRefDataRpt/dns:RefData') AS
RefDataList(RefDataElement) Common
Table Expression CTE i stedet for en tabel variabel. ;WITH XMLNAMESPACES ('urn:iso:std:iso:20022:tech:xsd:head.003.001.01' AS rns, -- rns = root namespace 'urn:iso:std:iso:20022:tech:xsd:head.001.001.01'
AS hns, -- hns = header namespace 'urn:iso:std:iso:20022:tech:xsd:auth.017.001.02'
AS dns) -- dns = document namespace ,
xmlrs (XmlData) AS ( SELECT CAST(BulkColumn AS XML) FROM OPENROWSET(BULK 'M:\XML\ESMAsample.xml',
SINGLE_BLOB) AS XMLDATA ) SELECT
InstrumentId = RefDataList.RefDataElement.value('(dns:FinInstrmGnlAttrbts/dns:Id/text())[1]','varchar(12)'),
ShortName = RefDataList.RefDataElement.value('(dns:FinInstrmGnlAttrbts/dns:ShrtNm/text())[1]','varchar(50)'),
Amount = RefDataList.RefDataElement.value('(dns:FinInstrmGnlAttrbts/dns:NtnlCcy/text())[1]','decimal(19,4)'),
Currency = RefDataList.RefDataElement.value('(dns:FinInstrmGnlAttrbts/dns:NtnlCcy/@ccy)[1]','varchar(3)'),
Issr = RefDataList.RefDataElement.value('(dns:Issr/text())[1]','varchar(20)'), TradeId =
RefDataList.RefDataElement.value('(dns:TradgVnRltdAttrbts/dns:Id/text())[1]','varchar(50)'),
FirstTradeDatetime = RefDataList.RefDataElement.value('(dns:TradgVnRltdAttrbts/dns:FrstTradDt/text())[1]','varchar(50)') FROM
xmlrs AS t OUTER APPLY t.Xmldata.nodes('//rns:BizData/rns:Pyld/dns:Document/dns:FinInstrmRptgRefDataRpt/dns:RefData') AS
RefDataList(RefDataElement) Andet
eksempel med en #tabel og XML direkte i koden og uden namespaces og ingen
root tag og uden /text() selvom det er standard XPath: CREATE TABLE #XmlTable(XmlData Xml); INSERT INTO #XmlTable(XmlData) SELECT '<Info>Validated
order</Info>
<Data>1234567890</Data> 'As
XmlData; SELECT tbl.XmlData.value('Info[1]','varchar(20)') AS Info,
tbl.XmlData.value('Data[1]','bigint') AS Data FROM
#XmlTable AS tbl DROP TABLE #XmlTable;
Tredje eksempel med en #tabel og XML
direkte i koden og uden namespaces og med en 1-til-mange-til-mange relation,
fordi der er flere Test som indeholder en Subject med flere Cap elementer og
flere fix elementer. Jeg ønsker at danne one big table (obt)
tabel. Jeg anvender termen List for at angive,
at der kan være mellem 0 og mange elementer som jeg termer Element og sætter
xml-elementnavnene foran for at få sigende referencer: TestList.TestElement, SubjectCapList.CapElement,
SubjectFitList.FitElement og SupportCapList.CapElement. CREATE TABLE #XmlTable(XmlData Xml); INSERT INTO #XmlTable(XmlData) SELECT '<First>
<Test id="83847">
<Identifier>123456789</Identifier>
<ProductItemCode>8888</ProductItemCode>
<subject>
<cap>15</cap>
<cap>25</cap>
<cap>100</cap>
<fit>6</fit>
<fit>7</fit>
</subject>
<support type = "ABC">
<cap>9</cap>
</support>
</Test> <Test id="83848">
<Identifier>987654321</Identifier>
<ProductItemCode>9999</ProductItemCode>
<subject>
<cap>150</cap>
<cap>2</cap>
<cap>10</cap>
</subject>
<support type = "XYZ">
<cap>19</cap>
</support>
</Test> </First>'As
XmlData; SELECT Id
= TestList.TestElement.value('@id', 'int'), -- an attribute from Test tag
Identifier = TestList.TestElement.value('(Identifier/text())[1]','varchar(10)'),
ProductItemCode = TestList.TestElement.value('(ProductItemCode/text())[1]','int'),
SubjectCap = SubjectCapList.CapElement.value('text()[1]', 'int'),
SubjectFit = SubjectFitList.FitElement.value('text()[1]', 'int'),
SupportType = TestList.TestElement.value('(support/@type)[1]','varchar(max)'),--attribute
SupportCap = SupportCapList.CapElement.value('text()[1]', 'int'),
SupportCapx = SupportCapList.CapElement.value('(.)[1]', 'int') FROM
#XmlTable AS tbl OUTER APPLY tbl.XmlData.nodes('/First/Test') AS TestList(TestElement) OUTER APPLY
TestList.TestElement.nodes('subject/cap') AS SubjectCapList(CapElement) OUTER APPLY
TestList.TestElement.nodes('subject/fit') AS SubjectFitList(FitElement) OUTER APPLY
TestList.TestElement.nodes('support/cap') AS SupportCapList(CapElement) DROP TABLE #XmlTable; Brug af OUTER APPLY giver en slags Left
Outer Join, fordi fit element ikke findes under Test id="83848" og brug
af CROSS APPLY havde ikke medtaget den. Slutter med endnu en XML fil fundet på
internettet drive.google med en-til-mange relation i xml datastrukturen,
derfor anvendes to OUTER APPLY for at udlæse datapunkter fra både »en« og
»mange« delene sant absolutte og relative stier med XPath: DECLARE @XmlTable TABLE (XmlData
xml); INSERT INTO @XmlTable (XmlData) SELECT CAST(BulkColumn AS XML) FROM OPENROWSET(BULK 'M:\XML\EMIRsample.xml', SINGLE_BLOB) AS XMLDATA ;WITH XMLNAMESPACES ('urn:iso:std:iso:20022:tech:xsd:head.003.001.01' AS rns, -- rns = root namespace 'urn:iso:std:iso:20022:tech:xsd:head.001.001.01'
AS hns, -- hns = header namespace 'urn:iso:std:iso:20022:tech:xsd:auth.030.001.01'
AS dns) -- dns = document namespace -- BizData element root with
own namespace has two children: a header (Hdr) element with own namespace -- and a payload (Pyld)
element with root namespace. -- The Pyld element has a
child called Document with own namespace. SELECT -- hele
stien, den d.v.s. den absolutte sti (fuldstændige sti) giver mindre query
plan og bedre svartid. FromIdx
= BizData.e.value('(rns:Hdr/hns:AppHdr/hns:Fr/hns:OrgId/hns:Id/hns:OrgId/hns:Othr/hns:Id/text())[1]','varchar(5)'), --
næsten hele stien fordi // springer nogle tags, d.v.s den relative sti. FromId
= BizData.e.value('(rns:Hdr/hns:AppHdr/hns:Fr/hns:OrgId/hns:Id/hns:OrgId//hns:Id/text())[1]','varchar(5)'), ToId =
BizData.e.value('(rns:Hdr/hns:AppHdr/hns:To/hns:OrgId/hns:Id/hns:OrgId//hns:Id/text())[1]','varchar(5)'), NbRcrds
= BizData.e.value('(rns:Pyld/dns:Document/dns:DerivsTradRpt/dns:RptHdr/dns:NbRcrds/text())[1]','int'),
-- hele stien, den d.v.s. den absolutte sti
(fuldstændige sti) giver mindre query plan og bedre svartid. RCPLEIx
= RptList.e.value('(dns:Tx/dns:ValtnUpd/dns:CtrPtySpcfcData/dns:CtrPty/dns:RptgCtrPty/dns:Id/dns:LEI/text())[1]', 'varchar(20)'), -- // springer nogle tags over
for at nå Id og LEI d.v.s den relative sti. Starter med punktum lig med start
fra Rpt, -- fordi Tx kan også være Pos, og
ValtnUpd kan også være New, Mod, PosCmpnt m.fl. så undgås at angive flere -- XPath stier, men det koster
med en større query plan og en langsommere svartid. RCPLEI
= RptList.e.value('(.//dns:RptgCtrPty/dns:Id/dns:LEI/text())[1]','varchar(20)'), OCPLEI
= RptList.e.value('(.//dns:OthrCtrPty/dns:Id/dns:LEI/text())[1]','varchar(20)'), SALEI = RptList.e.value('(.//dns:SubmitgAgt/dns:LEI/text())[1]','varchar(20)'), Amount = RptList.e.value('(.//dns:Valtn/dns:CtrctVal/dns:Amt/text())[1]','decimal(19,4)'), Currency
= RptList.e.value('(.//dns:Valtn/dns:CtrctVal/dns:Amt/@Ccy)[1]','varchar(3)'), -- an attribute TimeStmp
= CAST(RptList.e.value('(.//dns:TmStmp/text())[1]','varchar(20)') AS datetime2(0)), Tp =
RptList.e.value('(.//dns:Tp/text())[1]','varchar(10)') FROM
@XmlTable AS t OUTER APPLY t.XmlData.nodes('//rns:BizData') AS BizData(e) OUTER APPLY t.XmlData.nodes('//rns:BizData/rns:Pyld/dns:Document/dns:DerivsTradRpt/dns:TradData/dns:Rpt') AS RptList(e) Eller: DECLARE @XmlTable xml SELECT @XmlTable =
BulkColumn FROM OPENROWSET(BULK 'M:\XML\EMIRsample.xml', SINGLE_BLOB) AS XMLDATA ;WITH XMLNAMESPACES ('urn:iso:std:iso:20022:tech:xsd:head.003.001.01' AS rns, -- rns = root namespace 'urn:iso:std:iso:20022:tech:xsd:head.001.001.01'
AS hns, -- hns = header namespace 'urn:iso:std:iso:20022:tech:xsd:auth.030.001.01'
AS dns) -- dns = document namespace -- BizData element root with
own namespace has two children: a header (Hdr) element with own namespace -- and a payload (Pyld)
element with root namespace. -- The Pyld element has a
child called Document with own namespace. SELECT -- hele
stien, den d.v.s. den absolutte sti (fuldstændige sti) giver mindre query
plan og bedre svartid. FromIdx
= BizData.e.value('(rns:Hdr/hns:AppHdr/hns:Fr/hns:OrgId/hns:Id/hns:OrgId/hns:Othr/hns:Id/text())[1]','varchar(5)'), --
næsten hele stien fordi // springer nogle tags, d.v.s den relative sti. FromId
= BizData.e.value('(rns:Hdr/hns:AppHdr/hns:Fr/hns:OrgId/hns:Id/hns:OrgId//hns:Id/text())[1]','varchar(5)'), ToId =
BizData.e.value('(rns:Hdr/hns:AppHdr/hns:To/hns:OrgId/hns:Id/hns:OrgId//hns:Id/text())[1]','varchar(5)'), NbRcrds
= BizData.e.value('(rns:Pyld/dns:Document/dns:DerivsTradRpt/dns:RptHdr/dns:NbRcrds/text())[1]','int'),
-- hele stien, den d.v.s. den absolutte sti
(fuldstændige sti) giver mindre query plan og bedre svartid. RCPLEIx
= RptList.e.value('(dns:Tx/dns:ValtnUpd/dns:CtrPtySpcfcData/dns:CtrPty/dns:RptgCtrPty/dns:Id/dns:LEI/text())[1]', 'varchar(20)'), -- // springer
nogle tags over for at nå Id og LEI d.v.s den relative sti. Starter med
punktum lig med start fra Rpt, -- fordi Tx kan
også være Pos, og ValtnUpd kan også være New, Mod, PosCmpnt m.fl. så undgås
at angive flere -- XPath stier,
men det koster med en større query plan og en langsommere svartid. RCPLEI
= RptList.e.value('(.//dns:RptgCtrPty/dns:Id/dns:LEI/text())[1]','varchar(20)'), OCPLEI
= RptList.e.value('(.//dns:OthrCtrPty/dns:Id/dns:LEI/text())[1]','varchar(20)'), SALEI = RptList.e.value('(.//dns:SubmitgAgt/dns:LEI/text())[1]','varchar(20)'), Amount
= RptList.e.value('(.//dns:Valtn/dns:CtrctVal/dns:Amt/text())[1]','decimal(19,4)'), Currency =
RptList.e.value('(.//dns:Valtn/dns:CtrctVal/dns:Amt/@Ccy)[1]','varchar(3)'), -- an attribute TimeStmp = CAST(RptList.e.value('(.//dns:TmStmp/text())[1]','varchar(20)') AS datetime2(0)), Tp =
RptList.e.value('(.//dns:Tp/text())[1]','varchar(10)') FROM
@XmlTable.nodes('//rns:BizData') AS BizData(e) OUTER APPLY
@XmlTable.nodes('//rns:BizData/rns:Pyld/dns:Document/dns:DerivsTradRpt/dns:TradData/dns:Rpt') AS RptList(e)
Omdøbe skema navn på et tabel eller view
eller stored procedure fra [Crm].[Customer] til [Shared].[Customer]: ALTER
SCHEMA [Shared] TRANSFER [Crm].[Customer] Omdøbe et feltnavn Kontrollen
med IF sikrer, at koden kan udføres flere gange uden fejl. Sammenligning
af små bogstaver og store bogstaver sker med Case-Sensitive. Her
omdøbes feltet standard til Standard. IF
EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = 'Shared' AND TABLE_NAME = 'Customer' AND COLUMN_NAME COLLATE Danish_Norwegian_CS_AS
= 'standard' COLLATE
Danish_Norwegian_CS_AS) EXEC
sp_rename 'Shared.Customer.standard', 'Standard', 'COLUMN'; Omdøbe et tabelnavn Her omdøbes tabellen customer til Customer. IF
EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'Shared' AND TABLE_NAME COLLATE
Danish_Norwegian_CS_AS = 'customer' COLLATE
Danish_Norwegian_CS_AS) EXEC
sp_rename 'Shared.customer', 'Customer'; Brugere på en SQL Server SELECT
sp.name AS LoginName ,sp.type_desc AS LoginType, IIF(slog.sysadmin
= 1 OR slog.securityadmin = 1 OR slog.serveradmin = 1 OR slog.setupadmin = 1
OR slog.processadmin = 1 OR slog.dbcreator = 1 OR slog.bulkadmin = 1,1,0) AS
AdminDbRole FROM
sys.server_principals sp INNER JOIN master..syslogins slog ON
sp.sid = slog.sid WHERE
sp.type <> 'R' AND sp.name NOT
LIKE '##%' AND sp.name NOT LIKE 'NT SERVICE%' AND sp.name NOT LIKE 'NT
AUTHORITY%'and sp.is_disabled = 0 Vis
kørende bruger-processer – en overvågning, se deres SQL sætninger Kræver adgang for almindelige
brugere: GRANT VIEW SERVER STATE TO
[LAN\ad-gruppe]; SELECT login_name ,COUNT(session_id) AS session_count FROM sys.dm_exec_sessions GROUP BY login_name; Kræver
en indstilling i SSMS, hvor der under Tools og Option søges på xml:
SET DEADLOCK_PRIORITY -10; SELECT DB_NAME(S.database_id) AS DB, T.text Query , SUBSTRING(T.text, (R.statement_start_offset/2)+1, ((CASE R.statement_end_offset WHEN
-1 THEN DATALENGTH(T.text) ELSE R.statement_end_offset END
- R.statement_start_offset)/2) + 1) AS Statement ,
P.query_plan ,
R.status, S.status, R.command ,
S.session_id,
R.blocking_session_id,
S.host_name, S.login_name, S.program_name ,
S.login_time,
S.last_request_start_time, S.last_request_end_time ,
S.memory_usage,
S.cpu_time s_cpu_time,
S.total_scheduled_time, S.total_elapsed_time
s_total_elapsed_time ,
R.cpu_time r_cpu_time,
R.total_elapsed_time r_total_elapsed_time ,
S.reads s_reads,
S.writes s_writes,
S.logical_reads s_logical_reads, S.row_count
s_row_count ,
R.reads r_reads,
R.writes r_writes,
R.logical_reads s_Logical_reads, R.row_count
r_row_count ,
S.deadlock_priority, R.wait_time, R.wait_type, R.last_wait_type, R.wait_resource, R.open_transaction_count ,
R.open_resultset_count, R.transaction_id, R.percent_complete, R.estimated_completion_time FROM sys.dm_exec_sessions AS
S WITH (NOLOCK) INNER JOIN sys.dm_exec_requests AS
R WITH (NOLOCK) ON S.session_id = R.session_id OUTER APPLY sys.dm_exec_sql_text(R.sql_handle) AS T -- function
call OUTER APPLY sys.dm_exec_query_plan(R.plan_handle) AS P -- function call WHERE ISNULL(T.text,'') <> 'sp_server_diagnostics' AND
S.session_id <>
@@SPID AND
S.session_id >=
50 AND R.status <> 'background' ORDER BY 1, S.session_id; Wait type
beskrivelser
Mere om
PAGEIOLATCH
Endnu mere om
PAGEIOLATCH Flere eksempler på
overvågninger Vis
kørende tabeller i TEMPDB og deres antal rækker her og nu SELECT TableSchemaName
= SCHEMA_NAME(t.schema_id), TableName
= t.name, TableRowCount = FORMAT(SUM(p.rows) , 'N0', 'da-DK') FROM
tempdb.sys.tables t INNER JOIN tempdb.sys.partitions p ON
t.object_id = p.object_id AND p.index_id IN(0, 1) GROUP BY
t.schema_id, t.name Vis
indholdet af en tabel i TEMPDB her og nu, f.eks. en global ## tabel SELECT * FROM
tempdb.INFORMATION_SCHEMA.TABLES WHERE
TABLE_NAME = '##T_5361_b6ae9a336bbe43fea487f0f336fd3d9c' Stoppe
en forespørgsel på en SQL Server SELECT OBJECT_NAME(P.object_id)
AS TableName, Resource_type, Session_ID
= request_session_id FROM sys.dm_tran_locks L INNER JOIN sys.partitions P ON L.resource_associated_entity_id =
p.hobt_id WHERE OBJECT_NAME(P.object_id) = 'Archive_Customer' KILL
angiv talværdien fra ovenstående Session_ID Slet
en bruger på en SQL Server SELECT
session_id FROM
sys.dm_exec_sessions WHERE login_name = 'bruger' KILL
angiv talværdien fra ovenstående Session_ID DROP LOGIN 'bruger' Kopiering
af en tabel struktur SELECT
TOP(0) * INTO Archive_Customer FROM
dbo.Customer Top(0)
giver 0 rækker men vi får tabel strukturen til at danne den nye kopi tabel. Felter
i en tabel eller kolonner i en tabel
(STRING_AGG) SELECT
COLUMN_NAME + ',' FROM
INFORMATION_SCHEMA.COLUMNS WHERE
TABLE_SCHEMA = 'dbo' AND TABLE_NAME = 'DimCustomer' Løbende
tildeling af felter til en streng variabel kaldet aggregeret streng: DECLARE
@ColumnList varchar(max) = '' SELECT
@ColumnList = @ColumnList + ', ' + t.COLUMN_NAME FROM ( SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = 'dbo' AND TABLE_NAME = 'DimCustomer' AND COLUMN_NAME NOT LIKE '%_meta' )
t SET @ColumnList = SUBSTRING(@ColumnList, 3,
LEN(@ColumnList)) SELECT @ColumnList Eller ved at udbytte at @ColumnList
starter som NULL som default værdi: DECLARE
@ColumnList varchar(max) SELECT
@ColumnList = CONCAT(@ColumnList + ', ', COLUMN_NAME) FROM
INFORMATION_SCHEMA.COLUMNS WHERE
TABLE_SCHEMA = 'dbo' AND TABLE_NAME = 'DimCustomer' AND COLUMN_NAME NOT LIKE '%_meta' SELECT @ColumnList Aggregeret streng eller
string aggregation via xml til en streng variabel: DECLARE @ColumnList
varchar(max) SET @ColumnList
= SUBSTRING( (SELECT CONCAT(', ', QUOTENAME(COLUMN_NAME)) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = 'dbo' AND
TABLE_NAME = 'DimCustomer' AND COLUMN_NAME NOT LIKE '%_meta' FOR XML PATH('') ), 3, 99999999) SELECT @ColumnList STUFF
1,2 erstatter strengens første to tegn ', ' med '' den tomme streng: DECLARE @ColumnList
varchar(max) SET @ColumnList
= STUFF( (SELECT CONCAT(', ',
QUOTENAME(COLUMN_NAME)) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = 'dbo' AND
TABLE_NAME = 'DimCustomer' AND COLUMN_NAME NOT LIKE '%_meta' FOR XML PATH('') ),1,2,'') SELECT @ColumnList STUFF
er sjov: SELECT STUFF('SQL
Tutorial', 1, 3, 'Python') becomes Python Tutorial Aggregeret streng
eller string aggregation via json til en streng variabel, JSON fra version 2016: DECLARE @ColumnList
varchar(max) SET @ColumnList
= JSON_VALUE(REPLACE( (SELECT _ = QUOTENAME(COLUMN_NAME) FROM
INFORMATION_SCHEMA.COLUMNS WHERE
TABLE_SCHEMA = 'dbo' AND TABLE_NAME = 'DimCustomer' AND COLUMN_NAME NOT LIKE '%_meta' FOR
JSON PATH),'"},{"_":"',', '),'$[0]._') Aggregeret streng
eller string aggregation via string aggregate funktion, STRING_AGG fra version 2017: DECLARE
@ColumnList varchar(max) SELECT
@ColumnList = STRING_AGG(CONVERT(varchar(max), QUOTENAME(COLUMN_NAME)),
', ') WITHIN GROUP (ORDER BY
ORDINAL_POSITION ASC) FROM
INFORMATION_SCHEMA.COLUMNS WHERE
TABLE_SCHEMA = 'dbo' AND TABLE_NAME = 'DimCustomer' AND COLUMN_NAME NOT LIKE '%_meta' SELECT @ColumnList Aggregeret streng med
XML eller String_Agg: CREATE
TABLE #TestData (GroupID INT, Value NVARCHAR(50)); INSERT
INTO #TestData SELECT
TOP 50000 GroupID = ABS(CHECKSUM(NEWID())) % 10000, Value = 'Item' +
CAST(ROW_NUMBER() OVER (ORDER BY NEWID()) AS VARCHAR(10)); SELECT
GroupID, STUFF((SELECT ',' + Value FROM #TestData t2 WHERE t1.GroupID =
t2.GroupID FOR XML PATH('')), 1, 1, '') AS Aggregated FROM
#TestData t1 GROUP BY GroupID; SELECT
GroupID, STRING_AGG(Value, ',') AS Aggregated FROM
#TestData GROUP BY GroupID; CONVERT(varchar(max) er en angivelse af
input datatype som også bliver til output datatype der tilsidesætter default
output datatype på 8000 tegn for varchar: »STRING_AGG aggregation result
exceeded the limit of 8000 bytes. Use LOB types to avoid result truncation.« Ønskes @ColumnList begrænset til 8000
tegn, så LEFT til at udtage de første 8000 tegn. ColumnList kan være et felt
i en tabel med data typen varchar(8000): DECLARE
@ColumnList varchar(8000) SELECT
@ColumnList = LEFT(STRING_AGG(CONVERT(varchar(max), QUOTENAME(COLUMN_NAME)),
', ') WITHIN GROUP (ORDER BY
ORDINAL_POSITION ASC), 8000) Når et tekst felt er defineret således:
[Tekst] [nvarchar](max) NULL og ønskes afkortet til 4000 tegn eller
8000 tegn som varchar, må der castes: [Tekst_4000] = CAST([Tekst] AS nvarchar(4000)), [Tekst_8000] = CAST([Tekst] AS varchar(8000)) Med en linked server ønskes data overført dynamisk som en ny tabel hver
gang, men tekst-kolonnerne har en anden collation som medtages i INTO men der
ønskes anvendt modtager databasens collation som angives i SQL sætningen. Vist med en kolonne: DROP TABLE IF EXISTS [order].[Transaction] SELECT [Reporter] = [ReporterName] COLLATE Danish_Norwegian_CI_AS INTO [order].[Transaction] FROM [<linkedserver>].[<database>].[order].[Transaction] Kan ikke anvende COLLATE DATABASE_DEFAULT,
fordi SELECT sætningen sendes over til den anden server via linked server, og
de returnerede data ønskes at være med COLLATE Danish_Norwegian_CI_AS. Vist med alle kolonner genereret med
dynamisk sql, hvor der er taget højde for, at datatypen decimal bliver til
numeric via linked server overførsel til ny tabel og derfor skal konverteres
tilbage til decimal, selvom der gælder: »Decimal and Numeric are synonyms and
can be used interchangeably.« Måtte man have en xml kolonne, så må man
i stedet anvende OpenQuery, hvor jeg har opfundet en dummy datatype xmlnvarchar
til at indgå i Replace senere hen til oprindelige xml data type: DECLARE @ColumnList varchar(max) SELECT @ColumnList = STRING_AGG(CONVERT(varchar(max), CASE
WHEN COLLATION_NAME IS
NOT NULL THEN
CONCAT(QUOTENAME(COLUMN_NAME),'=',QUOTENAME(COLUMN_NAME), ' COLLATE Danish_Norwegian_CI_AS') ELSE
CASE WHEN
DATA_TYPE = 'decimal'
THEN
CONCAT(QUOTENAME(COLUMN_NAME),'=', 'CONVERT(decimal(',NUMERIC_PRECISION,',',NUMERIC_SCALE,'),',
QUOTENAME(COLUMN_NAME),')') WHEN DATA_TYPE = 'xml' THEN CONCAT(QUOTENAME(COLUMN_NAME),'=', 'CONVERT(xmlnvarchar(max),',QUOTENAME(COLUMN_NAME),')') ELSE QUOTENAME(COLUMN_NAME) END END),',') WITHIN GROUP (ORDER BY
ORDINAL_POSITION ASC) FROM [<linkedserver>].[<database>].INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = 'order' AND
TABLE_NAME = 'Transaction' --SELECT @ColumnList DECLARE @sql nvarchar(max) SET @sql = CONCAT( 'DROP TABLE IF EXISTS [order].[Transaction] SELECT ',
@ColumnList, ' INTO [order].[Transaction] FROM [<linkedserver>].[<database>].[order].[Transaction]') --PRINT @sql EXECUTE (@sql) --When the table has a column of data type xml must
use OpenQuery. DECLARE @sql nvarchar(max) SET @sql = CONCAT( 'DROP TABLE IF EXISTS [order].[Transaction] SELECT ', REPLACE(@ColumnList,'xmlnvarchar(max)','xml'), ' INTO [order].[Transaction] FROM OPENQUERY([<linkedserver>], ''SELECT ', REPLACE(@ColumnList,'xmlnvarchar(max)','nvarchar(max)'), ' FROM
[<database>].[order].[Transaction]'')') --PRINT @sql EXECUTE (@sql) Slet
alle tabeller, views, stored procedures i en database SET NOCOUNT ON DECLARE @SQL AS NVARCHAR(MAX) DECLARE @SchemaName SYSNAME DECLARE @TableName SYSNAME DECLARE @TableType SYSNAME DECLARE @Scan CURSOR LOCAL STATIC FORWARD_ONLY READ_ONLY FOR SELECT TABLE_SCHEMA, TABLE_NAME, TABLE_TYPE FROM DM_PROD.INFORMATION_SCHEMA.TABLES --Include tables and views. WHERE TABLE_SCHEMA != 'dbo'
UNION ALL SELECT ROUTINE_SCHEMA, ROUTINE_NAME, ROUTINE_TYPE FROM DM_PROD.INFORMATION_SCHEMA.ROUTINES –Include stored procedures. WHERE ROUTINE_SCHEMA != 'dbo' ORDER BY TABLE_SCHEMA, TABLE_NAME, TABLE_TYPE OPEN @Scan FETCH NEXT FROM CursorName INTO @SchemaName, @TableName, @TableType WHILE @@FETCH_STATUS = 0 BEGIN IF @TableType = 'BASE
TABLE' BEGIN SET @SQL = N'DROP
TABLE IF EXISTS ['+ @SchemaName+'].['+ @TableName+ ']' --PRINT @SQL EXEC [OrdreMart].sys.sp_executesql @SQL END IF @TableType = 'VIEW' BEGIN SET @SQL = N'DROP
VIEW IF EXISTS ['+ @SchemaName+'].['+ @TableName+ ']'
--PRINT @SQL EXEC [OrdreMart].sys.sp_executesql @SQL END IF @TableType = 'PROCEDURE' BEGIN SET @SQL = N'DROP
PROCEDURE IF EXISTS ['+ @SchemaName+'].['+ @TableName+ ']' --PRINT @SQL EXEC [OrdreMart].sys.sp_executesql @SQL END FETCH NEXT FROM CursorName INTO @SchemaName, @TableName, @TableType END CLOSE @Scan DEALLOCATE @Scan Bemærk DROP PROCEDURE IF EXISTS [base].[Customer_Rep] Msg 3705, Level 16, State 1, Line 3 Cannot use DROP PROCEDURE with 'base.Customer_Rep' because 'base.Customer_Rep' is a table. Use DROP TABLE. Tjek inden drop sætningen om det er
en stored procedure: IF EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_SCHEMA = 'base' AND ROUTINE_NAME = 'Customer_Rep') DROP PROCEDURE [base].[Customer_Rep]; Seneste
Identity værdi for alle tabeller i en database WITH
cteColumns AS ( SELECT OBJECT_SCHEMA_NAME(c.object_id) AS [SchemaName],
OBJECT_NAME(c.object_id) AS [TableName] FROM sys.all_columns c INNER JOIN sys.types t ON c.system_type_id
= t.system_type_id WHERE c.is_identity
= 1 AND t.name IN ('int', 'bigint', 'smallint', 'tinyint') ) SELECT [SchemaName],
[TableName], IDENT_CURRENT([SchemaName]
+ '.' + [TableName]) AS [IdentityValue] FROM
cteColumns ORDER BY [IdentityValue] DESC Felter
i et view eller en tabel SELECT
TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, COLUMN_DEFAULT, IS_NULLABLE, DATA_TYPE FROM
INFORMATION_SCHEMA.COLUMNS WHERE
TABLE_SCHEMA = 'schema' AND TABLE_NAME = 'view/table' ORDER BY
ORDINAL_POSITION SELECT OBJECT_SCHEMA_NAME(c.object_id) AS 'Schema', OBJECT_NAME(c.object_id)
AS 'View', c.name AS 'Column' FROM sys.columns AS c INNER
JOIN sys.views AS v ON c.object_id = v.object_id WHERE v.name = 'navn
på et view' |