Question

I have the following script:

-- available space in each of the database files

PRINT @@SERVERNAME
--SQLDWDEV01
USE [BodenStage]
GO
SELECT name 
,(CAST(ROUND((size/128.0 - CAST(FILEPROPERTY(name, 'SpaceUsed') AS int)/128.0),2) AS NUMERIC(18,2))) AS AvailableSpaceInMB  
,(CAST(ROUND((size/128.0 - FILEPROPERTY(name, 'SpaceUsed')/128.0)/1024.00,2) AS NUMERIC(18,2))) AS AvailableSpaceInGB 
FROM sys.database_files;  
GO

and as much as I don't like shrinking neither data nor log files, sometimes it is needed. Not my fault.

There are ways to shrink data files without accruing fragmentation as described here:

TRUNCATEONLY

Releases all free space at the end of the file to the operating system but does not perform any page movement inside the file. The data file is shrunk only to the last allocated extent.

How can I save the results of DBCC SHRINKFILE into a table?

Please see below it is one way of saving the results of DBCC SQLPERF into a table.

select @@servername as [Server]
       ,db_name() as [database]
go

SET NOCOUNT ON

begin try
  drop table #radhe
end try
begin catch
end catch


create table #radhe (
  DatabaseName varchar(100)
  , LOGSIZE_MB NUMERIC(18, 9)
  , LOGSPACE_USED NUMERIC(18, 9)
  , LOGSTATUS NUMERIC(18, 9)) 


INSERT #Radhe(DatabaseName, LOGSIZE_MB, LOGSPACE_USED, LOGSTATUS) 
EXEC('DBCC SQLPERF(LOGSPACE);')


select  DatabaseName, 
     LOGSIZE_GB=CONVERT(NUMERIC(18,2) ,ISNULL( ROUND(LOGSIZE_MB/1024.00,2),0)),
     PERC_USED =CONVERT(NUMERIC(4,2) ,ISNULL( ROUND(LOGSPACE_USED,2),0))
from #radhe
order by logsize_mb desc

This is now I generate the DBCC SHRINKFILE commands:

DECLARE @db VARCHAR(108)
DECLARE @dbid INT
DECLARE @amount INT

SELECT @db = 'MY_DATABASSEStage'
SELECT @dbid = DB_ID(@DB) 
SELECT @amount = 0

