Alternative to replace Union and subqueries to create Indexed view - sql-server

When I go to run this query on SQL Server to create an indexed view, an error occurs to remove subqueries and Union
CREATE VIEW [dbo].[view_ToolGroup]
WITH SCHEMABINDING
AS
SELECT
toolGroup.ToolGroupId,toolGroupToTool.ToolId, toolGroupApp.AppId as TGAppId,
purposeToToolGroup.PurposeId as TGPurposeId, TGRole.RoleId as TGRoleId
FROM
[dbo].[toolGroup], [dbo].[purposeToTG], [dbo].[toolGroupToTool],
[dbo].[toolGroupToeApp] as toolGroupApp,
[dbo].[toolGroupToeAppToeRole] as toolGroupAppRole,
[dbo].[eRole] as TGRole
WHERE
toolGroup.ToolGroupId = purposeToToolGroup.ToolGroupId
and toolGroup.ToolGroupId = toolGroupToTool.ToolGroupId
and toolGroup.ToolGroupId = toolGroupApp.ToolGroupId
and toolGroupApp.toolGroupToeApplicationID=toolGroupAppRole.toolGroupToeApplicationID
and toolGroupAppRole.ToolgroupToeApplicationID in
(select ToolgroupToeApplicationID
from [dbo].[ToolgroupToeApplication])
and toolGroupAppRole.RoleId = TGRole.RoleId
UNION
SELECT
toolGroup.ToolGroupId, toolGroup.ToolGroupName,
null, null, null, null, null, null, null, null
FROM
[dbo].[toolGroup], [dbo].[toolGroupToeApplication]
WHERE
toolGroup.ToolGroupId = toolGroupToeApplication.ToolGroupId
and toolGroup.ToolGroupId not in
(select PurposeToToolGroup.ToolGroupId from [dbo].[PurposeToToolGroup])
and toolGroup.ToolGroupId in (select distinct ToolGroupId
from [dbo]. [toolGroupToeApplication] )'
GO
CREATE UNIQUE CLUSTERED INDEX IDX_view_ToolGroup
ON view_ToolGroup(ToolGroupId, ToolId, TGPurposeId, TGRoleId)
GO
Can anybody suggest an alternative solution to replace UNION and subqueries?

