T-SQL基于视图列获取表列

时间:2017-01-25 22:18:14

标签: sql-server tsql

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

表:潜在客户

  • 列:Id,Date,CustId,SalesId

表:人

  • 列:Id,First,Last,email

表:地址

  • 列:Id,Line1,Line2,City,State,Zip

然后是这样的观点......

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]就是我在这里尝试解决的问题。

1 个答案:

答案 0 :(得分:0)

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

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

请在此处查看完整的限制列表Updatable Views。如果这是你可能的方式,请评论。

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

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

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

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

See the updated columns: Customer.Email and Addr1

所以,你需要的只是在更新基表时向DBA人员询问是否也更新了这个INSTEAD OF UPDATE触发器,你就完成了!。

这不是一个很好的解决方案吗?我觉得再去sys.views和frieds要好得多...让我知道这是否有帮助。欢迎投票;)。

UPDATE:可更新视图的一些示例(使用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后的基表)。

enter image description here

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

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.

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

我希望我已经正确地理解了你想要做的事情并希望这会有所帮助。