Referência rápida SQL Server


Índice

1- Sobre o SQL Server

O SQL Server é um sistema de banco de dados relacional criado pela Microsoft. Sua primeira versão foi publicada em 1989.

Para saber mais sobre os tipos de banco de dados, acesse aqui: Tipos de banco de dados

//TODO: Complementar


2- Referências sobre a linguagem

//TODO: Complementar


3- Instalação

Se não tiver um banco de dados SQL Server para utilizar (por exemplo da empresa que você estiver trabalhando), você pode instalar uma versão gratuita no seu computador.

Será necessário ter um computador com os seguintes requisitos mínimos:

Faça o download do SQL Server 2019 Express no site: https://www.microsoft.com/en-us/Download/details.aspx?id=101064

Depois, execute a instalação:

Para economizar memória ao iniciar o Windows o serviço do SQL Server está marcado para não iniciar junto com o Windows.

Toda vez que você for utilizar o SQL Server, você precisa inicia-lo, para isso faça o seguinte:


4- IDEs

SQL Server Management Studio (SSMS)

Faça a instalação do SQL Server Management Studio (SSMS) no site da Microsoft e realize a instalação: https://docs.microsoft.com/en-us/sql/ssms/download-sql-server-management-studio-ssms

Abra o SQL Server Management Studio:

(iniciar / SQL Server Management Studio)

Para conectar, utilize:

Então, clique no botão “New query”

Azure Data Studio

//TODO: Complementar


5- Hello World

Usando um banco de dados de exemplo

Inicie o SQL Server Management Studio:

Para conectar, utilize:

Abra uma nova query:

Faça o download do banco de dados de exemplo AdventureWorks: https://github.com/Microsoft/sql-server-samples/releases/download/adventureworks/AdventureWorks-oltp-install-script.zip

Descompacte o arquivo em uma pasta do seu computador, por exemplo: C:\temp\AdventureWorks-oltp-install-script

Abra o arquivo: instawdb.sql

Altere no script o caminho onde você baixou os arquivos:

Habilite o SQLCMD Mode:

Execute o script:

Deve ser exibida mensagem de sucesso:

Atualize a lista de bancos de dados:

Expanda a lista do banco recém criado:

Agora, você pode executar uma query para consultar dados de uma das tabelas, por exemplo:

SELECT * FROM Sales.Customer

E execute precionando F5, ou clicando no botão executar:

Criando um banco de dados

Inicie o SQL Server Management Studio:

Para conectar, utilize:

Crie o novo banco de dados:

Abra uma nova query:

Para criar uma tabela com 3 registros, digite:

CREATE TABLE Book (
	BookId	INT,
	Title	VARCHAR(100) NOT NULL,
	Author	VARCHAR(100) NOT NULL,
	CONSTRAINT PK_Book PRIMARY KEY (BookId)
)
go

INSERT INTO Book (BookId, Title, Author) VALUES (1, 'Clean Code', 'Robert C. Martin')
INSERT INTO Book (BookId, Title, Author) VALUES (2, 'Domain-Driven Design', 'Eric Evans')
INSERT INTO Book (BookId, Title, Author) VALUES (3, 'Implementing Domain-Driven Design', 'Vaughn Vernon')
go

Então, execute pressionando F5, ou clicando no botão Executar.

Para pesquisar todos os registros da tabela, digite o seguinte:

SELECT * FROM Book


6- Básico

//TODO: Complementar

Comentários de código

//TODO: Complementar

Interação com o usuário

//TODO: Complementar

Variáveis

//TODO: Complementar

Tipos

//TODO: Complementar

Palavras reservadas

//TODO: Complementar

Funções

//TODO: Complementar

Built-in Functions

//TODO: Complementar

Strings

//TODO: Complementar

Coleções

//TODO: Complementar

Operadores

//TODO: Complementar


7- Estruturas condicionais

//TODO: Complementar


8- Declaração e tratamento de exceções

//TODO: Complementar


Print

Para imprimir mensagens durante a execução de um comando utilize o comando print

PRINT Mensagem para ser exibida

Flush print buffer

