2012-05-22 11 views
6

Nel mio DB ho due tavoli Items (Id, ..., ToatlViews int) e ItemViews (id, ItemId, timestamp)Aggiornamento numero di colonne da dati in un'altra tabella

In tabella ItemViews ho memorizzare tutte le viste di un oggetto come vengono al sito. Di tanto in tanto voglio chiamare una stored procedure per aggiornare il campo Items.ToatlViews. Ho provato a fare questo SP usando un cursore ... ma l'istruzione di aggiornamento è sbagliata. Puoi aiutarmi a correggerlo? Posso farlo senza cursore?

CREATE PROCEDURE UpdateItemsViews 
AS 
BEGIN 
    -- SET NOCOUNT ON added to prevent extra result sets from 
    -- interfering with SELECT statements. 
    SET NOCOUNT ON; 

    DECLARE @currentItemId int 
    DECLARE @currentItemCursor CURSOR 
    SET @currentItemCursor = CURSOR FOR SELECT Id FROM dbo.Items 

    OPEN @currentItemCursor 
    FETCH NEXT FROM @currentItemCursor INTO @currentItemId 
    WHILE @@FETCH_STATUS = 0 
    BEGIN 
     Update dbo.Items set TotalViews = count(*) 
       from dbo.ItemViews where [email protected] 
     FETCH NEXT FROM @currentItemCursor INTO @currentItemId 
    END 
END 
GO 
+0

Vi raccomando di fare ogni sforzo per non usare i cursori quando scrivete SQL, poiché ci sarà sempre un modo basato su 'set' per scrivere ciò che volete contro il database. Ovviamente ci sarà sempre un'eccezione a questa regola. –

risposta

18

È possibile utilizzare un'istruzione UPDATE diretta

update Items set TotalViews = 
    (select COUNT(id) from ItemViews where ItemViews.ItemId = Items.Id) 

si potrebbe desiderare di provare le prestazioni per i vari modi per farlo se questo è importante

+0

+1 per l'eleganza. Parlando di prestazioni; Ricordo di aver letto da qualche parte che 'count (1)' invece di 'count (id)' è migliore (non che probabilmente lo notereste). Poiché il campo id deve essere selezionato come parte della query ... –

+1

@mouters è un equivoco. 'COUNT (1)' è migliore di 'COUNT (id)' se stiamo parlando di accuratezza, ed è solo più accurato (o diverso in qualsiasi modo) se 'id' è nullable. Se vedi questo dichiarato da qualche parte (oltre alla tua memoria), per favore segnalalo perché dovrebbe essere corretto o chiarito. –

+0

Bel commento - Penso che avrò difficoltà a trovare di nuovo l'articolo. –

8

Si potrebbe utilizzare update ... from invece di un cursore:

update i 
set  TotalViews = iv.cnt 
from dbo.Item i 
join (
     select ItemId 
     ,  count(*) as cnt 
     from dbo.ItemViews 
     group by 
       ItemId 
     ) iv 
on  i.Id = iv.ItemId 
2
;WITH x AS 
(
    SELECT ItemID, c = COUNT(*) 
    FROM dbo.ItemViews 
    GROUP BY ItemID 
) 
UPDATE i 
SET TotalViews = x.c 
FROM dbo.Items AS i 
INNER JOIN x 
ON x.ItemID = i.ItemID; 

Ma perché si desidera memorizzare questo valore, quando è sempre possibile ottenere il conteggio in fase di esecuzione? Dovrai eseguire questa dichiarazione di aggiornamento ogni volta che tocchi la tabella ItemViews in qualsiasi modo, altrimenti il ​​conteggio memorizzato con Elementi sarà errato.

Che cosa si può prendere in considerazione facendo invece è la creazione di una vista indicizzata:

CREATE VIEW dbo.ItemViewCount 
WITH SCHEMABINDING 
AS 
    SELECT ItemID, ItemCount = COUNT_BIG(*) 
     FROM dbo.ItemViews 
     GROUP BY ItemID; 
GO 
CREATE UNIQUE CLUSTERED INDEX x ON dbo.ItemViewCount(ItemID); 

Puoi partecipare alla vista nelle query e sapere che il conteggio è sempre aggiornato (senza pagare la penalità di scansione per il conteggio di ciascun articolo). Lo svantaggio della vista indicizzata è che si paga quel costo in modo incrementale quando ci sono inserimenti/aggiornamenti/cancella alla tabella ItemViews.

0

Ho trovato questa domanda/risposta un anno dopo che è stato scritto e risposto. la risposta era ok, ma cercavo qualcosa di più automatico. Ho finito per scrivere un trigger per ricalcolare automaticamente la colonna quando una riga pertinente nell'altra tabella è stata inserita, cancellata o aggiornata.

penso che sia una soluzione migliore che correre manualmente qualcosa a che fare il ricalcolo in quanto non v'è alcuna possibilità di qualcuno dimenticare di eseguire il codice:

CREATE TRIGGER [dbo].[TriggerItemTotalViews] 
    ON [dbo].[ItemViews] 
    AFTER INSERT, DELETE, UPDATE 
AS 
BEGIN 
SET NOCOUNT ON; 

UPDATE [Items] 
SET [TotalViews] = 
    (
    SELECT COUNT(id) 
    FROM [ItemViews] 
    WHERE [ItemViews].[ItemId] = [Items].[ItemId] 
    ) 
WHERE [Items].[ItemId] IN 
    (
    SELECT [ItemId] FROM [INSERTED] 
    UNION 
    SELECT [ItemId] FROM [DELETED] 
    ) 
END 
0

stesso ma diverso:

declare @productId int = 24; 
declare @classificationTypeId int = 86; 

update s 
set CounterByProductAndClassificationType = row_num 
from Samples s 
join 
(
    select row_number() over (order by (select Id)) row_num, Id 
    from Samples 
    where 
     ProductId = @productId and 
     ClassificationTypeId = @classificationTypeId 
) s_row on s.Id = s_row.Id 
Problemi correlati