Cómo ver el historial de consultas en SQL Server Management Studio

159

¿El historial de consultas está almacenado en algunos archivos de registro? En caso afirmativo, ¿puede decirme cómo encontrar su ubicación? Si no, ¿me puede dar algún consejo sobre cómo verlo?

mstaniloiu
fuente
1
http://www.ssmstoolspack.com/ proporciona una ventana de historial si eso es lo que busca.
TI

Respuestas:

226

[Desde esta pregunta probablemente se cerrará como un duplicado.]

Si SQL Server no se ha reiniciado (y el plan no se ha desalojado, etc.), puede encontrar la consulta en la memoria caché del plan.

SELECT t.[text]
FROM sys.dm_exec_cached_plans AS p
CROSS APPLY sys.dm_exec_sql_text(p.plan_handle) AS t
WHERE t.[text] LIKE N'%something unique about your query%';

Si perdió el archivo porque Management Studio se bloqueó, puede encontrar archivos de recuperación aquí:

C:\Users\<you>\Documents\SQL Server Management Studio\Backup Files\

De lo contrario, deberá usar algo más para ayudarlo a guardar su historial de consultas, como SSMS Tools Pack como se menciona en la respuesta de Ed Harper, aunque no es gratuito en SQL Server 2012+. O puede configurar un seguimiento ligero filtrado en su nombre de usuario o nombre de host (pero utilice un seguimiento del lado del servidor, no Profiler, para esto).


Como comentó @Nenad-Zivkovic, podría ser útil unirse sys.dm_exec_query_statsy ordenar por last_execution_time:

SELECT t.[text], s.last_execution_time
FROM sys.dm_exec_cached_plans AS p
INNER JOIN sys.dm_exec_query_stats AS s
   ON p.plan_handle = s.plan_handle
CROSS APPLY sys.dm_exec_sql_text(p.plan_handle) AS t
WHERE t.[text] LIKE N'%something unique about your query%'
ORDER BY s.last_execution_time DESC;
Aaron Bertrand
fuente
9
También podría ayudar a unirse sys.dm_exec_query_statsy buscar u ordenar porlast_execution_time
Nenad Zivkovic
No funciona con SQL Server 2000 y funciona desde SQL Server 2005
Durai Amuthan. H
@Duraiamuthan Bueno, la pregunta se refiere a Management Studio, por lo que debería ser seguro presumir 2005+. 2000 no tenía Management Studio, tenía Query Analyzer. El 2000 también está fuera de soporte por muchos años. Si desea resolver este problema para SQL Server 2000, probablemente debería hacer una nueva pregunta etiquetada con esa versión específica (si no existe un duplicado, que no verifiqué).
Aaron Bertrand
1
@AaronBertrand Mi comentario es complementario a su respuesta. Ayudará a otros
Durai Amuthan.H
3
@AaronBertrand Eres un dios entre los hombres.
AnotherDeveloper
49

Uno tardío pero con suerte útil ya que agrega más detalles ...

No hay forma de ver las consultas ejecutadas en SSMS de forma predeterminada. Sin embargo, hay varias opciones.

Leer el registro de transacciones: no es una tarea fácil porque está en formato propietario. Sin embargo, si necesita ver consultas que se ejecutaron históricamente (excepto SELECCIONAR), esta es la única forma.

Puede usar herramientas de terceros para esto, como ApexSQL Log y SQL Log Rescue (gratis pero solo SQL 2000). Consulte este hilo para obtener más detalles aquí Explorador / Analizador de registro de transacciones de SQL Server

Analizador de SQL Server: el más adecuado si solo desea comenzar a auditar y no está interesado en lo que sucedió antes. Asegúrese de usar filtros para seleccionar solo las transacciones que necesita. De lo contrario, terminará con toneladas de datos muy rápidamente.

Rastreo de SQL Server: el más adecuado si desea capturar todos o la mayoría de los comandos y mantenerlos en un archivo de rastreo que se pueda analizar más adelante.

Disparadores: más adecuados si desea capturar DML (excepto seleccionar) y almacenarlos en algún lugar de la base de datos

Djordje Kujundzic
fuente
Crear un archivo de seguimiento en el SQL Server Profiler ( msdn.microsoft.com/en-us/library/ms175047(v=sql.110).aspx ) usando la plantilla estándar es una buena manera de supervisar las declaraciones ejecutadas.
javiniar.leonard
16

