SQL Server · DDL · SCADA Database Lab

SQL DDL Scripting for Automation

Learn how to create and safely evolve an industrial SQL Server schema with CREATE, ALTER, constraints, indexes, views and temporal history—using SQF_DB.dbo.tblEvent as the running automation example.

CREATE and ALTER Constraints Indexes and views Temporal history

Learning Overview

Level: SQL fundamentalsFormat: Blog + practical labExample: SQF_DB.dbo.tblEventEstimated time: 180 minutes

Prerequisites / What You’ll Need

  • SQL Server or SQL Server Express test instance
  • SQL Server Management Studio (SSMS) or Azure Data Studio
  • Permission to create objects in a disposable practice database
  • A database backup before modifying an existing plant database
  • Basic knowledge of tables, rows, columns and SQL data types
Core idea

DDL changes the contract between SQL Server and every SCADA, historian, report, API and script that uses the database. Treat a schema change like an engineered deployment—review dependencies, validate data, test rollback and verify the final catalog.

  • Build objects in dependency order: database, schema, parent tables, child tables, constraints, indexes and views.
  • Validate existing rows before adding stricter constraints or changing data types.
  • Keep DDL out of cyclic SCADA Runtime code; deploy it through a controlled migration.
  • Verify results from SQL Server catalog views, not only from a “Command completed” message.

SQL DDL for SCADA and Industrial Automation

This guide covers SQL Server DDL commands, CREATE TABLE for SCADA, ALTER TABLE constraints, industrial database deployment and a complete tblEvent DDL lab.

1. What Is DDL?

DDL means Data Definition Language. It defines or changes the structure and metadata of database objects. The most familiar DDL verbs are CREATE, ALTER, DROP and TRUNCATE.

CommandPurposeAutomation example
CREATECreate a new objectCreate tblEvent, a view or an index
ALTERChange an existing objectAdd OperatorName or a foreign key
DROPRemove an object definitionRemove an obsolete test view
TRUNCATERemove all table rows by deallocating data pagesClear a disposable staging table

DDL is broader than tables. It also creates or changes schemas, constraints, indexes, views, procedures, functions, triggers, sequences, users and many other SQL Server objects.

2. DDL vs DML, DCL and TCL

GroupCommon statementsWhat changes
DDLCREATE, ALTER, DROP, TRUNCATEStructure and metadata
DMLSELECT, INSERT, UPDATE, DELETE, MERGERows and result sets
DCLGRANT, DENY, REVOKEPermissions
TCLBEGIN TRANSACTION, COMMIT, ROLLBACK, SAVE TRANSACTIONTransaction boundaries
GO is not T-SQL: it is a batch separator interpreted by client tools such as SSMS and sqlcmd. Do not send GO through an ODBC command, ADO command or application API; split the batches in the client or remove it where the syntax permits.

3. Understand Objects and Deployment Order

SQL Server cannot create a child relationship before the referenced parent object exists. A predictable automation deployment normally follows this sequence:

  1. Create or select the database.
  2. Create custom schemas if required.
  3. Create reference tables such as Equipment, Shift and AlarmPriority.
  4. Create transactional tables such as tblEvent.
  5. Clean or seed required data.
  6. Add foreign keys and business constraints.
  7. Create workload-driven indexes.
  8. Create views, procedures and reporting objects.
  9. Grant the minimum application permissions.
  10. Run verification queries and record the migration version.

Use two-part names such as dbo.tblEvent. An unqualified table name can resolve differently for users with different default schemas.

4. Create a Database and Schema

For a new lab, create the database from the master context. Keep production file sizing, recovery model, backup and high-availability decisions under DBA control.

USE [master];
GO

IF DB_ID(N'SQF_DB') IS NULL
BEGIN
    CREATE DATABASE [SQF_DB];
END;
GO

USE [SQF_DB];
GO

IF SCHEMA_ID(N'automation') IS NULL
    EXEC(N'CREATE SCHEMA automation AUTHORIZATION dbo;');
GO
Expected output: DB_ID('SQF_DB') returns a database ID, and SCHEMA_ID('automation') returns a schema ID.

A custom schema can separate application objects from dbo, but this tutorial keeps dbo.tblEvent to match the supplied table reference.

5. CREATE TABLE: Build tblEvent

