Our ETL flow has a long-running SELECT INTO-statement, that's creating a table on the fly, and populating it with several hundred million records.

The statement looks something like SELECT ... INTO DestTable FROM SrcTable

For monitoring purposes, we would like to get a rough idea of the progress of this statement, while it is executing (approx. rowcount, written number of bytes, or similar).

We tried the following to no avail:

-- Is blocked by the SELECT INTO statement:
select count(*) from DestTable with (nolock)

-- Returns 0, 0:
select rows, rowmodctr
from sysindexes with (nolock)
where id = object_id('DestTable')

-- Returns 0:
select rows
from sys.partitions
where object_id = object_id('DestTable')

Furthermore, we can see the transaction in sys.dm_tran_active_transactions, but I was not able to find a way to get the count of affected rows on a given transaction_id (something similar to @@ROWCOUNT perhaps, but with the transaction_id as argument).

I understand that on SQL Server the SELECT INTO-statement is both a DDL and a DML statement in one, and as such, the implicit table creation will be a locking operation. I still think there must be some clever way to obtain some kind of progress information while the statement is running.

  • If you used a global temp table ##TABLE, could you perform a Select with count on the index column on the ##TABLE to get the number of records already written and approximate the amount of records in total to be written?
    – CoveGeek
    Commented Feb 16, 2016 at 1:16

4 Answers 4


I suspect that rows in sys.partitions is 0 due to not being committed yet. But this does not mean that SQL Server is unaware of what will go there if the Transaction does commit. The key is in remembering that all operations go through the Buffer Pool (i.e. memory) first, regardless of COMMIT or ROLLBACK of the operation. Hence, we can look in sys.dm_os_buffer_descriptors for that info:


SELECT  --OBJECT_NAME(sp.[object_id]) AS [TableName], sdobd.*, '---', sp.*, '---', sau.*
       SUM(sdobd.[row_count]) AS [BufferPoolRows],
       SUM(sp.[rows]) AS [AllocatedRows],
       COUNT(*) AS [DataPages]
FROM sys.dm_os_buffer_descriptors sdobd
INNER JOIN  sys.allocation_units sau
        ON sau.[allocation_unit_id] = sdobd.[allocation_unit_id]
INNER JOIN  sys.partitions sp
        ON  (   sau.[type] = 1
            AND sau.[container_id] = sp.[partition_id]) -- IN_ROW_DATA
        OR  (   sau.[type] = 2
            AND sau.[container_id] = sp.[hobt_id]) -- LOB_DATA
        OR  (   sau.[type] = 3
            AND sau.[container_id] = sp.[partition_id]) -- ROW_OVERFLOW_DATA
WHERE   sdobd.[database_id] = DB_ID()
AND     sdobd.[page_type] = N'DATA_PAGE'
AND     sp.[object_id] = (SELECT so.[object_id]
                          FROM   sys.objects so
                          WHERE  so.[name] = 'TestDump')

If you want to see the details, uncomment the first row of items in the SELECT list, comment out the remaining 3 lines.

I tested by running the following in one Session and then repeatedly running the query above in another.

SELECT so1.*
INTO   dbo.TestDump
FROM   sys.objects so1
CROSS JOIN sys.objects so2
CROSS JOIN sys.objects so3;
  • 1
    This is creative. Just want to add a warning that enumerating a big buffer pool is very slow.
    – usr
    Commented Feb 12, 2016 at 18:25
  • 1
    This does assume that no pages have been evicted from the buffer pool yet. Commented Feb 12, 2016 at 19:20
  • @MartinSmith Can pages be evicted prior to the commit? Commented Feb 12, 2016 at 19:23
  • 5
    @srutzky - yes. The transaction log has all the info needed to rollback. Dirty pages can be written to disc - e.g at a checkpoint or by the Eager writer especially in this case then removed from the buffer pool. Commented Feb 12, 2016 at 19:24

For monitoring purposes, we would like to get a rough idea of the progress of this statement, while it is executing.

One off or ongoing?

If this is a need that can be anticipated in advance* you could use sys.dm_exec_query_profiles

Connection 1 (session 55)


SELECT so1.*
INTO   dbo.TestDump
FROM   sys.all_objects so1
CROSS JOIN sys.all_objects so2
CROSS JOIN sys.all_objects so3
CROSS JOIN sys.all_objects so4
CROSS JOIN sys.all_objects so5;

Connection 2

select row_count
from sys.dm_exec_query_profiles
WHERE physical_operator_name = 'Table Insert' 
    AND session_id = 55;

You may need to sum the rows counts returned if the SELECT INTO is using parallelism.

* The session you want to monitor using this DMV must be enabled for statistics collection using SET STATISTICS PROFILE ON or SET STATISTICS XML ON. Requesting an "actual" execution plan from SSMS works as well (because it sets the latter option).


I don't think there's a way to get row counts, but you can estimate amount of data written by looking at:

SELECT writes 
  FROM sys.dm_exec_requests WHERE session_id = <x>;

SELECT COUNT(*) FROM sys.dm_db_database_page_allocations
(<dbid>, OBJECT_ID(N'dbo.newtablename'), 0, NULL, 'LIMITED');

If you have some kind of idea of how many pages the heap should take up when done, you should be able to work out % complete. The latter query won't be fast as the table gets bigger. And probably safest to run the above under READ UNCOMMITTED (and it's not often I recommend that, for anything).


If you could change the INSERT from a

SELECT ... INTO DestTable FROM SrcTable

to a

INSERT DestTable SELECT ... FROM SrcTable

then your select count(*) from DestTable with (nolock) query would work.

If this isn't possible then you can use sp_WhoIsActive (or delve into the DMVs) to monitor how many writes the query does. This would be a rather rough gauge but could be useful if you base lined the number of writes it normally does.

You should be able to get minimal logging with the INSERT above if you add WITH (TABLOCK).

  • Thank you for this comment. We want to obtain minimal logging, which is why we're using the SELECT ... INTO approach (and also because we're kind of lazy...)
    – Dan
    Commented Feb 12, 2016 at 13:11
  • 1
    You should be able to get minimal logging with the INSERT above if you add WITH(TABLOCK) Commented Feb 12, 2016 at 13:26
  • @JamesAnderson - If the table is left as a heap this will just cause blocking again as it takes a BULK_OPERATION lock. Commented Feb 13, 2016 at 13:28
  • Ah I didn't realize this technique was an advantage of using INSERT vs SELECT INTO; (more on the differences here) Commented Jan 24, 2021 at 21:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.