【问题标题】:T-SQL Get Table Column based on View ColumnT-SQL 根据视图列获取表列
【发布时间】:2017-02-02 14:44:30
【问题描述】:

我有一个连接几个不同表的 SQL 视图,并且在某些情况下会更改返回的列 [编辑,而不是名称] 。使用此数据集的应用程序需要偶尔更新视图基础表中的一个或多个列。我有一个简单的虚构示例来说明这一点……

表:领先

  • 列:Id、Date、CustId、SalesId

表格:人

  • 列:Id、First、Last、email

表格:地址

  • 列:Id、Line1、Line2、城市、州、邮编

然后是这样的视图……

Create view uvw_LeadActivity
As
Select
    L.Id as ‘LeadId’,
    C.Id as ‘CustomerId’,
    C.Last as ‘Customer.LastName’,
    C.First as ’Customer.FirstName’,
    C.email as ’Customer.Email’,
    A.Id as ‘AddressId’,
    A.Line1 as ‘Address1’,
    A.Line2 as ‘Address2’,
    A.City, 
    A.State,
    A.Zip,
    S.Id as ‘SalesId’,
    S.Last as ‘Sales.LastName’,
    S.First as Sales.FirstName’,
    S.email as Sales.Email’,
    L.Date
From
    Lead L
    Inner join Person C on C.Id = L.CustId
    Inner join Person S on S.Id = L.SalesId 

在应用程序中,我有视图列名称、值以及值是否已更改的指示符。我想发送视图列名称和已更改的值。因此,如果用户更新了 Customer.Email 列,我需要能够找出视图列来自的表和列来更新它。

我觉得我很接近,但我错过了一些东西,我希望很简单。我有这个 SQL 语句,用于尝试获取视图映射到的表和列...

SELECT  
    v.object_Id VIEW_ID,
    v.name AS VIEW_NAME,
    t.object_id AS TABLE_ID,
    t.name AS TABLE_NAME,  
    c.name AS COLUMN_NAME,
    c.column_id AS COLUMN_ID
FROM  
 sys.views v
 JOIN sys.sql_dependencies d ON d.object_id = v.object_id  
 JOIN sys.objects t ON t.object_id = d.referenced_major_id  
 JOIN sys.columns c ON c.object_id = d.referenced_major_id AND c.column_id = d.referenced_minor_id  
WHERE  
 v.name='[VIEWNAME]'

我的结果看起来像这样(精简):

VIEW_ID     VIEW_NAME           TABLE_ID    TABLE_NAME  COLUMN_NAME COLUMN_ID
1703311661  uvw_LeadActivity    199671759   Lead        Id          1
1703311661  uvw_LeadActivity    199671760   Person      Id          1
1703311661  uvw_LeadActivity    199671760   Person      Last        2
1703311661  uvw_LeadActivity    199671760   Person      First       3

我真的希望它看起来像这样(浓缩):

VIEW_ID     VIEW_NAME           VIEW_COLUMN         TABLE_ID    TABLE_NAME  COLUMN_NAME COLUMN_ID
1703311661  uvw_LeadActivity    LeadId              199671759   Lead        Id          1
1703311661  uvw_LeadActivity    Customer.Id         199671760   Person      Id          1
1703311661  uvw_LeadActivity    Customer.LastName   199671760   Person      Last        2
1703311661  uvw_LeadActivity    Customer.FirstName  199671760   Person      First       3

事实是,我并不真正需要整个结果(查询),只需要能够(使用此示例)获取 Customer.Email 的表和列。有了它,我可以编写需要的 SQL 来更新。我无法对这个逻辑进行硬编码,因为 SQL DBA 会随着时间的推移根据业务需要更新视图。

我希望这个问题很清楚。提前致谢!

更新:我希望它能够将单个列和更新值或多个列/值对接收到存储过程中,并从该输入更新基于表。这是总体大纲...

create proc usp_UpdateData
    @EntityId int,
    @columnName varchar(max),
    @newVaule varchar(max)
as

Declare @baseTableName varchar(max)
Declare @baseColumnName varchar(max)
Declare @sqlCmd varchar(max)
DECLARE @ParmDefinition varchar(max);

Select @baseTableName=[Table], @baseColumnName=[Column] from [MAGICQUERY]

SET @ParmDefinition = N'@Table varchar(max), @Column varchar(max), @Value varchar(max) @Id int'; 
SET @sqlCmd = N'Update @Table Set @Column = @Value Where Id = @Id';

