Migration DDL from Oracle to SQLServer - sql-server

I would like to migrate DDL from Oracle to SQLServer.
It was able to migrate to a certain extent.
However, some items can not be migrated.
Oracle DDL:
CREATE TABLE ExampleTbl
(
code CHAR(3) NOT NULL,
code2 CHAR(3) NOT NULL,
username VARCHAR2(255) NOT NULL,
d DATETIME
CONSTRAINT PK_Example PRIMARY KEY (code, code2) USING INDEX
PCTFREE 10
INITRANS 2 -- <-?
MAXTRANS 255 -- <-?
TABLESPACE TBSP01
STORAGE(INITIAL 64K NEXT 1M MINEXTENTS 1 MAXEXTENTS 2147483645 BUFFER_POOL DEFAULT) -- <-?
LOGGING -- <-?
ENABLE -- <-?
)
PCTFREE 10
MAXTRANS 255
TABLESPACE TBSP01
STORAGE(INITIAL 64K NEXT 1M MINEXTENTS 1 MAXEXTENTS 2147483645 BUFFER_POOL DEFAULT) -- <-?
NOCACHE -- <-?
LOGGING
/
COMMENT ON TABLE ExampleTbl IS 'Table comment!'
/
SQLServer DDL:
CREATE TABLE [dbo].[ExampleTbl](
[code] [char](10) NOT NULL,
[code2] [char](10) NOT NULL,
[username] [varchar](255) NOT NULL,
[d] [datetime] NULL,
CONSTRAINT [PK_ExampleTbl] PRIMARY KEY CLUSTERED
(
[code] ASC,
[code2] ASC
)
WITH
(
PAD_INDEX = OFF,
STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON,
FILLFACTOR = 90 -- FillFactor = 100 - Oracle.PCTFREE(10)
) ON [TBSP01] -- Oracle.TableSpace
) ON [TBSP01] -- Oracle.TableSpace
GO
EXEC sys.sp_addextendedproperty
#name=N'MS_Description',
#value=N'Table comment!' , -- Oracle.Comment
#level0type=N'SCHEMA',
#level0name=N'dbo',
#level1type=N'TABLE',
#level1name=N'ExampleTbl'
GO
Don't worry about column names.
How do I migrate these?
INITRANS, MAXTRANS, STORAGE, LOGGING, ENABLE, NOCACHE.
And, are there any other problems?

CREATE TABLE Statement
Converting CREATE TABLE statement keywords and clauses:
Oracle SQL Server
1 ENABLE constraint attribute Removed
Storage and physical attributes:
Oracle SQL Server
1 PCTFREE num Removed
2 PCTUSED num Removed
3 INITRANS num Removed
4 MAXTRANS num Removed
5 COMPRESS [BASIC] | COMPRESS num | NOCOMPRESS Removed
6 LOGGING | NOLOGGING Removed
7 SEGMENT CREATION IMMEDIATE | DEFERRED Removed
8 TABLESPACE name ON name
9 LOB (column) STORE AS BASIC FILE (params) Removed
10 PARALLEL num | NOPARALLEL Removed
11 NOCACHE Removed
12 NOMONITORING Removed
STORAGE clause:
Oracle SQL Server
1 INITIAL num Removed
2 NEXT num Removed
3 MINEXTENTS num Removed
4 MAXEXTENTS num | UNLIMITED Removed
5 PCTINCREASE num Removed
6 FREELISTS num Removed
7 FREELIST GROUPS num Removed
8 BUFFER_POOL DEFAULT | KEEP | RECYCLE Removed
9 FLASH_CACHE DEFAULT | KEEP | NONE Removed
10 CELL_FLASH_CACHE DEFAULT | KEEP | NONE Removed
LOB storage clause:
Oracle SQL Server
1 TABLESPACE name Removed
2 DISABLE | ENABLE STORAGE IN ROW Removed
3 CHUNK num Removed
4 NOCACHE Removed
5 LOGGING Removed
More Details http://www.sqlines.com/oracle-to-sql-server

Related

Hierarchical Oracle inserts. Ensure that every parent has at most one child

