Рефакторинг представления tsql, которое использует row_number () для возврата строк с уникальным значением столбца - PullRequest
5 голосов
/ 18 октября 2011

У меня есть представление sql, которое я использую для извлечения данных. Допустим, это большой список продуктов, которые связаны с покупателями. Представление должно возвращать только одну строку для каждого продукта, независимо от того, с какими клиентами оно связано. Я использую функцию row_number для достижения этой цели. (Этот пример упрощен, общая ситуация была бы запросом, в котором должна быть только одна строка, возвращенная для каждого уникального значения некоторого столбца X. Какая строка возвращается, не важно)

CREATE VIEW productView AS
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 temp.prodcut_numbering = 1

Теперь предположим, что общее количество строк в этом представлении составляет ~ 1 миллион, а выполнение select * из productView занимает 10 секунд. Выполнение запроса, такого как select * from productView, где productID = 10, занимает столько же времени. Я считаю, что это потому, что запрос оценивается на

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)

При указанном выше примере выборки запрос «получить все» ниже

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

требуется ~ 1000 мс для запуска. Добавление явного условия:

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

Занимает одинаковое количество времени. Эти 1000 мс для довольно простого запроса уже слишком велики и неправильно масштабируются (вверх) при добавлении дополнительных похожих объединений.

Ответы [ 4 ]

3 голосов
/ 24 октября 2011

Попробуйте следующий подход, используя общее табличное выражение (CTE).С предоставленными вами тестовыми данными он возвращает конкретные ProductIds менее чем за секунду.

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
2 голосов
/ 19 октября 2011

Что делать, если вы сделали что-то вроде:

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

Тогда фильтр на productId должен помочь.Хотя может быть хуже без фильтрации.

1 голос
/ 29 октября 2011

Проблема в том, что ваша модель данных имеет недостатки.У вас должно быть три таблицы:

  • Клиенты (customerId, ...)
  • Продукты (productId, ...)
  • ProductSales (customerId, productId)

Кроме того, таблицу продаж, вероятно, следует разделить на 1-ко-многим (Sales и SalesDetails).Если вы не исправите свою модель данных, вы просто будете бегать кругами вокруг хвоста, преследуя проблемы с красной сельдью.Если система не вашего дизайна, исправить это.Если босс не позволит вам исправить это, то исправьте это.Если вы не можете это исправить, то исправьте это.Для плохой модели данных, которую вы предлагаете, нелегкого выхода.

0 голосов
/ 04 июня 2014

это, вероятно, будет достаточно быстро, если вам действительно все равно, к какому клиенту вы вернете

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
;
...