【问题标题】:Problems enforcing referential integrity on SQL Server tables在 SQL Server 表上强制执行参照完整性的问题
【发布时间】:2010-05-09 03:10:27
【问题描述】:

我有一个由CustomersQuoteQuoteDetail 表组成的 SQL Server 2005 数据库。我想要/需要强制执行引用完整性,这样当在QuoteDetail 上进行插入时,报价和客户表也会受到影响。我已尽力在我的桌子上设置主键/外键,但需要一些帮助。这是我的桌子现在的脚本(请不要笑):

客户:

CREATE TABLE [dbo].[Customers](
    [pkCustID] [int] IDENTITY(1,1) NOT NULL,
    [CompanyName] [nvarchar](50) NULL,
    [Address] [nvarchar](50) NULL,
    [City] [nvarchar](50) NULL,
    [State] [nvarchar](2) NULL,
    [ZipCode] [nvarchar](5) NULL,
    [OfficePhone] [nvarchar](12) NULL,
    [OfficeFAX] [nvarchar](12) NULL,
    [Email] [nvarchar](50) NULL,
    [PrimaryContactName] [nvarchar](50) NULL,

    CONSTRAINT [PK_Customers] 
      PRIMARY KEY CLUSTERED([pkCustID] ASC)
)

行情:

CREATE TABLE [dbo].[Quotes](
    [pkQuoteID] [int] IDENTITY(1,1) NOT NULL,
    [fkCustomerID] [int] NOT NULL,
    [QuoteDate] [timestamp] NOT NULL,
    [NeedbyDate] [datetime] NULL,
    [QuoteAmt] [decimal](6, 2) NOT NULL,
    [QuoteApproved] [bit] NOT NULL,
    [fkOrderID] [int] NOT NULL,

    CONSTRAINT [PK_Bids] 
       PRIMARY KEY CLUSTERED ([pkQuoteID] ASC)
) 
GO

ALTER TABLE [dbo].[Quotes] WITH CHECK 
   ADD CONSTRAINT [fkCustomerID] 
   FOREIGN KEY([fkCustomerID]) REFERENCES [dbo].[Customers] ([pkCustID])

报价详情:

CREATE TABLE [dbo].[QuoteDetail](
[ID] [int] IDENTITY(1,1) NOT NULL,
[fkQuoteID] [int] NOT NULL,
[fkCustomerID] [int] NOT NULL,
[fkPartID] [int] NULL,
[PartNumber1] [float] NOT NULL,
[Qty1] [int] NOT NULL,
[PartNumber2] [float] NULL,
[Qty2] [int] NULL,
[PartNumber3] [float] NULL,
[Qty3] [int] NULL,
[PartNumber4] [float] NULL,
[Qty4] [int] NULL,
[PartNumber5] [float] NULL,
[Qty5] [int] NULL,
[PartNumber6] [float] NULL,
[Qty6] [int] NULL,
[PartNumber7] [float] NULL,
[Qty7] [int] NULL,
[PartNumber8] [float] NULL,
[Qty8] [int] NULL,
[PartNumber9] [float] NULL,
[Qty9] [int] NULL,
[PartNumber10] [float] NULL,
[Qty10] [int] NULL,
[PartNumber11] [float] NULL,
[Qty11] [int] NULL,
[PartNumber12] [float] NULL,
[Qty12] [int] NULL,
[PartNumber13] [float] NULL,
[Qty13] [int] NULL,
[PartNumber14] [float] NULL,
[Qty14] [int] NULL,
[PartNumber15] [float] NULL,
[Qty15] [int] NULL,
[PartNumber16] [float] NULL,
[Qty16] [int] NULL,
[PartNumber17] [float] NULL,
[Qty17] [int] NULL,
[PartNumber18] [float] NULL,
[Qty18] [int] NULL,
[PartNumber19] [float] NULL,
[Qty19] [int] NULL,
[PartNumber20] [float] NULL,
[Qty20] [int] NULL,
    CONSTRAINT [PK_QuoteDetail] 
       PRIMARY KEY CLUSTERED([ID] ASC)
)
GO

