Thiago Zavaschi R2 www.zavaschi.com

31May/113

SQL Server e SQL Azure – Licenciamento + Billing

Então pessoal, como estão?

Sei que estou postando fortemente sobre Business Intelligence, mas hoje gostaria de iniciar algo diferente.

Se eu fosse elencar as dúvidas que recebo mais frequentemente dos clientes e parceiros, com certeza seria licenciamento em primeiro lugar.

A plataforma de dados da Microsoft é o SQL Server e o seu braço na nuvem é o SQL Azure. Por isso vou dividir esse conjunto de posts da seguinte forma (vou adicionar os links a esse post conforme os posts seguintes saírem), mas não necessariamente nesta mesma ordem.

  • SQL Server, Analysis Services, Integration Services, preciso de uma licença para cada um?
  • SQL Server com Cluster, Mirror e Log Shipping, o que preciso licenciar?
  • SQL Server virtualizado e ambientes híbridos: Virtualizado + não-virtualizado.
  • SQL Azure – Espaço, transferência? Como funciona e quanto custa?

A ideia dos posts é não é entrar na precificação (com excessão do SQL Azure), pois há várias modalidades contratuais e esses aspectos de contratos e programas de relacionamento com a Microsoft eu não irei abordar.

Gostaria de um feedback de vocês que leem o blog (nos comentários) sobre esta sequência de posts. Isso é muito importante! Aceito sugestões!!

Abraços,
Thiago Zavaschi

20Jan/113

Permissão para ver a estrutura, mas sem os dados!

Recentemente o Carlos Barini (http://twitter.com/cabarini) me perguntou se era possível liberar para um user ver a estrutura de uma tabela, mas não ter a permissão de leitura na tabela.

É possível sim, claro que a configuração do permissionamento pode ser adaptado para cada caso. No exemplo aqui vou concentrar nesse caso perguntado.

Vamos criar um login e um user na database “demo”.

USE [master]
GO
CREATE LOGIN [usuario] WITH PASSWORD=N'123', DEFAULT_DATABASE=[demo], CHECK_EXPIRATION=OFF, CHECK_POLICY=OFF
GO
USE [demo]
GO
CREATE USER [usuario] FOR LOGIN [usuario]
GO

Vamos usar a mesma tabela de empregados que usamos no artigo anterior.

O usuário “usuario” tem permissão de select na tabela. O que podemos fazer é simplesmente:

1) Remover a permissão de select no objeto.

2) GRANT na permissão de VIEW DEFINITION:

DENY SELECT ON [dbo].[Empregado] TO [usuario]

GRANT VIEW DEFINITION ON [dbo].[Empregado] TO [usuario]

E teremos ao executar o select:

Msg 229, Level 14, State 5, Line 1
The SELECT permission was denied on the object 'Empregado', database 'demo', schema 'dbo'.

Mas ao tentar ver a estrutura:

image

Espero que te ajude Carlos!

Grande abraço,
Thiago Zavaschi

8Dec/102

SQL Server Analysis Services sobre HTTP/HTTPs

Olá pessoal,

Quando pensamos no SSAS, lembramos que o único modo de autenticação que temos é através de Windows Authentication. Não há algo como o Mixed mode do SQL Server.

Mas os clientes que vão consumir os dados do nosso servidor de análise nem sempre está no mesmo domínio que nós. Então como proceder?

É possível sim (SSAS enterprise only) fazer essa conexão externa através de um pump. Ou seja, você cria uma passagem no seu IIS para o seu servidor de análise.

Primeiramente ative seu IIS no seu servidor. (Aqui estou utilizando o IIS7, os passos para o IIS 6 podem ser observados de uma maneira geral neste post, em inglês: Configuring SSAS over HTTP, apesar de falar do SQL Server 2000, os passos são parecidos).

Crie um site no IIS. Dentro deste site crie um diretório virtual e mapeie para um diretório físico. No meu caso nomeei o diretório como “olap” e mapeei para “C:\inetpub\wwwroot\olap”.

imageMesmo que seja usado HTTP (sem SSL) na porta 80, libere no firewall a porta 80 e a porta 2725, para que o Office consiga fazer o acesso ao servidor de análise.