As per all the suggestion above, there is no direct way of doing this. However we can cheat. You could do the following
Break up the statements into two views
Add index to each view
Replace IN with INNER JOIN
User NOT IN while calling the views
As Damien suggested, it would be foolish of me to try and suggest or attempt to judge the logic at this time since we have no idea what the tables represent. I have however restructured the code as per the above which you could use as a template to structure your actual query.
Hope this helps
--Drop Index If Already Aresent
IF EXISTS (SELECT * FROM sys.views WHERE object_id = OBJECT_ID(N'[dbo].[vw_ToolGroup_One]'))
DROP VIEW dbo.vw_ToolGroup_One
GO
IF EXISTS (SELECT * FROM sys.views WHERE object_id = OBJECT_ID(N'[dbo].[vw_ToolGroup_Two]'))
DROP VIEW dbo.vw_ToolGroup_Two
GO
--Drop Dependant Tables
IF OBJECT_ID(N'toolGroup_tmp')>0
BEGIN
DROP TABLE toolGroup_tmp
END
CREATE TABLE toolGroup_tmp (ToolGroupId INT,ToolGroupName VARCHAR(100))
INSERT INTO toolGroup_tmp
SELECT 1,'ONE'
GO
IF OBJECT_ID(N'purposeToTG_tmp')>0
BEGIN
DROP TABLE purposeToTG_tmp
END
CREATE TABLE purposeToTG_tmp (ToolGroupId INT,PurposeId int)
INSERT INTO purposeToTG_tmp
SELECT 1,1
GO
IF OBJECT_ID(N'toolGroupToTool_tmp')>0
BEGIN
DROP TABLE toolGroupToTool_tmp
END
CREATE TABLE toolGroupToTool_tmp (ToolGroupId INT,ToolId INT)
INSERT INTO toolGroupToTool_tmp
SELECT 1,1
GO
IF OBJECT_ID(N'toolGroupToeApp_tmp')>0
BEGIN
DROP TABLE toolGroupToeApp_tmp
END
CREATE TABLE toolGroupToeApp_tmp (ToolGroupId INT,AppId INT,toolGroupToeApplicationID INT)
INSERT INTO toolGroupToeApp_tmp
SELECT 1,1,1
GO
IF OBJECT_ID(N'toolGroupToeAppToeRole_tmp')>0
BEGIN
DROP TABLE toolGroupToeAppToeRole_tmp
END
CREATE TABLE toolGroupToeAppToeRole_tmp (ToolGroupId INT,RoleId INT,toolGroupToeApplicationID INT)
INSERT INTO toolGroupToeAppToeRole_tmp
SELECT 1,1,1
GO
IF OBJECT_ID(N'ToolgroupToeApplication_tmp')>0
BEGIN
DROP TABLE ToolgroupToeApplication_tmp
END
CREATE TABLE ToolgroupToeApplication_tmp (ToolGroupId INT,ToolgroupToeApplicationID INT)
INSERT INTO ToolgroupToeApplication_tmp
SELECT 1,1
GO
IF OBJECT_ID(N'PurposeToToolGroup_tmp')>0
BEGIN
DROP TABLE PurposeToToolGroup_tmp
END
CREATE TABLE PurposeToToolGroup_tmp (ToolGroupId INT)
INSERT INTO PurposeToToolGroup_tmp
SELECT 1
GO
IF OBJECT_ID(N'eRole_tmp')>0
BEGIN
DROP TABLE eRole_tmp
END
CREATE TABLE eRole_tmp (RoleId INT)
INSERT INTO eRole_tmp
SELECT 1
GO
--Create Views
DECLARE #SQL NVARCHAR(MAX)
SET #SQL = '
CREATE VIEW dbo.vw_ToolGroup_One WITH SCHEMABINDING
AS
SELECT
tg.ToolGroupId,
tg.ToolGroupName,
tgtt.ToolId,
tga.AppId AS TGAppId,
pttg.PurposeId AS TGPurposeId,
tgr.RoleId AS TGRoleId
FROM dbo.toolGroup_tmp tg
INNER JOIN dbo.purposeToTG_tmp pttg
ON tg.ToolGroupId = pttg.ToolGroupId
INNER JOIN dbo.toolGroupToTool_tmp tgtt
ON tg.ToolGroupId = tgtt.ToolGroupId
INNER JOIN dbo.toolGroupToeApp_tmp tga
ON tg.ToolGroupId = tga.ToolGroupId
INNER JOIN dbo.toolGroupToeAppToeRole_tmp tgar
ON tga.toolGroupToeApplicationID = tgar.toolGroupToeApplicationID
INNER JOIN dbo.ToolgroupToeApplication_tmp tgta
ON tgta.ToolgroupToeApplicationID = tgar.ToolgroupToeApplicationID
INNER JOIN dbo.eRole_tmp tgr
ON tgar.RoleId = tgr.RoleId
'
EXEC SP_EXECUTESQL #SQL
SET #SQL = '
CREATE VIEW dbo.vw_ToolGroup_Two WITH SCHEMABINDING
AS
SELECT tg.ToolGroupId,
tg.ToolGroupName,
NULL AS ToolId,
NULL AS TGAppId,
NULL AS TGPurposeId,
NULL AS TGRoleId
FROM dbo.toolGroup_tmp tg
INNER JOIN dbo.ToolgroupToeApplication_tmp tgtea
ON tg.ToolGroupId = tgtea.ToolGroupId
'
EXEC SP_EXECUTESQL #SQL
-- Create Indexes
CREATE UNIQUE CLUSTERED INDEX IDX_view_ToolGroup_One
ON vw_ToolGroup_One(ToolGroupId, ToolGroupName, ToolId, TGPurposeId, TGRoleId);
CREATE UNIQUE CLUSTERED INDEX IDX_view_ToolGroup_Two
ON vw_ToolGroup_Two(ToolGroupId, ToolGroupName);
GO
-- Invoke Query
SELECT * FROM vw_ToolGroup_One
UNION ALL
SELECT * FROM vw_ToolGroup_Two tgt
WHERE NOT EXISTS ( SELECT 1
FROM dbo.PurposeToToolGroup_tmp pttg
WHERE pttg.ToolGroupId = tgt.ToolGroupId)

