2011-10-18 9 views
5

Ho una vista sql, che sto usando per recuperare i dati. Diciamo che è un grande elenco di prodotti, che sono collegati ai clienti che li hanno acquistati. La vista dovrebbe restituire solo una riga per prodotto, indipendentemente dal numero di clienti a cui è collegato. Sto usando la funzione row_number per raggiungere questo obiettivo. (Questo esempio è semplificato, la situazione generica sarebbe una query in cui ci dovrebbe essere solo una riga restituita per ogni valore unico di X. colonna Quale riga viene restituita non è importante)Rifattorizzazione di una vista tsql che utilizza row_number() per restituire righe con un valore di colonna univoco

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 

Ora consente di dire che la il numero totale di righe in questa vista è ~ 1 milione e la selezione in esecuzione * da productView richiede 10 secondi. Esecuzione di una query come selezionare * da productView dove productID = 10 richiede lo stesso tempo. Credo che questo sia perché la query viene valutata a questo

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 

Penso che questa è la causa del subquery interna da valutare in pieno ogni volta. Idealmente mi piacerebbe usare qualcosa lungo le seguenti linee

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 

Ma questo non sembra essere consentito. C'è un modo per fare qualcosa di simile?

EDIT -

Dopo molti esperimenti, il vero problema credo che sto avendo è come forzare un join per tornare esattamente 1 fila. Ho provato ad usare external apply, come suggerito di seguito. Alcuni esempi di codice.

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) 

Con il set di esempio sopra riportato, la query 'avere tutto' sotto

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

prende ~ 1000 ms per l'esecuzione. Aggiunta di una condizione esplicita:

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

Richiede una quantità identica di tempo. Questo 1000 ms per una query abbastanza semplice è già troppo e ridimensiona il modo sbagliato (verso l'alto) quando si aggiungono altri join simili.

+0

Avete bisogno effettivi dati dei clienti o solo esistenza di o solo un CustomerID? La sottoquery è valutata perché il "10" non è noto in anticipo. E stai chiedendo esattamente la decima fila. Da qui la mia prima domanda sull'output desiderato – gbn

+1

Osservazione veramente buona - SQL non è in grado di applicare il filtro di visualizzazione nella sottoquery. Hai davvero bisogno della flessibilità della vista? Se hai usato una funzione SPROC o una tabella con valori con filtri definiti (ProductID nel tuo esempio), potresti creare il filtro nella subquery. E nel caso in cui la tua PARTITION BY e il FILTER siano gli stessi (ProductId), non avrai affatto bisogno di PARTITION - quindi SELECT TOP 1 dovrebbe essere sufficiente. – StuartLC

+0

Ho bisogno dei dettagli del cliente (o valori nulli se nessuno esiste), non solo dell'esistenza di uno. Devo anche usare una vista, refactoring dell'app che recupera i dati non è possibile. – John

risposta

2

E se hai fatto qualcosa di simile:

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

Poi il filtro productId dovrebbe aiutare. Potrebbe essere peggio senza filtri, però.

3

Provare il seguente approccio, utilizzando una Common Table Expression (CTE). Con i dati di test forniti, restituisce ProductId specifici in meno di un secondo.

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

Sembra funzionare molto più velocemente di altri metodi, ma causa comunque la valutazione dell'intera sottoquery. L'esecuzione di "select * from ProductTest' da sola richiede all'incirca lo stesso tempo e ha lo stesso piano di esecuzione, come con la clausola where. – John

+0

Penso che questo sia il meglio che riuscirai a ottenere a causa della natura dei panorami stessi. L'altra opzione è creare una stored procedure, o forse una funzione valutata a livello di tabella, che passi nel productid e possa essere in grado di fare il filtraggio direttamente sulla porzione della query desiderata. –

1

Il problema è che il modello di dati è difettoso. Si dovrebbe avere tre tabelle:

  • clienti (CustomerId, ...)
  • Prodotti (ProductId, ...)
  • VenditeProdotto (customerid, productId)

Inoltre, la vendita la tabella dovrebbe probabilmente essere suddivisa in 1-a-molti (Sales and SalesDetails).A meno che tu non aggiusti il ​​tuo modello di dati, farai solo giri intorno alla tua ricerca di problemi di aringhe rosse. Se il sistema non è il tuo design, risolvilo. Se il capo non ti consente di risolverlo, risolvilo. Se non riesci a risolverlo, risolvilo. Non esiste una soluzione facile per il modello di dati errati che stai proponendo.

0

questo sarà probabilmente abbastanza veloce se davvero non si cura quale cliente vi riporterà

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 
;