Copie para a pasta mapeada (C:\inetpub\wwwroot\olap) todos os arquivos do diretório: C:\<local da instalacao do SQL Server>\MSAS10_50.<instancia>\OLAP\bin\isapi.

No meu caso o diretório é:
C:\Program Files\Microsoft SQL Server\MSAS10_50.MSSQLSERVER\OLAP\bin\isapi.

O diretório mapeado fica semelhante ao mostrado abaixo:

image

Após isso vá no gerenciador do IIS, clique no diretório virtual que você criou e depois clique em Mapeamentos de Manipulador (Handler Mapping) e depois clique em “Adicionar Mapeamento de Script” (Add Map Script).

Dê um nome para o mapeamento (no meu caso usei olap mesmo) e mapeie para a dll chamada msmdpump.dll que você copiou do diretório do SQL Server.

image

O último detalhe agora é se o ISS não estiver no mesmo servidor do SSAS. Se este for o cenário você deve acertar o arquivo (com o notepad mesmo) msmdpump.ini que está no diretório mapeado para o diretório virtual. O pedaço que você deve ajustar é o <ServerName>. Se for no mesmo servidor, deixe localhost.

<ConfigurationSettings>
<ServerName>localhost</ServerName>
<SessionTimeout>3600</SessionTimeout>
<ConnectionPoolSize>100</ConnectionPoolSize>
</ConfigurationSettings>

Pronto!

Agora você pode usar como servidor para a conexão o seguinte endereço:

http://<servername/ip>/olap/msmdpump.dll.

Lembrando que você deve passar as credenciais, e estas devem estar no domínio do SSAS. O usuário e senha ficaria algo como:

<domínio>\usuario (com a barra mesmo), e a senha normal do usuário.

Este usuário vai ter as permissões normais definidas nas roles da base do SSAS que você estará fazendo o acesso.

É isso, espero que tenham gostado. :-)

Abraços,
Thiago Zavaschi

22Sep/105

Faça um DBA feliz! #1

Olá pessoal!

Você como desenvolvedor deve pensar que agradar àquele cara que fica sentado junto ao pessoal de infra é difícil, mas não é não!

Essa série irei passar pequenas dicas que podem facilitar (e muito) a vida do DBA. No final das contas ele pode até te pagar uma cerveja por isso :).

É muito comum encontrar cenários de bases de dados SQL Server unificadas, e diversos (eu falei, diversooos!) sistemas que a utilizam.

É muito comum também existirem certas queries que não estão, digamos, muito otimizadas e que forçam muito o servidor. É papel do DBA identificar, tentar corrigir se possível e avisar aos responsáveis.

O problema é justamente saber a quem avisar… Existe uma solução muito simples pra isso.

Quando o DBA está visualizando o que está acontecendo no banco, se a aplicação informar, aparece o nome da aplicação, caso contrário aparece: .NET sqlClient Data Provider (nome genérico e sem significado de negócio direto).

É possível especificar um parâmetro opcional na connection string chamado Application Name e resolver isso:

Data Source=SERVIDOR;Initial Catalog=BASEDEDADOS;User Id=USER;Password=PASS;Application Name=NomeAplicacao;

Isso resulta numa bela visão para facilidar o tracking do DBA!
Comando executado:

select spid, program_name, * from sys.sysprocesses
where program_name <> ''

image

É isso pessoal. A ideia desta série é ter posts curtos e diretos! O próximo deve ser relacionado ao SQL dinâmico, não percam!

Abraços e excelente fim de noite!

12May/101

Verificando espaço em disco e o espaço ocupado por tabelas!

Olá pessoal!

Inspirado no post do Diego Nogare, o qual comentei (com os scripts deste post), resolvi postar aqui também os scripts sobre verificação de espaço usado em disco e por tabelas!

Verificar o tamanho ocupado por tabelas no banco (a.k.a achar maiores tabelas):

Ordenado por quantidade de linhas:

SELECT object_name(id), rowcnt, dpages * 8 AS [tamanho KB] FROM sysindexes
WHERE indid IN (1,0) AND objectproperty(id,'isusertable') = 1
ORDER BY rowcnt DESC

Ordenado por tamanho físico:

SELECT object_name(id), rowcnt, dpages * 8 AS [tamanho KB] FROM sysindexes
WHERE indid IN (1,0) AND objectproperty(id,'isusertable') = 1
ORDER BY [tamanho KB] DESC

E para mostrar o espaço em disco disponível (valor absoluto e percentual) e o espaço total dos discos, é possível utilizar (a fonte deste script é o SQLDBATips):

DECLARE @hr int
DECLARE @fso int
DECLARE @drive char(1)
DECLARE @odrive int
DECLARE @TotalSize varchar(20)
DECLARE @MB bigint ; SET @MB = 1048576

CREATE TABLE #drives (drive char(1) PRIMARY KEY,
                      FreeSpace int NULL,
                      TotalSize int NULL)

INSERT #drives(drive,FreeSpace)
EXEC master.dbo.xp_fixeddrives

EXEC @hr=sp_OACreate 'Scripting.FileSystemObject',@fso OUT
IF @hr <> 0 EXEC sp_OAGetErrorInfo @fso

DECLARE dcur CURSOR LOCAL FAST_FORWARD
FOR SELECT drive from #drives
ORDER by drive

OPEN dcur

FETCH NEXT FROM dcur INTO @drive

WHILE @@FETCH_STATUS=0
BEGIN

        EXEC @hr = sp_OAMethod @fso,'GetDrive', @odrive OUT, @drive
        IF @hr <> 0 EXEC sp_OAGetErrorInfo @fso
        EXEC @hr = sp_OAGetProperty @odrive,'TotalSize', @TotalSize OUT
        IF @hr <> 0 EXEC sp_OAGetErrorInfo @odrive
        UPDATE #drives
        SET TotalSize=@TotalSize/@MB
        WHERE drive=@drive
        FETCH NEXT FROM dcur INTO @drive

END

CLOSE dcur
DEALLOCATE dcur

EXEC @hr=sp_OADestroy @fso
IF @hr <> 0 EXEC sp_OAGetErrorInfo @fso

SELECT drive,
       FreeSpace as 'Livre(MB)',
       TotalSize as 'Total(MB)',
       CAST((FreeSpace/(TotalSize*1.0))*100.0 as int) as 'Livre(%)'
FROM #drives
ORDER BY drive

DROP TABLE #drives

RETURN
GO

A saída do script deverá ser semelhante a:

drive Livre(MB)   Total(MB)   Livre(%)
----- ----------- ----------- -----------
C     9541        49999       19
D     46002       317966      14
F     37549       99998       37
K     68581       476269      14

É isso pessoal, fazia tempo que não postava algo masi focado a área de DBA (a qual gosto muito também), espero que seja útil!

Abraços,
Thiago Zavaschi

7Jan/100

Verificando o status do seu Cluster SQL Server

Olá a todos,

O post de hoje é uma dica rápida para os profissionais que precisam trabalhar com instâncias clusterizadas do SQL Server e gostariam de saber algumas informações sobre as mesmas!

Temos o uso do SERVERPROPERTY e das DMV’s: sys.dm_io_cluster_shared_drives / sys.dm_os_cluster_nodes.

Para extrair algumas informações:

SELECT
    SERVERPROPERTY('IsClustered') as _1_Eh_Clusterizada,
    SERVERPROPERTY('ComputerNamePhysicalNetBIOS') as NoAtual,
    SERVERPROPERTY('Edition') as Edicao,
    SERVERPROPERTY('MachineName') as VirtualName,
    SERVERPROPERTY('InstanceName') as NomeInstancia,
    SERVERPROPERTY('ServerName') as Virtual_e_InstanceNames,
    SERVERPROPERTY('ProductVersion') as Versao,
    SERVERPROPERTY('ProductLevel') as NomeVersaoSemHotfixes

 

E para extrair outras informações é bastante interessante utilizar as seguintes queries:

SELECT * FROM sys.dm_io_cluster_shared_drives