Related

How to insert column from another table when inserting it from userdefined datatable?

CREATE PROC [dbo].[usp_InsertBulkShipmentData]
(
#RetVal VARCHAR(1000) OUTPUT,
#ship dbo.ShipmentData READONLY
)
AS
DECLARE #ShipmentID BIGINT
Declare #Output table (id int)
BEGIN
INSERT INTO
dbo.Shipment (ShipmentType,scaccode,ShipmentControl,countryLoading,PortLoading,ShipperName,ShipperAddressOne,ShipperCity,ShipperCountry,ShipperProvince,
ShipperZip,ShipperPhone,ShipperEmail,ConsigneeName,ConsigneeAddressOne,ConsigneeCity,ConsigneeCountry,ConsigneeState,ConsigneeZip,
ConsigneePhone,ConsigneeEmail,ResponseCode,astrayExportDate,bondType,bondDestPort,bondOnwardscac,bondCarrierIRS,
bondNumber,bondTransferIRS,bondForeignPort,bondDepartureDate,MexiPediNumber)
OUTPUT Inserted.id into #Output
SELECT sh.DDcode,s.SCACCode,s.ShipmentControl,s.CountryLoading,s.ProvinceLoading,s.ShipperName,s.ShipperAddress,s.ShipperCity,s.ShipperCountry,s.ShipperProvince,
s.ShipperZip,s.ShipperPhone,s.ShipperEmail,s.ConsigneeName,s.ConsigneeAddressOne,s.ConsigneeCity,s.ConsigneeCountry,s.ConsigneeState,s.ConsigneeZip,
s.ConsigneePhone,s.ConsigneeEmail,s.CustomControl,s.DateShipmentLeft,s.InbondEntryType,s.InbondDestination,s.OnwardCarrier,s.BondCarrier,
s.Inbond,s.bondTransfer,s.ForeignPortDestination,s.EstimatedDeparture,s.MexiPediNumber
FROM #ship s INNER JOIN ShipmentTypeDropDown sh on sh.DisplayName=s.ShipmentType
select id from #Output
SET #RetVal = 'true|Record Inserted Successfully|'
END
BEGIN
SELECT #ShipmentID=id;
INSERT INTO dbo.Ship_commodity([Description],Quantity,manifestUnitCode,
[weight],weightUnitCode,countryCode,customsShipmentValue,commodityCode,MarksNumbers)
SELECT sp.CommoditiesDescription,sp.Quantity,qu.DDCode,sp.
[Weight],wu.DDCode,sp.CountryOrigin,sp.Value,sp.HarmonizedCode,sp.MarksNumbers
FROM #ship sp INNER JOIN WeightUnitDropDown wu on wu.DisplayName=sp.WeightUnitCode
INNER JOIN QuantityUnitDropDown qu on qu.DisplayName=sp.QuantityUnit
END
id is autogenerated in Shipment table. I wan to insert this id into Ship_commodity where ShipmentID=id.
I am inserting the records from user defined datatable, for every record , id is generated in Shipment table. When inserting into
Ship_commodity table i want to insert the Shipment id for every record , where ShipmentID=id.
How should i achieve this? please suggest.
In your first insert, you need to get the primary key of #ship to be able to join the output table to it (make sure to add extra columns to #output):
OUTPUT inserted.id, inserted.scaccode, inserted.ShipmentControl into #Output
Then you can join #output to #ship (i'm assuming Ship_commodity has a column fkShipmentId):
INSERT INTO dbo.Ship_commodity
(fkShipmentId, [Description], Quantity, manifestUnitCode, [weight], weightUnitCode,
countryCode, customsShipmentValue, commodityCode, MarksNumbers)
SELECT
i.id,
sp.CommoditiesDescription,
sp.Quantity,
qu.DDCode,
sp.[Weight],
wu.DDCode,
sp.CountryOrigin,
sp.Value,
sp.HarmonizedCode,
sp.MarksNumbers
FROM #ship sp
INNER JOIN WeightUnitDropDown wu on wu.DisplayName=sp.WeightUnitCode
INNER JOIN QuantityUnitDropDown qu on qu.DisplayName=sp.QuantityUnit
INNER JOIN #output i ON i.scaccode = sp.scaccode AND i.ShipmentControl = sp.ShipmentControl;

