SQL-запрос для обновления количества объектов на основе имени события

Представьте, что я владелец множества книжных магазинов. Я веду базу данных обо всех событиях, происходящих во всех моих многочисленных книжных магазинах. Следует отметить два события: «Книга добавлена» и «Книга удалена», когда книга добавляется в инвентарь истории и когда она продается в магазине. Примером схемы может быть bookstore_id, event_name, `время.

Теперь предположим, что у меня есть вторая таблица, которая поддерживает текущее состояние каждого книжного магазина, поэтому схема будет bookstore_id, num_books.

Я хочу иметь возможность использовать первую таблицу, чтобы получить количество всех событий «Добавлена ​​книга» для книжного магазина, вычесть количество всех событий «Книга удалена» для книжного магазина, а затем обновить количество книг в каждом книжном магазине в второй стол.

Единственный способ, которым я могу это сделать, требует использования курсора, но я предполагаю, что есть более «SQL-подобный» способ сделать это, который более основан на наборах и не требует курсора.


person matt49383    schedule 16.03.2016    source источник
comment
После обновления вы удаляете все строки событий, чтобы иметь возможность обработать следующий пакет транзакций, или вы отслеживаете те, которые уже использовались для корректировки счетчиков?   -  person HABO    schedule 16.03.2016


Ответы (6)


Вы можете подсчитать события, используя предложение GROUP BY.

Если бы мы создали 2 подтаблицы, в которых мы подсчитываем добавленные и соответственно удаленные книги, мы можем просто вычесть результаты и обновить их в родительской таблице. Это будет выглядеть так:

UPDATE      b
SET         b.numbooks = AddedBooks.BooksAdded - RemovedBooks.BooksRemoved
FROM        dbo.Books b
INNER JOIN  (SELECT     be.book_id, count(*) AS BooksAdded
            FROM        dbo.BookEvents be
            WHERE       be.event = 'BookAdded'
            GROUP BY    be.book_id, be.event) AS AddedBooks 
                                    ON b.bookid = AddedBooks.book_id
INNER JOIN  (SELECT     be.book_id, count(*) AS BooksRemoved
            FROM        dbo.BookEvents be
            WHERE       be.event = 'BookRemoved'
            GROUP BY    be.book_id, be.event) AS RemovedBooks  
                                    ON b.bookid = RemovedBooks.book_id
person Ric .Net    schedule 16.03.2016

select bookstore_id
     , sum(case when event_name = "Book Removed" then -1 else 1 end) as "num books"
  from bookstores  
 group by bookstore_id 

если более 2 событий

select bookstore_id
     , sum(case when event_name = "Book Removed" then -1 
                when event_name = "Book Added"   then  1  
           end) as "num books"
  from bookstores  
 group by bookstore_id 

И я бы просто сделал это представление, если вы не столкнулись с проблемами производительности.

person paparazzo    schedule 16.03.2016
comment
Я думаю, что в таблице больше, чем просто эти два события. И, конечно же, вы не учли end - person shawnt00; 16.03.2016

Мы можем использовать CTE для получения деталей по отдельности и их обработки.

With CTE_Add AS
(
Select Bkstr_ID, Count(event_Name) As Added From temp Where event = 'Added' Group by Bkstr_ID
), CTE_Rem As
(
Select Bkstr_ID, Count(event_Name) As Removed From temp Where event = 'Removed'      Group by Bkstr_ID
)
Select A.Bkstr_ID,  Added - Removed
From CTE_Add A
Left Join CTE_Rem R On A.Bkstr_ID= R.Bkstr_ID

Это даст вам идентификатор и счет. Вместо выбора вы можете использовать оператор Insert

person Sravan Kumar    schedule 16.03.2016

Я бы использовал SUM(CASE WHEN ...). Ниже приведен пример.

If object_id('tempdb..#BoookStores') Is Not Null Drop Table #BoookStores
Create Table #BoookStores (bookstore_id int, num_books int)

/* We have 3 stores */
Insert #BoookStores (bookstore_id, num_books)
Values  (1, 0), (2, 0), (3, 0)

If object_id('tempdb..#Events') Is Not Null Drop Table #Events
Create Table #Events (bookstore_id int, event_name varchar(10), time dateTime Default(GetDate()) )

Insert #Events (bookstore_id, event_name)
Values
(1, 'Added'), (1, 'Added'), (1, 'Added'), (1, 'Added'), -- Added 4 books to 1. store
(2, 'Added'), (2, 'Added'), (2, 'Added'), -- Added 3 books to 2. store
(3, 'Added'), (3, 'Added'), -- Added 2 books to 3. store

/* removed 2 books from each stores */
(1, 'Removed'), (1, 'Removed'), 
(2, 'Removed'), (2, 'Removed'),
(3, 'Removed'), (3, 'Removed')

/* Calculate adds and removes. Update the results */
;With Tmp As (
    Select  E.bookstore_id,
            Sum(Case When E.event_name = 'Added' Then 1 Else 0 End) As AddCount,
            Sum(Case When E.event_name = 'Removed' Then 1 Else 0 End) As RemoveCount
    From    #Events E
    Group By E.bookstore_id
)
Update  BS Set num_books = T.AddCount-T.RemoveCount
From    #BoookStores BS
        Inner Join Tmp T On T.bookstore_id = BS.bookstore_id 

/* check results*/
Select * From #BoookStores BS
person Decoded    schedule 16.03.2016

Что-то вроде этого доставит вас в бейсбольный парк. Аналогичная логика может быть использована для INSERT.

UPDATE tableA
SET tableA.num_books = tableB.num_books
FROM secondTable AS TableA
INNER JOIN (
    SELECT bookstore_id,
        SUM(CASE 
                WHEN event_name = 'Books Added'
                    THEN 1
                END) - SUM(CASE 
                WHEN event_name = 'Books Removed'
                    THEN 1
                END
            ) AS num_books
    FROM firstTable
    GROUP BY bookstore_id
    ) TableB ON TableA.bookstore_id = tableB.bookstore_id
person JNevill    schedule 16.03.2016

Вы можете попробовать запрос, как показано ниже:

update t1
set num_books=inventory
FROM bs t1 LEFT JOIN
(select bookstore_id,SUM(case when event_name like 'A' then 1 when event_name like 'R' then -1 else NULL end)  as inventory
from bse 
group by bookstore_id) t2
on t1.bookstore_id=t2.bookstore_id

Текущая демонстрация SQL

person DhruvJoshi    schedule 16.03.2016