The attached DDL uses tblEvent as a furnace or process event table. The following beginner version preserves that model while using datetime2(3), fixed-precision engineering values and a binary status flag.

USE [SQF_DB];
GO

CREATE TABLE [dbo].[tblEvent]
(
    [ID]             int IDENTITY(1,1) NOT NULL,
    [DT]             datetime2(3) NOT NULL,
    [TM]             varchar(10) NULL, -- legacy display value; DT remains authoritative
    [SQF_No]         int NULL,
    [ChargeNo]       varchar(50) NULL,
    [Event_From]     varchar(100) NULL,
    [Event_To]       varchar(100) NULL,
    [Temp_Set]       decimal(9,3) NULL,
    [Temp_Act]       decimal(9,3) NULL,
    [Cp_Set]         decimal(9,3) NULL,
    [Cp_Act]         decimal(9,3) NULL,
    [Oil_Set]        decimal(9,3) NULL,
    [Oil_Act]        decimal(9,3) NULL,
    [Jacket_Set]     decimal(9,3) NULL,
    [Jacket_Act]     decimal(9,3) NULL,
    [Fan_Status]     bit NULL,
    CONSTRAINT [PK_tblEvent] PRIMARY KEY CLUSTERED ([ID])
);
GO
Design note

The supplied DDL used datetime, float and varchar(10) status values. Those types can be valid legacy choices, but the companion data-types guide explains why datetime2(3), decimal(p,s) and bit are often clearer for a new automation table.

6. ALTER TABLE: Add, Change and Drop Columns

Add a column

ALTER TABLE [dbo].[tblEvent]
ADD [OperatorName] nvarchar(100) NULL;
GO

Change a type or length

-- First confirm that existing values fit the new definition.
SELECT MAX(LEN([Event_From])) AS MaxEventFromLength
FROM [dbo].[tblEvent];

ALTER TABLE [dbo].[tblEvent]
ALTER COLUMN [Event_From] varchar(150) NULL;
GO

Drop a column

-- Check dependencies before removal.
ALTER TABLE [dbo].[tblEvent]
DROP COLUMN [OperatorName];
GO
Production caution: ALTER COLUMN can scan or rewrite a large table, acquire schema locks, grow the transaction log and fail because of indexes, constraints or incompatible data. Rehearse with production-sized data and a measured maintenance window.

7. Add Rules with Constraints

Data types define representation; constraints enforce additional rules. Give constraints explicit, predictable names so future migrations can reference them reliably.

ConstraintRuletblEvent example
PRIMARY KEYUnique, non-null row identityID
NOT NULLValue is mandatoryDT
DEFAULTValue supplied when column is omittedUTC insertion time
CHECKValue satisfies a Boolean conditionTemperature engineering range
UNIQUENo duplicate key valuesApproved event natural key
FOREIGN KEYChild value exists in the parent keySQF_No references Equipment
ALTER TABLE [dbo].[tblEvent]
ADD CONSTRAINT [DF_tblEvent_DT]
    DEFAULT (SYSUTCDATETIME()) FOR [DT];

ALTER TABLE [dbo].[tblEvent]
ADD CONSTRAINT [CK_tblEvent_TempAct]
    CHECK ([Temp_Act] IS NULL OR [Temp_Act] BETWEEN -100.000 AND 1800.000);

-- Add only if the process truly guarantees one event at this grain.
ALTER TABLE [dbo].[tblEvent]
ADD CONSTRAINT [UQ_tblEvent_Charge_DT]
    UNIQUE ([ChargeNo], [DT]);
GO

A default acts only when an insert omits the column; it does not repair existing NULL values. A unique constraint is a business rule, not merely a performance setting.

8. Create the Equipment Foreign Key

Create the parent table first, seed it and check for orphaned child values before adding the relationship.

CREATE TABLE [dbo].[Equipment]
(
    [EquipmentID]   int NOT NULL,
    [EquipmentName] nvarchar(100) NOT NULL,
    [IsActive]      bit NOT NULL
        CONSTRAINT [DF_Equipment_IsActive] DEFAULT (1),
    CONSTRAINT [PK_Equipment] PRIMARY KEY ([EquipmentID]),
    CONSTRAINT [UQ_Equipment_Name] UNIQUE ([EquipmentName])
);
GO