My sample database has a table that stores employees, where the employee_id is the primary key and the manager_id is a foreign key to the employee_id
What is shown in the picture below is a hierarchical structure in a company.
The employee_id has no manager (it is the boss!), the 1021 manages 1022.
When I execute
SELECT last_name, employee_id, manager_id, LEVEL
FROM my_employees
START WITH employee_id = 1020
CONNECT BY PRIOR employee_id = manager_id;
I am taking back the hierarchy of the company:
LAST_NAME EMPLOYEE_ID MANAGER_ID LEVEL
------------------------- ----------- ---------- ----------
Test 1020 1
Test2 1021 1020 2
Test3 1022 1021 3
I would like to enhance this functionality in order to verify that each parent (ie EMPLOYEE_ID) will have a unique (ONE) child only.
For example this is possible in my table:
LAST_NAME EMPLOYEE_ID MANAGER_ID LEVEL
------------------------- ----------- ---------- ----------
Test 1020 1
Test2 1021 1020 2
Test3 1022 1021 3
Test4 1023 1021 3
Test4 and Test3 have the same manager (test3 and test4 are one the same level).
My request is to find an easy way in the database schema to ensure that "an employee can not have two managers no matter the level" AND a manager cannot have more than 1 employees that belong to the next level.
The sql script that created my table/trigger and sequence is the following:
--------------------------------------------------------
-- DDL for Sequence MY_EMPLOYEES_SEQ
--------------------------------------------------------
CREATE SEQUENCE "HR"."MY_EMPLOYEES_SEQ" MINVALUE 1 MAXVALUE 999999999999999 INCREMENT BY 1 START WITH 1020 CACHE 20 NOORDER NOCYCLE NOKEEP NOSCALE GLOBAL ;
--------------------------------------------------------
-- DDL for Table MY_EMPLOYEES
--------------------------------------------------------
CREATE TABLE "HR"."MY_EMPLOYEES"
( "EMPLOYEE_ID" NUMBER(6,0),
"FIRST_NAME" VARCHAR2(20 BYTE),
"LAST_NAME" VARCHAR2(25 BYTE),
"EMAIL" VARCHAR2(25 BYTE),
"PHONE_NUMBER" VARCHAR2(20 BYTE),
"HIRE_DATE" DATE,
"SALARY" NUMBER(8,0),
"MANAGER_ID" NUMBER(6,0)
) SEGMENT CREATION IMMEDIATE
PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255
NOCOMPRESS LOGGING
STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1
BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT CELL_FLASH_CACHE DEFAULT)
TABLESPACE "USERS" ;
COMMENT ON COLUMN "HR"."MY_EMPLOYEES"."EMPLOYEE_ID" IS 'The primary key';
COMMENT ON COLUMN "HR"."MY_EMPLOYEES"."FIRST_NAME" IS 'First Name';
COMMENT ON COLUMN "HR"."MY_EMPLOYEES"."LAST_NAME" IS 'Last Name';
COMMENT ON COLUMN "HR"."MY_EMPLOYEES"."EMAIL" IS 'Email Id';
COMMENT ON COLUMN "HR"."MY_EMPLOYEES"."PHONE_NUMBER" IS 'Phone Number
';
COMMENT ON COLUMN "HR"."MY_EMPLOYEES"."HIRE_DATE" IS 'Hire Date';
COMMENT ON COLUMN "HR"."MY_EMPLOYEES"."SALARY" IS 'Enfornced by check';
--------------------------------------------------------
-- DDL for Index MY_EMPLOYEES_UK1
--------------------------------------------------------
CREATE UNIQUE INDEX "HR"."MY_EMPLOYEES_UK1" ON "HR"."MY_EMPLOYEES" ("EMAIL")
PCTFREE 10 INITRANS 2 MAXTRANS 255 COMPUTE STATISTICS
STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1
BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT CELL_FLASH_CACHE DEFAULT)
TABLESPACE "USERS" ;
--------------------------------------------------------
-- DDL for Index MY_EMPLOEES_PK
--------------------------------------------------------
CREATE UNIQUE INDEX "HR"."MY_EMPLOEES_PK" ON "HR"."MY_EMPLOYEES" ("EMPLOYEE_ID")
PCTFREE 10 INITRANS 2 MAXTRANS 255 COMPUTE STATISTICS
STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1
BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT CELL_FLASH_CACHE DEFAULT)
TABLESPACE "USERS" ;
--------------------------------------------------------
-- DDL for Trigger MY_EMPL_TRIG_1
--------------------------------------------------------
CREATE OR REPLACE EDITIONABLE TRIGGER "HR"."MY_EMPL_TRIG_1"
before insert on "HR"."MY_EMPLOYEES"
for each row
begin
if inserting then
if :NEW."EMPLOYEE_ID" is null then
select MY_EMPLOYEES_SEQ.nextval into :NEW."EMPLOYEE_ID" from dual;
end if;
end if;
end;
/
ALTER TRIGGER "HR"."MY_EMPL_TRIG_1" ENABLE;
--------------------------------------------------------
-- Constraints for Table MY_EMPLOYEES
--------------------------------------------------------
ALTER TABLE "HR"."MY_EMPLOYEES" MODIFY ("EMPLOYEE_ID" NOT NULL ENABLE);
ALTER TABLE "HR"."MY_EMPLOYEES" MODIFY ("LAST_NAME" NOT NULL ENABLE);
ALTER TABLE "HR"."MY_EMPLOYEES" MODIFY ("EMAIL" NOT NULL ENABLE);
ALTER TABLE "HR"."MY_EMPLOYEES" MODIFY ("HIRE_DATE" NOT NULL ENABLE);
ALTER TABLE "HR"."MY_EMPLOYEES" ADD CONSTRAINT "MY_EMPLOEES_PK" PRIMARY KEY ("EMPLOYEE_ID")
USING INDEX PCTFREE 10 INITRANS 2 MAXTRANS 255 COMPUTE STATISTICS
STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1
BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT CELL_FLASH_CACHE DEFAULT)
TABLESPACE "USERS" ENABLE;
ALTER TABLE "HR"."MY_EMPLOYEES" ADD CONSTRAINT "MY_EMPLOYEES_UK1" UNIQUE ("EMAIL")
USING INDEX PCTFREE 10 INITRANS 2 MAXTRANS 255 COMPUTE STATISTICS
STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1
BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT CELL_FLASH_CACHE DEFAULT)
TABLESPACE "USERS" ENABLE;
ALTER TABLE "HR"."MY_EMPLOYEES" ADD CONSTRAINT "MY_EMPLOYEES_CHK1" CHECK (salary > 0) ENABLE;
You just need to create an Unique Index on your table to ensure that no Employee will be duplicated avoid that an Employee will have more than one Manager.
create unique index uk_unique_employee on "HR"."MY_EMPLOYEES" (EMPLOYEE_ID);
EDIT
About your last comment: "Jorge, what is wrong wit the table is that it possible to create a employee with a manager that does not EXIST."
So you just need to add a foreign key constraint to the EMPLOYEE_ID in the MANAGER_ID column like:
alter table "HR"."MY_EMPLOYEES"
add constraint fk_employee_manager
foreign key (MANAGER_ID)
references "HR"."MY_EMPLOYEES" (EMPLOYEE_ID);
Note that in order to create a FOREIGN KEY the column must be a primary key or have an index on it (which the previous command ensured).

