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

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).

Related

Error when inserting record into a database Oracle SQLPLUS

I am new to this, so this can be silly, but..
I have 3 created tables:
create table faculty (id_faculty number(2) Primary Key, //id , 2 digits, primary key
faculty_name varchar2(30) constraint f_name Not Null, // name, up to 30 symbols, not null
dean varchar2(20), // dean, up to 20 symbols
telephone varchar2(8)); //number, up to 8 symbols
create table student (id_student number(3) Primary Key, //id, 3 digits, primary key
student_name varchar(20) constraint s_name Not Null, // name, up to 20 symbols, not null
student_surname varchar(20) constraint s_surname Not Null, // name, up to 20 symbols, not null
course_year number(1) constraint s_course_year Check(course_year>0 and course_year<=6), // number, 1 digit, in range 1-6
faculty_id number(2), // id, 2 digits
constraint s_fkey Foreign key (Faculty_ID) References faculty(ID_faculty)); // foreign key to table faculty to id_faculty
create table money (id_payout number(4) Primary Key,
student_id number(3),
constraint m_fkey Foreign key (student_ID) References student(ID_student),
payout_date date constraint p_date Not Null,
stipend number(5,2) Check(stipend<151),
compensation number(5,2));
Then I created view:
create or replace view stud_stip AS
Select f.faculty_name, student_surname, student_name, s.course_year,
m.stipend, m.payout_date
from
faculty f inner join student s on f.id_faculty=s.faculty_id
inner join money m on m.student_id=s.id_student
where
m.payout_date = ( select distinct max(payout_date)
from money
where money.student_id=money.student_id)
with check option;
This is how the view looks
Then I wanted to add information to a view:
insert into stud_stip values ('Partikas tehnologiju', 'Lapa', 'Jana', 2, 120, '03.12.1998');
This is the error I got:
ORA-01779: cannot modify a column which maps to a non key-preserved table
I have googled all over the internet and did not solve my error, please don't send me other topics about this question, because I probably did read it.
I will be very grateful for the answers. Thank you in advance.
The way to do it is to create an instead of trigger on a view which would - in turn - insert rows into appropriate tables.
As all ID columns are primary keys and you didn't explain how you populate them, I'll do it using a sequence, in the same instead of trigger.
SQL> create sequence seqs;
Sequence created.
SQL> create or replace trigger trg_stud_stip
2 instead of insert on stud_stip
3 for each row
4 begin
5 insert into faculty (id_faculty, faculty_name)
6 values (seqs.nextval, :new.faculty_name);
7
8 insert into student (id_student, student_name, student_surname, course_year)
9 values (seqs.nextval, :new.student_name, :new.student_surname, :new.course_year);
10
11 insert into money (id_payout, stipend, payout_date)
12 values (seqs.nextval, :new.stipend, :new.payout_date);
13 end;
14 /
Trigger created.
Testing (don't insert strings into DATE datatype columns!):
SQL> insert into stud_stip
2 (faculty_name, student_surname, student_name, course_year, stipend, payout_date)
3 values
4 ('Partikas tehnologiju', 'Lapa', 'Jana', 2, 120, date '1998-12-03');
1 row created.
SQL> select * from faculty;
ID_FACULTY FACULTY_NAME DEAN TELEPHON
---------- ------------------------------ -------------------- --------
1 Partikas tehnologiju
SQL> select * from student;
ID_STUDENT STUDENT_NAME STUDENT_SURNAME COURSE_YEAR FACULTY_ID
---------- -------------------- -------------------- ----------- ----------
2 Jana Lapa 2
SQL> select * from money;
ID_PAYOUT STUDENT_ID PAYOUT_DAT STIPEND COMPENSATION
---------- ---------- ---------- ---------- ------------
3 03.12.1998 120
SQL>
It works. Now that you know how, improve it if necessary.

ORA-02291: integrity constraint (INA.member#mem_id) violated - parent key not found

Error report - ORA-02291: integrity constraint (INA.member#mem_id) violated - parent key not found.
into INA.member
(mem_id,mem_insertaddress,address_type,effective_date,end_date,adress,city,zip_code,phone_number,last_name,first_name)
values
(19889218,191166765,'Z2','01-AUG-2013','07-MAY-2016','45 NEWYORK','ATLANTIC','NY',011101,2012922341,'BOB','GUY');
when try to run getting error message
ORA-02291: integrity constraint (INA.member#mem_id) violated - parent key not found.
really appreciate for your help.
Assume you have two more tables, namely socialClubs and clubMembership, and neccessary constraints( primary and foreign ) defined as below :
SQL> create table socialClubs( -- parent(look-up) table for "clubMembership" table.
2 id int primary key,
3 name varchar2(500)
4 );
Table created
SQL> create table clubMembership( -- parent table for "member" table.
2 id int primary key,
3 club_id int,
4 constraint fk_cmmb_sc_id
5 foreign key(club_id)
6 references socialClubs(id)
7 );
Table created
SQL> create table member(
2 mem_id int primary key,
3 mem_address varchar2(500),
4 address_type varchar2(10),
5 effective_date date,
6 end_date date,
7 adress varchar2(500),
8 city varchar2(50),
9 zip_code varchar2(10),
10 phone_number number(15),
11 last_name varchar2(50),
12 first_name varchar2(50),
13 constraint fk_cmmb_mem_id
14 foreign key(mem_id)
15 references clubMembership(id)
16 );
Table created
SQL> insert into socialClubs values(1,'Mathematics');
1 row inserted
SQL> insert into member
2 (mem_id,
3 mem_address,
4 address_type,
5 effective_date,
6 end_date,
7 adress,
8 city,
9 zip_code,
10 phone_number,
11 last_name,
12 first_name)
13 values
14 (19889218,
15 191166765,
16 'Z2',
17 date'2013-08-01',
18 date'2016-05-07',
19 '45 NEWYORK',
20 'ATLANTIC NY',
21 011101,
22 2012922341,
23 'BOB',
24 'GUY');
ORA-02291: integrity constraint (ASKIMEMUR.FK_CMMB_MEM_ID) violated - parent key not found
raises ORA-02291 error,
since there's no (parent) record found in clubMembership table for member_id 19889218 of member table which has an inline definition of constraint fk_cmmb_mem_id foreign key(mem_id) references clubMembership(id).
So insert the necessary member_id into clubMembership table and go on without problem :
SQL> insert into clubMembership values(19889218,1);
1 row inserted
SQL> insert into member
2 (mem_id,
3 mem_address,
4 address_type,
5 effective_date,
6 end_date,
7 adress,
8 city,
9 zip_code,
10 phone_number,
11 last_name,
12 first_name)
13 values
14 (19889218,
15 191166765,
16 'Z2',
17 date'2013-08-01',
18 date'2016-05-07',
19 '45 NEWYORK',
20 'ATLANTIC NY',
21 011101,
22 2012922341,
23 'BOB',
24 'GUY');
1 row inserted
SQL> commit;
Commit complete

How to compare two attributes from two different tables in a trigger?

So I have two tables:
CREATE TABLE SALE(
OID_PA NUMBER(*,0) PRIMARY KEY,
Amount INTEGER NOT NULL,
Delivery_DATE DATE NOT NULL,
OID_V NUMBER(*,0) NOT NULL,
CONSTRAINT "AMOUNTCHECK" CHECK(Amount >=0),
FOREIGN KEY (OID_V) REFERENCES TICKET(OID_V));
and
CREATE TABLE PRODUCT
( Code NUMBER(*,0) PRIMARY KEY,
Stock INTEGER NOT NULL,
Price NUMBER(4,2) NOT NULL,
Production_Cost NUMBER NOT NULL,
Model VARCHAR2(50) NOT NULL,
TipoMueble VARCHAR2(50) NOT NULL,
TipoMaterial VARCHAR2(50) NOT NULL,
OID_PA NUMBER(*,0) NOT NULL,
CONSTRAINT "TipoMueble" CHECK(TipoMueble IN
('Canteado','Complementos','Glaciar',
'GrupoDiseño','Tiradores','Vitrinas')),
CONSTRAINT "TipoMaterial" CHECK(TipoMaterial IN ('Aluminio','Lacados',
'Polilaminado','Madera','Cristal','Chapa','Granito','Formica',
'Aglomerado','Marmol','Elementos de ferreteria')),
CONSTRAINT "StockNegativo" CHECK(Stock >=0),
CONSTRAINT "Precio" CHECK(Price>=0),
CONSTRAINT "CosteProduccion" CHECK (Production_cost>=0),
FOREIGN KEY (OID_PA) REFERENCES PAQUETE(OID_PA));
And now I want to create a trigger, the moment I modify the attribute "Amount" in the table "SALE" then it should check the table "PRODUCT".
if "stock" (in PRODUCT) is less than Amount (in SALE) then
RAISE_APPLICATION_ERROR(-20003, 'Not enough stock');
But I don't know how to make the trigger check in both tables at the same time.
right now my code looks likethis right now, it also has some errors that I'm not sure how to fix.
CREATE OR REPLACE TRIGGER TR_ENOUGH_STOCK
AFTER INSERT OR UPDATE OF Amount ON SALE
BEGIN
SELECT a.Amount, b.Stock
FROM ( SALE a inner join PRODUCT b on (a.Code = b.Code))
IF b.Stock < a.Amount THEN
RAISE APPLICATION ERROR(-20003, 'Not enough stock');
END IF;
END;
/
And I'm getting the following errors:
Error(209,5): PL/SQL: SQL Statement ignored
Error(212,5): PL/SQL: ORA-00933: SQL command not properly ended
Error(214,9): PLS-00103: Encountered the symbol "IF" when expecting one of
the following: ; <an identifier> <a double-quoted delimited-identifier>
Here's an example of how you might do that.
Create tables & a trigger:
SQL> create table sale (oid_pa number, amount number);
Table created.
SQL> create table product (code number, stock number, oid_pa number);
Table created.
SQL>
SQL> create or replace trigger trg_biu_stock
2 before insert or update
3 on sale
4 for each row
5 declare
6 l_stock product.stock%type;
7 begin
8 select stock into l_stock
9 from product
10 where oid_pa = :new.oid_pa;
11
12 if :new.amount > l_stock then
13 raise_application_error(-20000, 'Not enough stock');
14 end if;
15 end;
16 /
Trigger created.
SQL>
Insert sample records into the PRODUCT table:
SQL> insert into product values (1, 100, 10);
1 row created.
SQL> insert into product values (2, 50, 20);
1 row created.
SQL> select * From product;
CODE STOCK OID_PA
---------- ---------- ----------
1 100 10
2 50 20
SQL>
Let's test it:
SQL> -- 90 is less OID_PA = 10 stock value (which is 100)
SQL> insert into sale values (10, 90);
1 row created.
SQL> -- let's try to update it to a value larger than stock value:
SQL> update sale set amount = 200 where oid_pa = 10;
update sale set amount = 200 where oid_pa = 10
*
ERROR at line 1:
ORA-20000: Not enough stock
ORA-06512: at "HR.TRG_BIU_STOCK", line 9
ORA-04088: error during execution of trigger 'HR.TRG_BIU_STOCK'
SQL> -- how about a value which doesn't exceed the stock value?
SQL> update sale set amount = 5 where oid_pa = 10;
1 row updated.
SQL> select * From sale;
OID_PA AMOUNT
---------- ----------
10 5
SQL>
As of your attempt: apart from trigger code having wrong syntax, it wouldn't work anyway because you can't reference a table trigger is based on because it is right now being changed, i.e. it is mutating. Oracle raises an error in such cases (see an example below). The correct way is to reference that table's columns using :new or :old, as I did (see an example above).
SQL> create or replace trigger trg_biu_stock
2 before insert or update
3 on sale
4 for each row
5 declare
6 l_stock product.stock%type;
7 begin
8 select p.stock into l_stock
9 from product p
10 join sale s
11 on s.oid_pa = p.oid_pa
12 where p.oid_pa = :new.oid_pa;
13
14 if :new.amount > l_stock then
15 raise_application_error(-20000, 'Not enough stock');
16 end if;
17 end;
18 /
Trigger created.
SQL> update sale set amount = 50 where oid_pa = 10;
update sale set amount = 50 where oid_pa = 10
*
ERROR at line 1:
ORA-04091: table HR.SALE is mutating, trigger/function may not see it
ORA-06512: at "HR.TRG_BIU_STOCK", line 4
ORA-04088: error during execution of trigger 'HR.TRG_BIU_STOCK'
SQL>

Migration DDL from Oracle to SQLServer

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

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