Como retornar o número de linhas e o espaço ocupado por tabela – Parte III

Oi Pessoal,

Dizem que não dá sorte começar o ano com pendências do ano anterior. Nem sempre é possível saldar todas essas pendências, mas quanto menos pendências houver, melhor o ano se inicia. Eu achei que tinha feito a minha última postagem do ano, mas se assim o fizesse deixaria uma pendência para o ano de 2009. Não acredito muito em superstição (até porque não conseguirei saldar todas as pendências (rs)), mas não custa deixar uma pendência a menos.

No artigo anterior, demonstrei uma forma um pouco mais sofisticada de recuperar o número de linhas e o espaço ocupado por tabela. Além de apresentar uma forma de capturar essas métricas essenciais de volumetria de dados, foi possível evoluir para outras métricas úteis como espaço referente a índices, espaço reservado, espaço não utilizado, etc. Ao finalizar o artigo, fiz a observação a cerca de expor as tabelas do banco diretamente bem como o problema da compatibilidade com versões posteriores já que os scripts apresentados foi baseado em SQL Server 2000. Para diminuir um pouco essas limitações, demonstrarei como recuperar as mesmas informações através dos objetos baseados no SQL Server 2005.

A evolução do SQL Server 2000 para o 2005 e superiores mudou a nomenclatura de diversos objetos de sistema. Anteriormente utilizava tabelas como sysdatabases, sysobjects, etc. Agora as mesmas foram substituídas por Views como sys.databases, sys.objects, etc. Embora visualmente pareça que os nomes sejam os mesmos com apenas um ponto para delimitar objeto e schema, o conteúdo não é exatamente o mesmo. Realizar uma consulta contra a sysobjects e a sys.objects demonstra que algumas colunas sumiram e outras apareceram. No SQL Server 2000, utilizou-se a tabela sysindexes, mas se optarmos pelos objetos baseados nas edições superiores, a escolha natural seria procurar seu equivalente próximo, ou seja, sys.indexes. Em todo caso, uma consulta contra a sys.indexes revela que boa parte das colunas utilizadas na solução anterior não estão mais disponíveis. O SQL Server 2005 mantem views com o mesmo nome das tabelas de sistema do SQL Server 2000 (sysdatabases, sysobjects e sysindexes ainda podem ser usadas), mas devemos evitá-las, pois, não se sabe até qual release elas estarão disponíveis. A idéia é utilizar os objetos do 2005.

Para demonstrar como recuperar as métricas de armazenamento com uma solução baseada em 2005, utilizarei as mesmas tabelas e scripts apresentados na solução anterior.

— Criação de tabelas
CREATE TABLE T1 (ID INT IDENTITY(1,1),
    IDSEC UNIQUEIDENTIFIER DEFAULT NEWID(),
    TIPO CHAR(3) DEFAULT ‘REG’)

— Criação de tabelas
CREATE TABLE T2 (ID INT IDENTITY(1,1),
    IDSEC UNIQUEIDENTIFIER DEFAULT NEWID(),
    TIPO CHAR(3) DEFAULT ‘REG’)

— Popula os registros com os valores DEFAULT
DECLARE @Iterador INT
SET @Iterador = 1

WHILE @Iterador <= 10000
BEGIN
    INSERT INTO
T1 DEFAULT VALUES
    INSERT INTO T2 DEFAULT VALUES
    SET @Iterador = @Iterador + 1
END

— Exclui dois mil registros de T2
DELETE FROM T2 WHERE ID <= 2000

— Cria uma nova tabela T3 apartir de T1
SELECT ID * 2 AS Codigo, TIPO INTO T3 FROM T1 WHERE ID > 3500

— Cria os índices
CREATE CLUSTERED INDEX IXCT1_ID ON T1 (ID)
CREATE NONCLUSTERED INDEX IXNT1_IDSEC ON T1 (IDSEC)
CREATE NONCLUSTERED INDEX IXNT2_TIPO ON T2 (TIPO)

No SQL Server 2005 foi introduzido o conceito de particionamento, ou seja, é possível dividir tabelas (e índices também) em camadas ou partições de forma a melhorar o desempenho das consultas, contenções em virtude de bloqueios, etc. Toda tabela será particionada mesmo que o particionamento não seja explícito. Quando o particionamento não é explícito, dizemos que a tabela contém uma única partição. A relação de partições está localizada em sys.partitions e um SELECT poderá nos demonstrar isso.

