INSERT 示例 (Transact-SQL)

本主题将提供使用 INSERT 语句的示例。示例按以下类别进行分组。

类别

作为特征的语法元素

基本语法

INSERT • 表值构造函数

处理列值

IDENTITY • NEWID • 默认值 • 用户定义类型

插入来自其他表的数据

INSERT…SELECT • INSERT…EXECUTE • WITH 公用表表达式 • TOP

指定目标表,而非标准表

视图 • 表变量

向远程表中插入行

链接服务器 • OPENQUERY 行集函数 • OPENDATASOURCE 行集函数

从表或数据字段中大容量加载数据

INSERT…SELECT • OPENROWSET 函数

通过使用提示覆盖查询优化器的默认行为

表提示

捕获 INSERT 语句的结果

OUTPUT 子句

基本语法

本节中的示例说明了使用最低要求的语法的 INSERT 语句的基本功能。

A. 插入单行数据

下面的示例在 Production.UnitMeasure 表中插入一行。该表中的列是 UnitMeasureCode、Name 和 ModifiedDate。由于提供了所有列的值并按表中各列的顺序列出这些值,因此不必在列列表中指定列名。

USE AdventureWorks;
GO
INSERT INTO Production.UnitMeasure
VALUES (N'FT', N'Feet', '20080414');
GO

B. 插入多行数据

下面的示例使用表值构造函数在单个 INSERT 语句中将三行插入 Production.UnitMeasure 表。由于提供了所有列的值并按表中各列的顺序列出这些值,因此不必在列列表中指定列名。

USE AdventureWorks;
GO
INSERT INTO Production.UnitMeasure
VALUES (N'FT2', N'Square Feet ', '20080923'), (N'Y', N'Yards', '20080923'), (N'Y3', N'Cubic Yards', '20080923');
GO

C. 按与表列顺序不同的顺序插入数据

下面的示例使用列列表显式指定插入到每个列中的值。Production.UnitMeasure 表中的列顺序为 UnitMeasureCode、Name、ModifiedDate;但这些列的列出顺序与 column_list 中的顺序不同。

USE AdventureWorks;
GO
INSERT INTO Production.UnitMeasure (Name, UnitMeasureCode,
    ModifiedDate)
VALUES (N'Square Yards', N'Y2', GETDATE());
GO

处理列值

本节中的示例说明将值插入列中的方法,这些列是使用 IDENTITY 属性或 DEFAULT 值定义的列,或者是用 uniqueidentifer 之类的数据类型定义的列,或者是用户定义类型列。

A. 将数据插入其列具有默认值的表

下面的示例演示了如何将行插入到包含自动生成值或具有默认值的列的表中。Column_1 是一个计算列,它通过将一个字符串与插入 column_2 的值进行串联,自动生成一个值。Column_2 是用默认约束定义的。如果没有为该列指定值,将使用默认值。Column_3 是用 rowversion 数据类型定义的,它自动生成一个唯一的、递增的二进制数字。Column_4 不自动生成值。如果没有为该列指定值,将插入 NULL。INSERT 语句插入一些行,这些行只有部分列包含值。在最后一个 INSERT 语句中,未指定列,只通过使用 DEFAULT VALUES 子句插入了默认值。

USE AdventureWorks;
GO
IF OBJECT_ID ('dbo.T1', 'U') IS NOT NULL
    DROP TABLE dbo.T1;
GO
CREATE TABLE dbo.T1 
(
    column_1 int IDENTITY, 
    column_2 varchar(30) 
        CONSTRAINT default_name DEFAULT ('my column default'),
    column_3 timestamp,
    column_4 varchar(40) NULL
);
GO
INSERT INTO dbo.T1 (column_4) 
    VALUES ('Explicit value');
INSERT INTO dbo.T1 (column_2, column_4) 
    VALUES ('Explicit value', 'Explicit value');
INSERT INTO dbo.T1 (column_2) 
    VALUES ('Explicit value');
INSERT INTO T1 DEFAULT VALUES; 
GO
SELECT column_1, column_2, column_3, column_4
FROM dbo.T1;
GO

B. 将数据插入到含标识列的表中

下面的示例演示了将数据插入到标识列中的不同方法。前两个 INSERT 语句允许为新行生成标识值。第三个 INSERT 语句用 SET IDENTITY_INSERT 语句覆盖列的 IDENTITY 属性,并将一个显式值插入到标识列中。

USE AdventureWorks;
GO
IF OBJECT_ID ('dbo.T1', 'U') IS NOT NULL
    DROP TABLE dbo.T1;