Utilizar “Print” para imprimir mensagens quando uma operação muito longa está sendo executada, nem sempre imprime os resultados no Sql Management Studio. Utilizar um RAISERROR, no lugar do print, como no comando abaixo força que todas as mensagens do buffer sejam instantaneamente exibidas

DECLARE @message VARCHAR(1000)
SET @message = 'Message...'
RAISERROR(@message, 0, 1) WITH NOWAIT

Identity

Para que uma coluna seja do tipo auto incremento no SQL Server utilizar o atributo “Identity” ao criar a coluna

Criar uma tabela com coluna Identity

CREATE TABLE [User] (
	UserID		INT		IDENTITY	NOT NULL
	, Email		VARCHAR(200)		NOT NULL
	, CONSTRAINT PK_User PRIMARY KEY (UserID)
)

Forçar um ID no insert em uma tabela com Identity

SET IDENTITY_INSERT [User] ON
INSERT INTO [User] (UserID, Email) VALUES (1, 'teste@teste.com.br')
SET IDENTITY_INSERT [User] OFF

Resetar o Identity

DBCC CHECKIDENT('[User]', RESEED, 0)

Obter o ID do último registro inserido

INSERT INTO [User] (Email) VALUES ('teste2@teste.com.br')
DECLARE @id INT
SELECT @id = CONVERT(INT, SCOPE_IDENTITY())

NOTAS:


Índices

Criar índice unique (Unique Index)

DROP INDEX UNIQUE_Supplier_Name ON Supplier
CREATE UNIQUE INDEX UNIQUE_Supplier_Name ON Supplier (ReferenceName)

Criar índice somente incluindo somente registros not null

CREATE UNIQUE NONCLUSTERED INDEX UNIQUE__fi_pdv_tb_Coupon__ds_offline_id
ON fi_pdv_tb_Coupon(id_omega, ds_offline_id)
WHERE ds_offline_id IS NOT NULL

Cursor

DECLARE @productID int

DECLARE cursor1 CURSOR FOR
SELECT ProductID FROM Product (NOLOCK)

OPEN cursor1

FETCH NEXT FROM cursor1 INTO
	@productID

WHILE @@FETCH_STATUS = 0
BEGIN
	PRINT '-- productID=' + convert(varchar(20), @productID)

	-- Executar aqui o que precisa ser executado

	FETCH NEXT FROM cursor1 INTO
		@productID
END

CLOSE cursor1
DEALLOCATE cursor1

Paginação

SELECT
	m.id,
	COUNT(1) OVER()
FROM
	Movie m
WHERE
	m.ChannelID = 1
ORDER BY
	m.Created
OFFSET @OFFSET ROWS FETCH NEXT @COUNT ROWS ONLY
CREATE PROCEDURE dbo.BrandPagedList (
	@page int,
	@pageSize int
) AS
BEGIN

WITH ListResult AS
(
	SELECT
		br.BrandID
		, br.[Name]

		, ROW_NUMBER() OVER(ORDER BY br.[Name] ASC) AS rowNumber
	FROM
		Brand (NOLOCK) br
)
SELECT * , (SELECT MAX(rowNumber) FROM ListResult) as TotalRows FROM ListResult WHERE rowNumber BETWEEN ((@pageSize * (@page - 1)) + 1) AND (@pageSize * @page);

Transação

Transaction + RaiseError

BEGIN TRY
	BEGIN TRANSACTION
	PRINT '--BEGIN TRANSACTION'

	--Execute...

	COMMIT
	PRINT '--COMMIT'

END TRY
BEGIN CATCH

	DECLARE @errorMessage nvarchar(max), @errorSeverity int, @errorState int;

	SELECT
		@errorMessage = ERROR_MESSAGE() + ' Line ' + cast(ERROR_LINE() as nvarchar(5)),
		@errorSeverity = ERROR_SEVERITY(),
		@errorState = ERROR_STATE();

	PRINT '--ROLLBACK'
	ROLLBACK

	RAISERROR (@errorMessage, 16, -1);
	RETURN

END CATCH

Datas

Operações com datas

TODO: Incluir exemplos