— Seleciona todas as partições
SELECT OBJECT_NAME(object_id) As Tabela,
    Partition_ID, Hobt_ID, Object_ID, Index_ID,
    Partition_Number, Rows
FROM sys.partitions
WHERE object_id IN (
    SELECT object_id FROM sys.tables
    WHERE NAME IN (‘T1’,‘T2’,‘T3’))

À primeira vista, esse SELECT pode não parecer muito intuitivo para iniciantes. Sendo assim vamos às explicações. A função OBJECT_NAME é necessária para que dado um certo object_id ela retorne o nome das tabelas. A coluna Object_ID representa o ID do objeto e a coluna Index_ID tem a mesma função da IndID na sysindexes, ou seja, representa 0 caso trate-se de uma Heap Table, 1 para índice clustered e 2 ou superior para índices nonclustered (estatísticas não são armazenadas em sys.partitions). A coluna Rows mostra o número de linhas existentes. Por fim há um filtro em object_id para selecionar apenas as tabelas cujo o nome seja T1 ou T2 ou T3. Deixarei as colunas Partition_ID, Hobt_ID e Partition_Number por último propositalmente já que essas merecem um comentário adicional.

O SELECT conseguiu exibir o nome das tabelas e suas respectivas linhas. Para obter o total da tabela, poderíamos obter o tamanho da linha e multiplicar pela quantidade de linhas, mas obter o tamanho exato da linha é algo um pouco mais trabalhoso, além do que não seria útil para calcular outras métricas como espaço não utilizado ou área alocado por índices. A questão é descobrir em que local essas métricas são armazenadas. O SQL Server 2005 possui uma view com todas as unidades de alocação (allocation units) no banco de dados. Entenda-se "unidade de alocação" toda área alocada para armazenar certos tipos de dados. Há unidades de alocação do tipo In-Row que armazenam dados cujo tipo são os mais comuns, LOB Data para armazenar tipos como Image, Text e Row-Overflow para algumas situações especiais onde tipos textuais são armazenados. O SQL Server mantem um controle de utilização das allocation units através da view sys.allocation_units (e da não documentada sys.system_internals_allocation_units). Para o propósito das métricas de armazenamentos, as seguintes colunas estão disponíveis nessa view:

  • allocation_unit_id: Identificador único da unidade de alocação
  • container_id: Identificador do Container (pode ser o partition_id ou o hobt_id)
  • total_pages: Total de páginas reservadas
  • used_pages: Total de páginas utilizadas
  • data_pages: Total de páginas de dados

Algumas dessas colunas são familiares, pois, representam conceitos semelhantes às colunas utilizadas em sysindexes (Reserved, Used e DPages). A coluna allocation_unit_id não representa algo muito diferente, pois, é apenas um identificador da unidade de alocação. A única coluna realmente "nova" é a container_id. Segundo a descrição do Books OnLine é uma coluna para realizar uma junção com a sys.partitions pela coluna partition_id ou hobt_id. Acredito que não há como evitar explicar o significado dessas colunas.

Como havia sido dito anteriormente, a partir do SQL Server 2005, todas tabelas e seus índices são particionados. Caso o particionamento não tenha sido explícito, entende-se que a tabela ou o índice possuem uma única partição (no caso eles próprios) e que essa partição consiste no próprio objeto. Toda partição irá receber um identificador e esse identificador é o partition_id. Caso uma tabela tenha múltiplas partições, haverá múltiplos partition_id para esse objeto. A coluna hobt_id é um identificador hobt. Hobt (leia-se hobbit) é o acrônimo de Heap Or BTree, ou seja, é um identificador de uma estrutura de organização que será uma Heap Table ou uma BTree (no caso um índice clustered). Toda tabela necessariamente será ou Heap Table ou Clustered Index. Não somente a tabela em si, mas caso haja múltiplas partições, todas essas partições irão obedecer a estrutura de organização de sua respectiva tabela. Isso significa que se a tabela for uma Heap Table e for particionada, cada partição representará uma estrutura Heap em separado. Se a tabela possuir um índice clustered e for particionada, cada partição possuirá uma estrutura indexação (BTree) em separado. Por isso, cada partição terá seu próprio identificador (Hobt_ID) seja uma estrutura Heap ou um índice clustered.