GO
CREATE TABLE dbo.T1 ( column_1 int IDENTITY, column_2 VARCHAR(30));
GO
INSERT T1 VALUES ('Row #1');
INSERT T1 (column_2) VALUES ('Row #2');
GO
SET IDENTITY_INSERT T1 ON;
GO
INSERT INTO T1 (column_1,column_2) 
    VALUES (-99, 'Explicit identity value');
GO
SELECT column_1, column_2
FROM T1;
GO

C. 通过使用 NEWID() 将数据插入到 uniqueidentifier 列中

下面的示例使用 NEWID() 函数获取 column_2 的 GUID。与标识列不同,数据库引擎不为 uniqueidentifier 数据类型的列自动生成值(如第二个 INSERT 语句所示)。

USE AdventureWorks;
GO
IF OBJECT_ID ('dbo.T1', 'U') IS NOT NULL
    DROP TABLE dbo.T1;
GO
CREATE TABLE dbo.T1 
(
    column_1 int IDENTITY, 
    column_2 uniqueidentifier,
);
GO
INSERT INTO dbo.T1 (column_2) 
    VALUES (NEWID());
INSERT INTO T1 DEFAULT VALUES; 
GO
SELECT column_1, column_2
FROM dbo.T1;
GO

D. 将数据插入到用户定义类型列中

下面的 Transact-SQL 语句将三行插入到 Points 表的 PointValue 列中。该列使用 CLR 用户定义类型 (UDT)。Point 数据类型由作为 UDT 属性公开的 X 和 Y 整数值组成。必须使用 CAST 或 CONVERT 函数,才能将以逗号分隔的 X 和 Y 值转换为 Point 类型。前两个语句使用 CONVERT 函数将字符串值转换为 Point 类型,第三个语句使用 CAST 函数。有关详细信息,请参阅操作 UDT 数据

INSERT INTO dbo.Points (PointValue) VALUES (CONVERT(Point, '3,4'));
INSERT INTO dbo.Points (PointValue) VALUES (CONVERT(Point, '1,5'));
INSERT INTO dbo.Points (PointValue) VALUES (CAST ('1,99' AS Point));

插入来自其他表的数据

本节中的示例说明将行从一个表插入另一个表的方法。

A. 使用 SELECT 和 EXECUTE 选项插入来自其他表的数据

下面的示例说明如何使用 INSERT…SELECT 或 INSERT…EXECUTE 将来自一个表的数据插入另一个表。每种方法都基于一个多表 SELECT 语句,该语句在列列表中包含一个表达式及一个文字值。

第一个 INSERT 语句使用 SELECT 语句从源表(Employee、SalesPerson 和 Contact)中派生数据,并将结果集存储在 EmployeeSales 表中。第二个 INSERT 语句使用 EXECUTE 子句调用包含 SELECT 语句的存储过程,第三个 INSERT 使用 EXECUTE 子句将 SELECT 语句作为文字字符串引用。

USE AdventureWorks;
GO
IF OBJECT_ID ('dbo.EmployeeSales', 'U') IS NOT NULL
    DROP TABLE dbo.EmployeeSales;
GO
IF OBJECT_ID ('dbo.uspGetEmployeeSales', 'P') IS NOT NULL
    DROP PROCEDURE uspGetEmployeeSales;
GO
CREATE TABLE dbo.EmployeeSales
( DataSource   varchar(20) NOT NULL,
  EmployeeID   varchar(11) NOT NULL,
  LastName     varchar(40) NOT NULL,
  SalesDollars money NOT NULL
);
GO
CREATE PROCEDURE dbo.uspGetEmployeeSales 
AS 
    SET NOCOUNT ON;
    SELECT 'PROCEDURE', e.EmployeeID, c.LastName, 
        sp.SalesYTD 
    FROM HumanResources.Employee AS e 
        INNER JOIN Sales.SalesPerson AS sp  
        ON e.EmployeeID = sp.SalesPersonID 
        INNER JOIN Person.Contact AS c
        ON e.ContactID = c.ContactID
    WHERE e.EmployeeID LIKE '2%'
    ORDER BY e.EmployeeID, c.LastName;
GO
--INSERT...SELECT example
INSERT dbo.EmployeeSales
    SELECT 'SELECT', e.EmployeeID, c.LastName, sp.SalesYTD 
    FROM HumanResources.Employee AS e
        INNER JOIN Sales.SalesPerson AS sp
        ON e.EmployeeID = sp.SalesPersonID 
        INNER JOIN Person.Contact AS c
        ON e.ContactID = c.ContactID
    WHERE e.EmployeeID LIKE '2%'
    ORDER BY e.EmployeeID, c.LastName;