Data do primeiro dia do mês

IF EXISTS(SELECT * FROM sys.objects WHERE name = 'fnFirstMonthDay' AND type in ('FN', 'IF', 'TF', 'FS', 'FT'))
	DROP FUNCTION dbo.fnFirstMonthDay
go

CREATE FUNCTION dbo.fnFirstMonthDay(@date DATE) RETURNS DATE AS
BEGIN

	IF @date IS NULL
		RETURN NULL

	RETURN DATEADD(day, ((DATEPART(D, @date) -1) * -1), @date)

END

Data do primeiro dia da semana

IF EXISTS(SELECT * FROM sys.objects WHERE name = 'fnFirstWeekDay' AND type in ('FN', 'IF', 'TF', 'FS', 'FT'))
	DROP FUNCTION dbo.fnFirstWeekDay
go

CREATE FUNCTION dbo.fnFirstWeekDay(@date DATE) RETURNS DATE AS
BEGIN

	IF @date IS NULL
		RETURN NULL

	RETURN DATEADD(day, ((DATEPART(DW, @date) -1) * -1), @date)

END

Operações com string

Obtém somente os caracteres numéricos de um texto

IF EXISTS(SELECT * FROM sys.objects WHERE name = 'fnGetNumericCharacters' AND type in ('FN', 'IF', 'TF', 'FS', 'FT'))
	DROP FUNCTION fnGetNumericCharacters
GO

CREATE FUNCTION dbo.fnGetNumericCharacters(@text VARCHAR(MAX)) RETURNS VARCHAR(MAX) AS
BEGIN

	IF @text IS NULL
		RETURN NULL

	DECLARE @index INT = PATINDEX('%[^0-9]%', @text)

	WHILE @index > 0
	BEGIN
		SET @text = STUFF(@text, @index, 1, '' )
		SET @index = PATINDEX('%[^0-9]%', @text )
	END

	IF len(@text) = 0
		RETURN NULL

	RETURN @text

END
GO

Remove acentuação de um texto

IF EXISTS(SELECT * FROM sys.objects WHERE name = 'fnRemoveDiacritics' AND type in ('FN', 'IF', 'TF', 'FS', 'FT'))
	DROP FUNCTION dbo.fnRemoveDiacritics
GO

CREATE FUNCTION dbo.fnRemoveDiacritics (@text VARCHAR(MAX))
RETURNS VARCHAR(MAX) AS
BEGIN

	SELECT @text = (@text collate SQL_Latin1_General_Cp1251_CS_AS)

    RETURN @text

END

Remove caracteres especiais de um texto

IF EXISTS(SELECT * FROM sys.objects where name = 'fnRemoveSpecialCharacters' AND type in ('FN', 'IF', 'TF', 'FS', 'FT'))
	DROP FUNCTION dbo.fnRemoveSpecialCharacters
GO

CREATE FUNCTION dbo.fnRemoveSpecialCharacters (@text VARCHAR(MAX), @allowSpaces BIT = 1)
RETURNS VARCHAR(MAX) AS
BEGIN

    DECLARE @matchExpression AS VARCHAR(100)

	IF @allowSpaces = 1
		SET @matchExpression = '%[^a-z0-9 ]%'
	ELSE
		SET @matchExpression = '%[^a-z0-9]%'

	WHILE PatIndex(@matchExpression, @text) > 0
        SET @text = Stuff(@text, PatIndex(@matchExpression, @text), 1, '')

    RETURN @text

END
GO

Transforma texto separado por vírgula em registros

IF EXISTS(SELECT name FROM sys.objects WHERE name = 'fnSplit' AND type in ('FN', 'IF', 'TF', 'FS', 'FT'))
	DROP FUNCTION dbo.fnSplit
GO

CREATE  FUNCTION dbo.fnSplit(@text VARCHAR(MAX), @separator VARCHAR(20) = ' ')
RETURNS @strings TABLE
(   
  position int IDENTITY PRIMARY KEY,
  value VARCHAR(MAX)
)
AS
BEGIN

DECLARE @index int = -1