Segundo o Books OnLine, dependendo do tipo de unidade de alocação, a junção com sys.partions se dá pela coluna partition_id ou pela hobt_id. O interessante é que um select na sys.partitions revela que essas colunas tem sempre o mesmo valor. De fato não é coincidência, há sempre o mesmo valor. A relação entre hobt_id e partition_id é de um para um o que significa que essas colunas serão exatamente as mesmas sempre. A razão pela qual elas existem é que caso a Microsoft deseje mudar a relação futura entre hobt_id e partition_id, as colunas já estão separadas. Enquanto isso não acontece, utilizarei para os exemplos a coluna hobt_id no momento de fazer a junção.

— Seleciona o Hobt_ID de T1
;WITH HI (Hobt_ID) AS (
SELECT Hobt_ID FROM sys.partitions
WHERE Object_ID = Object_ID(‘T1’))

SELECT
    allocation_unit_id, type_desc, container_id,
    total_pages, used_pages, data_pages
FROM sys.allocation_units
WHERE Container_ID IN (
    SELECT Hobt_ID FROM HI)

— Execução da sp_spaceused
EXEC sp_spaceused ‘T1’

Os resultados obtidos são:

allocation_unit_id

type_desc

container_id

total_pages

used_pages

data_pages

72057594043760640

IN_ROW_DATA

72057594038779904

42

42

40

72057594043826176

IN_ROW_DATA

72057594038845440

49

35

33

Name

Rows

Reserved

Data

Index_Size

Unused

T1

10000

728 KB

320 KB

296 KB

112 KB

A tabela T1 ocupa duas unidades de alocação do tipo IN_ROW_DATA que significa unidades de alocação para dados. Tanto a consulta quanto a stored procedure sp_spaceused demonstram a mesma informação só que de forma um pouco diferente. Se somarmos o total de páginas alocadas (42 + 49) obteremos 91 páginas reservadas para a tabela T1. Se somarmos o total de páginas utilizadas (42 + 35) obteremos 77 páginas efetivamente utilizadas para a tabela T1. Se somarmos o total de páginas de dados (40 + 33) obteremos 73 páginas de dados utilizadas para a tabela T1.

Utilizando as mesmas regras postuladas no artigo anterior e que o tamanho da página é de 8K, sabemos que existem 728Kb de área reservada (91 * 8), 616Kb de área utilizada (77 * 8) e 584Kb de dados (73 * 8). A área não utilizada é a diferença entre a área reservada (728Kb) e a área utilizada (616Kb) totalizando 112Kb ou 14 páginas de 8Kb. Alguns números batem com o resultado da sp_spaceused, mas dessa vez a semelhança não foi tão exata quanto a comparação que utilizou a SysIndexes. A área de dados retornada consiste apenas em 320Kb e não 584Kb. Entretanto, isso não significa que há divergência nos mecanismos apresentados pela sp_spaceused, sysindexes e sys.allocation_units. Significa apenas que o SQL Server 2005 faz algumas separações que não eram realizadas antes.

A consulta anterior considerou todos os Hobt_IDs de T1, isso inclui o Hobt_ID de seu índice clustered e de seu índice NonClustered. A diferenciação entre esses índices é fornecida pela coluna Index_ID conforme explicado anteriormente. Se for necessário obter apenas as métricas da tabela em si, deve-se considerar apenas o índice clustered (se a tabela possuir um), ou sua estrutura Heap. Isso significa que o Index_ID necessita ser 0 ou 1. Refazendo-se a consulta tem-se:

— Seleciona o Hobt_ID de T1 referente ao índice cluster ou a Heap Table
;WITH HI (Hobt_ID) AS (
SELECT Hobt_ID FROM sys.partitions
WHERE Object_ID = Object_ID(‘T1’) AND Index_ID IN (0,1))

SELECT
    allocation_unit_id, type_desc, container_id,
    total_pages, used_pages, data_pages
FROM sys.allocation_units
WHERE Container_ID IN (
    SELECT Hobt_ID FROM HI)

— Execução da sp_spaceused
EXEC sp_spaceused ‘T1’

Os resultados obtidos são:

allocation_unit_id

type_desc

container_id

total_pages

used_pages

data_pages

72057594043760640

IN_ROW_DATA

72057594038779904

42

42