ALTER TABLE [dbo].[QuoteDetail] WITH CHECK 
  ADD CONSTRAINT [FK_QuoteDetail_Customers] 
  FOREIGN KEY([fkCustomerID]) REFERENCES [dbo].[Customers] ([pkCustID])

ALTER TABLE [dbo].[QuoteDetail] WITH CHECK 
   ADD CONSTRAINT [FK_QuoteDetail_PartList] 
   FOREIGN KEY([fkPartID]) REFERENCES [dbo].[PartList] ([RecID])

ALTER TABLE [dbo].[QuoteDetail] WITH CHECK 
   ADD CONSTRAINT [FK_QuoteDetail_Quotes] 
   FOREIGN KEY([fkQuoteID]) REFERENCES [dbo].[Quotes] ([pkQuoteID])

您对如何设置这些以使客户中的客户 ID 与报价中的客户 ID 相同(引用完整性)以及在向 QuoteDetial 进行插入时将客户 ID 插入到报价和客户中的建议/指导将不胜感激。

更新:对表进行了更改,现在是存储过程:

USE [Diel_inventory]
GO
/****** Object:  StoredProcedure [dbo].[AddQuote]    Script Date: 05/08/2010 14:57:28 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER procedure [dbo].[AddQuote] as
Declare @CustID int,
@CompanyName nvarchar(50),
@Address nvarchar(50),
@City nvarchar(50),
@State nvarchar(2),
@ZipCode nvarchar(5),
@Phone nvarchar(12),
@FAX nvarchar(12),
@Email nvarchar(50),
@ContactName nvarchar(50),
@QuoteID int,
@QuoteDate datetime,
@NeedbyDate datetime,
@QuoteAmt decimal,
@ID int,
@QuoteDetailPartID int,
@PartNumber float,
@Quantity int

begin

Insert into dbo.Customers
(CompanyName, Address, City, State, ZipCode, OfficePhone, OfficeFAX, Email, PrimaryContactName)
Values (@CompanyName, @Address, @City, @State, @ZipCode, @Phone, @FAX, @Email, @ContactName)

set @CustID = scope_identity()


Insert into dbo.Quotes 
(fkCustomerID,NeedbyDate,QuoteAmt)
Values(@CustID,@NeedbyDate,@QuoteAmt)

set @QuoteID = scope_identity() 


Insert into dbo.QuoteDetail
(ID) values(@ID)

set @ID=scope_identity()



Insert into dbo.QuoteDetailParts
(QuoteDetailPartID, QuoteDetailID, PartNumber, Quantity)
values (@ID, @QuoteDetailPartID, @PartNumber, @Quantity) 
END

在 SSMS 中,参数在此存储过程的树形视图中不可见。另外,当我尝试从我的 ASPX 页面调用这个存储过程时,我收到了

System.Data.SqlClient.SqlException: Procedure AddQuote has no parameters and arguments were supplied

这是在我的 ASPX 页面上定义的 SQLDataSource:

<asp:SqlDataSource runat="server" ID="SDSAddQuote" 
    ConnectionString="<%$ ConnectionStrings:Diel_inventoryConnectionString %>" 
    InsertCommand="AddQuote" InsertCommandType="StoredProcedure">
    <InsertParameters>
    <asp:ControlParameter ControlID="txtCompanyName" type="String" Name="CompanyName" />
    <asp:ControlParameter ControlID="txtCompanyAddress" Type="String" Name="CompanyAddress" />
    <asp:ControlParameter ControlID="txtCompanyCity" Type="String" Name="CompanyCity" />
    <asp:ControlParameter ControlID="ddlCompanyState" Type="String" Name="CompanyState" />
    <asp:ControlParameter ControlID="txtCompanyZip" Type="String" Name="CompanyZip" />
    <asp:ControlParameter ControlID="calNeedByDate" Type="DateTime" Name="NeedByDate" />
    <asp:ControlParameter ControlID="txtPartNumber1" Type="String" Name="PartNumber1" />
    <asp:ControlParameter ControlID="txtQty1" Type="Int16" Name="Qty1" />
            <asp:ControlParameter ControlID="txtPartNumber2" Type="String" Name="PartNumber2" />
    <asp:ControlParameter ControlID="txtQty2" Type="Int16" Name="Qty2" />
            <asp:ControlParameter ControlID="txtPartNumber3" Type="String" Name="PartNumber3" />
    <asp:ControlParameter ControlID="txtQty3" Type="Int16" Name="Qty3" />
            <asp:ControlParameter ControlID="txtPartNumber4" Type="String" Name="PartNumber4" />
    <asp:ControlParameter ControlID="txtQty4" Type="Int16" Name="Qty4" />
            <asp:ControlParameter ControlID="txtPartNumber5" Type="String" Name="PartNumber5" />
    <asp:ControlParameter ControlID="txtQty5" Type="Int16" Name="Qty5" />
            <asp:ControlParameter ControlID="txtPartNumber6" Type="String" Name="PartNumber6" />
    <asp:ControlParameter ControlID="txtQty6" Type="Int16" Name="Qty6" />
            <asp:ControlParameter ControlID="txtPartNumber7" Type="String" Name="PartNumber7" />
    <asp:ControlParameter ControlID="txtQty7" Type="Int16" Name="Qty7" />
            <asp:ControlParameter ControlID="txtPartNumber8" Type="String" Name="PartNumber8" />
    <asp:ControlParameter ControlID="txtQty8" Type="Int16" Name="Qty8" />
            <asp:ControlParameter ControlID="txtPartNumber9" Type="String" Name="PartNumber9" />
    <asp:ControlParameter ControlID="txtQty9" Type="Int16" Name="Qty9" />
            <asp:ControlParameter ControlID="txtPartNumber10" Type="String" Name="PartNumber10" />
    <asp:ControlParameter ControlID="txtQty10" Type="Int16" Name="Qty10" />
            <asp:ControlParameter ControlID="txtPartNumber11" Type="String" Name="PartNumber11" />
    <asp:ControlParameter ControlID="txtQty11" Type="Int16" Name="Qty11" />
            <asp:ControlParameter ControlID="txtPartNumber12" Type="String" Name="PartNumber12" />
    <asp:ControlParameter ControlID="txtQty12" Type="Int16" Name="Qty12" />
            <asp:ControlParameter ControlID="txtPartNumber13" Type="String" Name="PartNumber13" />
    <asp:ControlParameter ControlID="txtQty13" Type="Int16" Name="Qty13" />
            <asp:ControlParameter ControlID="txtPartNumber14" Type="String" Name="PartNumber14" />
    <asp:ControlParameter ControlID="txtQty14" Type="Int16" Name="Qty14" />
            <asp:ControlParameter ControlID="txtPartNumber15" Type="String" Name="PartNumber15" />
    <asp:ControlParameter ControlID="txtQty15" Type="Int16" Name="Qty15" />
            <asp:ControlParameter ControlID="txtPartNumber16" Type="String" Name="PartNumber16" />
    <asp:ControlParameter ControlID="txtQty16" Type="Int16" Name="Qty16" />
            <asp:ControlParameter ControlID="txtPartNumber17" Type="String" Name="PartNumber17" />
    <asp:ControlParameter ControlID="txtQty17" Type="Int16" Name="Qty17" />
            <asp:ControlParameter ControlID="txtPartNumber18" Type="String" Name="PartNumber18" />
    <asp:ControlParameter ControlID="txtQty18" Type="Int16" Name="Qty18" />
            <asp:ControlParameter ControlID="txtPartNumber19" Type="String" Name="PartNumber19" />
    <asp:ControlParameter ControlID="txtQty19" Type="Int16" Name="Qty19" />     
           <asp:ControlParameter ControlID="txtPartNumber20" Type="String" Name="PartNumber20" />
    <asp:ControlParameter ControlID="txtQty20" Type="Int16" Name="Qty20" />   
    </InsertParameters>
</asp:SqlDataSource>

为什么我的参数不可用,尽管在我的存储过程的开头声明了它们?
谢谢, 席德

【问题讨论】:

  • 您在存储过程中将它们声明为 局部变量 -- NOT 作为存储过程的参数!!
  • 如果你想为你的存储过程定义参数,你需要把它们放在AS关键字之前

标签: sql-server-2005 database-design


【解决方案1】:

你到底想做什么??

我想要/需要强制引用 完整性使得当插入 在quotedetail,报价和 客户表也受到影响。

“...报价和客户表也受到影响”是什么意思。影响如何??

使用外键的参照完整性意味着您不能插入在Customers 表中找不到的QuotefkCustomerID

同样,您不能插入在Quote 表中找不到的值为fkQuoteIDQuoteDetail

那么当插入QuoteDetail 时你想做什么呢?另外两个表应该如何“受影响”??

附注:由于您的QuoteDetail 表引用了Quote 表,而后者又引用了Customers 表,因此在QuoteDetails 中包含fkCustomerId 确实没有意义。我假设是否将Quote 分配给客户编号。 12345,那么属于该报价的所有QuoteDetails 也将与客户编号相关。 12345对吧??所以只需从QuoteDetails 引用Quote,这就足够了!否则,你只是让你的模型过于复杂而没有任何收获,真的。

旁注 #2:我会强烈建议您删除 QuoteDetail 中的那 20 对 PartNumberX / QtyX - 将它们放入单独的表中,例如QuoteDetailParts,它引用了 QuoteDetail 详细信息并具有

QuoteDetailPartID    INT   PK
QuoteDetailID        INT   FK  --> to QuoteDetail table
PartNumber           FLOAT
Quantity             INT

作为它的字段。这是更简洁的方法和设计 - 如果需要,这将允许您仅存储两个或三个部分 - 如果需要,还可以存储 21、25 - 而无需更改数据库!

更新:如果要将值插入QuoteDetail,首先需要在Quote 中有一个条目,因此在Customers 中有一个条目。在您的存储过程中,您可以执行以下操作:

CREATE PROCEDURE dbo.InsertQuoteDetail
  (define list of parameters you want to pass in)
AS BEGIN
  DECLARE @CustomerID INT

  INSERT INTO dbo.Customers(list of fields)
    VALUES(list of values)

  SET @CustomerID = SCOPE_IDENTITY()

  DECLARE @QuoteID INT

  INSERT INTO dbo.Quote(CustomerID, (list of other fields))
     VALUES(@CustomerID, ....other values.....)

  SET @QuoteID = SCOPE_IDENTITY()

  INSERT INTO dbo.QuoteDetails(QuoteID, ....other fields....)
     VALUES(@QuoteID, ...... other values......)

【讨论】:

  • @marc_s:感谢您的回复,夜猫子规则! :) 当在 QuoteDetail 中插入新记录时,我希望 Quotes 和 Customers 表都更新。一个新的 CustomerID 被插入到客户中,并且在 Quotes 中也被引用。同样,新的 QuoteID 被插入到 Quotes 中并在 QuoteDetail 中引用。如果这没有意义,请告诉我,我会在早上再试一次。谢谢!
  • @SidC:这里是周六上午 10:40 :-) 晨鸟统治! :-)
  • @SIdC:你不能这样做——你必须先插入客户,记下新的客户ID;然后将 Quote settnig 其 fkCustomerID 插入新插入的 CustomerID,记下 QuoteID;然后最后根据需要插入 QuoteDetails,使用新创建的 QuoteID 仅引用 Quote 表。这就是参照完整性的全部意义所在。
  • @marc_s:所以,在我的存储过程中,我需要先将字段插入到客户表中,但是我如何获取客户ID,我可以从存储过程中获取它吗?那么,我可以从同一个存储过程中插入 Quote 表吗?同样适用于 QuoteDetails 和 QuoteDetailParts?非常感谢:)
  • @SidC:是的,您需要先将值插入到客户表中。您可以使用 CustomerID = SCOPE_IDENTITY() 获取新的客户 ID,同样适用于 Quote 表。
【解决方案2】:

修复您的 QuoteDetail 表格设计。

如果您正确设计数据库,您的参照完整性就会自行解决。在third-normal form (3NF) 上参考此good example

每条记录有 20 个“PartNumber”列将每个报价限制为 20 个项目,使报价计算复杂化,容易出错并重复数据。

  • 您的Customers 表还可以,但是
  • Quotes 不应有 QuoteAmt 列:这应根据您的 QuoteDetail 表计算得出。
  • 由于QuoteDetail 与引用CustomerIDQuotes 相关,因此您无需将CustomerID 再次存储在QuoteDetail 中。

任何被“引用”的数据都应该始终来自被引用的表。客户姓名应由Customers 表提供;它不应该存储在您的Quotes 表中。同样,Quotes 不应存储其(报价)总数,因为该值取决于 QuoteDetail 中的数据。

这是QuoteDetail的更好设计:

CREATE TABLE [dbo].[QuoteDetail](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [fkQuoteID] [int] NOT NULL,   // which Quote this line/detail belongs to
    [fkPartID] [int] NOT NULL,    // which Part this line describes
    [Quantity] [int] NOT NULL,    // how many Parts
    [UnitPrice] [float] NOT NULL  // how much per Part
)

现在您可以将每个 LineTotal 计算为 Quantity * UnitPrice,然后将其相加以计算您的报价总额 (QuoteAmt)。这种设计将确保您的报价总额始终正确,并且不会重复任何数据。您可以(并且应该)使用视图来计算这些显示值。

请参阅my question 了解发票和报价的数据库设计(与发票非常相似)。修改部分忽略,看表设计。

【讨论】:

  • 非常感谢! QuoteDetail中的ID会是PK吗?
  • 是的。您可以使用fkQuoteIDfkPartID 作为组合键,但是您不能以不同的单价拥有两个相同的部分(这可能适合也可能不适合您的域问题)。为了保持灵活性,我不推荐它。这种方法对于您可能只想调整数量的购物车商品很有用。单个 PK 也使删除和更新更加优雅,因为您只需处理一个参数。
  • 我只选择了这张表中的一个 PK。另一个问题:我的 ASPX 表单有 20 个用于 PartNumber 的文本框控件和 20 个用于 Quantity 的文本框控件。如何将这些值发送到数据库?也就是说,我将使用什么插入语法?例如,对于每个 txtPartNumber(从 1 到 20)插入 txtPartNumber.text 值(@PartNumber)?
  • 在尝试做更多事情之前,首先获取输入以将一行添加到报价中。如果您的QuoteDetails 表引用了您的Parts 表(而不是带有文本描述的通用行),那么您不希望用户提供txtPartName。它应该从您的Parts 表中拉出以进行显示。向用户提供仅提交部件 ID 的部件列表,例如。下拉或自动完成字段。一旦你有这个工作,你可以使用line[n].IDline[n].Quantity和可选的line[n].UnitPrice的输入ID属性命名模式用于多行。
猜你喜欢
  • 2012-07-14
  • 1970-01-01
  • 1970-01-01
  • 2019-01-15
  • 2021-06-04
  • 1970-01-01
  • 1970-01-01
  • 2013-08-14
  • 1970-01-01
相关资源
最近更新 更多