EXECUTE sp_executesql @sqlCmd, @ParmDefinition, @Table = @baseTableName, @Column=@baseColumnName, @Value=@newVaule, @Id=@EntityId;

所以 [MAGICQUERY] 是我在这里尝试解决的问题。

【问题讨论】:

  • 最终目标是更新视图还是我读错了? 因此,如果用户更新了 Customer.Email 字段,我需要能够找出视图列来自的表和列来更新它。
  • 看看INFORMATION_SCHEMA的意见
  • 我不认为,现有的元数据允许您动态查找视图在哪个别名下吐出列的值...如果我理解正确的话,我想到的唯一方法是一个自维护的映射表...
  • @SCEV,请参阅示例的更新答案。

标签: sql-server tsql


【解决方案1】:

让我提出一个与你目前所遵循的方向不同的方向。

有一种方法可以将应用于视图列值的更新传播到实际的基表。此类视图称为可更新。但是,有一些限制,例如在更新中只能使用一个表,并且视图列必须直接引用表数据,即不允许计算或聚合列。

Updatable Views 处查看完整的限制列表。如果这对你来说是一种可能的方式,请发表评论。

如果您的观点并不意味着可更新的要求,那么文档建议使用 INSTEAD OF 触发器。

我个人认为搜索内部 SQL Server 引用表只是为了获取视图基表中的列名并稍后更新它不是一个好主意。如果可更新视图不​​适合您,您可以考虑在 EDIT/UPDATE 操作期间更改应用程序以直接使用基表。


UPDATE1:第二次尝试,现在使用 INSTEAD OF 触发器:

当您尝试从 2 个以上的表中更新视图和引用列时,会发生以下情况:

update uvw_LeadActivity set 
[Customer.Email] = 'andrew2@server.com', -- col from Person
Address1='Addr1 St.'                     -- col from Address
go

Msg 4405, Level 16, State 1, Line 1
View or function 'uvw_LeadActivity' is not updatable because the modification affects multiple base tables.

现在,如果我们为视图定义 INSTEAD OF UPDATE 触发器,如下所示:

--drop trigger tru_uvwLeadActvity
--go

CREATE TRIGGER tru_uvwLeadActvity
ON uvw_LeadActivity
INSTEAD OF UPDATE
AS
BEGIN
  UPDATE Person
  SET
    Email = INSERTED.[Customer.Email],
    Last = INSERTED.[Customer.LastName],
    First = INSERTED.[Customer.FirstName]    
  FROM INSERTED
  WHERE INSERTED.CustomerId = Person.Id    
  ;

  UPDATE Person
  SET
    Email = INSERTED.[Sales.Email],
    Last = INSERTED.[Sales.LastName],
    First = INSERTED.[Sales.FirstName]    
  FROM INSERTED
  WHERE INSERTED.SalesId = Person.Id    
  ;

  UPDATE Address
  SET  
    Line1 = INSERTED.Address1,
    Line2 = INSERTED.Address2,
    City = INSERTED.City,
    State = INSERTED.State,
    Zip = INSERTED.Zip    
  FROM INSERTED
  WHERE INSERTED.AddressId = Address.Id    
  ;

  print 'Updating 2 tables from uvw_LeadActivity'
END
go

然后我们在视图上调用我们的表间更新:

set nocount on
update uvw_LeadActivity set 
[Customer.Email] = 'andrew2@server.com', Address1='Addr1 St.'
go

我们得到了我们的信息:

Updating 2 tables from uvw_LeadActivity

再次从视图中选择显示我们的列确实已更新:

因此,您只需在更新基表时询问 DBA 人员,以同时更新此 INSTEAD OF UPDATE 触发器,您就完成了!

这不是一个很好的解决方案吗?我认为这比去 sys.views 和炸薯条要好得多......如果这有帮助,请告诉我。也欢迎投票;)。


更新:一些可更新视图的示例(使用 SQL Server 2008 R2 测试):