40

Name

Rows

Reserved

Data

Index_Size

Unused

T1

10000

728 KB

320 KB

296 KB

112 KB

O total de páginas retornado para área de dados é igual a 40. Considerando que cada página tem 8K, um total de 40 páginas equivale a 320Kb que é exatamente igual a área de dados retornado pela sp_spaceused. Com a alteração, pode-se obter a métrica de dados correta, só que as outras métricas ficaram em divergência. Essa divergência tem suas explicações nos dados que são retornados pela stored procedure sp_spaceused e nos que são retornados na sys.allocation_units. Nem todos os dados armazenados em sys.allocation_units são retornados pela stored procedure sp_spaceused (emboras as métricas apresentadas sejam exatamente as mesmas do final do artigo anterior).

Quando a view sys.allocation_units é consultada suas colunas dependerão do filtro realizado em Index_ID na view sys.partitions. Se Index_ID for 0 (heap table) ou 1 (clustered index), os dados retornados referem-se a tabela propriamente dita. Se Index_ID for maior que 1, os dados referem-se aos índices nonclustered. Nesse caso, a coluna data_pages representa a área ocupada pelos nós folhas do índice nonclustered em questão e essa métrica não é retornada pela stored procedure sp_spaceused. A tabela possui duas unidades de alocação com os valores 40 e 33 em data_pages. 40 refere-se à área de dados utilizada no índice clustered (IXCT1_ID) (que representa os dados da própria tabela) enquanto que 33 representa a área de dados utilizada no índice nonclustered (IXNT1_IDSEC). Se o índice NonClustered for eliminado, as métricas demonstradas serão equivalentes.

— Elimina o índice nonclustered em T1
DROP INDEX T1.IXNT1_IDSEC

— Seleciona os Hobt_IDs de T1
;WITH HI (Hobt_ID) AS (
SELECT Hobt_ID FROM sys.partitions
WHERE Object_ID = Object_ID(‘T1’))

SELECT
    allocation_unit_id, type, type_desc, container_id,
    total_pages, used_pages, data_pages
FROM sys.allocation_units
WHERE Container_ID IN (
SELECT Hobt_ID FROM HI)

— Execução da sp_spaceused
EXEC sp_spaceused ‘T1’

— Recria o índice Nonclustere em T1
CREATE NONCLUSTERED INDEX IXNT1_IDSEC ON T1 (IDSEC)

Eliminando-se o índice, o seguinte resultado é obtido:

allocation_unit_id

type_desc

container_id

total_pages

used_pages

data_pages

72057594043760640

IN_ROW_DATA

72057594038779904

42

42

40

Name

Rows

Reserved

Data

Index_Size

Unused

T1

10000

336 KB

320 KB

16 KB

112 KB

Dessa vez, as métricas foram exatamente as mesmas, pois, senão há índice nonclustered, não haverá outra unidade de alocação para "divergir" o valor em data_pages. Basta multiplicar os valores de páginas por 8 e exatamente os mesmos resultados serão obtidos. Utilizo o termo "divergir" porque na verdade não há uma real divergência. É apenas uma questão de interpretação. Voltemos aos resultados "divergentes".

— Seleciona o Hobt_ID de T1
;WITH HI (Hobt_ID) AS (
SELECT Hobt_ID FROM sys.partitions
WHERE Object_ID = Object_ID(‘T1’))

SELECT
    allocation_unit_id, type_desc, container_id,
    total_pages, used_pages, data_pages
FROM sys.allocation_units
WHERE Container_ID IN (
    SELECT Hobt_ID FROM HI)

— Execução da sp_spaceused
EXEC sp_spaceused ‘T1’

Os resultados obtidos são:

allocation_unit_id

type_desc

container_id

total_pages

used_pages

data_pages

72057594043760640

IN_ROW_DATA

72057594038779904

42

42

40

72057594043826176

IN_ROW_DATA

72057594038845440

49

35

33

Name

Rows

Reserved

Data

Index_Size

Unused

T1

10000

728 KB

320 KB

296 KB

112 KB