SELECT * FROM sys.dm_os_cluster_nodes

Espero que as queries sejam úteis a vocês!

Abraços,

Thiago Zavaschi

5Jan/102

Prevendo o crescimento da suas databases com o SQL Server Data Mining Add-ins for Microsoft Office 2007 – Parte I

Olá pessoal,

O artigo de hoje é a base de um dos itens/indicadores utilizados nas suas análises de health check dos servidores SQL Server: Verificar o crescimento dos seus arquivos de base de dados e log.

Antes de mais nada vamos revisar um ponto importante quanto ao crescimento das databases:

É muito comum encontrar em servidores configurações como: crescimento proporcional 10% para a base e para o arquivo de log e com irrestrito por exemplo. A imagem a seguir mostra onde visualizar estas informações.

img1

Em que isso impacta? Valores pequenos de taxas de crescimento para bases que crescem muito é extremamente prejudicial, pois teremos altas taxas (overhead) de I/O para o crescimento dos arquivos. Por outro lado, valores muito grandes podem gerar um uso descontrolado dos seus discos.

Em um mundo/cenário ideal os crescimentos das databases são monitorados e o aumento físico dos mesmos são planejados e executados de maneira manual. Claro que pela comodidade, é difícil DBAs optarem por esta abordagem.

Mas e como monitorar, ou melhor, prever o crescimento, já que muitos DBAs deixam essas configurações automáticas? Uma das maneiras que eu acho muito elegante é através da utilização do Add-in para o Office 2007, mais especificamente a opção Forecast.

O conteúdo deste post pode ser adaptado para fazer outras previsões através do Office!

O que preciso para começar? 

“Então Thiago, o que eu preciso para iniciar?”

Será necessário basicamente:

Microsoft Office Excel 2007; (a princípio não funciona no Office 2010 beta, mas estou verificando se há algum meio).

E um dos pares abaixo:

SQL Server Analysis Service 2008;
Data Mining Add-Ins for Office 2007 (SQL 2008);
http://www.microsoft.com/downloads/details.aspx?familyid=AF070F2C-46F4-47B6-B7BF-48979B999AEB&displaylang=en

OU

SQL Server Analysis Service 2005;
Data Mining Add-Ins for Office 2007 (SQL 2005);
http://www.microsoft.com/downloads/details.aspx?familyid=7C76E8DF-8674-4C3B-A99B-55B17F3C4C51&displaylang=en

Mãos a obra!

Coletando os dados de tamanho das databases

O primeiro passo é definir um mecanismo para a coleta dos dados.

Para isso vamos criar uma tabela que armazenará os dados relativos ao tamanho dos arquivos. Essa tabela poderá ter variações para outros fins, mas manterei ela com um certo grau de simplicidade.

CREATE TABLE TamanhoBaseHistorico
(
    id INT PRIMARY KEY IDENTITY,
    nome varchar(128) NOT NULL, -- nome da base
    nome_fisico VARCHAR(2000) NOT NULL, -- nome físico do arquivo, com diretório
    tamanho INT NOT NULL, -- tamanho do arquivo em Kb
    dblog BIT NOT NULL, -- se o arquivo é de log
    data DATETIME NOT NULL -- data da medição
)
GO

De tempos em tempos algum mecanismo deverá coletar os dados necessários e inserí-los nesta tabela.

Entre outras formas (como por exemplo o powershell), podemos fazer uso da system stored procedure chamada sp_msforeachdb e associar a execução da mesma a um job do banco de dados.

Essa stored procedure executa um comando para cada database, substituindo a “tag” [?] pelo nome da base. As informações virão da system view chamada sys.database_files.

O comando que será agendado como um job no SQL Server Agent será, para o nosso caso, o seguinte:

EXECUTE sp_msforeachdb
        'INSERT INTO TamanhoBaseHistorico
        SELECT name, physical_name, size, type, GETDATE()
        FROM [?].sys.database_files'

 

Não vou entrar nos méritos da criação de jobs no Agent, mas vamos assumir que estes dados sejam carregados todos os meses, uma vez por mês.

Na próxima parte veremos o processo de instalação do add-in e a utilização do mesmo!

