重构tsql视图,该视图使用row_number()返回具有唯一列值的行

重构tsql视图,该视图使用row_number()返回具有唯一列值的行,sql,sql-server,tsql,sql-view,Sql,Sql Server,Tsql,Sql View,我有一个sql视图,用于检索数据。比如说,这是一个庞大的产品列表,与购买这些产品的客户相关。无论视图链接到多少客户,每个产品都应该只返回一行。我正在使用row_number函数来实现这一点。(此示例已简化,一般情况是,对于某个列X的每个唯一值,只应返回一行。返回哪一行并不重要) 现在假设此视图中的行总数约为100万,从productView运行select*需要10秒。在productID=10的productView中执行诸如select*之类的查询需要相同的时间。我相信这是因为查询的计算结果

我有一个sql视图,用于检索数据。比如说,这是一个庞大的产品列表,与购买这些产品的客户相关。无论视图链接到多少客户,每个产品都应该只返回一行。我正在使用row_number函数来实现这一点。(此示例已简化,一般情况是,对于某个列X的每个唯一值,只应返回一行。返回哪一行并不重要)

现在假设此视图中的行总数约为100万,从productView运行select*需要10秒。在productID=10的productView中执行诸如select*之类的查询需要相同的时间。我相信这是因为查询的计算结果是这样的

SELECT * FROM 
    (SELECT 
        Row_number() OVER(PARTITION BY products.Id ORDER BY products.Id) AS product_numbering,
        customer.Id
        //various other columns
    FROM products
    LEFT OUTER JOIN customer ON customer.productId = prodcut.Id
    //various other joins
    ) as temp
WHERE prodcut_numbering = 1 and prodcut.Id = 10
我认为这会导致每次都对内部子查询进行完整计算。理想情况下,我希望使用以下内容

SELECT 
    Row_number() OVER(PARTITION BY products.productID ORDER BY products.productID) AS product_numbering,
    customer.id
    //various other columns
FROM products
    LEFT OUTER JOIN customer ON customer.productId = prodcut.Id
    //various other joins
WHERE prodcut_numbering = 1
但这似乎是不允许的。有没有类似的方法

编辑-

经过大量的实验,我认为我面临的实际问题是如何强制联接返回恰好1行。我尝试使用外部应用,如下所示。一些示例代码

CREATE TABLE Products (id int not null PRIMARY KEY)
CREATE TABLE Customers (
        id int not null PRIMARY KEY,
        productId int not null,
        value varchar(20) NOT NULL)

declare @count int = 1
while @count <= 150000
begin
        insert into Customers (id, productID, value)
        values (@count,@count/2, 'Value ' + cast(@count/2 as varchar))      
        insert into Products (id) 
        values (@count)
        SET @count = @count + 1
end

CREATE NONCLUSTERED INDEX productId ON Customers (productID ASC)
运行大约需要1000毫秒。添加显式条件:

select * from Products
outer apply (select top 1 * 
            from Customers
            where Products.id = Customers.productID) Customers
where Customers.value = 'Value 45872'

需要相同的时间。对于一个相当简单的查询来说,这1000毫秒已经太多了,并且在添加其他类似联接时会以错误的方式(向上)缩放。

如果您执行以下操作会怎么样

SELECT ...
FROM products
OUTER APPLY (SELECT TOP 1 * from customer where customerid = products.buyerid) as customer
...

那么productId上的过滤器应该会有所帮助。不过,如果不进行筛选,情况可能会更糟。

请尝试以下方法,使用公共表表达式(CTE)。使用您提供的测试数据,它将在不到一秒钟的时间内返回特定的ProductID

create view ProductTest as 

with cte as (
select 
    row_number() over (partition by p.id order by p.id) as RN, 
    c.*
from 
    Products p
    inner join Customers c
        on  p.id = c.productid
)

select * 
from cte
where RN = 1
go

select * from ProductTest where ProductId = 25

问题是您的数据模型有缺陷。您应该有三个表:

  • 客户(客户ID,…)
  • 产品(产品ID,…)
  • ProductSales(customerId、productId)

此外,sale表可能应该分为1对多(Sales和SalesDetails)。除非你修正了你的数据模型,否则你只会绕着你的尾巴转一圈,去追逐那些转移注意力的问题。如果系统不是您的设计,请修复它。如果老板不让你去修,那就修吧。如果你不能修复它,那么修复它。对于你提出的糟糕的数据模型,没有一个简单的解决方法。

如果你真的不在乎你带回了哪个客户,那么这可能足够快了

select p1.*, c1.*
FROM products p1
Left Join (
        select p2.id, max( c2.id) max_customer_id
        From product p2
        Join customer c2 on
        c2.productID = p2.id
        group by 1
) product_max_customer
Left join customer c1 on
c1.id = product_max_customer.max_customer_id
;

您需要实际的客户详细信息,还是只需要客户ID?子查询是evaluatede,因为“10”事先不知道。你要的正是第十排。因此,我的第一个问题是关于期望的输出非常好的观察-SQL无法将视图过滤器应用到子查询中。您真的需要视图的灵活性吗?如果将存储过程或表值函数与“已定义”筛选器(示例中为ProductID)一起使用,则可以将筛选器构建到子查询中。如果您的分区依据和筛选器相同(ProductId),则根本不需要该分区-因此选择TOP 1就足够了。我需要实际的客户详细信息(如果不存在,则为空值),而不仅仅是某个客户的存在。我还必须使用视图,重构检索数据的应用程序是不可能的。@John,这对我来说没有意义;在帖子中,你说无论与多少客户绑定,每个产品都能获得1行。所以,在我看来,你甚至不需要加入客户表。我遗漏了什么吗?这个例子很简单,假设我需要一个客户的详细信息,只要我得到一些详细信息,我得到哪一个并不重要。这听起来很奇怪,这是我正在使用的系统的方式(不是我的设计!)。这看起来确实比其他方法快得多,但它仍然会导致对整个子查询进行评估。单独执行
select*fromProductTest
所需的时间大致相同,执行计划也相同,就像执行where子句一样。由于视图本身的性质,我认为这是您能够获得的最好结果。另一个选项是创建一个存储过程,或者可能是一个表值函数,它传入productid,并且可以直接对所需查询的部分进行过滤。
create view ProductTest as 

with cte as (
select 
    row_number() over (partition by p.id order by p.id) as RN, 
    c.*
from 
    Products p
    inner join Customers c
        on  p.id = c.productid
)

select * 
from cte
where RN = 1
go

select * from ProductTest where ProductId = 25
select p1.*, c1.*
FROM products p1
Left Join (
        select p2.id, max( c2.id) max_customer_id
        From product p2
        Join customer c2 on
        c2.productID = p2.id
        group by 1
) product_max_customer
Left join customer c1 on
c1.id = product_max_customer.max_customer_id
;