SQL Server - Inserting default values bcp

In SQL Server, how to insert default values while using bcp command?
Scenario is from the below table, while running bcp command, column 'sno' is identity column, where values should increment automatically by 1, data for values column should come from datafile and values for date column should automatically updated to today's date and values for status column should updated as Flag1.
For normal usage I know how to create bcp format file. For the above scenario, how can I create a format file and insert data to table1?
Table format:
CREATE TABLE [dbo].[table1]
(
SNo int IDENTITY(1,1) NOT NULL,
values varchar(13) NOT NULL,
date datetime NOT NULL,
status varchar(50)
)
Table1:
sno | values | date | status
-----+----------+------------+--------
1 | 111111 | 2015-08-17 | Flag1
2 | 222222 | 2015-08-17 | Flag1
Basically, you just need to put 0 as the host column number to avoid a column from being inserted by bcp.
So assuming you have a default constraint for your [date] column:
ALTER TABLE dbo.table1
ADD CONSTRAINT DF_Table1_Date DEFAULT(SYSDATETIME()) FOR [Date]
and somehow you have also set up some way to calculate the [status] - then you could use this format file:
12.0
4
1 SQLCHAR 0 12 ";" 0 SNo ""
2 SQLCHAR 0 13 ";" 2 values SQL_Latin1_General_CP1_CI_AS
3 SQLDATETIME 0 24 ";" 0 date ""
4 SQLCHAR 0 50 "\r\n" 0 status SQL_Latin1_General_CP1_CI_AS
and thus you would be really only importing the [values] column - the SNo is automatically set by SQL Server (identity column), the [date] column is automatically set to the current date&time by means of the default constraint - now you'll have to find a way to fill in the [status] column upon or after insert!