-- Find orphaned values before adding the foreign key.
SELECT DISTINCT e.[SQF_No]
FROM [dbo].[tblEvent] AS e
LEFT JOIN [dbo].[Equipment] AS q
    ON q.[EquipmentID] = e.[SQF_No]
WHERE e.[SQF_No] IS NOT NULL
  AND q.[EquipmentID] IS NULL;

ALTER TABLE [dbo].[tblEvent] WITH CHECK
ADD CONSTRAINT [FK_tblEvent_Equipment]
    FOREIGN KEY ([SQF_No])
    REFERENCES [dbo].[Equipment] ([EquipmentID]);

ALTER TABLE [dbo].[tblEvent]
CHECK CONSTRAINT [FK_tblEvent_Equipment];
GO
Expected output: the orphan query returns zero rows, and the foreign key is both enabled and trusted.

A foreign key does not automatically create an index on the child column. Add one when join and filter workload justifies it.

9. Add a Computed Column

A computed column derives its value from other columns. PERSISTED stores the result physically and updates it when dependent values change.

ALTER TABLE [dbo].[tblEvent]
ADD [TempDeviation] AS
    (CONVERT(decimal(10,3), [Temp_Act] - [Temp_Set])) PERSISTED;
GO

SELECT [ID], [Temp_Set], [Temp_Act], [TempDeviation]
FROM [dbo].[tblEvent];
Important correction to the reference: a computed expression involving float or real is imprecise and cannot be used as an index key. PERSISTED alone does not make every expression indexable. The expression must meet SQL Server determinism, precision, ownership, data-type and session SET requirements.
SELECT
    COLUMNPROPERTY(OBJECT_ID(N'dbo.tblEvent'), N'TempDeviation', 'IsDeterministic') AS IsDeterministic,
    COLUMNPROPERTY(OBJECT_ID(N'dbo.tblEvent'), N'TempDeviation', 'IsPrecise')       AS IsPrecise;
GO

10. Create Indexes for Real Queries

CREATE INDEX is DDL, but indexing is a workload decision. Every index consumes storage and adds work to inserts and updates. Begin with the actual SCADA/report queries.

-- Useful for charge lookups.
CREATE INDEX [IX_tblEvent_ChargeNo]
ON [dbo].[tblEvent] ([ChargeNo]);

-- Useful for equipment time-range queries.
CREATE INDEX [IX_tblEvent_SQFNo_DT]
ON [dbo].[tblEvent] ([SQF_No], [DT])
INCLUDE ([ChargeNo], [Temp_Set], [Temp_Act], [Fan_Status]);
GO

Column order matters. The index (SQF_No, DT) supports an equality filter on equipment followed by a timestamp range. Test execution plans and write overhead before adding more indexes.

11. CREATE OR ALTER VIEW for Reporting

A view gives reporting clients a stable, permission-friendly interface. In supported SQL Server versions, CREATE OR ALTER VIEW removes separate create-versus-alter branching.

CREATE OR ALTER VIEW [dbo].[vw_ChargeSummary]
AS
SELECT
    [ChargeNo],
    [SQF_No],
    MIN([DT]) AS [StartTime],
    MAX([DT]) AS [EndTime],
    DATEDIFF(MINUTE, MIN([DT]), MAX([DT])) AS [DurationMinutes],
    MAX(CASE WHEN [Event_To] = 'Complete' THEN 1 ELSE 0 END) AS [IsComplete]
FROM [dbo].[tblEvent]
GROUP BY [ChargeNo], [SQF_No];
GO

SELECT TOP (20) *
FROM [dbo].[vw_ChargeSummary]
ORDER BY [StartTime] DESC;

A regular view stores its definition, not a separate copy of the result. Avoid SELECT * in long-lived interfaces because new or reordered base-table columns can surprise consumers.

12. Enable System-Versioned Temporal History

A system-versioned temporal table maintains a current table plus a history table. SQL Server requires a primary key and exactly one period made from two datetime2 columns.