TSQL Force Cast all columns in a view definition

I have about 500 simple Sql Views that each reference 1 or 2 underlying tables.
The views are used to interface to another system.
How do I force cast all columns to reflect the current datatypes
I want to do this so that if underlying table columns change, the output of the Views remain unaffected.
SELECT
CAST (T1.ColumnA AS NUMERIC(38, 0)),
CAST (T1.ColumnB AS DATE),
CAST (T1.ColumnC AS VARCHAR(900)),
CAST (T2.ColumnA AS TIME(7)),
CAST (T2.ColumnB AS VARCHAR(4))
FROM
Table1 T1
LEFT JOIN
Table2 T2 ON T1.ID = T2.ID
All suggestions welcome.
You do not need to do anything. Even if the table columns types are changed, these in the view(s) are not. If you want to change the columns types in the view, you need to explicitly do this by dropping/recreating the view or using sp_refreshview. So, there is nothing to worry about.
Here is a code, demonstrating this:
DROP TABLE IF EXISTS [dbo].[StackOverflow];
CREATE TABLE [dbo].[StackOverflow]
(
[ColumnA] INT
,[ColumnB] NVARCHAR(25)
,[ColumnC] DATETIME2(0)
);
GO
DROP VIEW IF EXISTS [dbo].[vw_StackOverflow];
GO
CREATE VIEW [dbo].[vw_StackOverflow]
AS
SELECT [ColumnA]
,[ColumnB]
,[ColumnC]
FROM [dbo].[StackOverflow];
GO
SELECT C.[name]
,T.[name]
,C.[max_length]
,c.[precision]
,c.[scale]
FROM [sys].[columns] C
INNER JOIN [sys].[types] T
ON C.[system_type_id] = T.[system_type_id]
WHERE [object_id] = OBJECT_ID('[dbo].[vw_StackOverflow]');
GO
ALTER TABLE [dbo].[StackOverflow]
ALTER COLUMN [ColumnA] BIGINT;
ALTER TABLE [dbo].[StackOverflow]
ALTER COLUMN [ColumnB] NVARCHAR(12);
ALTER TABLE [dbo].[StackOverflow]
ALTER COLUMN [ColumnC] DATETIME2(7);
GO
SELECT C.[name]
,T.[name]
,C.[max_length]
,c.[precision]
,c.[scale]
FROM [sys].[columns] C
INNER JOIN [sys].[types] T
ON C.[system_type_id] = T.[system_type_id]
WHERE [object_id] = OBJECT_ID('[dbo].[vw_StackOverflow]');
GO
EXEC sp_refreshview N'[dbo].[vw_StackOverflow]';
GO
SELECT C.[name]
,T.[name]
,C.[max_length]
,c.[precision]
,c.[scale]
FROM [sys].[columns] C
INNER JOIN [sys].[types] T
ON C.[system_type_id] = T.[system_type_id]
WHERE [object_id] = OBJECT_ID('[dbo].[vw_StackOverflow]');
GO

SQL Server trigger on Insert and Update