SQL Server 2005 - odd clustered index size

Existing table structure
CREATE TABLE [MYTABLE](
[ROW1] [numeric](18, 0) NOT NULL,
[ROW2] [numeric](18, 0) NOT NULL,
[ROW3] [numeric](18, 0) NOT NULL,
[ROW4] [numeric](18, 0) NULL,
CONSTRAINT [MYTABLE_PK] PRIMARY KEY CLUSTERED ([ROW1] ASC, [ROW2] ASC, [ROW3] ASC)
)
This table has 2 non-clustered indexes, and the following stats:
RowCount: 5260744
Data Space: 229.609 MB
Index Space: 432.125 MB
I wanted to reduce the size of the indexes, and use a surrogate primary key as the clustered index, instead of the natural composite key.
New table structure
CREATE TABLE [dbo].[TEST_RUN_INFO](
[ROW1] [numeric](18, 0) NOT NULL,
[ROW2] [numeric](18, 0) NOT NULL,
[ROW3] [numeric](18, 0) NOT NULL,
[ROW4] [numeric](18, 0) NULL,
[ID] [int] IDENTITY(1,1) NOT NULL,
CONSTRAINT [MYTABLE_PK] PRIMARY KEY CLUSTERED ([ID] ASC)
)
Still with only 2 non-clustered indexes, here's the new stats:
RowCount: 5260744
Data Space: 249.117 MB
Index Space: 470.867 MB
Question
Can someone account for how a clustered index using 3 NUMERIC(18,0) columns is smaller than a clustered index using a single INT column?
I rebuilt the indexes before and after the changes, and the fill factor is set to 0 for both structures.
The two non-clustered indexes are the same, and were not changed to include the new ID column.
sys.dm_db_index_physical_stats
Stats taken with the ID column
Composite clustered index
INDEX TYPE DEPTH LEVEL PAGECOUNT RECORDCOUNT RECORDSIZE
1 CLUSTERED 3 0 31884 5260744 47
1 CLUSTERED 3 1 143 31884 34
1 CLUSTERED 3 2 1 143 34
5 NONCLUSTERED 3 0 27404 5260744 40
5 NONCLUSTERED 3 1 167 27404 46
5 NONCLUSTERED 3 2 1 167 46
6 NONCLUSTERED 3 0 27400 5260744 40
6 NONCLUSTERED 3 1 164 27400 46
6 NONCLUSTERED 3 2 1 164 46
INT clustered index
INDEX TYPE DEPTH LEVEL PAGECOUNT RECORDCOUNT RECORDSIZE
1 CLUSTERED 3 0 31887 5260744 47
1 CLUSTERED 3 1 54 31887 11
1 CLUSTERED 3 2 1 54 11
5 NONCLUSTERED 4 0 29893 5260744 44
5 NONCLUSTERED 4 1 198 29893 50
5 NONCLUSTERED 4 2 3 198 50
5 NONCLUSTERED 4 3 1 3 50
6 NONCLUSTERED 4 0 29891 5260744 44
6 NONCLUSTERED 4 1 193 29891 50
6 NONCLUSTERED 4 2 2 193 50
6 NONCLUSTERED 4 3 1 2 50
The clustered index leaf pages include all the columns of the table (not just the key columns). By adding a surrogate primary key you have just increased the length of all rows in the leaf pages by 4 bytes. Multiply that out by 5,260,744 rows and that equals an additional 20 MB to store the ID column.
The key is narrower however so you may well have fewer non leaf level pages (use sys.dm_db_index_physical_stats to see this) and as the clustered index key is used as the row locator in the non clustered indexes this can make those smaller (but less covering) too.