ALTER TABLE [dbo].[tblEvent]
ADD
    [ValidFrom] datetime2(7) GENERATED ALWAYS AS ROW START HIDDEN
        CONSTRAINT [DF_tblEvent_ValidFrom] DEFAULT (SYSUTCDATETIME()),
    [ValidTo] datetime2(7) GENERATED ALWAYS AS ROW END HIDDEN
        CONSTRAINT [DF_tblEvent_ValidTo]
        DEFAULT (CONVERT(datetime2(7), '9999-12-31 23:59:59.9999999')),
    PERIOD FOR SYSTEM_TIME ([ValidFrom], [ValidTo]);
GO

ALTER TABLE [dbo].[tblEvent]
SET (SYSTEM_VERSIONING = ON
    (HISTORY_TABLE = [dbo].[tblEvent_History], DATA_CONSISTENCY_CHECK = ON));
GO

History begins when system versioning is enabled; it cannot reconstruct changes that occurred before that time. Period values use UTC transaction begin times.

DECLARE @PointInTime datetime2(7) = DATEADD(HOUR, -1, SYSUTCDATETIME());

SELECT [ID], [DT], [SQF_No], [Temp_Act], [ValidFrom], [ValidTo]
FROM [dbo].[tblEvent]
FOR SYSTEM_TIME AS OF @PointInTime
ORDER BY [ID];
GO
Maintenance note: some operations require SYSTEM_VERSIONING = OFF. Perform the off/maintenance/on sequence in a transaction, specify the same history table when re-enabling and understand that history is not captured while versioning is off.

13. Rename Objects Carefully

SQL Server does not provide a general ALTER ... RENAME statement for tables and columns. sp_rename can rename them, but it does not rewrite every dependent module or external SCADA reference.

-- Examples only: inventory dependencies first.
EXEC sys.sp_rename
    @objname = N'dbo.tblEvent.Fan_Status',
    @newname = N'FanStatus',
    @objtype = N'COLUMN';

EXEC sys.sp_rename
    @objname = N'dbo.tblEvent',
    @newname = N'tblFurnaceEvent';
GO

Search views, procedures, functions, jobs, reports, WinCC scripts, ODBC queries and application code before renaming. A safer compatibility migration often adds the new interface, moves consumers, monitors usage and removes the old interface later.

14. DELETE vs TRUNCATE vs DROP

OperationRemovesWHEREIdentity effectTypical use
DELETESelected or all rowsYesDoes not reset the identity counterControlled data retention
TRUNCATE TABLEAll rows by page deallocationNoResets identity to seedClear an eligible staging/test table
DROP TABLEObject definition and dataNoIdentity disappears with tableRemove an obsolete object
-- DML: remove an approved time range.
DELETE FROM [dbo].[tblEvent]
WHERE [DT] < DATEADD(MONTH, -12, SYSUTCDATETIME());

-- DDL: all rows, no WHERE. Use only on an eligible disposable table.
TRUNCATE TABLE [dbo].[tblEvent_Stage];

-- DDL: remove the object itself.
DROP TABLE IF EXISTS [dbo].[tblEvent_Stage];
GO
TRUNCATE restrictions matter: it cannot be used in several dependency scenarios, including a table referenced by a foreign key, and temporal maintenance may require versioning to be disabled. It also does not fire DELETE triggers.

15. Use Transactions and Test Rollback

Many SQL Server DDL statements participate in transactions. That does not make every migration low-risk: long-running schema changes can hold locks and consume log space. Test the exact script and rollback path.

SET XACT_ABORT ON;

BEGIN TRY
    BEGIN TRANSACTION;

    ALTER TABLE [dbo].[tblEvent]
    ADD [MigrationTest] int NULL;

    -- Verification can run before COMMIT.
    IF COL_LENGTH(N'dbo.tblEvent', N'MigrationTest') IS NULL
        THROW 50001, 'MigrationTest column was not created.', 1;

    -- In a real approved deployment, use COMMIT here.
    ROLLBACK TRANSACTION;
END TRY
BEGIN CATCH
    IF XACT_STATE() <> 0 ROLLBACK TRANSACTION;
    THROW;
END CATCH;
GO
Expected output: the test column is absent after rollback. Confirm with COL_LENGTH.

16. Write Repeatable, Idempotent Migrations

An idempotent migration can be re-run without duplicating an object. Guard clauses help, but they are not a substitute for a migration history: if an object exists with the wrong definition, a simple existence check can hide drift.