I am looking to create a SQL Server trigger that moves a record from one table to an identical replica table if the record matches a specific condition.
Questions: do I need to specify each column, or can I use a wildcard?
Can I use something like:
SET #RecID = (SELECT [RecoID] FROM Inserted)
IF NULLIF(#RecID, '') IS NOT NULL
(then insert....)
THANKS!
There's a lot of stuff you "CAN" do in a trigger, but that doesn't mean you should. I'd would urge to to avoid setting scalar variables within a trigger at all costs. Even if you 100% sure your table will never have more that 1 row inserted per transaction because that's how the app is designed... You'll be in for very rude awakening when you find out that not all transactions come through the application.
Below is a quick demonstration of both types of triggers...
USE tempdb;
GO
IF OBJECT_ID('tempdb.dbo.PrimaryTable', 'U') IS NOT NULL
DROP TABLE dbo.PrimaryTable;
GO
IF OBJECT_ID('tempdb.dbo.TriggerScalarLog', 'U') IS NOT NULL
DROP TABLE dbo.TriggerScalarLog;
GO
IF OBJECT_ID('tempdb.dbo.TriggerMultiRowLog', 'U') IS NOT NULL
DROP TABLE dbo.TriggerMultiRowLog;
GO
CREATE TABLE dbo.PrimaryTable (
Pt_ID INT NOT NULL IDENTITY (1,1) PRIMARY KEY CLUSTERED,
Col_1 INT NULL,
Col_2 DATE NOT NULL
CONSTRAINT df_Col2 DEFAULT (GETDATE())
);
GO
CREATE TABLE dbo.TriggerScalarLog (
Pt_ID INT,
Col1_Old INT,
Col1_New INT,
Col2_Old DATE,
Col2_New DATE
);
GO
CREATE TABLE dbo.TriggerMultiRowLog (
Pt_ID INT,
Col1_Old INT,
Col1_New INT,
Col2_Old DATE,
Col2_New DATE
);
GO
--=======================================================
CREATE TRIGGER dbo.PrimaryCrudScalar ON dbo.PrimaryTable
AFTER INSERT, UPDATE, DELETE
AS
SET NOCOUNT ON;
DECLARE
#Pt_ID INT,
#Col1_Old INT,
#Col1_New INT,
#Col2_Old DATE,
#Col2_New DATE;
SELECT
#Pt_ID = ISNULL(i.Pt_ID, d.Pt_ID),
#Col1_Old = d.Col_1,
#Col1_New = i.Col_1,
#Col2_Old = d.Col_2,
#Col2_New = i.Col_2
FROM
Inserted i
FULL JOIN Deleted d
ON i.Pt_ID = d.Pt_ID;
INSERT dbo.TriggerScalarLog (Pt_ID, Col1_Old, Col1_New, Col2_Old, Col2_New)
VALUES (#Pt_ID, #Col1_Old, #Col1_New, #Col2_Old, #Col2_New);
GO -- DROP TRIGGER dbo.PrimaryCrudScalar;
CREATE TRIGGER PrimaryCrudMultiRow ON dbo.PrimaryTable
AFTER INSERT, UPDATE, DELETE
AS
SET NOCOUNT ON;
INSERT dbo.TriggerMultiRowLog (Pt_ID, Col1_Old, Col1_New, Col2_Old, Col2_New)
SELECT
ISNULL(i.Pt_ID, d.Pt_ID),
d.Col_1,
i.Col_1,
d.Col_2,
i.Col_2
FROM
Inserted i
FULL JOIN Deleted d
ON i.Pt_ID = d.Pt_ID;
GO -- DROP TRIGGER dbo.TriggerMultiRowLog;
--=======================================================
--=======================================================
-- --insert test...
INSERT dbo.PrimaryTable (Col_1)
SELECT TOP 100
o.object_id
FROM
sys.objects o;
SELECT 'INSERT Scarar results';
SELECT * FROM dbo.TriggerScalarLog tsl;
SELECT 'INSERT Multi-Row results';
SELECT * FROM dbo.TriggerMultiRowLog tmrl;
UPDATE pt SET
pt.Col_1 = pt.Col_1 + rv.RandomVal,
pt.Col_2 = DATEADD(DAY, rv.RandomVal, pt.Col_2)
FROM
dbo.PrimaryTable pt
CROSS APPLY ( VALUES (ABS(CHECKSUM(NEWID())) % 10000 + 1) ) rv (RandomVal);
SELECT 'UPDATE Scarar results';
SELECT * FROM dbo.TriggerScalarLog tsl;
SELECT 'UPDATE Multi-Row results';
SELECT * FROM dbo.TriggerMultiRowLog tmrl;
DELETE pt
FROM
dbo.PrimaryTable pt;
SELECT 'DELETE Scarar results';
SELECT * FROM dbo.TriggerScalarLog tsl;
SELECT 'DELETE Multi-Row results';
SELECT * FROM dbo.TriggerMultiRowLog tmrl;
You could, but I'd recommend against it. If your source table changed things would start failing.
Also, in your example if you were to ever have more than one row inserted at a time you would get thrown an error (or have unpredictable results). I'd recommend a more set based approach:
INSERT table2 ( user_id ,
user_name ,
RecoID
)
SELECT user_id ,
user_name ,
RecoID
FROM inserted i
LEFT JOIN table2 t ON i.RecoID = t.RecoID
WHERE t.RecoID IS NULL;
EDIT:
If you want to stop the insert happening on your original table then you'll need to do something along the lines of:
CREATE TRIGGER trigger_name
ON table_orig
INSTEAD OF INSERT
AS
BEGIN
-- make sure we aren't triggering from ourselves from another trigger
IF TRIGGER_NESTLEVEL() <= 1
return;
-- insert into the table_copy if the inserted row is already in table_orig (not null)
INSERT table_copy ( user_id ,
user_name ,
RecoID
)
SELECT user_id ,
user_name ,
RecoID
FROM inserted i
LEFT JOIN table_orig c ON i.RecoID = c.RecoID
WHERE t.RecoID IS NOT NULL;
-- insert into table_orig if the inserted row is not already in table_orig (null)
INSERT table_orig ( user_id ,
user_name ,
RecoID
)
SELECT user_id ,
user_name ,
RecoID
FROM inserted i
LEFT JOIN table_orig c ON i.RecoID = c.RecoID
WHERE t.RecoID IS NULL;
END;
The instead of will stop the insert if you don't want it to actually be inserted, so you'll need to do that yourself (the second insert statement).
Please note I changed some nulls to not nulls and the table we are left joining to in some cases.

How to know the data type of a field in trigger?

How can i know the data type of a field inside a trigger. I am able to get the field name and it's value inside a trigger after insert as follows:
DECLARE #AfterInserted XML
SELECT #AfterInserted = (
SELECT *
FROM INSERTED
WHERE User_Key = User_Key
FOR XML RAW, ROOT
);
CREATE TABLE #XML(
FieldName nvarchar(250),
Value nvarchar(250));
Insert Into #XML(FieldName, Value)
select T.N.value('local-name(.)', 'nvarchar(100)'),
T.N.value('.', 'nvarchar(250)')
from #AfterInserted.nodes('/root/row/#*') as T(N)
I also need data type of the field too. something like T.N.value('Data-type')?
Thanks
Not exactly sure if this will work for your purpose, but:
SELECT SQL_VARIANT_PROPERTY(your_column, 'BaseType')
FROM your_table
Will return a column's field type as NVARCHAR.
You can also use Precision, Scale, MaxLength, Collation and TotalBytes as the 2nd parameter for SQL_VARIANT_PROPERTY.
There is no need to use XML to get meta-data related to the table that the Trigger is on. You can query the system catalog to get the info directly.
The Trigger is an object with an object_id, so the ##PROCID system variable will have the object_id of the Trigger itself, within the context of the Trigger.
Using the value of ##PROCID, you can look in sys.objects at the row for that specific object_id and the parent_object_id field will be the object_id of the Table that the Trigger is on.
Using the value of parent_object_id, you can query sys.tables / sys.objects to get table-level info, or query sys.columns to get column-level info.
The example below illustrates the above info:
SET ANSI_NULLS ON
SET QUOTED_IDENTIFIER ON
SET NOCOUNT ON
GO
-- DROP TABLE dbo.TriggerTest
IF (OBJECT_ID('dbo.TriggerTest') IS NULL)
BEGIN
PRINT 'Creating TriggerTest...'
CREATE TABLE dbo.TriggerTest (Col1 INT, Col2 VARCHAR(30), Col3 DATETIME)
END
--
IF (OBJECT_ID('dbo.TR_TriggerTest_IU') IS NOT NULL)
BEGIN
PRINT 'Dropping TR_TriggerTest_IU...'
DROP TRIGGER dbo.TR_TriggerTest_IU
END
GO
PRINT 'Creating TR_TriggerTest_IU...'
GO
CREATE TRIGGER dbo.TR_TriggerTest_IU
ON dbo.TriggerTest
AFTER INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON
-- get Table info
SELECT *
FROM sys.tables st
WHERE st.[object_id] = ( -- get parent_object_id of this Trigger
-- which will be the table that the
-- Trigger is on
SELECT so.parent_object_id
FROM sys.objects so
WHERE so.[object_id] = ##PROCID
)
-- get Column info
SELECT *
FROM sys.columns sc
-- Custom types will repeat values of system_type_id and produce a
-- cartesian product, so filter using "is_user_defined = 0"
INNER JOIN sys.types st
ON st.system_type_id = sc.system_type_id
AND st.is_user_defined = 0
WHERE sc.[object_id] = ( -- get parent_object_id of this Trigger
-- which will be the table that the
-- Trigger is on
SELECT so.parent_object_id
FROM sys.objects so
WHERE so.[object_id] = ##PROCID
)
END
GO
INSERT INTO dbo.TriggerTest (Col1, Col2, Col3) VALUES (1, 'a', GETDATE())