Identify data inconsistency in a Tuple-like table

I have a table that holds availability status for workers. Here is the structure:
CREATE TABLE [dbo].[Availability]
(
[OID] BIGINT IDENTITY (1, 1) NOT NULL,
[LocumID] BIGINT NOT NULL,
[AvailableDate] SMALLDATETIME NOT NULL,
[AvailabilityStatusID] INT NOT NULL,
[LastModifiedAt] TIMESTAMP NOT NULL,
CONSTRAINT [PK_Availability] PRIMARY KEY CLUSTERED ([OID] ASC) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY];
And here is a result:
OID LocumID AvailableDate AvailabilityStatusID LastModifiedAt
-------------------- -------------------- ----------------------- -------------------- ------------------
1 1 2009-03-02 00:00:00 1 0x0000000000201A8C
2 2 2009-03-04 00:00:00 1 0x0000000000201A8D
3 1 2009-03-05 00:00:00 1 0x0000000000201A8E
4 1 2009-03-06 00:00:00 1 0x0000000000201A8F
5 2 2009-03-07 00:00:00 1 0x0000000000201A90
6 7 2009-03-09 00:00:00 1 0x0000000000201A91
7 1 2009-03-11 00:00:00 1 0x0000000000201A92
8 1 2009-03-12 00:00:00 2 0x0000000000201A93
9 1 2009-03-14 00:00:00 1 0x0000000000201A94
10 1 2009-03-16 00:00:00 1 0x0000000000201A95
Now, the table has over 3mil record and I noticed that there are inconsistencies in my data. I need to somehow find rows where for any [AvailableDate], the [LocumID] (regardless of how many,) must be unique. So, basically, a worker can have one of these [AvailabilityStatusID] = 1, 2, 3, or 4 on one date. However, in this table, there are errors where a worker is entered twice or more against a [AvailableDate] with same [AvailabilityStatusID] or different [AvailabilityStatusID]
How can I detect these records?
Regards.
WITH x AS
(
SELECT LocumID, dt = AvailableDate
FROM dbo.Availability
GROUP BY LocumID, AvailableDate
HAVING COUNT(*) > 1
)
SELECT a.OID, a.LocumID, a.AvailableDate,
a.AvailabilityStatusID, a.LastModifiedAt
FROM x
INNER JOIN dbo.Availability AS a
ON x.LocumID = a.LocumID
AND x.dt = a.AvailableDate
ORDER BY a.LocumID, a.AvailableDate;
Once you clean this data up (not sure what your rule will be regarding which rows to keep), you should consider a unique constraint on (LocumID, AvailableDate). Here is how you would create the constraint (though you will not be able to create it until you have removed the duplicates):
ALTER TABLE dbo.Availability
ADD CONSTRAINT uq_l_ad
UNIQUE (LocumID, AvailableDate);
Of course now you will have new errors returned to your application (Msg 2627), since your current code clearly doesn't already check if a LocumID/AvailabilityDate combination already exists before adding a new one.

Considerations when dropping columns in large tables