El paquete de herramientas SSMS agrega funcionalidad para registrar el historial de ejecución, entre otras cosas.

Ed Harper
fuente
22
Ya no es gratis a partir de SSMS 2012.
mattmc3
6

Como otros han señalado, puede usar SQL Profiler, pero también puede aprovechar su funcionalidad a través de los procedimientos almacenados del sistema sp_trace_ *. Por ejemplo, este fragmento de SQL (al menos en 2000; creo que es lo mismo para SQL 2008, pero tendrá que verificar dos veces) capturas RPC:Completedy SQL:BatchCompletedeventos para todas las consultas que tarden más de 10 segundos en ejecutarse, y guarde la salida en un archivo de rastreo que puede abrir en el generador de perfiles SQL en una fecha posterior:

DECLARE @TraceID INT
DECLARE @ON BIT
DECLARE @RetVal INT
SET @ON = 1

exec @RetVal = sp_trace_create @TraceID OUTPUT, 2, N'Y:\TraceFile.trc'
print 'This trace is Trace ID = ' + CAST(@TraceID AS NVARCHAR)
print 'Return value = ' + CAST(@RetVal AS NVARCHAR)
-- 10 = RPC:Completed
exec sp_trace_setevent @TraceID, 10, 1, @ON     -- Textdata
exec sp_trace_setevent @TraceID, 10, 3, @ON     -- DatabaseID
exec sp_trace_setevent @TraceID, 10, 12, @ON        -- SPID
exec sp_trace_setevent @TraceID, 10, 13, @ON        -- Duration
exec sp_trace_setevent @TraceID, 10, 14, @ON        -- StartTime
exec sp_trace_setevent @TraceID, 10, 15, @ON        -- EndTime

-- 12 = SQL:BatchCompleted
exec sp_trace_setevent @TraceID, 12, 1, @ON     -- Textdata
exec sp_trace_setevent @TraceID, 12, 3, @ON     -- DatabaseID
exec sp_trace_setevent @TraceID, 12, 12, @ON        -- SPID
exec sp_trace_setevent @TraceID, 12, 13, @ON        -- Duration
exec sp_trace_setevent @TraceID, 12, 14, @ON        -- StartTime
exec sp_trace_setevent @TraceID, 12, 15, @ON        -- EndTime

-- Filter for duration [column 13] greater than [operation 2] 10 seconds (= 10,000ms)
declare @duration bigint
set @duration = 10000
exec sp_trace_setfilter @TraceID, 13, 0, 2, @duration

Puede encontrar el ID para cada evento de seguimiento, columnas, etc. en Books Online; solo busque los sprocs sp_trace_create , sp_trace_setevent y sp_trace_setfiler . Luego puede controlar el seguimiento de la siguiente manera:

exec sp_trace_setstatus 15, 0       -- Stop the trace
exec sp_trace_setstatus 15, 1       -- Start the trace
exec sp_trace_setstatus 15, 2       -- Close the trace file and delete the trace settings

... donde '15' es el ID de rastreo (según lo informado por sp_trace_create, que el primer script inicia, arriba).

Puede verificar qué rastreos se están ejecutando con:

select * from ::fn_trace_getinfo(default)

Lo único que diré con precaución : no sé cuánta carga pondrá esto en su sistema; agregará algo, pero lo grande que es "algo" probablemente depende de qué tan ocupado esté su servidor.

Chris J
fuente
Código útil Sin embargo, solo funcionó para mí cuando eliminé la extensión de archivo ".trc".
Steve Smith el
5

El sistema no registra consultas de esa manera. Sin embargo, si sabe que desea hacerlo con anticipación, puede usar el Analizador de SQL para registrar lo que ingresa y realizar un seguimiento de las consultas durante el tiempo que se ejecuta el Analizador.

Tiamina
fuente
5

Utilizo la consulta a continuación para rastrear la actividad de la aplicación en un servidor SQL que no tiene habilitado el generador de perfiles de rastreo. El método usa Query Store (SQL Server 2016+) en lugar de los DMV. Esto proporciona una mejor capacidad para buscar datos históricos, así como búsquedas más rápidas. Es muy eficiente capturar consultas de ejecución corta que sp_who / sp_whoisactive no puede capturar.

