Dynamic if exists - sql-server

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

Related

How to use the extracted index type in the CREATE TABLE statement in Azure Synapse?

I'm creating new tables in Azure Synapse based on existing tables in an automated way and for that I need to extract various bits of information. I've figured out most of it except the INDEX type.
I'm even able to extract the INDEX type of an existing table by using the below script:
DECLARE #idx varchar(100);
SELECT #idx = (
(select i.type_desc
from sys.indexes i
inner join sys.tables t
on t.object_id = i.object_id
inner join sys.schemas s
on t.schema_id = s.schema_id
where
t.name = '<table_name>'
and s.name = 'dbo')
);
SELECT #idx;
This would output something as follows:
CLUSTERED COLUMNSTORE
Now, I want to prepare a CREATE TABLE (CTAS) statement where I need to use this index type that I'm extracting into #idx. I'm trying the below but it doesn't work.
CREATE TABLE dbo.sample_hash_table
WITH
(
#idx INDEX,
DISTRIBUTION = HASH(emp_id),
PARTITION
(
[load_date] RANGE LEFT FOR VALUES (N'2022-01-01T00:00:00.000', N'2023-01-01T00:00:00.000', N'2024-01-01T00:00:00.000', N'2025-01-01T00:00:00.000', N'2026-01-01T00:00:00.000')
)
)
AS
Select * from sample_table
My rest of the automation went very well, but this one little thing is causing trouble. I'm not sure how to resolve this. Can someone please help?

Is there a way to check if an object is a table or a view in SQL Server?

I have a view that I am changing to a table in SQL Server.
I am dropping the view and then the next part of my code I am establishing the table.
My code works the first time I run it (when the object is a view), but when I need to run it multiple times, I get this error:
Cannot use DROP VIEW with 'engineer.Well' because 'engineer.Well' is a table. Use DROP TABLE.
I've been looking online but cannot find a way to check if an object is a table or a view, and the subsequently drop the object.
Any advice would be greatly appreciated.
Right now it looks like this,
IF OBJECT_ID('engineer.well') IS NOT NULL
BEGIN
DROP TABLE [engineer].[Well]
PRINT '<<< DROPPED TABLE Vendor >>>'
END
I am playing around with a way to check if the object is a table and then drop it, or check if it is a view then drop it.
You can query the system views.
DECLARE #Type varchar(2)
SELECT #Type = type
FROM sys.objects o
JOIN sys.schemas s ON o.schema_id = s.schema_id
WHERE o.name = 'well'
AND s.name = 'engineer'
IF #Type = 'U'
BEGIN
DROP TABLE [engineer].[Well]
PRINT '<<< DROPPED TABLE Vendor >>>'
END
IF #Type = 'V'
BEGIN
DROP VIEW [engineer].[Well]
PRINT '<<< DROPPED VIEW Vendor >>>'
END
OBJECTPROPERTY(OBJECT_ID('name'), 'IsView')
https://learn.microsoft.com/en-us/sql/t-sql/functions/objectproperty-transact-sql?view=sql-server-2017
or
SELECT id, type FROM sysobjects where id=OBJECT_ID('objectName')
You can query sys.objects table:
select type_desc, * from sys.objects where object_id = object_id('[dbo].[DimDates]')

How does one Remove a Partition from a Table?

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!

SQL Server ambiguous query validation

I have just come across a curious SQL Server behaviour.
In my scenario I have a sort of dynamic database, so I need to check the existence of tables and columns before run queries involving them.
I can't explain why the query
IF 0 = 1 -- Check if NotExistingTable exists in my database
BEGIN
SELECT NotExistingColumn FROM NotExistingTable
END
GO
executes correctly, but the query
IF 0 = 1 -- Check if NotExistingColumn exists in my ExistingTable
BEGIN
SELECT NotExistingColumn FROM ExistingTable
END
GO
returns Invalid column name 'NotExistingColumn'.
In both cases the IF block is not executed and contains an invalid query (the first misses a table, the second a column).
Is there any reason why SQL engine checks for syntax erorrs just in one case?
Thanks in advance
Deffered name resolution:
Deferred name resolution can only be used when you reference nonexistent table objects. All other objects must exist at the time the stored procedure is created. For example, when you reference an existing table in a stored procedure you cannot list nonexistent columns for that table.
You can look through the system tables for the existence of a specific table / column name
SELECT t.name AS table_name,
SCHEMA_NAME(schema_id) AS schema_name,
c.name AS column_name
FROM sys.tables AS t
INNER JOIN sys.columns c ON t.OBJECT_ID = c.OBJECT_ID
WHERE c.name LIKE '%colname%'
AND t.name LIKE '%tablename%'
ORDER BY schema_name, table_name;
The query above will pull back all tables / columns with partial match of a columnname and tablename, just change the like % for exact match.