I have a table of call data that has grown to 1.3 billion rows and 173 gigabytes of data There are two columns that we no longer use, one is char(15) and the other is varchar(24). They have both been getting inserted with NULL for some time, I've been putting off removing the columns because I am unsure of the implications. We have limited space on both the drive with the database and the drive with the transaction log.
In addition I found this post saying the space would not be available until a DBCC REINDEX was done. I see this as both good and bad. It's good because dropping the columns should be very fast and not involve a lot of logging, but bad because the space will not be reclaimed. Will newly inserted records take up less space though? That would be fine in my case as we prune the old data after 18 months so the space will gradually decrease.
If we did a DBCC REINDEX (or ALTER INDEX REBUILD) would that actually help since the columns are not part of any index? Would that take up log space or lock the table so it could not be used?
I found your question interesting, so decided to model it on a development database.
SQL Server 2008, database size 400 Mb, log 2.4 Gb.
I assume, from link provided you created a table with clustered index:
CREATE TABLE [dbo].[big_table](
[recordID] [int] IDENTITY(1,1) NOT NULL,
[col1] [varchar](50) NOT NULL,
[col2] [char](15) NULL,
[col3] [varchar](24) NULL,
CONSTRAINT [PK_big_table] PRIMARY KEY CLUSTERED
(
[recordID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
This table consist of 12 Million records.
sp_spaceused big_table, true
name-big_table, rows-12031303, reserved-399240 KB, data-397760 KB, index_size-1336 KB, unused-144 KB.
drop columns
sp_spaceused big_table, true
Table size stays the same. Database and log size remained the same.
add 3 million of rows to the rest of the table
name-big_table, rows-15031303, reserved-511816 KB, data-509904 KB, index_size-1752 KB, unused-160 KB.
database size 500 Mb, log 3.27 Gb.
After
DBCC DBREINDEX( big_table )
Log is the same size, but database size increased to 866 Mb
name-big_table, rows-12031303, reserved-338376 KB, data-337704 KB, index_size-568 KB, unused-104 KB.
Again add 3 million rows to see if they going into available space within database.
Database size is the same, log 3.96 Gb, which clearly shows they are.
Hope it makes sense.
No, newly inserted records would not take up less space. I was looking at this exact issue earlier today as it happens.
Test table
CREATE TABLE T
(
id int identity primary key,
FixedWidthColToBeDropped char(10),
VariableWidthColToBeDropped varchar(10),
FixedWidthColToBeWidened char(7),
FixedWidthColToBeShortened char(20),
VariableWidthColToBeWidened varchar(7),
VariableWidthColToBeShortened varchar(20),
VariableWidthColWontBeAltered varchar(20)
)
Offsets Query
WITH T
AS (SELECT ISNULL(LEFT(MAX(name), 30), 'Dropped') AS column_name,
MAX(column_id) AS column_id,
ISNULL(MAX(case
when column_id IS NOT NULL THEN max_inrow_length
END), MAX(max_inrow_length)) AS max_inrow_length,
leaf_offset,
CASE
WHEN leaf_offset < 0 THEN SUM(CASE
WHEN column_id IS NULL THEN 2 ELSE 0
END)
ELSE MAX(max_inrow_length) - MAX(CASE
WHEN column_id IS NULL THEN 0
ELSE max_inrow_length
END)
END AS wasted_space
FROM sys.system_internals_partition_columns pc
JOIN sys.partitions p
ON p.partition_id = pc.partition_id
LEFT JOIN sys.columns c
ON column_id = partition_column_id
AND c.object_id = p.object_id
WHERE p.object_id = object_id('T')
GROUP BY leaf_offset)
SELECT CASE
WHEN GROUPING(column_name) = 0 THEN column_name
ELSE 'Total'
END AS column_name,
column_id,
max_inrow_length,
leaf_offset,
SUM(wasted_space) AS wasted_space
FROM T
GROUP BY ROLLUP ((column_name,
column_id,
max_inrow_length,
leaf_offset))
ORDER BY GROUPING(column_name),
CASE
WHEN leaf_offset > 0 THEN leaf_offset
ELSE 10000 - leaf_offset
END
Initial State of the Table
column_name column_id max_inrow_length leaf_offset wasted_space
------------------------------ ----------- ---------------- ----------- ------------
id 1 4 4 0
FixedWidthColToBeDropped 2 10 8 0
FixedWidthColToBeWidened 4 7 18 0
FixedWidthColToBeShortened 5 20 25 0
VariableWidthColToBeDropped 3 10 -1 0
VariableWidthColToBeWidened 6 7 -2 0
VariableWidthColToBeShortened 7 20 -3 0
VariableWidthColWontBeAltered 8 20 -4 0
Total NULL NULL NULL 0
Now make some changes
ALTER TABLE T
ALTER COLUMN FixedWidthColToBeWidened char(12)
ALTER TABLE T
ALTER COLUMN FixedWidthColToBeShortened char(10)
ALTER TABLE T
ALTER COLUMN VariableWidthColToBeWidened varchar(12)
ALTER TABLE T
ALTER COLUMN VariableWidthColToBeShortened varchar(10)
ALTER TABLE T
DROP COLUMN FixedWidthColToBeDropped, VariableWidthColToBeDropped
Look at the table again
column_name column_id max_inrow_length leaf_offset wasted_space
------------------------------ ----------- ---------------- ----------- ------------
id 1 4 4 0
Dropped NULL 10 8 10
Dropped NULL 7 18 7
FixedWidthColToBeShortened 5 10 25 10
FixedWidthColToBeWidened 4 12 45 0
Dropped NULL 10 -1 2
VariableWidthColToBeWidened 6 12 -2 0
Dropped NULL 20 -3 2
VariableWidthColWontBeAltered 8 20 -4 0
VariableWidthColToBeShortened 7 10 -5 0
Total NULL NULL NULL 31
Insert a row and look at the page
INSERT INTO T
([FixedWidthColToBeWidened]
,[FixedWidthColToBeShortened]
,[VariableWidthColToBeWidened]
,[VariableWidthColToBeShortened])
VALUES
('1','2','3','4')
DECLARE #DBCCPAGE nvarchar(100)
SELECT TOP 1 #DBCCPAGE = 'DBCC PAGE(''tempdb'',' + CAST(file_id AS VARCHAR) + ',' + CAST(page_id AS VARCHAR) + ',3)'
FROM T
CROSS APPLY sys.fn_PhysLocCracker(%%physloc%%)
DBCC TRACEON(3604)
EXEC (#DBCCPAGE)
Returns
Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS
Record Size = 75
Memory Dump #0x000000000D5CA060
0000000000000000: 30003900 01000000 26a44500 00000000 †0.9.....&¤E.....
0000000000000010: ffffffff ffffff7f 00322020 20202020 †ÿÿÿÿÿÿÿ..2
0000000000000020: 20202003 00000000 98935c0d 00312020 † ......\..1
0000000000000030: 20202020 20202020 200a0080 00050049 † ......I
0000000000000040: 004a004a 004a004b 003334†††††††††††††.J.J.J.K.34
Slot 0 Column 1 Offset 0x4 Length 4 Length (physical) 4
id = 1
Slot 0 Column 67108868 Offset 0x8 Length 0 Length (physical) 10
DROPPED = NULL
Slot 0 Column 67108869 Offset 0x0 Length 0 Length (physical) 0
DROPPED = NULL
Slot 0 Column 67108865 Offset 0x12 Length 0 Length (physical) 7
DROPPED = NULL
Slot 0 Column 67108866 Offset 0x19 Length 0 Length (physical) 20
DROPPED = NULL
Slot 0 Column 6 Offset 0x49 Length 1 Length (physical) 1
VariableWidthColToBeWidened = 3
Slot 0 Column 67108867 Offset 0x0 Length 0 Length (physical) 0
DROPPED = NULL
Slot 0 Column 8 Offset 0x0 Length 0 Length (physical) 0
VariableWidthColWontBeAltered = [NULL]
Slot 0 Column 4 Offset 0x2d Length 12 Length (physical) 12
FixedWidthColToBeWidened = 1
Slot 0 Column 5 Offset 0x19 Length 10 Length (physical) 10
FixedWidthColToBeShortened = 2
Slot 0 Column 7 Offset 0x4a Length 1 Length (physical) 1
VariableWidthColToBeShortened = 4
Slot 0 Offset 0x0 Length 0 Length (physical) 0
KeyHashValue = (010086470766)
You can see the dropped (and altered) columns are still consuming space even though the table was actually empty when the schema was changed.
The impact of the dropped columns in your case will be 15 bytes wasted for the char one and 2 bytes for the varchar one unless it is the last column in the variable section when it will take up no space.

Resources