/* Adjust script to your needs.
    Run full script (F5) -> Interact with UI -> Run full script again (F5)
    Output will contain the queries completed in that timeframe.
*/

/* Requires Query Store to be enabled:
    ALTER DATABASE <db> SET QUERY_STORE = ON
    ALTER DATABASE <db> SET QUERY_STORE (OPERATION_MODE = READ_WRITE, MAX_STORAGE_SIZE_MB = 100000)
*/

USE <db> /* Select your DB */

IF OBJECT_ID('tempdb..#lastendtime') IS NULL
    SELECT GETUTCDATE() AS dt INTO #lastendtime
ELSE IF NOT EXISTS (SELECT * FROM #lastendtime)
    INSERT INTO #lastendtime VALUES (GETUTCDATE()) 

;WITH T AS (
SELECT 
    DB_NAME() AS DBName
    , s.name + '.' + o.name AS ObjectName
    , qt.query_sql_text
    , rs.runtime_stats_id
    , p.query_id
    , p.plan_id
    , CAST(p.last_execution_time AS DATETIME) AS last_execution_time
    , CASE WHEN p.last_execution_time > #lastendtime.dt THEN 'X' ELSE '' END AS New
    , CAST(rs.last_duration / 1.0e6 AS DECIMAL(9,3)) last_duration_s
    , rs.count_executions
    , rs.last_rowcount
    , rs.last_logical_io_reads
    , rs.last_physical_io_reads
    , q.query_parameterization_type_desc
FROM (
    SELECT *, ROW_NUMBER() OVER (PARTITION BY plan_id, runtime_stats_id ORDER BY runtime_stats_id DESC) AS recent_stats_in_current_priod
    FROM sys.query_store_runtime_stats 
    ) AS rs
INNER JOIN sys.query_store_runtime_stats_interval AS rsi ON rsi.runtime_stats_interval_id = rs.runtime_stats_interval_id
INNER JOIN sys.query_store_plan AS p ON p.plan_id = rs.plan_id
INNER JOIN sys.query_store_query AS q ON q.query_id = p.query_id
INNER JOIN sys.query_store_query_text AS qt ON qt.query_text_id = q.query_text_id
LEFT OUTER JOIN sys.objects AS o ON o.object_id = q.object_id
LEFT OUTER JOIN sys.schemas AS s ON s.schema_id = o.schema_id
CROSS APPLY #lastendtime
WHERE rsi.start_time <= GETUTCDATE() AND GETUTCDATE() < rsi.end_time
    AND recent_stats_in_current_priod = 1
    /* Adjust your filters: */
    -- AND (s.name IN ('<myschema>') OR s.name IS NULL)
UNION
SELECT NULL,NULL,NULL,NULL,NULL,NULL,dt,NULL,NULL,NULL,NULL,NULL,NULL, NULL
FROM #lastendtime
)
SELECT * FROM T
WHERE T.query_sql_text IS NULL OR T.query_sql_text NOT LIKE '%#lastendtime%' -- do not show myself
ORDER BY last_execution_time DESC

TRUNCATE TABLE #lastendtime
INSERT INTO #lastendtime VALUES (GETUTCDATE()) 
Martin Thøgersen
fuente
4
SELECT deqs.last_execution_time AS [Time], dest.text AS [Query], dest.*
FROM sys.dm_exec_query_stats AS deqs
CROSS APPLY sys.dm_exec_sql_text(deqs.sql_handle) AS dest
WHERE dest.dbid = DB_ID('msdb')
ORDER BY deqs.last_execution_time DESC

Esto debería mostrar la hora y la fecha en que se ejecutó una consulta

Jose ortiz
fuente
3

El historial de consultas se puede ver usando las vistas del sistema:

  1. sys.dm_exec_query_stats
  2. sys.dm_exec_sql_text
  3. sys.dm_exec_query_plan

Por ejemplo, usando la siguiente consulta:

select  top(100)
        creation_time,
        last_execution_time,
        execution_count,
        total_worker_time/1000 as CPU,
        convert(money, (total_worker_time))/(execution_count*1000)as [AvgCPUTime],
        qs.total_elapsed_time/1000 as TotDuration,
        convert(money, (qs.total_elapsed_time))/(execution_count*1000)as [AvgDur],
        total_logical_reads as [Reads],
        total_logical_writes as [Writes],
        total_logical_reads+total_logical_writes as [AggIO],
        convert(money, (total_logical_reads+total_logical_writes)/(execution_count + 0.0)) as [AvgIO],
        [sql_handle],
        plan_handle,
        statement_start_offset,
        statement_end_offset,
        plan_generation_num,
        total_physical_reads,
        convert(money, total_physical_reads/(execution_count + 0.0)) as [AvgIOPhysicalReads],
        convert(money, total_logical_reads/(execution_count + 0.0)) as [AvgIOLogicalReads],
        convert(money, total_logical_writes/(execution_count + 0.0)) as [AvgIOLogicalWrites],
        query_hash,
        query_plan_hash,
        total_rows,
        convert(money, total_rows/(execution_count + 0.0)) as [AvgRows],
        total_dop,
        convert(money, total_dop/(execution_count + 0.0)) as [AvgDop],
        total_grant_kb,
        convert(money, total_grant_kb/(execution_count + 0.0)) as [AvgGrantKb],
        total_used_grant_kb,
        convert(money, total_used_grant_kb/(execution_count + 0.0)) as [AvgUsedGrantKb],
        total_ideal_grant_kb,
        convert(money, total_ideal_grant_kb/(execution_count + 0.0)) as [AvgIdealGrantKb],
        total_reserved_threads,
        convert(money, total_reserved_threads/(execution_count + 0.0)) as [AvgReservedThreads],
        total_used_threads,
        convert(money, total_used_threads/(execution_count + 0.0)) as [AvgUsedThreads],
        case 
            when sql_handle IS NULL then ' '
            else(substring(st.text,(qs.statement_start_offset+2)/2,(
                case
                    when qs.statement_end_offset =-1 then len(convert(nvarchar(MAX),st.text))*2      
                    else qs.statement_end_offset    
                end - qs.statement_start_offset)/2  ))
        end as query_text,
        db_name(st.dbid) as database_name,
        object_schema_name(st.objectid, st.dbid)+'.'+object_name(st.objectid, st.dbid) as [object_name],
        sp.[query_plan]
from sys.dm_exec_query_stats as qs with(readuncommitted)
cross apply sys.dm_exec_sql_text(qs.[sql_handle]) as st
cross apply sys.dm_exec_query_plan(qs.[plan_handle]) as sp
WHERE st.[text] LIKE '%query%'

Las consultas actuales se pueden ver usando el siguiente script:

select ES.[session_id]
      ,ER.[blocking_session_id]
      ,ER.[request_id]
      ,ER.[start_time]
      ,DateDiff(second, ER.[start_time], GetDate()) as [date_diffSec]
      , COALESCE(
                    CAST(NULLIF(ER.[total_elapsed_time] / 1000, 0) as BIGINT)
                   ,CASE WHEN (ES.[status] <> 'running' and isnull(ER.[status], '')  <> 'running') 
                            THEN  DATEDIFF(ss,0,getdate() - nullif(ES.[last_request_end_time], '1900-01-01T00:00:00.000'))
                    END
                ) as [total_time, sec]
      , CAST(NULLIF((CAST(ER.[total_elapsed_time] as BIGINT) - CAST(ER.[wait_time] AS BIGINT)) / 1000, 0 ) as bigint) as [work_time, sec]
      , CASE WHEN (ER.[status] <> 'running' AND ISNULL(ER.[status],'') <> 'running') 
                THEN  DATEDIFF(ss,0,getdate() - nullif(ES.[last_request_end_time], '1900-01-01T00:00:00.000'))
        END as [sleep_time, sec] --Время сна в сек
      , NULLIF( CAST((ER.[logical_reads] + ER.[writes]) * 8 / 1024 as numeric(38,2)), 0) as [IO, MB]
      , CASE  ER.transaction_isolation_level
        WHEN 0 THEN 'Unspecified'
        WHEN 1 THEN 'ReadUncommited'
        WHEN 2 THEN 'ReadCommited'
        WHEN 3 THEN 'Repetable'
        WHEN 4 THEN 'Serializable'
        WHEN 5 THEN 'Snapshot'
        END as [transaction_isolation_level_desc]
      ,ER.[status]
      ,ES.[status] as [status_session]
      ,ER.[command]
      ,ER.[percent_complete]
      ,DB_Name(coalesce(ER.[database_id], ES.[database_id])) as [DBName]
      , SUBSTRING(
                    (select top(1) [text] from sys.dm_exec_sql_text(ER.[sql_handle]))
                  , ER.[statement_start_offset]/2+1
                  , (
                        CASE WHEN ((ER.[statement_start_offset]<0) OR (ER.[statement_end_offset]<0))
                                THEN DATALENGTH ((select top(1) [text] from sys.dm_exec_sql_text(ER.[sql_handle])))
                             ELSE ER.[statement_end_offset]
                        END
                        - ER.[statement_start_offset]
                    )/2 +1
                 ) as [CURRENT_REQUEST]
      ,(select top(1) [text] from sys.dm_exec_sql_text(ER.[sql_handle])) as [TSQL]
      ,(select top(1) [objectid] from sys.dm_exec_sql_text(ER.[sql_handle])) as [objectid]
      ,(select top(1) [query_plan] from sys.dm_exec_query_plan(ER.[plan_handle])) as [QueryPlan]
      ,NULL as [event_info]--(select top(1) [event_info] from sys.dm_exec_input_buffer(ES.[session_id], ER.[request_id])) as [event_info]
      ,ER.[wait_type]
      ,ES.[login_time]
      ,ES.[host_name]
      ,ES.[program_name]
      ,cast(ER.[wait_time]/1000 as decimal(18,3)) as [wait_timeSec]
      ,ER.[wait_time]
      ,ER.[last_wait_type]
      ,ER.[wait_resource]
      ,ER.[open_transaction_count]
      ,ER.[open_resultset_count]
      ,ER.[transaction_id]
      ,ER.[context_info]
      ,ER.[estimated_completion_time]
      ,ER.[cpu_time]
      ,ER.[total_elapsed_time]
      ,ER.[scheduler_id]
      ,ER.[task_address]
      ,ER.[reads]
      ,ER.[writes]
      ,ER.[logical_reads]
      ,ER.[text_size]
      ,ER.[language]
      ,ER.[date_format]
      ,ER.[date_first]
      ,ER.[quoted_identifier]
      ,ER.[arithabort]
      ,ER.[ansi_null_dflt_on]
      ,ER.[ansi_defaults]
      ,ER.[ansi_warnings]
      ,ER.[ansi_padding]
      ,ER.[ansi_nulls]
      ,ER.[concat_null_yields_null]
      ,ER.[transaction_isolation_level]
      ,ER.[lock_timeout]
      ,ER.[deadlock_priority]
      ,ER.[row_count]
      ,ER.[prev_error]
      ,ER.[nest_level]
      ,ER.[granted_query_memory]
      ,ER.[executing_managed_code]
      ,ER.[group_id]
      ,ER.[query_hash]
      ,ER.[query_plan_hash]
      ,EC.[most_recent_session_id]
      ,EC.[connect_time]
      ,EC.[net_transport]
      ,EC.[protocol_type]
      ,EC.[protocol_version]
      ,EC.[endpoint_id]
      ,EC.[encrypt_option]
      ,EC.[auth_scheme]
      ,EC.[node_affinity]
      ,EC.[num_reads]
      ,EC.[num_writes]
      ,EC.[last_read]
      ,EC.[last_write]
      ,EC.[net_packet_size]
      ,EC.[client_net_address]
      ,EC.[client_tcp_port]
      ,EC.[local_net_address]
      ,EC.[local_tcp_port]
      ,EC.[parent_connection_id]
      ,EC.[most_recent_sql_handle]
      ,ES.[host_process_id]
      ,ES.[client_version]
      ,ES.[client_interface_name]
      ,ES.[security_id]
      ,ES.[login_name]
      ,ES.[nt_domain]
      ,ES.[nt_user_name]
      ,ES.[memory_usage]
      ,ES.[total_scheduled_time]
      ,ES.[last_request_start_time]
      ,ES.[last_request_end_time]
      ,ES.[is_user_process]
      ,ES.[original_security_id]
      ,ES.[original_login_name]
      ,ES.[last_successful_logon]
      ,ES.[last_unsuccessful_logon]
      ,ES.[unsuccessful_logons]
      ,ES.[authenticating_database_id]
      ,ER.[sql_handle]
      ,ER.[statement_start_offset]
      ,ER.[statement_end_offset]
      ,ER.[plan_handle]
      ,NULL as [dop]--ER.[dop]
      ,coalesce(ER.[database_id], ES.[database_id]) as [database_id]
      ,ER.[user_id]
      ,ER.[connection_id]
from sys.dm_exec_requests ER with(readuncommitted)
right join sys.dm_exec_sessions ES with(readuncommitted)
on ES.session_id = ER.session_id 
left join sys.dm_exec_connections EC  with(readuncommitted)
on EC.session_id = ES.session_id
where ER.[status] in ('suspended', 'running', 'runnable')
or exists (select top(1) 1 from sys.dm_exec_requests as ER0 where ER0.[blocking_session_id]=ES.[session_id])

Esta solicitud muestra todas las solicitudes activas y todas aquellas solicitudes que bloquean explícitamente las solicitudes activas.

Todos estos y otros scripts útiles se implementan como representaciones en la base de datos SRV , que se distribuye libremente. Por ejemplo, el primer script vino de la vista [inf]. [VBigQuery] , y el segundo vino de la vista [inf]. [VRequests] .

También hay varias soluciones de terceros para el historial de consultas. Uso el Administrador de consultas de Dbeaver : ingrese la descripción de la imagen aquí y el Historial de ejecución de consultas de SQL Tools , que está integrado en SSMS : ingrese la descripción de la imagen aquí

Evgeniy Gribkov
fuente
1

Esta característica no existe fuera de la caja en SSMS.

Si está utilizando SSMS 18 o posterior, puede probar SSMSPlus.

Tiene una función de historial de consultas.

https://github.com/akarzazi/SSMSPlus

Descargo de responsabilidad: soy el autor.

Adelos
fuente
0

puede usar "Generar script automáticamente en cada guardado", si está usando Management Studio. Esto no es ciertamente un registro. Marque si es útil para usted ..;)