IF COL_LENGTH(N'dbo.tblEvent', N'OperatorName') IS NULL
BEGIN
    ALTER TABLE [dbo].[tblEvent]
    ADD [OperatorName] nvarchar(100) NULL;
END;

IF NOT EXISTS
(
    SELECT 1
    FROM sys.indexes
    WHERE [object_id] = OBJECT_ID(N'dbo.tblEvent')
      AND [name] = N'IX_tblEvent_SQFNo_DT'
)
BEGIN
    CREATE INDEX [IX_tblEvent_SQFNo_DT]
    ON [dbo].[tblEvent] ([SQF_No], [DT]);
END;
GO

Store each migration under source control with a version, description, author, test evidence and deployment status. Apply migrations once in sequence and compare the actual schema with the expected definition.

17. DDL Safety for PLC, SCADA and Historian Systems

RiskEngineering control
SCADA writes fail after a column changeTest parameter types, nullability and insert lists against the new schema
Reports break after rename/dropInventory dependencies and deploy compatibility views
Large table blocks RuntimeMeasure the change on production-sized data and schedule a maintenance window
Historian time interpretation changesDocument UTC/local-time policy and validate conversion at boundaries
Runtime account can alter schemaGrant only required DML/execute rights; deploy DDL with a separate controlled identity
Partial deploymentUse transactions where appropriate, migration status and post-deployment verification
Recommended operating model

SCADA Runtime should read, write or execute approved procedures. A separate release process should own DDL. Never run CREATE, ALTER, TRUNCATE or DROP on every screen load, cyclic script or reconnect event.

18. Verify the Final Schema

Use SQL Server catalog views to prove that columns, data types, constraints, indexes and temporal settings match the intended design.

-- Columns and types
SELECT
    c.[column_id], c.[name] AS [ColumnName],
    t.[name] AS [DataType], c.[max_length],
    c.[precision], c.[scale], c.[is_nullable], c.[is_identity],
    c.[is_computed], c.[generated_always_type_desc]
FROM sys.columns AS c
JOIN sys.types AS t ON t.[user_type_id] = c.[user_type_id]
WHERE c.[object_id] = OBJECT_ID(N'dbo.tblEvent')
ORDER BY c.[column_id];

-- Constraints
SELECT [name], [type_desc], [is_disabled], [is_not_trusted]
FROM sys.objects
WHERE [parent_object_id] = OBJECT_ID(N'dbo.tblEvent')
  AND [type] IN ('PK','F','UQ','C','D');

-- Indexes
SELECT [name], [type_desc], [is_unique], [is_disabled]
FROM sys.indexes
WHERE [object_id] = OBJECT_ID(N'dbo.tblEvent')
  AND [index_id] > 0;

-- Temporal status: 0 = none, 2 = system-versioned temporal table
SELECT [name], [temporal_type_desc],
       OBJECT_SCHEMA_NAME([history_table_id]) AS [HistorySchema],
       OBJECT_NAME([history_table_id]) AS [HistoryTable]
FROM sys.tables
WHERE [object_id] = OBJECT_ID(N'dbo.tblEvent');
GO

19. Common DDL Errors and Fixes

SymptomLikely causeCheck or fix
“There is already an object named...”Script was rerunUse migration history and verify existing definition
ALTER COLUMN fails converting dataExisting values do not fitProfile invalid rows and migrate in stages
Foreign key conflictsOrphaned child valuesRun a left anti-join and correct mappings
Cannot drop columnConstraint, index, view or computed dependencyInventory and remove/alter dependencies in order
Computed-column index failsExpression is nondeterministic/imprecise or SET options differCheck COLUMNPROPERTY, data types and required session settings
Cannot truncate tableForeign key, temporal, replication or other restrictionReview dependencies; use an approved alternative
Object name resolves incorrectlySchema omittedUse two-part names such as dbo.tblEvent
Application reports syntax error near GOAPI sent the SSMS batch separator to SQL ServerRemove GO or split batches client-side

20. Complete Practical DDL Lab

Hands-on
Lab safety
  • Run only in a disposable practice database.
  • Do not point the script at a live SCADA database.
  • Complete steps in order and verify each checkpoint.
1

Create the Shift reference table

Use a time range check and a unique shift name.