WHILE (LEN(@text) > 0)
  BEGIN
    SET @index = CHARINDEX(@separator , @text)
    IF (@index = 0) AND (LEN(@text) > 0)
      BEGIN  
        INSERT INTO @strings VALUES (@text)
          BREAK
      END
    IF (@index > 1)
      BEGIN  
        INSERT INTO @strings VALUES (LEFT(@text, @index - 1))  
        SET @text = RIGHT(@text, (LEN(@text) - @index))
      END
    ELSE
      SET @text = RIGHT(@text, (LEN(@text) - @index))
    END
  RETURN
END

Transforma os registros em um texto separado por vírgula

SELECT
	a.ItemID,
	(SELECT
		STUFF(
				(SELECT ',' + CONVERT(VARCHAR(10), si.SupplierID) FROM SupplierItem si (NOLOCK) WHERE a.ItemID = si.ItemID AND si.Active = 1 FOR XML PATH(''))
			,1,1,'')
	) as SupplierIdList
FROM
	Item a (nolock)
WHERE
	a.ItemID in (303216, 5)

Completa texto com espaços ou zeros

IF EXISTS(SELECT name FROM sys.objects WHERE Name = 'fnPadString' AND type in ('FN', 'IF', 'TF', 'FS', 'FT'))
	DROP FUNCTION dbo.fnPadString
GO

CREATE FUNCTION dbo.fnPadString (@text VARCHAR(MAX), @padWith char(1), @length int) RETURNS VARCHAR(MAX) AS
BEGIN

DECLARE @ret VARCHAR(MAX)
SELECT @ret = ISNULL(REPLICATE(@padWith, @length - LEN(ISNULL(@text,0))), '') + @text
RETURN @ret

END
GO

Update

Atualiza linhas duplicadas

;WITH cte AS
(
	SELECT
		cp.id_omega
		, cp.id_coupon
		, cp.dt_coupon
		, cp.ts_control
		, cp.ds_offline_id
		, ROW_NUMBER() OVER(PARTITION BY cp.ds_offline_id ORDER BY cp.ds_offline_id) AS RowNumber
	FROM
		fi_pdv_tb_Coupon cp (nolock)
		INNER JOIN (
			SELECT id_omega, ds_offline_id, COUNT(ds_offline_id) as qtd FROM fi_pdv_tb_Coupon (NOLOCK) GROUP BY id_omega, ds_offline_id HAVING COUNT(ds_offline_id) > 1
		) b
		on cp.id_omega = b.id_omega
		and cp.ds_offline_id = b.ds_offline_id
)

UPDATE cte SET ds_offline_id = ds_offline_id + '_' + convert(varchar(10), RowNumber) where RowNumber > 1

Converte um texto para ser utilizado como url

IF EXISTS(SELECT * FROM sys.objects WHERE name = 'fnGetUrl' AND type in ('FN', 'IF', 'TF', 'FS', 'FT'))
	DROP FUNCTION dbo.fnGetUrl
go