sql script, if and go statements

I need to surround the following sql script with an if statment that checks the existence of one table. There's a lot more fields to the statement but the snippet below should be enough to get the idea.
If I surround this whole batch with an if statement it doesn't like that i have GOs between if statments. If i take out the GOs it complains about TMP_FIELD being an invalid column.
What are some ways to do this the right way? All i'm doing is taking a bunch of fields and changing from varchar to datetime. This is part of a setup.exe file so I just need it to run once and not for future upgrades. The way I determine that is if a certain table exists then don't run the script.
IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = object_id(N'MY_TABLE') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
ALTER TABLE MY_TABLE ADD TMP_FIELD datetime
GO
IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = object_id(N'MY_TABLE') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
UPDATE MY_TABLE SET TMP_FIELD = modifiedDate
GO
IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = object_id(N'MY_TABLE') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
ALTER TABLE MY_TABLE DROP COLUMN modifiedDate
GO
IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = object_id(N'MY_TABLE') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
ALTER TABLE MY_TABLE ADD modifiedDate datetime
GO
IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = object_id(N'MY_TABLE') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
UPDATE MY_TABLE SET modifiedDate = TMP_FIELD
GO
IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = object_id(N'MY_TABLE') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
ALTER TABLE MY_TABLE DROP COLUMN TMP_FIELD
GO
You don't really need to do all that gymnastic for changing the type of a column, do you?
create table MY_TABLE (
modifiedDate varchar(20)
)
go
insert MY_TABLE (modifiedDate) values ('2012-10-20 17:50:41')
go
select * from MY_TABLE
go
alter table MY_TABLE alter column modifiedDate datetime
go
select * from MY_TABLE
go
drop table MY_TABLE
go
So, I would write your statement like this:
if exists (select table_name from INFORMATION_SCHEMA.TABLES where TABLE_NAME = 'MY_TABLE')
begin
alter table MY_TABLE alter column modifiedDate datetime
end
The GOs mark the end of a batch of TSQL statements. You can't mix DDL (data definition language) statements like ALTER TABLE with DML (data manipulation language) statements like UPDATE TABLE in the same batch.
Each batch is compiled on it's own. So when the ALTER TABLE and UPDATE TABLE statements are in the same batch, SQL Server can't compile the update statement because the column modifiedData hasn't actually been created yet.
If you want to get around 'Invalid column ..' errors, one option is to use dynamic SQL.
e.g. :
create table dbo.t1 (id int primary key, cola varchar(20))
go
insert dbo.t1 values (1, 'one')
insert dbo.t1 values (2, 'two')
go
if not exists(select * from INFORMATION_SCHEMA.COLUMNS where TABLE_NAME = 't1' and COLUMN_NAME = 'colb')
BEGIN
-- add new column 'colb', and set its value initially to existing values in 'cola'
ALTER TABLE dbo.t1 ADD colb varchar(20)
DECLARE #v NVARCHAR(500)
SET #v = N'UPDATE dbo.t1 SET colb = cola'
EXEC (#v) -- use dynamic SQL, otherwise will get Invalid column name colb error.
END
GO
Note that dynamic SQL should be considered a last resort. David Brabant's answer seems to be the way to go for your original problem.

Resources