views:

75

answers:

3

To make story short, i'm building self-learning banner management system. Users will be able to insert these banners to their site when banners will be shown based on sales/impressions ratio.

I have 4 tables

Banners 
bannerID      int
bannerImage   varchar....

SmartBanners
smartBannerID      int
smartBannerArrayID int
bannerID           int
impressionsCount   int
visibility         tinyint (percents)

SmartBannerArrays 
smartBannerArrayID int
userID             int

Statistics
bannerID          int
saleAmountPerDay  decimal...

Each night i need to generate new "visibility" for each SmartBanner based on whole SmartBannerArray that same user has. So i need to get sum of impressions and sales for each bannerID in SmartBannerArray.

All comes to my mind is to use double cursor, one will loop thought SmartBannerArrays get needed values for sum of impressions and sales and then inner loop which will access each SmartBanner and change it's "visibility" percentage based on (sales/impressions)/(sumOfSales/sumOfImpressions)*100

Hope you get the picture...

Is there any other way to design better tables or not to use double cursor to avoid server overload ?

MORE INFO Each sale written to Statistics table , field is updated because i need daily sum per banner rather each sale. User create BannerArray, choose products he would like to promote. Each product he chose is written to Banners table with proper image and other info. SmartBanners table stores bannerID so as Statistics table, while BannerArray table assigns this group of banners to certain user.

+2  A: 

The picture is not so clear. Why cursors at all, what is wrong with this?

SELECT SUM(saleAmountPerDay)/SUM(impressionsCount) 
FROM SmartBanners sb        INNER JOIN
     SmartBannerArrays sba  ON sb.smartBannerArrayID = sba.smartBannerArrayID INNER JOIN
     Statistics ss          ON sb.bannerID = ss.bannerID 
GROUP BY smartBannerArrayID

Which can be used then as subquery to calculate 'visibility' directly.

EDIT2: Illustrating the principle (SQL not optimized), why not:

UPDATE SmartBanners
SET visibility = 
    ROUND( 100. * 
    (
    SELECT SUM(saleAmountPerDay)/SUM(impressionsCount) 
    FROM SmartBanners sb        INNER JOIN
         Statistics ss          ON sb.bannerID = ss.bannerID 
    WHERE sb.smartBannerID = SmartBAnners.smartBannerID
    ) /
    (
    SELECT SUM(saleAmountPerDay)/SUM(impressionsCount) 
    FROM SmartBanners sb        INNER JOIN
         SmartBannerArrays sba  ON sb.smartBannerArrayID = sba.smartBannerArrayID INNER JOIN
         Statistics ss          ON sb.bannerID = ss.bannerID 
    WHERE sb.smartBannerArrayID = SmartBAnners.smartBannerArrayID
    ) )

if you want to avoid the cursors.

Also, not to forget - if this data is not going to be updated often (what's the frequency of the update of the statistics table? I imagine you update it periodically from some logs) AND if you are looking to maximize read performance you might consider triggers.

EDIT3: In MS SQL you can also use OVER clause, just a short example

SELECT 
       SUM(saleAmountPerDay) OVER(PARTITION BY BannerID) AS 'TotalSalesByBanner',
       SUM(impressionsCount) OVER(PARTITION BY BannerID) AS 'TotalImpressionsByBanner',
       SUM(saleAmountPerDay) OVER(PARTITION BY smartBannerArrayID) AS 'TotalSalesByArray',
       SUM(impressionsCount) OVER(PARTITION BY smartBannerArrayID) AS 'TotalImpressionsByBanner'    
FROM   
       SmartBanners sb INNER JOIN
       Statistics ss   ON sb.bannerID = ss.bannerID
GROUP BY
       SmartBannerID

This SQL is not tested.

Unreason
I will still be needed to have on cursor to set "visibility" otherwise each time new banner will be generated this huge query will run...Thanks for saving one cursor....
eugeneK
If you find things useful, feel free to upvote. Now, I edited the answer. If you would like exact example of the query can you explain further (edit your question) the relation between the SmartBanners and Banners tables?
Unreason
Problem is that i need to update each banner, thus to loop thought them. Think of that, i have lots of stats, lots of banners and lots of banner arrays. I need to update visibility of each banner so next time user accesses the array it will show most sold product..
eugeneK
Ok, I've updated the answer, pls check if it works on MS SQL server - there could be issues with locking (2notes: I did not specially emphasize but the queries use INNER JOIN which means that if there will be no statistics for certain banner then the the stats would not be updated, turning join on ss to LEFT JOIN solves that, also you should take special care for impressionsCount = 0)
Unreason
@augeneK, correlated queries used in the above are similar to loops/cursors; the query should update the stats for all of the SmartBanners with no cursors.
Unreason
Will this update list of 1000 banners at once with the same value ?
eugeneK
lol we you answered before i've asked... thanks i will try that
eugeneK
@eugeneK, no, not with the same value - each banner gets its own value (basically subqueries are performed for each row and are filtered on the values of the external query).
Unreason
lol, well I saw that I was a bit imprecise in the most important point...
Unreason
+1  A: 

In many cases the use of cursors can be avoided by using derived tables. An introduction to the use of them can be found here.

W van Noort
thanks, read it
eugeneK
A: 

I think you're asking the right question, which is: is there any way to design better tables? My advice: worry less about server overload, which is no big deal if you're running queries only once a day. Worry more about a comprehensible structure.

What about this:

 Banners
 -------
 BannerID
 UserID
 BannerImage
 BannerVisibility  // A denormalized value that you calculate once a day

 Banner_Sales
 ------------
 BannerID
 Date
 SalesAmt

 Banner_Impressions
 ------------------
 BannerID
 Date
 Impressions

You don't need a SmartBannerArray - or cursors - to sum the sales and impressions. Just use a SQL sum:

 SELECT SUM(bs.SalesAmt) AS SALES, SUM(bi.Impressions) AS IMPRESSIONS, 
   SUM(bs.SalesAmt) / SUM(bi.Impressions) AS RATIO
 FROM Banners b
 JOIN Banner_Sales bs
 JOIN Banner_Impressions bi
 WHERE b.UserID = ? // For total sales / impressions for a user

Substitute

WHERE b.BannerID = ? // For total sales / impressions for a banner

Good luck!

P.S. tinyint's are for integers, not percentages. You'll need a double. :)

Summer
He chose to store percentages as integers, in the question formula is multiplied by 100. If such precision is satisfactory there's no problem with tinyints.
Unreason
I need to have SmartBannerArray because the advertisement module i have will show group of banners for each user.++ Thanks Unreason, i don't need more than that
eugeneK