How do you check if a certain index exists in a table?

Something like this:
SELECT
*
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
WHERE CONSTRAINT_NAME ='FK_TreeNodesBinaryAssets_BinaryAssets'
and TABLE_NAME = 'TreeNodesBinaryAssets'
but for indexes.
You can do it using a straight forward select like this:
SELECT *
FROM sys.indexes
WHERE name='YourIndexName' AND object_id = OBJECT_ID('Schema.YourTableName')
For SQL 2008 and newer, a more concise method, coding-wise, to detect index existence is by using the INDEXPROPERTY built-in function:
INDEXPROPERTY ( object_ID , index_or_statistics_name , property )
The simplest usage is with the IndexID property:
If IndexProperty(Object_Id('MyTable'), 'MyIndex', 'IndexID') Is Null
If the index exists, the above will return its ID; if it doesn't, it will return NULL.
AdaTheDEV, I used your syntax and created the following and why.
Problem: Process runs once a quarter taking an hour due to missing index.
Correction: Alter query process or Procedure to check for index and create it if missing... Same code is placed at the end of the query and procedure to remove index since it is not needed but quarterly. Showing Only drop syntax here
-- drop the index
begin
IF EXISTS (SELECT * FROM sys.indexes WHERE name='Index_Name'
AND object_id = OBJECT_ID('[SchmaName].[TableName]'))
begin
DROP INDEX [Index_Name] ON [SchmaName].[TableName];
end
end
If the hidden purpose of your question is to DROP the index before making INSERT to a large table, then this is useful one-liner:
DROP INDEX IF EXISTS [IndexName] ON [dbo].[TableName]
This syntax is available since SQL Server 2016. Documentation for IF EXISTS:
https://blogs.msdn.microsoft.com/sqlserverstorageengine/2015/11/03/drop-if-exists-new-thing-in-sql-server-2016/
In case you deal with a primery key instead, then use this:
ALTER TABLE [TableName] DROP CONSTRAINT IF EXISTS [PK_name]
A slight deviation from the original question however may prove useful for future people landing here wanting to DROP and CREATE an index, i.e. in a deployment script.
You can bypass the exists check simply by adding the following to your create statement:
CREATE INDEX IX_IndexName
ON dbo.TableName
WITH (DROP_EXISTING = ON);
Read more here: CREATE INDEX (Transact-SQL) - DROP_EXISTING Clause
N.B. As mentioned in the comments, the index must already exist for this clause to work without throwing an error.
Wrote the below function that allows me to quickly check to see if an index exists; works just like OBJECT_ID.
CREATE FUNCTION INDEX_OBJECT_ID (
#tableName VARCHAR(128),
#indexName VARCHAR(128)
)
RETURNS INT
AS
BEGIN
DECLARE #objectId INT
SELECT #objectId = i.object_id
FROM sys.indexes i
WHERE i.object_id = OBJECT_ID(#tableName)
AND i.name = #indexName
RETURN #objectId
END
GO
EDIT: This just returns the OBJECT_ID of the table, but it will be NULL if the index doesn't exist. I suppose you could set this to return index_id, but that isn't super useful.
-- Delete index if exists
IF EXISTS(SELECT TOP 1 1 FROM sys.indexes indexes INNER JOIN sys.objects
objects ON indexes.object_id = objects.object_id WHERE indexes.name
='Your_Index_Name' AND objects.name = 'Your_Table_Name')
BEGIN
PRINT 'DROP INDEX [Your_Index_Name] ON [dbo].[Your_Table_Name]'
DROP INDEX [our_Index_Name] ON [dbo].[Your_Table_Name]
END
GO
EXEC sp_helpindex '[[[SCHEMA-NAME.TABLE-NAME]]]'
GO
To check Clustered Index exist on particular table or not:
SELECT * FROM SYS.indexes
WHERE index_id = 1 AND name IN (SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE TABLE_NAME = 'Table_Name')

Resources