2011-10-18 17 views
5

Mam widok sql, którego używam do pobierania danych. Powiedzmy, że jest to duża lista produktów, które są powiązane z klientami, którzy je kupili. Widok powinien zwracać tylko jeden wiersz na produkt, niezależnie od liczby klientów, z którymi jest połączony. Używam funkcji row_number, aby to osiągnąć. (Ten przykład jest uproszczony, rodzajowy sytuacja byłaby zapytanie tam gdzie powinien być tylko jeden rząd powrócił do każdej unikatowej wartości jakiejś kolumny X. który jest zwracany wiersz nie jest ważna)Refaktoryzacja widoku tsql, który używa row_number() do zwracania wierszy z unikalną wartością kolumny

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 

Teraz powiedzmy, że całkowita liczba wierszy w tym widoku wynosi ~ 1 milion, a wybór opcji * z productView zajmuje 10 sekund. Wykonywanie kwerendy, takie jak wybierz * z productView gdzie productID = 10 trwa tyle samo czasu. Wierzę, że to dlatego, że zapytanie zostanie oceniona na tym

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 

myślę, że to jest przyczyną wewnętrzną podkwerenda być oceniane w pełni każdej chwili. Idealnie chciałbym użyć czegoś wzdłuż następujących linii:

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 

Ale wydaje się, że to nie jest dozwolone. Czy istnieje sposób na zrobienie czegoś podobnego?

EDIT -

Po wielu eksperymentach, rzeczywisty problem wierzę, mam to w jaki sposób wymusić dołączyć do powrotu dokładnie 1 wiersz. Próbowałem użyć zewnętrznego zastosowania, jak sugerowano poniżej. Jakiś przykładowy kod.

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) 

Z powyższego zestawu próbek, 'dostać wszystko' query poniżej

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

trwa ~ 1000ms uruchomić. Dodanie wyraźnego warunku:

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

Trwa to samo identycznie. To 1000ms dla dość prostej kwerendy jest już za dużo i skaluje się w niewłaściwy sposób (w górę) przy dodawaniu dodatkowych podobnych sprzężeń.

+0

Czy potrzebujesz rzeczywistych danych klienta lub po prostu istnienia lub po prostu IDklienta? Podzapyt jest oceniany, ponieważ "10" nie jest znane z góry. I pytasz dokładnie o 10. wiersz. Stąd moje pierwsze pytanie o pożądaną wydajność – gbn

+1

Naprawdę dobra obserwacja - SQL nie jest w stanie zastosować filtra widoku do podzapytania. Czy naprawdę potrzebujesz elastyczności widoku? Jeśli użyłeś funkcji SPROC lub funkcji wycenionej w tabeli z "zdefiniowanymi" filtrami (ProductID w twoim przykładzie), możesz wbudować filtr w podzapytanie. A w przypadku, gdy twoja PARTYCJA PRZEZ i FILTR są takie same (ProductId), nie potrzebujesz wcale PARTYCJI - więc SELECT TOP 1 powinien wystarczyć. – StuartLC

+0

Potrzebuję rzeczywistych informacji o kliencie (lub wartości zerowych, jeśli nie istnieją), a nie tylko o istnieniu jednego. Muszę też użyć widoku, refaktoryzując aplikację, która pobiera dane nie jest możliwa. – John

Odpowiedz

2

Co jeśli zrobiłem coś takiego:

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

Następnie filtr na ProductId powinno pomóc. Jednak może być gorzej bez filtrowania.

3

Spróbuj zastosować następujące podejście, używając wspólnego wyrażania tabel (CTE). Dzięki podanym przez Ciebie danym testowym, zwróci on określone Dane Produktu w czasie krótszym niż sekunda.

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 
+0

Wydaje się, że działa to znacznie szybciej niż inne metody, ale nadal powoduje oszacowanie całego podzapytania. Wykonywanie samego "select * from ProductTest" odbywa się mniej więcej w tym samym czasie i ma taki sam plan wykonania, jak w przypadku klauzuli where. – John

+0

Uważam, że jest to najlepsze, co możesz uzyskać ze względu na naturę samych widoków. Inną opcją jest utworzenie procedury składowanej lub funkcji o wartości tabelarycznej, która przechodzi w productid i może być w stanie wykonać filtrowanie bezpośrednio na wybranej części zapytania. –

1

Problem polega na tym, że model danych jest wadliwy. Trzeba mieć trzy tabele:

  • klientów (customerId, ...)
  • Produkty (ProductId, ...)
  • ProductSales (customerId, productId)

Ponadto sprzedaż tabela powinna prawdopodobnie zostać podzielona na liczby od 1 do wielu (dane dotyczące sprzedaży i sprzedaży).Jeśli nie naprawisz swojego modelu danych, po prostu uruchomisz kręgi wokół ogona ścigającego problemy z czerwonym śledziem. Jeśli system nie jest twoim projektem, napraw go. Jeśli boss nie pozwoli ci go naprawić, napraw go. Jeśli nie możesz tego naprawić, napraw to. Nie ma łatwego wyjścia na zły model danych, który proponujesz.

0

będzie to prawdopodobnie dość szybko, jeśli naprawdę nie obchodzi, które klient przynieść z powrotem

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 
;