create table Address (Id int not null identity(1,1), Line1 varchar(30), Line2 varchar(30), City varchar(30), State varchar(10), Zip varchar(10))
insert into Address (Line1,Line2, City, State, Zip) values ('Addr1_1', 'Addr_1_2', 'Houston', 'TX', '77001')
insert into Address (Line1,Line2, City, State, Zip) values ('Addr2_1', 'Addr_2_2', 'Atlanta', 'GA', '30301')
go
create table Person(Id int not null identity(1,1), First varchar(30), Last varchar(30), email varchar(30), fk_address_id int)
insert into Person (First, Last, email, fk_address_id) values ('Andrew', 'Customer', 'customer@server.com', 1)
insert into Person (First, Last, email, fk_address_id) values ('John', 'Sales', 'sales@server.com', 2)
go
create table Lead (Id int not null identity(1,1), Date datetime, CustId int, SalesId int)
insert into Lead (Date, CustId, SalesId) values (GETDATE(), 1,2)
go

Create view uvw_LeadActivity --WITH SCHEMABINDING
As
Select
    L.Id as 'LeadId',
    C.Id as 'CustomerId',
    C.Last as 'Customer.LastName',
    C.First as 'Customer.FirstName',
    C.email as 'Customer.Email',
    A.Id as 'AddressId',
    A.Line1 as 'Address1',
    A.Line2 as 'Address2',
    A.City, 
    A.State,
    A.Zip,
    S.Id as 'SalesId',
    S.Last as 'Sales.LastName',
    S.First as 'Sales.FirstName',
    S.email as 'Sales.Email',
    L.Date
From
    dbo.Lead L
    Inner join dbo.Person C on C.Id = L.CustId
    Inner join dbo.Person S on S.Id = L.SalesId
    Inner join dbo.Address A on C.fk_address_id = A.id
go

select * from uvw_LeadActivity

update uvw_LeadActivity set [Customer.Email] = 'andrew@server.com'
go
select * from Person
go

产生以下结果:(注意第一个选择结果来自您,第二个来自我们更新 VIEW 后的基表)。

现在,如果您重命名基表列并尝试从视图中选择,会发生以下情况:

sp_RENAME 'Person.Last' , 'Last_Name', 'COLUMN'
go
select * from uvw_LeadActivity
go

Caution: Changing any part of an object name could break scripts and stored procedures.
Msg 207, Level 16, State 1, Procedure uvw_LeadActivity, Line 7
Invalid column name 'Last'.
Msg 207, Level 16, State 1, Procedure uvw_LeadActivity, Line 17
Invalid column name 'Last'.
Msg 4413, Level 16, State 1, Line 1
Could not use view or function 'uvw_LeadActivity' because of binding errors.

但如果你的视图是用SCHEMABINDING 定义的,就像

Create view uvw_LeadActivity WITH SCHEMABINDING
As
...

SQL Server 不允许您重命名基表列:

Msg 15336, Level 16, State 1, Procedure sp_rename, Line 444
Object 'Person.Last' cannot be renamed because the object participates in enforced dependencies.

这是防止意外基列重命名/删除的好方法。

我希望我已经正确理解了你想要做什么,并希望这会有所帮助。

【讨论】:

  • 感谢您的详细回复安德鲁斯。可更新视图是一种有趣的方法,我可能会成功,但它不是最佳的。现有视图有 200 多列与 7 个表连接(我的示例是一个非常简化的示例以说明我的需要),并且很可能需要更新来自多个基表的列。我试图在我的解释中不要变得太复杂。我真的很想为给定的视图列找到相应的基表和列。
  • @SCEV 我明白了。我认为只有 1 个表中的列的要求仅适用于单个 UPDATE 调用。因此,请查看您的应用程序中是否可以将更新范围缩小到仅更改的列,而不是包含所有 200 个列的 UPDATE。如果必须更新 2 个以上表中的列,则应将它们拆分为多个 UPDATE 调用,每个表 1 个。是的,效率不高。因此,我建议尝试直接使用基表。
  • 对,如果我找不到解决方案,我将不得不走这条路。有几个业务原因我不能只使用基表,数据库是“动态的”,因为 DBA 将添加列,甚至当业务决定他们需要捕获额外数据时,我是一个外部的表试图提供解决方案的顾问,每次发生这种情况时开发人员都不需要参与。他们只需要更新我的应用程序使用的主视图。看看我的更新,希望能更好地显示我想要的目标。
  • 我刚刚意识到我有一个错字,很可能是最错误的。 ;(我更新了OP
  • @SCEV 我刚刚添加了带有 INSTEAD OF UPDATE 触发器的示例。我觉得挺好看的,看看吧。
猜你喜欢
  • 1970-01-01
  • 2021-06-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-18
  • 2022-01-17
相关资源
最近更新 更多