SELECT SHRINKFILE_SCRIPT = 'USE ' + QUOTENAME( DB_NAME(smf.database_id)) + CHAR(10) 
                         + 'GO' + CHAR(10)  
                         + 'DBCC SHRINKFILE(''' + smf.name + '''' + ',' + CAST(@amount AS VARCHAR) + ', TRUNCATEONLY) WITH NO_INFOMSGS' + CHAR(10) 
                         + 'GO' + CHAR(10)  
, * 
FROM  sys.master_files smf
WHERE smf.database_id = @dbid

The script above produced the following results:

USE [MY_DATABASSEStage]
GO
DBCC SHRINKFILE('MY_DATABASSEStageStockLedger',0, TRUNCATEONLY) WITH NO_INFOMSGS
GO

USE [MY_DATABASSEStage]
GO
DBCC SHRINKFILE('MY_DATABASSEStageStockLedgerArchive',0, TRUNCATEONLY) WITH NO_INFOMSGS
GO


USE [MY_DATABASSEStage]
GO
DBCC SHRINKFILE('MY_DATABASSEStageProduct',0, TRUNCATEONLY) WITH NO_INFOMSGS
GO

here is the result of one of the scripts - after removing the with no_infomsgs enter image description here

there are similar questions but they don't give an answer that apply to my case:

Shrink logs in between user transaction in SQL Server

Save Results of DBCC ShrinkFile

Conclusion

My answer works fine, but the method used by srutzky is much simpler and does the job, just require a linked server.

for my test server I am using the following script to create the linked server:

USE [master]
GO

IF NOT EXISTS (SELECT srv.name FROM sys.servers srv WHERE srv.server_id != 0 AND srv.name = N'TESTDBCC')
BEGIN
        EXEC master.dbo.sp_addlinkedserver 
                                            @server = N'TESTDBCC', 
                                            @srvproduct=N'', 
                                            @provider=N'SQLNCLI11', 
                                            @datasrc=N'JPB01275\SQL2014', 
                                            @catalog=N'radhe'


        EXEC master.dbo.sp_addlinkedsrvlogin 
                                            @rmtsrvname=N'TESTDBCC',
                                            @useself=N'True',
                                            @locallogin=NULL,
                                            @rmtuser=NULL,
                                            @rmtpassword=NULL
END
GO

EXEC master.dbo.sp_serveroption 
                                @server=N'TESTDBCC', 
                                @optname=N'collation compatible', 
                                @optvalue=N'false'
GO

EXEC master.dbo.sp_serveroption @server=N'TESTDBCC', 
                                @optname=N'data access', 
                                @optvalue=N'true'
GO

EXEC master.dbo.sp_serveroption @server=N'TESTDBCC', 
                                @optname=N'dist', 
                                @optvalue=N'false'
GO

EXEC master.dbo.sp_serveroption @server=N'TESTDBCC', 
                                @optname=N'pub', 
                                @optvalue=N'false'
GO

EXEC master.dbo.sp_serveroption @server=N'TESTDBCC', 
                                @optname=N'rpc', 
                                @optvalue=N'true'
GO

EXEC master.dbo.sp_serveroption @server=N'TESTDBCC', 
                                @optname=N'rpc out', 
                                @optvalue=N'true'
GO

EXEC master.dbo.sp_serveroption @server=N'TESTDBCC', 
                                @optname=N'sub', 
                                @optvalue=N'false'
GO

EXEC master.dbo.sp_serveroption @server=N'TESTDBCC', 
                                @optname=N'connect timeout', 
                                @optvalue=N'0'
GO

EXEC master.dbo.sp_serveroption @server=N'TESTDBCC', 
                                @optname=N'collation name', 
                                @optvalue=null
GO

EXEC master.dbo.sp_serveroption @server=N'TESTDBCC', 
                                @optname=N'lazy schema validation', 
                                @optvalue=N'false'
GO

EXEC master.dbo.sp_serveroption @server=N'TESTDBCC', 
                                @optname=N'query timeout', 
                                @optvalue=N'0'
GO

EXEC master.dbo.sp_serveroption @server=N'TESTDBCC', 
                                @optname=N'use remote collation', 
                                @optvalue=N'true'
GO

EXEC master.dbo.sp_serveroption @server=N'TESTDBCC', 
                                @optname=N'remote proc transaction promotion', 
                                @optvalue=N'false'
GO

and using the linked server above I can collect the data as follows:

--Use the Linked Server via EXEC() AT

CREATE TABLE #ShrinkFileResults
(
    [DBID] SMALLINT,
    [FileID] INT,
    [CurrentSize] INT,
    [MinimumSize] INT,
    [UsedPages] INT,
    [EstimatedPages] INT
);

INSERT INTO #ShrinkFileResults
  ([DBID], [FileID], [CurrentSize], [MinimumSize], [UsedPages], [EstimatedPages])
  EXEC(N'
     USE [tempdb];
     DBCC SHRINKFILE(N''tempdev'', 0, TRUNCATEONLY);
  ') AT [TestDBCC];


INSERT INTO #ShrinkFileResults
  ([DBID], [FileID], [CurrentSize], [MinimumSize], [UsedPages], [EstimatedPages])
  EXEC(N'
     USE [radhe];
     DBCC SHRINKFILE(N''radhe_log'', 0, TRUNCATEONLY);
  ') AT [TestDBCC];

INSERT INTO #ShrinkFileResults
  ([DBID], [FileID], [CurrentSize], [MinimumSize], [UsedPages], [EstimatedPages])
  EXEC(N'
     USE [US16HSMMProduct_AFTER_CHANGES];
     DBCC SHRINKFILE(N''US16HSMMProduct_AFTER_CHANGES_log'', 0, TRUNCATEONLY);
  ') AT [TestDBCC];

INSERT INTO #ShrinkFileResults
  ([DBID], [FileID], [CurrentSize], [MinimumSize], [UsedPages], [EstimatedPages])
  EXEC(N'
     USE [US16HSMMProduct_AFTER_CHANGES];
     DBCC SHRINKFILE(N''US16HSMMProduct_AFTER_CHANGES_log'', 0, TRUNCATEONLY);
  ') AT [TestDBCC];

SELECT * FROM #ShrinkFileResults;

enter image description here

Was it helpful?

Solution

This can be accomplished somewhat easily by using a "loop back" Linked Server that has the 'remote proc transaction promotion' property set to false, which avoids the Cannot perform a shrinkfile operation inside a user transaction error by side-stepping the implicit transaction started by the INSERT...EXEC operation. The Linked Server will otherwise only require that the 'rpc out' property be set to true. The 'data access' and 'rpc' properties do not need to be enabled (at least not for me).

Create the Linked Server

USE [master];

DECLARE @LinkedServerName sysname;
SET @LinkedServerName = N'TestDBCC';

IF (NOT EXISTS (SELECT srv.name
                FROM   sys.servers srv
                WHERE  srv.server_id <> 0
                AND    srv.name = @LinkedServerName))
BEGIN
  EXEC [master].dbo.sp_addlinkedserver
    @server = @LinkedServerName, @srvproduct=N'', @provider=N'SQLNCLI11', @datasrc=N'DALI';
  EXEC [master].dbo.sp_addlinkedsrvlogin
    @rmtsrvname=@LinkedServerName,@useself=N'True',@locallogin=NULL,
    @rmtuser=NULL,@rmtpassword=NULL;
END;

--EXEC [master].dbo.sp_serveroption @server=@LinkedServerName,
--  @optname=N'data access', @optvalue=N'true';
--EXEC [master].dbo.sp_serveroption @server=@LinkedServerName,
--  @optname=N'rpc', @optvalue=N'true'; -- is_remote_login_enabled
EXEC [master].dbo.sp_serveroption @server=@LinkedServerName,
  @optname=N'rpc out', @optvalue=N'true';
EXEC [master].dbo.sp_serveroption @server=@LinkedServerName,
  @optname=N'remote proc transaction promotion', @optvalue=N'false';
GO

Use the Linked Server via EXEC() AT

CREATE TABLE #ShrinkFileResults
(
    [DBID] SMALLINT,
    [FileID] INT,
    [CurrentSize] INT,
    [MinimumSize] INT,
    [UsedPages] INT,
    [EstimatedPages] INT
);

INSERT INTO #ShrinkFileResults
  ([DBID], [FileID], [CurrentSize], [MinimumSize], [UsedPages], [EstimatedPages])
  EXEC(N'
     USE [tempdb];
     DBCC SHRINKFILE(N''tempdev'', 0, TRUNCATEONLY);
  ') AT [TestDBCC];

SELECT * FROM #ShrinkFileResults;

This worked for me on both SQL Server 2012 SP3 and SQL Server 2016 RTM.

NOTES

  • Using OPENQUERY does not work. Initially you get a "meta-data discovery" error which can be resolved by wrapping the DBCC call in another EXEC that uses the WITH RESULT SETS option. But then you get an error about "OPENQUERY cannot process object as it either does not exist or you don't have permission to it". But if you change the logical name of the file to shrink, then you get an error from DBCC stating that it cannot find a file of that name, along with a secondary error from OPENQUERY stating that the call did not return any result sets, all of which indicates that with the correct file name it is get farther.

  • This operation can also be rather simply accomplished via SQLCLR. One can create a UDF / Scalar function, in fact, using an external connection (not a Context Connection), which would allow for being called in a set-based operation in the SELECT list. That would also only take a few lines of code, and minimize security risk when compared to enabling xp_cmdshell or creating a Linked Server.

OTHER TIPS

The below process is how I managed to save the results of dbcc shrinkfile into a table

  • enable the use of xp_cmdshell
  • get a login or proxy to run the script

so we need the following script:

-- To allow advanced options to be changed.
EXEC sp_configure 'show advanced options', 1
GO

    -- To update the currently configured value for advanced options.
    RECONFIGURE
    GO

    -- To enable the feature.
    EXEC sp_configure 'xp_cmdshell', 1
    GO

    -- To update the currently configured value for this feature.
    RECONFIGURE
    GO

USE master
GO
-- Create the SQL login which will use xp_cmdshell.
CREATE LOGIN [SQL_User_Impresonated] WITH PASSWORD='Sql_P@ssword'
-- Create a proxy credential for xp_cmdshell assigned to a Domain/Windows account.

EXEC sp_xp_cmdshell_proxy_account 'DOMAIN\LOGINNAME', 'LOGINNAME_PASSWORD';
-- Grant database access to the SQL Server login account that you want to provide access.

EXEC sp_grantdbaccess 'SQL_User_Impresonated'
-- Grant execute permission on xp_cmdshell to the SQL Server login account.
GRANT exec ON sys.xp_cmdshell TO [SQL_User_Impresonated]
GO

--Configuration option 'show advanced options' changed from 1 to 1. Run the RECONFIGURE statement to install.
--Configuration option 'xp_cmdshell' changed from 0 to 1. Run the RECONFIGURE statement to install.

----------------------------------------------------------------
-- GRANT THE REQUIRED PERMISSIONS TO LOGIN SQL_User_Impresonated
----------------------------------------------------------------





--=======================================================================================================
-- before schrinking the log, lets see how much space we are currently using
--=======================================================================================================
declare @Radhe table(
  DatabaseName varchar(100)
  , LOGSIZE_MB decimal(18, 9)
  , LOGSPACE_USED decimal(18, 9)
  ,  LOGSTATUS decimal(18, 9)) 


insert @Radhe(DatabaseName, LOGSIZE_MB, LOGSPACE_USED, LOGSTATUS) 
EXEC('DBCC SQLPERF(LOGSPACE);')

select DatabaseName, 
     LOGSIZE_MB =CAST (ROUND(LOGSIZE_MB,2) AS NUMERIC(10,2)),
     LOGSIZE_GB =CAST (ROUND(LOGSIZE_MB/1024.00,2)AS NUMERIC(10,2)),
     LOGSPACE_USED =CAST(ROUND(LOGSPACE_USED,2)AS NUMERIC(10,2))
     FROM @Radhe
order by logsize_mb desc
--=======================================================================================================

enter image description here

--=======================================================================================================
-- process to shrink the log file and show the outcome
--=======================================================================================================
DECLARE       @sqlCommand   VARCHAR(1000)
DECLARE       @filePath     VARCHAR(100)
DECLARE       @fileName     VARCHAR(100)

SET    @filePath = 'C:\sqlBackup\'

SET    @fileName = 'Shrink_' +
       + CONVERT(VARCHAR, GETDATE(), 112) + '_' +
         RIGHT('00'+CAST(DATEPART(HOUR, GETDATE()) AS VARCHAR),2) + '_' +
         RIGHT('00'+CAST(DATEPART(MINUTE,GETDATE()) AS VARCHAR),2) + '.txt'

PRINT @filePath + @filename

       --SET    @sqlCommand = 'sqlcmd -S "JPB01275\SQL2014" -U SQL_User_Impresonated -P Sql_P@ssword -N -C -d "Radhe" -o "C:\sqlBackup\shrink.txt" -Q "DBCC SHRINKFILE(''Radhe_log'',0, TRUNCATEONLY)"'

       SET    @sqlCommand = 'sqlcmd -S "JPB01275\SQL2014" -U SQL_User_Impresonated -P Sql_P@ssword -N -C -d "Radhe" -o ' + '"' + @FILEPATH + @fileName + '"' + ' -Q "DBCC SHRINKFILE(''Radhe_log'',0, TRUNCATEONLY)"'


--PRINT       @sqlCommand

EXEC   master..xp_cmdshell @sqlCommand

--select QUOTENAME(@@servername)

BEGIN TRY
   DROP TABLE #RADHE
END TRY
BEGIN CATCH 
END CATCH


CREATE TABLE #RADHE
(THE_LINE VARCHAR(500) NULL)

select @sqlCommand=
'BULK INSERT #RADHE ' + CHAR(10) +
'FROM ' + '''' + @FILEPATH + @fileName + '''' + CHAR(10) +
   'WITH 
      (
         ROWTERMINATOR =''\n''
      )'

exec (@sqlCommand)


--querying an specific line of a heap
ALTER TABLE #RADHE
ADD ID INT NOT NULL IDENTITY(1,1) PRIMARY KEY CLUSTERED

BEGIN TRY
   DROP TABLE #X
END TRY
BEGIN CATCH 
END CATCH

BEGIN TRY
   DROP TABLE #Y
END TRY
BEGIN CATCH 
END CATCH
--=========================================================================================
-- CTE to extract all numbers from string: niikola
--=========================================================================================
Declare @txt varchar(4000) 
Declare @num varchar(10) = '%[0-9.,]%'
Declare @oth varchar(10) = '%[^0-9.,]%'


SELECT @txt =THE_LINE
FROM #RADHE
WHERE ID = 3

Set @txt+='X'

;With a as (
Select 1 as i,
Substring(@txt,Patindex(@num,@txt),patindex(@oth,Substring(@txt,Patindex(@num,@txt),4000))-1) as num, 
substring(@txt,Patindex(@num,@txt)+patindex(@oth,Substring(@txt,Patindex(@num,@txt),4000))-1,4000) as txt
Union All
Select i+1,
Substring(txt,Patindex(@num,txt),patindex(@oth,Substring(txt,Patindex(@num,txt),4000))-1), 
substring(txt,Patindex(@num,txt)+patindex(@oth,Substring(txt,Patindex(@num,txt),4000))-1,4000)
from a
Where txt like '%[0-9]%'
)
Select IND=i, NUM=CAST( num  AS INT)
INTO #Y
from a
Where num like '%[0-9]%'


CREATE TABLE #x
(
    [DBID] int,
    FileID int,
    CurrentSize int,
    MinimumSize int,
    UsedPages int,
    EstimatedPages int
)


-- Problems with the Insert
-- The select list for the INSERT statement contains fewer items than the insert list. The number of SELECT values must match the number of INSERT columns.
-- Column name or number of supplied values does not match table definition.


-- select * from #y

INSERT INTO #x 
(
    [DBID],
    FileID,
    CurrentSize,
    MinimumSize,
    UsedPages,
    EstimatedPages
)

SELECT *
FROM 
(
  SELECT IND,NUM
  FROM #Y
) RADHE
PIVOT
(
  SUM(NUM)
  FOR IND IN ([1], [2], [3],[4], [5], [6])
) PIV;


--==============================================================================
-- the outpout of DBCC Shrinkfile

-- https://msdn.microsoft.com/en-GB/library/ms189493.aspx

--Column name   Description
--DbId  Database identification number of the file the Database Engine tried to shrink.
--FileId    The file identification number of the file the Database Engine tried to shrink.
--CurrentSize   Number of 8-KB pages the file currently occupies.
--MinimumSize   Number of 8-KB pages the file could occupy, at minimum. This corresponds to the minimum size or originally created size of a file.
--UsedPages Number of 8-KB pages currently used by the file.
--EstimatedPages    Number of 8-KB pages that the Database Engine estimates the file could be shrunk down to.
--==============================================================================

use radhe

SELECT name ,size/128.0 - CAST(FILEPROPERTY(name, 'SpaceUsed') AS int)/128.0 AS AvailableSpaceInMB  
FROM sys.database_files;  

--SELECT SUM(user_object_reserved_page_count) AS [user object pages used],  
--(SUM(user_object_reserved_page_count)*1.0/128) AS [user object space in MB]  
--FROM sys.dm_db_file_space_usage;  


select *
FROM sys.dm_db_log_space_usage;  


SELECT 
the_database_name=db_name(dbid),
FileID,
CurrentSize,
MinimumSize,
UsedPages,
EstimatedPages
FROM #X

enter image description here

Licensed under: CC-BY-SA with attribution
Not affiliated with dba.stackexchange
scroll top