Abraços,

Thiago Zavaschi

11Dec/090

SQL Server Saturday Night

Pessoal segue um convite para mais um grande evento de SQL Server promovido pela comunidade técnica!

Eu vou palestrar no evento também! Espero que estejam todos presentes!

--

O SQL Server Day foi um sucesso, sendo assim, alguns participantes resolveram dar um presente de Natal para todos: O SQL Server Saturday Night. Um super-webcast de 5 horas de duração que será realizado no dia 19/12/2009 à partir das 18:00 horas.

A grade está montada da seguinte forma:
- Powershell & Transact-SQL (Laerte Jr) - 18:00 as 18:50 horas
- Profiler e Perfmon - (Vladimir Magalhães) - 18:50 as 19:40 horas
- Alta Disponibilidade: Mirroriing (Vitor Fava) - 19:40 às 20:30 horas
- Performance & Tuning - (Rodrigo Crespi) - 20:30 horas as 21:20 horas
- Database Snapshots (Alexandre Lopes) - 21:20 às 22:10 horas
- SQL Server 2008 R2 (Thiago Zavaschi) - 22:10 às 23:00 horas

Ou seja, voce não pode perder, né? Prepara o telão, chama os amigos, pede a pizza + guaraná .. e assista ao SQL Server Saturday Night !

Aonde eu meu inscrevo? http://msevents.microsoft.com/CUI/EventDetail.aspx?EventID=1032437130&Culture=pt-BR

Abraços,
Thiago Zavaschi

24Nov/092

Repostagem – Ranking e Windowing

 

Essa repostagem eu não colocarei com data anterior, há muitos leitores que não conhecem esse recurso direito, mesmo estando presente desde o SQL Server 2005. Enjoy!

Utilizando Funções de Rank e Windowing

Hoje não vou falar de um recurso novo do SQL Server 2008, porém é bastante interessante e acredito que valha a pena um pequeno artigo sobre (principalmente em português já que não achei muita coisa em português).

As quatro funções T-SQL que vou me ater são: ROW_NUMBER(), RANK(), DENSE_RANK() e NTILE().
A base que utilizarei nos exemplos é a amplamente conhecida Northwind, que pode ser baixada gratuitamente do site da microsoft, mas para poupar o trabalho dos leitores e/ou possíveis mudanças no link, coloquei no meu SkyDrive: aqui.
Vamos começar dando uma pequena olhada nos dados.
Usamos a seguinte query SQL (limitada a 15 resultados e ordenados pelo preço unitário, do maior para o menor):

SELECT TOP 15 ProductID, ProductName, UnitPrice FROM Products ORDER BY UnitPrice DESC

Lembro que é uma boa prática utilizar os comandos SQL em caixa alta (todas as letras em maiúsculas).
Teremos o seguinte resultado:

ProductID   ProductName                              UnitPrice
----------- ---------------------------------------- ----------
38          Côte de Blaye                            263.50
29          Thüringer Rostbratwurst                  123.79
9           Mishi Kobe Niku                          97.00
20          Sir Rodney's Marmalade                   81.00
18          Carnarvon Tigers                         62.50
59          Raclette Courdavault                     55.00
51          Manjimup Dried Apples                    53.00
62          Tarte au sucre                           49.30
43          Ipoh Coffee                              46.00
28          Rössle Sauerkraut                        45.60
27          Schoggi Schokolade                       43.90
63          Vegie-spread                             43.90
8           Northwoods Cranberry Sauce               40.00
17          Alice Mutton                             39.00
12          Queso Manchego La Pastora                38.00

ROW_NUMBER():
O ROW_NUMBER é a função de ranking mais simples existente. Consiste de atribuir o valor de ranking igual a 1 para o primeiro registro (de acordo com o OVER, ver exemplo a seguir para facilitar), e para cada registro subsequente. Vejam o exemplo a seguir utilizando o ROW_NUMBER(). Percebam também que devido ao ORDER BY do OVER não é mais necessário usá-lo após o WHERE como na query anterior.