GO
--INSERT...EXECUTE procedure example
INSERT EmployeeSales 
EXECUTE uspGetEmployeeSales;
GO
--INSERT...EXECUTE('string') example
INSERT EmployeeSales 
EXECUTE 
('
SELECT ''EXEC STRING'', e.EmployeeID, c.LastName, 
    sp.SalesYTD 
    FROM HumanResources.Employee AS e 
        INNER JOIN Sales.SalesPerson AS sp 
        ON e.EmployeeID = sp.SalesPersonID 
        INNER JOIN Person.Contact AS c
        ON e.ContactID = c.ContactID
    WHERE e.EmployeeID LIKE ''2%''
    ORDER BY e.EmployeeID, c.LastName
');
GO
--Show results.
SELECT DataSource,EmployeeID,LastName,SalesDollars
FROM dbo.EmployeeSales;
GO

B. 使用 WITH 公用表表达式定义插入的数据

下面的示例创建 NewEmployee 表。公用表表达式 (EmployeeTemp) 定义要插入到 NewEmployee 表中的来自一个或多个表的行。INSERT 语句引用公用表表达式中的列。

USE AdventureWorks;
GO
IF OBJECT_ID (N'HumanResources.NewEmployee', N'U') IS NOT NULL
    DROP TABLE HumanResources.NewEmployee;
GO
CREATE TABLE HumanResources.NewEmployee
(
    EmployeeID int NOT NULL,
    LastName nvarchar(50) NOT NULL,
    FirstName nvarchar(50) NOT NULL,
    Phone Phone NULL,
    AddressLine1 nvarchar(60) NOT NULL,
    City nvarchar(30) NOT NULL,
    State nchar(3) NOT NULL, 
    PostalCode nvarchar(15) NOT NULL,
    CurrentFlag Flag
);
GO
WITH EmployeeTemp (EmpID, LastName, FirstName, Phone, 
                   Address, City, StateProvince, 
                   PostalCode, CurrentFlag)
AS (SELECT 
        e.EmployeeID, c.LastName, c.FirstName, c.Phone,
        a.AddressLine1, a.City, sp.StateProvinceCode, 
        a.PostalCode, e.CurrentFlag
    FROM HumanResources.Employee e
        INNER JOIN HumanResources.EmployeeAddress AS ea
        ON e.EmployeeID = ea.EmployeeID
        INNER JOIN Person.Address AS a
        ON ea.AddressID = a.AddressID
        INNER JOIN Person.StateProvince AS sp
        ON a.StateProvinceID = sp.StateProvinceID
        INNER JOIN Person.Contact as c
        ON e.ContactID = c.ContactID
    )
INSERT INTO HumanResources.NewEmployee 
    SELECT EmpID, LastName, FirstName, Phone, 
           Address, City, StateProvince, PostalCode, CurrentFlag
    FROM EmployeeTemp;
GO

C. 使用 TOP 限制从源表插入的数据

下面的示例使用 TOP 子句限制从 Employee 表插入到 NewEmployee 表的行数。该示例将来自 Employee 表的由 10 位员工构成的第一个随机集合的地址数据插入其中。然后执行 SELECT 语句以验证 NewEmployee 表的内容。

USE AdventureWorks;
GO
IF OBJECT_ID (N'HumanResources.NewEmployee', N'U') IS NOT NULL
    DROP TABLE HumanResources.NewEmployee;
GO
CREATE TABLE HumanResources.NewEmployee
(
    EmployeeID int NOT NULL,
    LastName nvarchar(50) NOT NULL,
    FirstName nvarchar(50) NOT NULL,
    Phone Phone NULL,
    AddressLine1 nvarchar(60) NOT NULL,
    City nvarchar(30) NOT NULL,
    State nchar(3) NOT NULL, 
    PostalCode nvarchar(15) NOT NULL,
    CurrentFlag Flag
);
GO
INSERT TOP (10) INTO HumanResources.NewEmployee 
    SELECT
       e.EmployeeID, c.LastName, c.FirstName, c.Phone,
       a.AddressLine1, a.City, sp.StateProvinceCode, 
       a.PostalCode, e.CurrentFlag
    FROM HumanResources.Employee e
        INNER JOIN HumanResources.EmployeeAddress AS ea
        ON e.EmployeeID = ea.EmployeeID
        INNER JOIN Person.Address AS a
        ON ea.AddressID = a.AddressID
        INNER JOIN Person.StateProvince AS sp
        ON a.StateProvinceID = sp.StateProvinceID
        INNER JOIN Person.Contact as c
        ON e.ContactID = c.ContactID;
GO
SELECT  EmployeeID, LastName, FirstName, Phone,
        AddressLine1, City, State, PostalCode, CurrentFlag
FROM HumanResources.NewEmployee;
GO

指定目标表,而非标准表

本节中的示例说明如何通过指定视图或表变量来插入行。

A. 通过指定视图来插入数据

下面的示例将一个视图名指定为目标对象,但将新行插入到基础基表中。INSERT 语句中值的顺序必须与视图的列顺序相匹配。有关详细信息,请参阅通过视图修改数据

USE AdventureWorks;
GO
IF OBJECT_ID ('dbo.T1', 'U') IS NOT NULL
    DROP TABLE dbo.T1;
GO
IF OBJECT_ID ('dbo.V1', 'V') IS NOT NULL
    DROP VIEW dbo.V1;
GO
CREATE TABLE T1 ( column_1 int, column_2 varchar(30));
GO
CREATE VIEW V1 AS 
SELECT column_2, column_1 
FROM T1;
GO
INSERT INTO V1 
    VALUES ('Row 1',1);
GO
SELECT column_1, column_2 
FROM T1;
GO
SELECT column_1, column_2
FROM V1;
GO

B. 向表变量中插入数据

下面的示例将一个表变量指定为目标对象。

USE AdventureWorks;
GO
-- Create the table variable.
DECLARE @MyTableVar table(
    LocationID int NOT NULL,
    CostRate smallmoney NOT NULL,
    NewCostRate AS CostRate * 1.5,
    ModifiedDate datetime);

-- Insert values into the table variable.
INSERT INTO @MyTableVar (LocationID, CostRate, ModifiedDate)
    SELECT LocationID, CostRate, GETDATE() FROM Production.Location
    WHERE CostRate > 0;

-- View the table variable result set.
SELECT * FROM @MyTableVar;
GO  

向远程表中插入行

本节中的示例说明如何通过使用链接服务器行集函数引用一个远程目标表,向该表插入行。

A. 通过使用链接服务器向远程表插入数据

下面的示例将行插入一个远程表中。该示例从使用 sp_addlinkedserver 创建指向远程数据源的链接开始。然后,将链接服务器名称 MyLinkServer 指定为 server.catalog.schema.object 形式的由四个部分组成的对象名称的一部分。

USE master;
GO
-- Create a link to the remote data source. 
-- Specify a valid server name for @datasrc as 'server_name' or 'server_name\instance_name'.

EXEC sp_addlinkedserver @server = N'MyLinkServer',
    @srvproduct = N' ',
    @provider = N'SQLNCLI', 
    @datasrc = N'server_name',
    @catalog = N'AdventureWorks';
GO
USE AdventureWorks;
GO
-- Specify the remote data source in the FROM clause using a four-part name 
-- in the form linked_server.catalog.schema.object.

INSERT INTO MyLinkServer.AdventureWorks.HumanResources.Department (Name, GroupName)
VALUES (N'Public Relations', N'Executive General and Administration');
GO

B. 通过使用 OPENQUERY 函数向远程表插入数据

下面的示例通过指定 OPENQUERY 行集函数向远程表插入一行。在之前例子中创建的链接服务器名称用于此示例。

-- Use the OPENQUERY function to access the remote data source.

INSERT OPENQUERY (MyLinkServer, 'SELECT Name, GroupName FROM AdventureWorks.HumanResources.Department')
VALUES ('Environmental Impact', 'Engineering');
GO

C. 通过使用 OPENDATASOURCE 函数向远程表插入数据

下面的示例通过指定 OPENDATASOURCE 行集函数向远程表插入一行。通过使用 server_name 或 server_name\instance_name 格式,为该数据源指定一个有效的服务器名称。

-- Use the OPENDATASOURCE function to specify the remote data source.
-- Specify a valid server name for Data Source using the format server_name or server_name\instance_name.

INSERT INTO OPENDATASOURCE('SQLNCLI',
    'Data Source= <server_name>; Integrated Security=SSPI')
    .AdventureWorks.HumanResources.Department (Name, GroupName)
    VALUES (N'Standards and Methods', 'Quality Assurance');
GO

从表或数据字段中大容量加载数据

本节中的示例说明通过 INSERT 语句向表中大容量加载数据的两个方法。

A. 将数据插入堆中并按最小方式记录日志

下面的示例创建一个新表(一个堆),并使用最小方式记录日志将来自其他表中的数据插入到这个新表中。此示例假定 AdventureWorks 数据库的恢复模式设置为 FULL。为了确保使用最小方式记录日志,应在插入行之前将 AdventureWorks 数据库的恢复模式设置为 BULK_LOGGED,并在 INSERT INTO…SELECT 语句后重置为 FULL。此外,为目标表 Sales.SalesHistory 指定了 TABLOCK 提示。这确保语句在事务日志中占用最少的空间并且高效执行。

USE AdventureWorks;
GO
-- Create the target heap.
CREATE TABLE Sales.SalesHistory(
    SalesOrderID int NOT NULL,
    SalesOrderDetailID int NOT NULL,
    CarrierTrackingNumber nvarchar(25) NULL,
    OrderQty smallint NOT NULL,
    ProductID int NOT NULL,
    SpecialOfferID int NOT NULL,
    UnitPrice money NOT NULL,
    UnitPriceDiscount money NOT NULL,
    LineTotal money NOT NULL,
    rowguid uniqueidentifier ROWGUIDCOL  NOT NULL,
    ModifiedDate datetime NOT NULL );
GO
-- Temporarily set the recovery model to BULK_LOGGED.
ALTER DATABASE AdventureWorks
SET RECOVERY BULK_LOGGED;
GO
-- Transfer data from Sales.SalesOrderDetail to Sales.SalesHistory
INSERT INTO Sales.SalesHistory WITH (TABLOCK)
    (SalesOrderID, 
     SalesOrderDetailID,
     CarrierTrackingNumber, 
     OrderQty, 
     ProductID, 
     SpecialOfferID, 
     UnitPrice, 
     UnitPriceDiscount,
     LineTotal, 
     rowguid, 
     ModifiedDate)
SELECT * FROM Sales.SalesOrderDetail;
GO
-- Reset the recovery model.
ALTER DATABASE AdventureWorks
SET RECOVERY FULL;

B. 将 OPENROWSET 函数与 BULK 一起使用来将数据大容量加载到表中

下面的示例通过指定 OPENROWSET 函数,将来自数据文件的行插入表中。出于性能优化目的,指定了 IGNORE_TRIGGERS 表提示。有关更多示例,请参阅使用 BULK INSERT 或 OPENROWSET(BULK...) 导入大容量数据

-- Use the OPENROWSET function to specify the data source and specifies the IGNORE_TRIGGERS table hint.

INSERT INTO HumanResources.Department WITH (IGNORE_TRIGGERS) (Name, GroupName)
SELECT b.Name, b.GroupName 
FROM OPENROWSET (
    BULK 'C:\SQLFiles\DepartmentData.txt',
    FORMATFILE = 'C:\SQLFiles\BulkloadFormatFile.xml',
    ROWS_PER_BATCH = 15000)AS b ;
GO

通过使用提示覆盖查询优化器的默认行为

本节中的示例说明如何使用表提示在处理 INSERT 语句时暂时覆盖查询优化器的默认行为。

注意事项注意

由于 SQL Server 查询优化器通常会为查询选择最佳执行计划,因此我们建议仅在最后迫不得已的情况下才可由资深的开发人员和数据库管理员使用提示。

A. 使用 TABLOCK 提示指定锁定方法

下面的示例指定对 Production.Location 表采用排他 (X) 锁,并保持到 INSERT 语句结束。

USE AdventureWorks;
GO
INSERT INTO Production.Location WITH (XLOCK)
(Name, CostRate, Availability)
VALUES ( N'Final Inventory', 15.00, 80.00);
GO

捕获 INSERT 语句的结果

本节中的示例说明如何使用 OUTPUT 子句从 INSERT 语句影响的每一行返回信息(或基于的表达式)。这些结果可以返回到处理应用程序,以供在确认消息、存档以及其他此类应用程序要求中使用。

A 将 OUTPUT 用于 INSERT 语句

下面的示例将行插入到 ScrapReason 表中,并使用 OUTPUT 子句将语句的结果返回到 @MyTableVar 表变量。由于 ScrapReasonID 列使用 IDENTITY 属性定义,因此未在 INSERT 语句中为该列指定值。但应注意,数据库引擎为该列生成的值在 INSERTED.ScrapReasonID 列中的 OUTPUT 子句中返回。

USE AdventureWorks;
GO
DECLARE @MyTableVar table( ScrapReasonID smallint,
                           Name varchar(50),
                           ModifiedDate datetime);
INSERT Production.ScrapReason
    OUTPUT INSERTED.ScrapReasonID, INSERTED.Name, INSERTED.ModifiedDate
        INTO @MyTableVar
VALUES (N'Operator error', GETDATE());

--Display the result set of the table variable.
SELECT ScrapReasonID, Name, ModifiedDate FROM @MyTableVar;
--Display the result set of the table.
SELECT ScrapReasonID, Name, ModifiedDate 
FROM Production.ScrapReason;
GO

B. 将 OUTPUT 用于标识列和计算列

下面的示例创建 EmployeeSales 表,然后使用 INSERT 语句向其中插入若干行,并使用 SELECT 语句从源表中检索数据。EmployeeSales 表包含标识列 (EmployeeID) 和计算列 (ProjectedSales)。由于这些值是在插入操作期间由数据库引擎生成的,因此不能在 @MyTableVar 中定义上述两列。

USE AdventureWorks ;
GO
IF OBJECT_ID ('dbo.EmployeeSales', 'U') IS NOT NULL
    DROP TABLE dbo.EmployeeSales;
GO
CREATE TABLE dbo.EmployeeSales
( EmployeeID   int IDENTITY (1,5)NOT NULL,
  LastName     nvarchar(20) NOT NULL,
  FirstName    nvarchar(20) NOT NULL,
  CurrentSales money NOT NULL,
  ProjectedSales AS CurrentSales * 1.10 
);
GO
DECLARE @MyTableVar table(
  LastName     nvarchar(20) NOT NULL,
  FirstName    nvarchar(20) NOT NULL,
  CurrentSales money NOT NULL
  );

INSERT INTO dbo.EmployeeSales (LastName, FirstName, CurrentSales)
  OUTPUT INSERTED.LastName, 
         INSERTED.FirstName, 
         INSERTED.CurrentSales
  INTO @MyTableVar
    SELECT c.LastName, c.FirstName, sp.SalesYTD
    FROM HumanResources.Employee AS e
        INNER JOIN Sales.SalesPerson AS sp
        ON e.EmployeeID = sp.SalesPersonID 
        INNER JOIN Person.Contact AS c
        ON e.ContactID = c.ContactID
    WHERE e.EmployeeID LIKE '2%'
    ORDER BY c.LastName, c.FirstName;

SELECT LastName, FirstName, CurrentSales
FROM @MyTableVar;
GO
SELECT EmployeeID, LastName, FirstName, CurrentSales, ProjectedSales
FROM dbo.EmployeeSales;
GO

C. 插入从 OUTPUT 子句返回的数据

下面的示例捕获从 MERGE 语句的 OUTPUT 子句返回的数据,并将该数据插入到另一个表中。MERGE 语句根据在 SalesOrderDetail 表中处理的订单,每天更新 ProductInventory 表的 Quantity 列。它还删除库存降为 0 的产品所在的行。该示例捕获已删除的行并将这些行插入到另一个表 ZeroInventory 中,以跟踪库存为零的产品。

USE AdventureWorks;
GO
IF OBJECT_ID(N'Production.ZeroInventory', N'U') IS NOT NULL
    DROP TABLE Production.ZeroInventory;
GO
--Create ZeroInventory table.
CREATE TABLE Production.ZeroInventory (ProductID int);
GO

INSERT INTO Production.ZeroInventory (ProductID)
SELECT ProductID
FROM
(   MERGE Production.ProductInventory AS pi
    USING (SELECT ProductID, SUM(OrderQty) FROM Sales.SalesOrderDetail AS sod
           JOIN Sales.SalesOrderHeader AS soh
           ON sod.SalesOrderID = soh.SalesOrderID
           AND soh.OrderDate = '20030401'
           GROUP BY ProductID) AS src (ProductID, OrderQty)
    ON (pi.ProductID = src.ProductID)
    WHEN MATCHED AND pi.Quantity - src.OrderQty <= 0
        THEN DELETE
    WHEN MATCHED
        THEN UPDATE SET pi.Quantity = pi.Quantity - src.OrderQty
    OUTPUT $action, deleted.ProductID) AS Changes (Action, ProductID)
WHERE Action = 'DELETE';
GO
SELECT ProductID FROM Production.ZeroInventory;