Pecado
fuente
0

Si las consultas que le interesan son consultas dinámicas que fallan intermitentemente, puede registrar el SQL y la fecha y hora y el usuario en una tabla en el momento en que se crea la declaración dinámica. Sin embargo, se haría caso por caso, ya que requiere una programación específica y requiere un poco más de tiempo de procesamiento, así que hágalo solo para las pocas consultas que le preocupan más. Pero tener un registro de las declaraciones específicas ejecutadas realmente puede ayudar cuando intenta descubrir por qué falla solo una vez al mes. Las consultas dinámicas son difíciles de probar a fondo y, a veces, obtienes un valor de entrada específico que simplemente no funcionará y hacer este registro en el momento en que se crea el SQL es a menudo la mejor manera de ver qué específicamente no estaba en el sql que se construyó.

HLGEM
fuente
0

Un método ligeramente listo para usar sería crear una solución de script en AutoHotKey. Yo uso esto, y no es perfecto, pero funciona y es gratis. Esencialmente, esta secuencia de comandos asigna una tecla de acceso rápido a CTRL+ SHIFT+ Rque copiará el SQL seleccionado en SSMS ( CTRL+ C), guardará un archivo SQL de fecha y luego ejecutará la consulta resaltada ( F5). Si no está acostumbrado a los scripts AHK, el punto y coma inicial es un comentario.

;CTRL+SHIFT+R to run a query that is first saved off
^+r::
;Copy
Send, ^c
; Set variables
EnvGet, HomeDir, USERPROFILE
FormatTime, DateString,,yyyyMMdd
FormatTime, TimeString,,hhmmss
; Make a spot to save the clipboard
FileCreateDir %HomeDir%\Documents\sqlhist\%DateString%
FileAppend, %Clipboard%, %HomeDir%\Documents\sqlhist\%DateString%\%TimeString%.sql
; execute the query
Send, {f5}
Return

Las mayores limitaciones son que este script no funcionará si hace clic en "Ejecutar" en lugar de usar el método abreviado de teclado, y este script no guardará todo el archivo, solo el texto seleccionado. Pero siempre puede modificar el script para ejecutar la consulta y luego seleccionar todo ( CTRL+A ) antes de copiar / guardar.

El uso de un editor moderno con funciones de "buscar en archivos" le permitirá buscar en su historial de SQL. Incluso podría ser elegante y raspar sus archivos en una base de datos SQLite3 para consultar sus consultas.

mattmc3
fuente