Conforme explicado anteriormente, todos os resultados entre sys.allocation_units e a stored procedured são equivalente com excessão da coluna data_pages. Isso porque a stored procedure sp_spaceused considera como data apenas os dados da tabela desprezando as 33 páginas dos nós folhas do índice (o que de certa forma está correto). Assim sendo, "data" para a stored procedure sp_spaceused será apenas 40 páginas (que totalizam 320Kb) enquanto a sys.allocation_units considera 73 páginas (40 + 33) sendo 40 de dados efetivamente e as demais do índice nonclustered totalizando 584Kb.

Além dessa divergência, fica uma outra questão em aberto, e o valor 296KB retornado pela stored procedure sp_spaceused ? Como ele foi obtido ? Esse valor irá considerar o tamanho da tabela desconsiderando seus dados (além dos dados as tabelas possuem páginas para mapear os dados) e o tamanho dos índices nonclustered. Para calcular a área da tabela que não representa dados, basta capturar o espaço utilizado em sua unidade de alocação (heap ou índice clustered) e subtrair da área de dados. Nesse caso temos a diferença entre 42 e 40 que retorna 2 páginas totalizando 16KB. O espaço do índice é o used_pages da unidade de alocação cujo Index_Id seja maior que 1. Nesse caso temos 35 páginas que correspondem a 280Kb. Somando-se 280Kb aos 16Kb anteriores temos os 296Kb. Uma outra forma de obtê-lo é retirar a área de dados da tabela do total utilizado. Se o total utilizado é de 77 páginas (42 + 35) e a área de dados é de 40 páginas, a diferença é de 37 páginas. Se uma página ocupa 8Kb, 37 páginas ocupam uma área de 296Kb.

As diferenças entre os valores retornados pela stored procedure e pela view sys.allocation_units podem causar confusões se não se souber exatamente o que é retornado, mas como normalmente as métricas são consolidadas e levarão em consideração todos os índices, essas "divergências" não são um impecilho. Ainda que se deseje métricas menos granulares, é possível desde que se faça a consulta correta. Antes de prosseguir, gostaria de acrescentar mais algumas tabelas e índices para tornar a recuperação das métricas mais interessantes.

— Cria uma tabela T4 igual a T1 sem o índice NonClustered
CREATE TABLE T4 (ID INT IDENTITY(1,1),
    IDSEC UNIQUEIDENTIFIER DEFAULT NEWID(),
    TIPO CHAR(3) DEFAULT ‘REG’)

INSERT INTO T4 (IDSEC, TIPO) SELECT IDSEC, TIPO FROM T1

CREATE CLUSTERED INDEX IXCT4_ID ON T4 (ID)

— Cria uma tabela T5 com vários campos
CREATE TABLE T5 (ID INT IDENTITY(1,1),
    COL1 INT, COL2 INT, COL3 INT, COL4 INT,
    COL5 CHAR(1), COL6 CHAR(2), COL7 CHAR(4), COL8 CHAR(10))

— Insere 5000 registros
INSERT INTO T5 (COL1, COL2, COL3, COL4, COL5, COL6, COL7, COL8)
SELECT TOP 5000
    ID, ID * 2, ID * 3, ID * 4, ‘A’, ‘BB’, ‘CC’, ‘1234567890’
FROM T1
WHERE ID <= 5000

— Cria índices em T5
CREATE CLUSTERED INDEX IXCT5_ID ON T5 (ID)
CREATE NONCLUSTERED INDEX IXNT5_COL1 ON T5 (COL1)
CREATE NONCLUSTERED INDEX IXNT5_COL2 ON T5 (COL2)
CREATE NONCLUSTERED INDEX IXNT5_COL3 ON T5 (COL3)
CREATE NONCLUSTERED INDEX IXNT5_COL4 ON T5 (COL4)
CREATE NONCLUSTERED INDEX IXNT5_COL5 ON T5 (COL5)
CREATE NONCLUSTERED INDEX IXNT5_COL6 ON T5 (COL6)
CREATE NONCLUSTERED INDEX IXNT5_COL7 ON T5 (COL7)
CREATE NONCLUSTERED INDEX IXNT5_COL8 ON T5 (COL8)

Agora que há mais índices e tabelas seguem algumas consultas para obter métricas de armazenamento.

— Selecionar o espaço ocupado por tabela sem considerar os índices (T1 e T4 são iguais)
SELECT
    OBJECT_NAME(object_id) As Tabela, Rows As Linhas,
    Total_Pages * 8 As Reservado,
    Used_Pages * 8 As Utilizado,
    Data_Pages * 8 As Dados,
    (Total_Pages – Used_Pages) * 8 As NaoUtilizado