SELECT TOP 15 ProductID, ProductName, UnitPrice, ROW_NUMBER() OVER (ORDER BY UnitPrice DESC) AS ROW_COUNT FROM Products

Teremos o seguinte resultado:

ProductID   ProductName              UnitPrice   ROW_NUMBER
----------- ------------------------ --------    -----------
38          Côte de Blaye             263.50          1
29          Thüringer Rostbratwurst   123.79          2
9           Mishi Kobe Niku           97.00           3
20          Sir Rodney's Marmalade    81.00           4
18          Carnarvon Tigers          62.50           5
59          Raclette Courdavault      55.00           6
51          Manjimup Dried Apples     53.00           7
62          Tarte au sucre            49.30           8
43          Ipoh Coffee               46.00           9
28          Rössle Sauerkraut         45.60          10
27          Schoggi Schokolade        43.90          11
63          Vegie-spread              43.90          12
8           Northwoods Cranberry Sauce40.00          13
17          Alice Mutton              39.00          14
12          Queso Manchego La Pastora 38.00          15

Bastante simples, como eu disse. Porém é bastante útil, eu particularmente a utilizo para fazer paginação nos casos em que ela é feita no banco de dados mesmo. Para tal basta adicionar ao final (por exemplo): WHERE ROW_NUMBER >= 10 AND ROW_NUMBER <= 21.

RANK():
Agora vamos utilizar a função RANK para ranquearmos os produtos do produto mais caro para o mais barato.
Com a função RANK podemos fazer isso, o comportamento do valor de ranking é o seguinte:
Caso haja empate, ambos recebem o mesmo valor de ranking. Só que o registro seguinte ao empate não é o número imediatamente subsequente, mas sim o numero de rank do empate acrescido do numero de registros empatados.

SELECT TOP 15 ProductID, ProductName, UnitPrice, RANK() OVER (ORDER BY UnitPrice ASC) AS RANK FROM Products

Dado o fato de que não há repetição de preços no exemplo inicial, então estou trocando da ordenação do decrescente pata a crescente (ao invés de DESC estou utilizando ASC).

ProductID   ProductName                     UnitPrice    RANK
----------- -----------------------         --------    ---------
33          Geitost                         2.50            1
24          Guaraná Fantástica              4.50            2
13          Konbu                           6.00            3
52          Filo Mix                        7.00            4
54          Tourtière                       7.45            5
75          Rhönbräu Klosterbier            7.75            6
23          Tunnbröd                        9.00            7
19          Teatime Chocolate Biscuits      9.20            8
47          Zaanse koeken                   9.50            9
45          Rogede sild                     9.50            9
41          Jack's New England Clam Chowder 9.65           11
21          Sir Rodney's Scones             10.00          12
3           Aniseed Syrup                   10.00          12
74          Longlife Tofu                   10.00          12
46          Spegesild                       12.00          15

O RANK trabalhou de modo semelhante ao ROW_NUMBER, mas no caso em que houveram registros com igual preço unitário (UnitPrice, utilizado no OVER) ele tratou com a mesma 'pontuação'. Para os correligionários do futebol, é algo semelhante às posições dos times nas tabelas de campeonatos, e etc.

Mas e se eu desejar que (segundo o exemplo acima) após o "rank 9" que foi repetido fosse o número 10 e não o 11? Para tal temos o:

DENSE_RANK():
O comportamento do é o mesmo do RANK(), o que muda é que não há "pulos", ou seja, se uma tupla retornada contém o rank de número 37, podemos garantir que todos os número do intervalo [1, 36] já apareceram.
A query para o nosso exemplo a seguir é a seguinte:

SELECT TOP 15 ProductID, ProductName, UnitPrice, DENSE_RANK() OVER (ORDER BY UnitPrice ASC) AS DENSE_RANK FROM Products

E a sua execução sobre a base Northwind resulta em:

ProductID   ProductName                     UnitPrice   DENSE_RANK
----------- -------------------------------- ---------- ---------
33          Geitost                           2.50      1
24          Guaraná Fantástica                4.50      2
13          Konbu                             6.00      3
52          Filo Mix                          7.00      4
54          Tourtière                         7.45      5
75          Rhönbräu Klosterbier              7.75      6
23          Tunnbröd                          9.00      7
19          Teatime Chocolate Biscuits        9.20      8
47          Zaanse koeken                     9.50      9
45          Rogede sild                       9.50      9
41          Jack's New England Clam Chowder   9.65      10
21          Sir Rodney's Scones               10.00     11
3           Aniseed Syrup                     10.00     11
74          Longlife Tofu                     10.00     11
46          Spegesild                         12.00     12

Conforme havia dito, após uma repetição (registros com ProductID 47 e 45, por exemplo), no caso do número 9, temos na sequência o número 10, e não o 11 como no RANK(). O último valor do DENSE_RANK é 12, então também sabemos que todos os números de 1 a 11 já apareceram (com ou sem repetição).

NTILE():
Ao contrário das outras funções que apresentei o valor para este rankeamento não é baseado no valor, mas sim na quantidade de registros. Se temos 12 registros e utilizamos um NTILE(N) (onde o N é um número natural), o SQL tenta agrupar as tuplas de retorno naquele número passado como paramêtro, atribuindo valores iguais para os elementos do mesmo grupo. Um exemplo prático pode ser obtido com a seguinte query SQL:

SELECT ProductID, ProductName, UnitPrice, NTILE(3) OVER (ORDER BY ProductID ASC) AS NTILE FROM Products WHERE ProductID <= 15

A query acima pode fazer você indagar: "Ué Zavaschi, porque você não utilizou o TOP 15 como já havia feito para os demais exemplos?".

Uma rápida explicação:
A questão é que o TOP é algo como um "truncador" de retornos. Uma query que originalmente retornaria 100 linhas de registros, ao executar um TOP 15, dizemos que queremos apenas as 15 primeiras, no entanto o SQL internamente trabalhou com as 100 linhas.
Ao eu executar um NTILE() e aplicar um TOP 15, o NTILE foi aplicado sobre toda a coleção e após isto eu recuperaria os 15 primeiros registros. Devido a tal comportamento, não seria possível mostrar o agrupamento que o NTILE faz, já que muito possivelmente todos os primeiros 15 registros estivessem no mesmo grupo (recebendo 1 como valor).

Como retorno a query apresentada temos a seguinte saída:

ProductID   ProductName                       UnitPrice  NTILE (3)
----------- --------------------------------- ---------- ----------
1           Chai                              18.00      1
2           Chang                             19.00      1
3           Aniseed Syrup                     10.00      1
4           Chef Anton's Cajun Seasoning      22.00      1
5           Chef Anton's Gumbo Mix            21.35      1
6           Grandma's Boysenberry Spread      25.00      2
7           Uncle Bob's Organic Dried Pears   30.00      2
8           Northwoods Cranberry Sauce        40.00      2
9           Mishi Kobe Niku                   97.00      2
10          Ikura                             31.00      2
11          Queso Cabrales                    21.00      3
12          Queso Manchego La Pastora         38.00      3
13          Konbu                             6.00       3
14          Tofu                              23.25      3
15          Genen Shouyu                      15.50      3

Gostaria que reparassem também que apesar do OVER, o resultado do NTILE() é baseado principalmente no número de resultados, caso não seja possível uma divisão exata, o SQL Server tentará agrupar da melhor maneira possível, fazendo que alguns conjuntos possuam um número de elementos maior que outros.
Alterando um poquinho a query para recuperarmos um registro a mais, mas mantendo o mesmo número de divisões:

SELECT ProductID, ProductName, UnitPrice, NTILE(3) OVER (ORDER BY ProductID ASC) AS NTILE FROM Products WHERE ProductID <= 16

Nos leva ao seguinte resultado, onde o grupo dos "1" possui um elemento a mais (6 elementos) do que o grupo dos "2" e "3" (5 elementos cada):

