How does one Remove a Partition from a Table? - sql-server

I have managed to add a Partition to a Table (Logs) but needed to create a Rollback script in case it needs to be removed. Unfortunately, this has now failed and Logs now has no primary key as a result of failing part-way through the rollback script and I have no way to add it back as I get the error...
Column 'SuperLogId' is partitioning column of the index 'PK__Logs__0E6B88F2'. Partition columns for a unique index must be a subset of the index key.
when trying to run this:
ALTER TABLE dbo.Logs
ADD PRIMARY KEY CLUSTERED (Id ASC)
So I tried following this guide (https://www.patrickkeisler.com/2013/01/how-to-remove-undo-table-partitioning.html) and ended up having to write this to generate a script to merge all my dynamically-created partitions.
DECLARE #partitionsTable dbo.NVarCharCollectionTableType --User-defined table type to hold a collection of NVarChars.
INSERT INTO #partitionsTable
SELECT CONCAT('ALTER PARTITION FUNCTION Logs_SuperLogId_PartitionFunction() MERGE RANGE (', CONVERT(NVARCHAR, [Value]), ')')
FROM SYS.PARTITION_SCHEMES
INNER JOIN SYS.PARTITION_FUNCTIONS ON PARTITION_FUNCTIONS.FUNCTION_ID = PARTITION_SCHEMES.FUNCTION_ID
INNER JOIN SYS.PARTITION_RANGE_VALUES ON PARTITION_RANGE_VALUES.FUNCTION_ID = PARTITION_FUNCTIONS.FUNCTION_ID
WHERE PARTITION_SCHEMES.Name = 'Logs_SuperLogId_PartitionScheme'
AND PARTITION_FUNCTIONS.Name = 'Logs_SuperLogId_PartitionFunction'
ORDER BY [Value] ASC
DECLARE #statement NVARCHAR(MAX)
SELECT #statement =
CASE
WHEN #statement IS NULL
THEN CAST([Text] AS NVARCHAR(MAX))
ELSE CONCAT(#statement, '; ', [Text])
END
FROM #partitionsTable
ORDER BY [Text] ASC
SELECT #statement
EXECUTE SP_EXECUTESQL #statement
ALTER PARTITION SCHEME Logs_SuperLogId_PartitionScheme NEXT USED [PRIMARY]
The guide suggested this would help somehow but it didn't! I still get the same error when trying to re-add the Primary Key and still get these errors for trying to drop the Partition Function and Partition Scheme!
DROP PARTITION SCHEME Logs_SuperLogId_PartitionScheme
The partition scheme "Logs_SuperLogId_PartitionScheme" is currently being used to partition one or more tables.
DROP PARTITION FUNCTION CatLogs_CatSessionLogId_PartitionFunction
Partition function 'Logs_SuperLogId_PartitionFunction' is being used by one or more partition schemes.
How is my Partition Scheme still being used? Why can't I just get rid of it and it be not used anymore? I just want to de-partition my Logs table and re-add its original clustered primary key (which I had to previously remove and replace with a non-clustered primary key to make SuperLogId have a clustered index on it so it could be partitioned upon).
Update:
I was able to use the following hack to get the Partition removed from my table but I still can't drop the Partition Scheme or Function.
--HACK: Dummy Index to disassociate the table from the partitioning scheme.
CREATE CLUSTERED INDEX IX_Logs_Id ON dbo.Logs(Id) ON [Primary]
--Now that the table has been disassociated with the partition, this dummy index can be dropped.
DROP INDEX IX_Logs_Id ON dbo.Logs
I have since ran this script to find out which tables are using any Partitions in my database and it returns nothing, as expected.
SELECT DISTINCT TABLES.NAME
FROM SYS.PARTITIONS
INNER JOIN SYS.TABLES ON PARTITIONS.OBJECT_ID = TABLES.OBJECT_ID
WHERE PARTITIONS.PARTITION_NUMBER <> 1
This allowed me to re-add the Primary key but I still get the The partition scheme "Logs_SuperLogId_PartitionScheme" is currently being used... error when trying to drop the Partition Scheme.
Based on the Microsoft documentation (https://learn.microsoft.com/en-us/sql/t-sql/statements/drop-partition-scheme-transact-sql?view=sql-server-2017), the Partition Scheme should be droppable if there are no tables or indices references it. Therefore I subsequently also ran this script to check for an index using it...
SELECT DISTINCT indexes.NAME
FROM SYS.PARTITIONS
INNER JOIN SYS.indexes ON indexes.index_id = partitions.index_id
WHERE PARTITIONS.PARTITION_NUMBER <> 1
...And it returned nothing! So what on earth is using my Partition Scheme?!

I was able to remove the Partition from its table with the following code.
--HACK: Dummy Index to disassociate the table from the partitioning scheme.
CREATE CLUSTERED INDEX IX_Logs_Id ON dbo.Logs(Id) ON [Primary]
--Now that the table has been disassociated with the partition, this dummy index can be dropped.
DROP INDEX IX_Logs_Id ON dbo.Logs
Then, using the following script, found out that two indices were still holding onto the Partition Scheme.
SELECT SCHEMA_NAME(B.SCHEMA_ID) SCHEMANAME, B.NAME TABLENAME, C.INDEX_ID, C.NAME INDEXNAME, C.TYPE_DESC,
A.PARTITION_NUMBER, D.NAME DATASPACENAME, F.NAME SCHEMADATASPACENAME,
H.VALUE DATARANGEVALUE, A.ROWS,
J.IN_ROW_RESERVED_PAGE_COUNT, J.LOB_RESERVED_PAGE_COUNT,
J.IN_ROW_RESERVED_PAGE_COUNT+J.LOB_RESERVED_PAGE_COUNT TOTALPAGECOUNT,
I.LOCATION
FROM SYS.PARTITIONS A
JOIN SYS.TABLES B ON A.OBJECT_ID = B.OBJECT_ID
JOIN SYS.INDEXES C ON A.OBJECT_ID = C.OBJECT_ID AND A.INDEX_ID = C.INDEX_ID
JOIN SYS.DATA_SPACES D ON C.DATA_SPACE_ID = D.DATA_SPACE_ID
LEFT JOIN SYS.DESTINATION_DATA_SPACES E ON E.PARTITION_SCHEME_ID = D.DATA_SPACE_ID AND A.PARTITION_NUMBER = E.DESTINATION_ID
LEFT JOIN SYS.DATA_SPACES F ON E.DATA_SPACE_ID = F.DATA_SPACE_ID
LEFT JOIN SYS.PARTITION_SCHEMES G ON D.NAME = G.NAME
LEFT JOIN SYS.PARTITION_RANGE_VALUES H ON G.FUNCTION_ID = H.FUNCTION_ID AND H.BOUNDARY_ID = A.PARTITION_NUMBER
LEFT JOIN (SELECT DISTINCT DATA_SPACE_ID, LEFT(PHYSICAL_NAME, 1) LOCATION FROM SYS.DATABASE_FILES) I ON I.DATA_SPACE_ID = ISNULL(F.DATA_SPACE_ID, D.DATA_SPACE_ID)
LEFT JOIN SYS.DM_DB_PARTITION_STATS J ON J.OBJECT_ID = A.OBJECT_ID AND J.INDEX_ID = A.INDEX_ID AND J.PARTITION_NUMBER = A.PARTITION_NUMBER
ORDER BY 1, 2, 3, A.PARTITION_NUMBER
All I had to do was drop the two indices referencing the Partition Scheme then that allowed me to drop the Partition Scheme, then Partition Function.

Taking the SSMS UI route (rather than figuring out all the DDL script), R-click the partitioned table in the Object Explorer, Design, R-click design area, Indexes, select each partitioned index, expand Data Space Specification, select Data Space Type dropdown and select "Filegroup." Your index will be off the partition and back on PRIMARY.
However, you're not done. Hit F4 to bring up table properties on the right, and do the same process. Remember to Save when you're done. Freedom!

Related

How to identify all the tables that have (or don't have) a unique index?

What is the most efficient way in SQL Server to identify all tables that have a unique index?
This has been addressed elsewhere for Oracle: https://stackoverflow.com/a/28740458/3112914
My end goal is to identify tables in a database that can not be compared using SSDT Data Compare tool. For that tool to work "Tables must have the same primary key, unique index, or unique constraint." I can identify those with primary key or unique constraint using OBJECTPROPERTY, e.g.
SELECT
SCHEMA_NAME(schema_id) AS SchemaName,
name AS TableName,
OBJECTPROPERTY(OBJECT_ID,'TableHasPrimaryKey') AS HasPrimaryKey,
OBJECTPROPERTY(OBJECT_ID,'TableHasUniqueCnst') AS HasUniqueConstraint
FROM
sys.tables
There is an IsIndexed property but that doesn't say that is a unique index. https://learn.microsoft.com/en-us/sql/t-sql/functions/objectproperty-transact-sql?view=sql-server-2017
If you want to list all the tables that have a unique index you can join to sys.indexes and filter using column is_unique.
e.g.
-- list of tables that have a unique index
SELECT SCHEMA_NAME(schema_id) AS SchemaName,
name AS TableName
FROM
sys.tables
WHERE EXISTS (SELECT *
FROM sys.indexes i
WHERE i.object_id = tables.object_id AND is_unique = 1)
If you want to extend the show every table, along with a value indicating whether or not it has a unique index (ie extend the start you had above), you can wrap that EXISTS statement in an IIF or CASE, like this :
SELECT SCHEMA_NAME(schema_id) AS SchemaName,
name AS TableName,
OBJECTPROPERTY(OBJECT_ID, 'TableHasPrimaryKey') AS HasPrimaryKey,
OBJECTPROPERTY(OBJECT_ID, 'TableHasUniqueCnst') AS HasUniqueConstraint,
CASE
WHEN EXISTS (
SELECT *
FROM sys.indexes i
WHERE i.object_id = tables.object_id
AND is_unique = 1
)
THEN 1
ELSE 0
END AS HasUniqueIndex
FROM sys.tables

Cannot delete primary key constraint in SQL Server

When I open designer for a table in SSMS, first column Id has a primary key assigned to it (IDENTITY), and key icon is shown next to it. In the Indexes / Keys window I can see a PK_dbo.Lines entry with Name [PK_dbo.Lines] and type Primary Key.
ALTER TABLE dbo.Lines DROP CONSTRAINT [PK_dbo.Lines]
returns
Msg 3728, Level 16, State 1, Line 33
'PK_dbo.Lines' is not a constraint.
If I execute
SELECT *
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
I can see primary keys for all tables, except for table Lines...
This query also does not return any results:
select object_name (parent_obj) ObjectName, name
from sysobjects
where xtype = 'PK'
and parent_obj = (object_id('[dbo].[Lines]'))
Any idea what is going on here?
If you make any changes to tables / columns in a database SSMS needs to be manually refreshed in order to correctly reflect these changes. Could it be that it is as simple as right-clicking on Tables and select refresh?
Alternatively, you might have spelled the PK name incorrectly? Please try the following script to remove the PK from the table 'Lines':
declare #PK_Name nvarchar(200);
declare #strSQL nvarchar(max);
select #PK_Name = (
select i.name as IndexName
from sys.indexes as i
inner join sys.index_columns as ic
on i.object_id = ic.object_id
and i.index_id = ic.index_id
inner join sys.tables as t
on t.object_id = i.object_id
where i.is_primary_key = 1
and t.name = N'Lines'
)
set #strSQL = N'ALTER TABLE dbo.Lines DROP CONSTRAINT ' + #PK_Name
execute(#strSQL)
First the script retrieves the correct name for the PK and then it tries to drop the PK constraint.

Dynamic if exists

I want to check the condition if the index present in the table then fire stored procedure else other condition must be proceed.
Example:
IF EXISTS (SELECT name FROM sys.indexes WHERE object_id = OBJECT_ID(#TableName)
BEGIN
execute spTest1
END
ELSE
BEGIN
execute spTest2
END
Note: In the above script the #TableName will be pass dynamically in the format of within single quote for example 'tableName'.
There's nothing wrong with the SQL, apart from a missing close bracket on the end of the EXISTS line:
IF EXISTS (SELECT name FROM sys.indexes WHERE object_id = OBJECT_ID(#TableName))
BEGIN
execute spTest1
END
ELSE
BEGIN
execute spTest2
END
This will work, but you might always find an index without filtering the data in some way. You might want to be more specific about the type of indexes you are searching for as SQL will create some default indexes. For example if you create a table with a primary key, a clustered index will be created for you.
If no primary key is added you will usually have a HEAP type index, so this might be the type of index you are looking to filter.
If you run this against your target database, you will see the indexes that exist:
select st.name, st.object_id, si.name, si. index_id, si.type, si.type_desc
from sys.tables st
inner join sys.indexes si on si.object_id = st.object_id
In order to filter HEAP indexes, you could exclude clustered/non-clustered indexes you could filter by sys.indexes.index_id:
Heap: index_id = 0
Clustered: index_id = 1
Non Clustered: index_id = 2

Find all foreign key of a table that prevent insert into it in SQL Server

I have a database with about 200 tables with many foreign keys constraints. There is no data in the database and I need to insert rows into a specified table X.
Because of the many foreign keys I don't know the order of how to insert in other tables so that I can insert into table X. Some of foreign key constraint are hierarchical.
How can I find out the order of inserting so that I can successfully insert data into table X? Is there any SQL query that help me?
Edited
I want result in a table with column "TableName" ,"ParentTableDependece" that show tree view and can get select from it
Using this snippet, you can find all other tables and the foreign keys involved that reference your own table X:
;WITH ReferencingFK AS
(
SELECT
fk.Name AS 'FKName',
OBJECT_NAME(fk.parent_object_id) 'ParentTable',
cpa.name 'ParentColumnName',
OBJECT_NAME(fk.referenced_object_id) 'ReferencedTable',
cref.name 'ReferencedColumnName'
FROM
sys.foreign_keys fk
INNER JOIN
sys.foreign_key_columns fkc ON fkc.constraint_object_id = fk.object_id
INNER JOIN
sys.columns cpa ON fkc.parent_object_id = cpa.object_id AND fkc.parent_column_id = cpa.column_id
INNER JOIN
sys.columns cref ON fkc.referenced_object_id = cref.object_id AND fkc.referenced_column_id = cref.column_id
)
SELECT
FKName,
ParentTable,
ParentColumnName,
ReferencedTable,
ReferencedColumnName
FROM
ReferencingFK
WHERE
ReferencedTable = 'X' -- <=== put your table name here!
ORDER BY
ParentTable, ReferencedTable, FKName
Once you have all the tables that reference X, you might also need to repeat this for other tables (if they in turn depend on foreign key references)
Why not this?
EXEC sp_msdependencies #objname = 'X'
Raj
You can try:
EXEC sp_help 'TableName'
This will give you the whole details of the table i.e you can get the relations, indexes and other information related with the object you pass with it.

SQL Server Scripting Partitioning

Had a good look on the net and books online and couldn't find an answer to my question, so here goes.
Working on someone else's design, I have several tables all tied to the same partition schema and partition function. I wish to perform a split operation which would affect many hundreds of millions of rows.
To split is no problem:
ALTER PARTITION SCHEME [ps_Scheme] NEXT USED [FG1] ;
ALTER PARTITION FUNCTION [pfcn_Function]() SPLIT RANGE (20120331)
However, I'm concerned that this will affect many tables at once and is not desirable.
Therefore, I was going to create a new copy of the table and do the split on a new function
CREATE PARTITION FUNCTION [pfcn_Function1](INT)
AS RANGE RIGHT
FOR VALUES
(
20090101, 20090130, 20090131, 20090201...etc
)
CREATE PARTITION SCHEME [ps_Scheme1]
AS PARTITION [pfcn_Function1] TO
([FG1], [FG2] etc
CREATE TABLE [dbo].[myTableCopy]
(
....
) ON ps_Scheme1
Then I would switch the partition I wish to split across:
-- The partition numbers did not align because they are based on 2 different functions.
ALTER TABLE [Table] SWITCH PARTITION 173 TO [TableCopy] PARTITION 172
Finally my question is can this be automated? You can make a copy of the table easily in SQL using SELECT INTO, but I cannot see how to automate the partitioning of the table i.e. the bit on the end of the CREATE TABLE statement that points to the partition scheme.
Thanks for any responses.
Found this on books online:
You can turn an existing nonpartitioned table into a partitioned table in one of two ways.
One way is to create a partitioned clustered index on the table by using the CREATE INDEX statement.
This action is similar to creating a clustered index on any table, because SQL Server essentially
drops the table and re-creates it in a clustered index format. If the table already has a
partitioned clustered index applied to it, you can drop the index and rebuilding it on a partition
scheme by using CREATE INDEX with the DROP EXISTING = ON clause
I think this might solve my problem.
It can be automated, but I'm not sure is worth it. If is only 'several' tables, not hundreds, then is better to just script out each table and then build a script that does the copy out/split the copy/switch out/split the source/switch in.
Automating this would involve dynamically building the temp table definition(s), including all indexes, from sys.tables/sys.columns/sys.indexes/sys.index_columns and other similar views. Same way SMO Scripting does it.
Yes, you can switch partitions in a automated process. Here is a code sample you can customise. It is driven from a metadata table.
CREATE TABLE [dbo].[PartitionTableSetup](
[Id] [int] IDENTITY(1,1) NOT NULL,
[TableName] [varchar](256) NULL,
[SwitchTable] [varchar](256) NULL,
[Partition] [int] NULL)
select #merge = (
Select N'' + com + '' from (
Select N' ALTER TABLE '
+ TableName +
' SWITCH PARTITION 2 TO '
+ SwitchTable
+ ' PARTITION 2 Truncate table '
+ SwitchTable as com
,value
,1 as ord
From (
SELECT convert(datetime,value) as value
,pt.TableName
,pt.SwitchTable
FROM sys.partition_range_values AS RV
JOIN sys.partition_functions AS PF
ON RV.function_id = PF.function_id
Join dbo.[Partitions] pr
On name = PartitionFunction
Join dbo.PartitionTableSetup pt
On pt.[Partition] = pr.ID
WHERE datediff(d,convert(datetime,value),GETDATE()) > pr.[Range] -3
) a
Union all
Select N' ALTER PARTITION FUNCTION '
+ b.PartitionFunction
+ '() MERGE RANGE ('''
+ Convert(nvarchar,value,121)
+''')' as com
,value
,2 as ord
From (
SELECT convert(datetime,value) as value
,pr.PartitionFunction
FROM sys.partition_range_values AS RV
JOIN sys.partition_functions AS PF
ON RV.function_id = PF.function_id
Join dbo.[Partitions] pr
On name = PartitionFunction
WHERE datediff(d,convert(datetime,value),GETDATE()) > pr.[Range] -3
) b
) c Order by value
, ord
for xml path ('')
)
EXECUTE (#merge)

Resources