CREATE TABLE [dbo].[Shift]
(
    [ShiftID]   tinyint NOT NULL,
    [ShiftName] varchar(30) NOT NULL,
    [StartTime] time(0) NOT NULL,
    [EndTime]   time(0) NOT NULL,
    CONSTRAINT [PK_Shift] PRIMARY KEY ([ShiftID]),
    CONSTRAINT [UQ_Shift_Name] UNIQUE ([ShiftName]),
    CONSTRAINT [CK_Shift_TimeRange] CHECK ([StartTime] <> [EndTime])
);
GO
Expected: three catalog constraints appear for dbo.Shift.

For overnight shifts, StartTime < EndTime is not a valid universal rule because a 22:00–06:00 shift crosses midnight. The non-equality rule permits both same-day and overnight definitions; application logic determines duration.

2

Add and validate the Equipment relationship

Create dbo.Equipment, insert valid equipment numbers, run the orphan query and add FK_tblEvent_Equipment with WITH CHECK.

Expected: an insert with an unknown non-null SQF_No is rejected; valid and null values follow the approved design.
3

Create a suspicious-readings view

CREATE OR ALTER VIEW [dbo].[vw_SuspiciousReadings]
AS
SELECT [ID], [DT], [SQF_No], [ChargeNo], [Temp_Set], [Temp_Act]
FROM [dbo].[tblEvent]
WHERE [Temp_Act] IS NULL
  AND [Temp_Set] IS NOT NULL;
GO
Expected: the view returns only rows with a setpoint but no actual temperature.
4

Enable and query temporal history

Add the period columns, enable system versioning, update one test event and query FOR SYSTEM_TIME ALL plus an AS OF point.

Expected: the current table holds the latest row, while the history table exposes the previous version after an update.
5

Test a rollback deployment

Add a temporary column inside an explicit transaction, verify it, roll back, then verify that it no longer exists.

SELECT COL_LENGTH(N'dbo.tblEvent', N'MigrationTest') AS MigrationTestLength;
-- Expected after ROLLBACK: NULL
6

Record the deployment

Save the tested script with a migration number such as V001__create_tblEvent.sql. Record who approved it, target environment, backup reference, start/end time, result and rollback outcome.

Lab complete

You can now distinguish DDL from row-level SQL, build objects in dependency order, add constraints only after validating data, manage schema changes transactionally and verify the deployed catalog.

SSMS “Script Table As” Menu Explained

Menu optionGroupGenerated starting point
Create ToDDLCREATE TABLE
Alter ToDDLALTER TABLE template
Drop ToDDLDROP TABLE
Drop and Create ToDDLDestructive drop followed by create
Select ToDML/querySELECT template
Insert ToDMLINSERT template
Update ToDMLUPDATE template
Delete ToDMLDELETE template

Generated scripts are starting points. Review filegroups, constraints, indexes, permissions, dependencies, data preservation and environment-specific names before deployment.

Further Reading

Frequently Asked Questions

What is DDL in SQL Server?

Data Definition Language defines or changes structures such as tables, columns, constraints, views and indexes. Common verbs include CREATE, ALTER, DROP and TRUNCATE.

Is GO a T-SQL statement?

No. GO is a batch separator understood by tools such as SSMS and sqlcmd. Remove it or split batches before executing a script through ODBC, ADO or another application API.

Can SQL Server DDL be rolled back?

Many DDL statements are transactional, but you must test the exact migration. Large schema changes can still hold locks, consume log space and affect connected automation clients.

What is the difference between DELETE, TRUNCATE and DROP?

DELETE removes selected rows, TRUNCATE removes all rows and resets identity allocation, and DROP removes the table definition together with its data.

Should WinCC or another SCADA Runtime run DDL?

Normally no. Deploy DDL through a reviewed migration identity and grant Runtime only the DML or stored-procedure permissions it requires.

Get the SQL Reporting and Automation Syllabus

Share your details and a Softwell advisor will contact you with practical SQL, SCADA and industrial reporting training options.

Build reliable SQL databases for automation

Join practical online, classroom or corporate SQL Server, reporting and SCADA integration training.

Request Course Details
Verified learning pathway

Discuss SQL DDL and Automation Database Training

Explore practical SQL Server, PLC/SCADA integration and industrial data training options.

Content reviewed: 4 August 2026

☎ Call WhatsApp ✉ Email Enquire Now