CREATE FUNCTION dbo.fnGetUrl(@text varchar(200)) RETURNS VARCHAR(200) AS
BEGIN

	--remove
	SET @text = replace(@text, '&', '')
	SET @text = replace(@text, '.', '')
	SET @text = replace(@text, '(', '')
	SET @text = replace(@text, ')', '')
	SET @text = replace(@text, '''', '')
	SET @text = replace(@text, '"', '')
	SET @text = replace(@text, '%', '')
	SET @text = replace(@text, '$', '')
	SET @text = replace(@text, '+', '')

	--separador
	SET @text = replace(@text, ' e ', '-')
	SET @text = replace(@text, ',', '-')
	SET @text = replace(@text, ' - ', '-')
	SET @text = replace(@text, '/', '-')
	SET @text = replace(@text, '\', '-')
	SET @text = replace(@text, ' ', '-')

	SET @text = replace(@text, '--', '-')

	--remove acentuação
	SELECT @text = @text Collate SQL_Latin1_General_CP1253_CI_AI

	SET @text = ltrim(rtrim(lower(@text)))

	RETURN @text

END
GO

Select e Joins

Cross join

A cross join that does not have a WHERE clause produces the Cartesian product of the tables involved in the join

SELECT
	p.BusinessEntityID, t.Name AS Territory
FROM
	Sales.SalesPerson p
CROSS JOIN
	Sales.SalesTerritory t
ORDER BY
	p.BusinessEntityID;

Select usando WITH

--drop table [Local]

CREATE TABLE [Local]
	(LocalID int not null,
	LocalPaiID int null,
	Nome varchar(50) not null)
go

INSERT INTO [Local] values (100, null, 'Bloco 1');
	INSERT INTO [Local] values (110, 100, 'Prédio 1');
			INSERT INTO [Local] values (111, 110, '1º Andar');
			INSERT INTO [Local] values (112, 110, '2º Andar');
			INSERT INTO [Local] values (113, 110, '3º Andar');
			INSERT INTO [Local] values (114, 110, '4º Andar');
	INSERT INTO [Local] values (120, 100, 'Prédio 2');
			INSERT INTO [Local] values (121, 120, '1º Andar');
			INSERT INTO [Local] values (122, 120, '2º Andar');
			INSERT INTO [Local] values (123, 120, '3º Andar');
	INSERT INTO [Local] values (12, 100, 'Prédio 3');
INSERT INTO [Local] values (200, null, 'Bloco 2');
	INSERT INTO [Local] values (210, 200, 'Prédio 1');
	INSERT INTO [Local] values (220, 200, 'Prédio 2');
	INSERT INTO [Local] values (230, 200, 'Prédio 3');
INSERT INTO [Local] values (300, null, 'Bloco 3');
go


WITH Loc AS
		(SELECT 1 as Nivel, l.LocalID, l.Nome, CONVERT(VARCHAR(1000), l.Nome) as CaminhoCompleto FROM [Local] l (NOLOCK) WHERE l.LocalPaiID IS NULL
		UNION ALL
		SELECT Nivel+1, l.LocalID, l.Nome, CONVERT(VARCHAR(1000), Loc.CaminhoCompleto + ' / ' + l.Nome) as CaminhoCompleto
		FROM [Local] l (NOLOCK)
		INNER JOIN Loc on l.LocalPaiID = Loc.LocalID
		WHERE l.LocalPaiID is not null
		)
	SELECT LocalID, Nome, CaminhoCompleto FROM Loc;
go
IF EXISTS(SELECT name FROM sys.objects WHERE Name = 'fn_Local_Caminho_Completo' AND type in ('FN', 'IF', 'TF', 'FS', 'FT'))
	DROP FUNCTION dbo.fn_Local_Caminho_Completo
GO

CREATE FUNCTION dbo.fn_Local_Caminho_Completo(@LocalID int)
returns VARCHAR(1000)
AS
BEGIN

	DECLARE @caminho varchar(1000);

	WITH Loc AS
		(SELECT l.LocalID, l.LocalPaiID, l.Nome, CONVERT(VARCHAR(1000), l.Nome) as CaminhoCompleto FROM [Local] l (NOLOCK) WHERE l.LocalID = @LocalID
		UNION ALL
		SELECT l.LocalID, l.LocalPaiID, l.Nome, CONVERT(VARCHAR(1000), l.Nome + ' / ' + Loc.CaminhoCompleto) as CaminhoCompleto
		FROM [Local] l (NOLOCK)
		INNER JOIN Loc on l.LocalID = Loc.LocalPaiID
	)
	SELECT @caminho = CaminhoCompleto FROM Loc WHERE LocalPaiID IS NULL

	return @caminho;
END
go

ALTER TABLE [Local] ADD CaminhoCompleto AS dbo.fn_Local_Caminho_Completo(LocalID);
go

select * from [Local];
go

Enviar tabela como parâmetro

IF EXISTS(SELECT * FROM sys.procedures WHERE name = 'OrderInsert')
	DROP PROCEDURE OrderInsert
go

IF EXISTS(SELECT name FROM sys.types WHERE is_user_defined = 1 AND is_table_type = 1 AND name = 'OrderInsert_OrderItemTableType')
	DROP TYPE dbo.OrderInsert_OrderItemTableType
GO

CREATE TYPE OrderInsert_OrderItemTableType AS TABLE (
	  ItemID INT
	, Quantity INT
)
GO

IF EXISTS(SELECT * FROM sys.procedures WHERE name = 'OrderInsert')
	DROP PROCEDURE dbo.OrderInsert
GO

CREATE PROCEDURE dbo.OrderInsert (
	  @clientID INT
	, @totalValue DECIMAL(15,2)
	, @orderItems OrderInsert_OrderItemTableType READONLY
)
as
BEGIN

INSERT INTO [Order] (
	  ClientID
	, TotalValue
)
VALUES (
	@clientID
	, @totalValue
)

DECLARE @orderID INT
SET @orderID = CONVERT(INT, SCOPE_IDENTITY())

INSERT INTO OrderItem (
	OrderID
	, ItemID
	, Quantity
)
SELECT
	@orderID
	, ItemID
	, Quantity
FROM
	@orderItems

SELECT @orderID as id

END
go

RANK

Retorna a classificação de cada linha na partição de um conjunto de resultados. A classificação de uma linha é um mais o número de classificações que vêm antes da linha em questão.

USE AdventureWorks2012;  
GO  
SELECT
	i.ProductID
	, p.Name
	, i.LocationID
	, i.Quantity  
  , RANK() OVER(PARTITION BY i.LocationID ORDER BY i.Quantity DESC) AS Rank  
FROM
	Production.ProductInventory AS i   
	INNER JOIN Production.Product AS p   
		ON i.ProductID = p.ProductID  
WHERE
	i.LocationID BETWEEN 3 AND 4  
ORDER BY
	i.LocationID;  
ProductID   Name                   LocationID   Quantity Rank  
----------- ---------------------- ------------ -------- ----  
494         Paint - Silver         3            49       1  
495         Paint - Blue           3            49       1  
493         Paint - Red            3            41       3  
496         Paint - Yellow         3            30       4  
492         Paint - Black          3            17       5  
495         Paint - Blue           4            35       1  
496         Paint - Yellow         4            25       2  
493         Paint - Red            4            24       3  
492         Paint - Black          4            14       4  
494         Paint - Silver         4            12       5  

DENSE_RANK

Esta função retorna a posição de cada linha dentro de uma partição do conjunto de resultados, sem nenhum intervalo nos valores de classificação. A classificação de uma linha é um mais o número de valores de classificação distintos que vêm antes da linha em questão.

USE AdventureWorks2012;  
GO
SELECT
	TOP(10) BusinessEntityID
	, Rate
	, DENSE_RANK() OVER (ORDER BY Rate DESC) AS RankBySalary  
FROM
	HumanResources.EmployeePayHistory;
BusinessEntityID Rate                  RankBySalary  
---------------- --------------------- --------------------  
1                125.50                1  
25               84.1346               2  
273              72.1154               3  
2                63.4615               4  
234              60.0962               5  
263              50.4808               6  
7                50.4808               6  
234              48.5577               7  
285              48.101                8  
274              48.101                8  

Isolation Level

TODO: Adicionar texto de como os isolations levels funcionam

Obtém itens de uma tabela de fila usando LOCK para threads diferentes não pegarem o mesmo registro

CREATE PROCEDURE dbo.QueueGetNext (
	@queueOperationId INT = null) as
BEGIN

	DECLARE @queueId VARCHAR(36) = NULL

	SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
	BEGIN TRANSACTION

		SELECT TOP 1
			@queueId = QueueId
		FROM
			[Queue] WITH (UPDLOCK)
		WHERE
			QueueStatusId = 1 --Incluido
			AND (@queueOperationId IS NULL OR QueueOperationId = @queueOperationId)
		ORDER BY
			RequestDate ASC

		--Atualiza
		UPDATE
			[Queue]
		SET
			QueueStatusId = 2 --Processando
		WHERE
			QueueId = @queueId

	COMMIT TRANSACTION

	SELECT * FROM [Queue] WHERE QueueId = @queueId

END

Comentários