ProductID   ProductName                       UnitPrice NTILE (3)
----------- --------------------------------- --------- ----------
1           Chai                              18.00     1
2           Chang                             19.00     1
3           Aniseed Syrup                     10.00     1
4           Chef Anton's Cajun Seasoning      22.00     1
5           Chef Anton's Gumbo Mix            21.35     1
6           Grandma's Boysenberry Spread      25.00     1
7           Uncle Bob's Organic Dried Pears   30.00     2
8           Northwoods Cranberry Sauce        40.00     2
9           Mishi Kobe Niku                   97.00     2
10          Ikura                             31.00     2
11          Queso Cabrales                    21.00     2
12          Queso Manchego La Pastora         38.00     3
13          Konbu                             6.00      3
14          Tofu                              23.25     3
15          Genen Shouyu                      15.50     3
16          Pavlova                           17.45     3

"Zavaschi, gostei destas funções de ranking, mas esta última de NTILE me deu uma idéia... E se eu quiser aplicar (na mesma query) um ranking mas para grupos específicos dentre os resultados dessa query, é possível?"
Para tal utilizaremos o PARTITION BY.

PARTITION BY:
As funções de ranking pontem ser combinadas com funções de windowing (vou manter o termo em inglês, por questão de preferência). A função de windowing (janelamento ...) dividirá os registros retornados baseado no PARTITYON BY aliado à cláusula OVER utilizada na função de ranking. Com isso você obterá um ranking separado para cada partição. Vale lembrar que o PARTITION BY também pode ser usado com outras funções agragadoras, como MIN, MAX, etc.
Um exemplo disto pode ser visto a seguir (adaptando os exemplos anteriores para o seguinte exemplo):

SELECT ProductName, SupplierID, ROW_NUMBER() OVER (PARTITION BY SupplierID Order By SupplierID) AS PARTITIONBY FROM Products WHERE ProductID <= 15

Na query exemplo temos a aplicação da função de ranking ROW_NUMBER() sobre as partições geradas pelos registros onde cada Supplier é igual. Para facilitar a visualização estou ordenando também pelo SupplierID, mas não é necessário que o campo seja o mesmo.
A query exemplo nos retorna:

ProductName                              SupplierID  PARTITIONBY
---------------------------------------- ----------- ----------
Chai                                     1           1
Chang                                    1           2
Aniseed Syrup                            1           3
Chef Anton's Cajun Seasoning             2           1
Chef Anton's Gumbo Mix                   2           2
Grandma's Boysenberry Spread             3           1
Uncle Bob's Organic Dried Pears          3           2
Northwoods Cranberry Sauce               3           3
Mishi Kobe Niku                          4           1
Ikura                                    4           2
Queso Cabrales                           5           1
Queso Manchego La Pastora                5           2
Konbu                                    6           1
Tofu                                     6           2
Genen Shouyu                             6           3

No exemplo podemos ver que para janela (correspondida para os grupos onde os suppliers são iguais) temos a aplicação individual da nossa função de ranking.

---

Bom se você, leitor, chegou até aqui, parabéns! Além de mostrar que tem paciência em aguentar o que eu escrevi (hehe), ainda adquiriu um conhecimento bastante interessante sobre ranking no SQL Server.

Gostaria de reiterar que os exemplos são para fins didáticos e que quaisquer dúvidas e sugestões podem entrar em contato. :)

Abraços,
Thiago Zavaschi

19Oct/090

Links para a inscrição no SQL Server Day

Pessoal,

Saíram os links para a inscrição no maior evento online de SQL Server online do Brasil!

São ao todo três inscrições e não haverá sobreposição, ou seja, você poderá assistir todos!

SQL Server Day uma tempestade cerebral de SQL Server!

Data: 07/11/2009

09:30 ~ 14:00
http://msevents.microsoft.com/CUI/EventDetail.aspx?EventID=1032430235&Culture=pt-BR

14:00 ~ 19:00
http://msevents.microsoft.com/CUI/EventDetail.aspx?EventID=1032430237&Culture=pt-BR

19:00 ~ 22:00
http://msevents.microsoft.com/CUI/EventDetail.aspx?EventID=1032430239&Culture=pt-BR

Este é um evento realizado pela COMUNIDADE TÉCNICA com o apoio da MICROSOFT.

Nos vemos lá!

Abraços,
Thiago Zavaschi