FROM
    sys.partitions As P
    INNER JOIN sys.allocation_units As A ON P.hobt_id = A.container_id
WHERE
    Index_ID IN (0,1) AND Object_ID IN
    (SELECT Object_ID FROM sys.tables WHERE Name LIKE ‘T%’)
ORDER BY Tabela

— Selecionar o espaço ocupado apenas pelos índices NonClustered (T3 e T4 não aparecem)
SELECT
    OBJECT_NAME(P.object_id) As Tabela,
    I.Name As Indice,
    Rows As Linhas,
    Total_Pages * 8 As Reservado,
    Used_Pages * 8 As Utilizado,
    (Total_Pages – Used_Pages) * 8 As NaoUtilizado
FROM
    sys.partitions As P
    INNER JOIN sys.indexes As I ON P.object_id = I.object_id And P.Index_ID = I.Index_ID
    INNER JOIN sys.allocation_units As A ON P.hobt_id = A.container_id
WHERE
    P.Index_ID > 1 AND P.Object_ID IN
    (SELECT Object_ID FROM sys.tables WHERE Name LIKE ‘T%’)
ORDER BY
    Tabela, Indice

— Selecionar o espaço ocupado por tabela considerando os índices NonClustered
SELECT
    OBJECT_NAME(object_id) As Tabela, Rows As Linhas,
    SUM(Total_Pages * 8) As Reservado,
    SUM(Used_Pages * 8) As Utilizado,
    SUM((Total_Pages – Used_Pages) * 8) As NaoUtilizado
FROM
    sys.partitions As P
    INNER JOIN sys.allocation_units As A ON P.hobt_id = A.container_id
WHERE
    Object_ID IN
    (SELECT Object_ID FROM sys.tables WHERE Name LIKE ‘T%’)
GROUP BY OBJECT_NAME(object_id), Rows
ORDER BY Tabela

— Retornar o mesmo resultado que a stored procedure sp_spaceused
SELECT
    OBJECT_NAME(object_id) As Tabela, Rows As Linhas,
    SUM(Total_Pages * 8) As Reservado,
    SUM(CASE WHEN Index_ID > 1 THEN 0 ELSE Data_Pages * 8 END) As Dados,
        SUM(Used_Pages * 8) –
        SUM(CASE WHEN Index_ID > 1 THEN 0 ELSE Data_Pages * 8 END) As Indice,
    SUM((Total_Pages – Used_Pages) * 8) As NaoUtilizado
FROM
    sys.partitions As P
    INNER JOIN sys.allocation_units As A ON P.hobt_id = A.container_id
WHERE
    Object_ID IN
    (SELECT Object_ID FROM sys.tables WHERE Name LIKE ‘T%’)
GROUP BY OBJECT_NAME(object_id), Rows
ORDER BY Tabela

O SQL Server 2005 fornece mecanismos de consulta a metadados bastante poderosos em relação às suas versões anteriores. Com um bom entendimento de como funciona a sys.allocation_units é possível obter métricas de armazenamento muito poderosas que servem de forte subsídio para o planejamento de aquisição, alocação e manutenção do espaço em disco (entre outras coisas). Embora tenha focado o SQL Server 2005, os mesmos raciocínios e consultas apresentados funcionam para o SQL Server 2008.

Acho que agora iniciarei 2009 com uma pendência a menos. Não era pretenção esgotar esse assunto, mas apresentei as três principais formas de consultar informações de volumetria de dados no SQL Server. Agora basta adaptar as lógicas às reais necessidades. Um ótimo 2009 a todos.

[ ]s,

Gustavo

2 Respostas para “Como retornar o número de linhas e o espaço ocupado por tabela – Parte III

  1. Excelente Post parabéns!

    Gostaria de saber como eu faço para listar todos os dados de uma tabela através do seu OBJECT_ID, é possível?

    • Olá Ton,

      Você pode obter o nome dela com a OBJECT_NAME e usar a sp_help ‘Tabela’ para obter várias informações. Se preferir algo mais específico, de posse do OBJECT_ID, tente pesquisar tabelas de catálogo como sys.indexes, sys.tables, etc. O fonte da sp_help já é muito valioso nesse sentido.

      [ ]s,

      Gustavo

Deixe uma resposta

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s