Link to home
Start Free TrialLog in
Avatar of MFredin
MFredinFlag for United States of America

asked on

Banner rotation system

I'm looking for help on a banner rotation system.  I have 5 ads (could be more) that I am displaying all at once in a vertical column on my home page.  I want to give each banner an equal share of space near the top.

How can I rotate them equally without displaying the same ad twice on the page?
Avatar of gdemaria
gdemaria
Flag of United States of America image

Did you want the ads to show evenly for each user?

For example, you could create a session variable and increment it each time you show an ad..

<cfset session.adNumber = session.adNumber + 1>
... show add number session.adNumber...

<cfif session.adNumber gt maxAdNumber>
    <cfset session.adNumber = 1>
... reset to first ad

Or you can have a strategy to show the ads evenly across all users?   In that case, you can use a database level counter (so a field on the add table tells you which to show).

Of course there is a random number generator that you could trust will be even over a large number of displays...

Avatar of MFredin

ASKER

Thanks gdemaria,

I'm trying to show the ads in a rotating position evenly across users.  The main point is so the ad on the bottom doesn't always show up on the bottom... eventually it will be displayed on the top.  Just trying to be fair to all my advertisers.

assuming you have your ads stored in a database table, each time you display an ad, update the record with a listDisplayedDate = getDate()

Then, you can always fetch the ad with the oldest lastDisplayedDate to display...

select top 1 * from myAds order by lastDisplayDate asc

Avatar of MFredin

ASKER

Thanks gdemaria,

The only problem is, I only have 5 ads and they are always going to be displayed together.  What I'm really only worried about is their position.  If ad1 was displayed at the bottom position last time, it should be moved up next time, etc.  It's a vertical column of ads.
ugh, ok...   not sure how you are determining the position of the ad, but let's say you have them in the database.

A column with a simple number identifies their position where 1 is the top spot and 5 is the bottom spot.

<cfset maxPos = 5>  <!---- the number of ads ---->

update myBannerAds
   set  displayPos = case when displayPos = #maxPos# then 1 else displayPos + 1 end


If you run this update each time you display your page with ads, then you will rotate the ads effectively with every display.
I don't know. I'm thinking session variables might produce more even results.  Because it seems like the db update would be subject to race conditions.  At least it would under heavy load.

user 1 => updates positions                 [5,1,2,3,4]
user 2 => updates positions again       [1,2,3,4,5]
user 1 => reads positions and gets [1,2,3,4,5]     ** prev. ordering [5,1,2,3,4] is skipped
user 2 => reads positions and gets [1,2,3,4,5]


yes, that's true.  I could see a sequence being skipped occassionally but really very rarely (two processes hitting the table so closely to split a transaction)

it's not conventional, but you could do the update an select in a single cfquery..
<cfquery name="getAds"...>
  update ...

  select ...
</cfif>


But the session variables are per user, that's why I thought database to make it even between users.   Perhaps an application scope variable would satisfay both, although I don't love the idea of updating an application scope variable so frequently?  Not sure why though :)

>> but really very rarely (two processes hitting the table so closely to split a transaction)

Not necessarily. All you need is any delay to occur in between the time user 1 updates the information and reads it back.  That's entirely possible in busy applications.

>> I don't love the idea of updating an application scope variable

Perhaps for the same reason I don't love the db idea here: race conditions ;-) You'd have to lock the application scope (exclusive/single thread) every time you updated.  Even if it's only for a split second.

Instead I'd store the banners in the application scope and toggle a "row" number stored in a session variable.  The locking is a non-issue.

> Instead I'd store the banners in the application scope and toggle a "row" number stored in a session variable.  The locking is a non-issue.

yup, agreed.  As long at the asker is ok with a little less even rotation among ads.  If determinging the ad by-user,  the user is not likely to browse a multipe of 5 pages (5,10,15) thus the ads per user will not be even.   The way around this would be to alter the initial ad order at the start of each session.

For example, if each user starts their rotation with 1,2,3,4,5... that puts ad #1 at the top every time and will always get more exposure. If the user views only 3 pages, ad 4 and 5 won't get to the top.  

The way around this would be to alter the starting sequence of the ads, first user 1,2,3,4,5; then 2nd user 2,3,4,5,1;  third user 3,4,5,1,2; etc.   This would give each ad an equal starting point.  But then how do you determine the starting ad for each user?  Same problem, isn't it?

In summary, I think that the asker is faced with a choice between occasionally skipping a sequence or the challenge of an even balance between ads.   Either approach would give a pretty good rotation, but not perfect, depending on how exact you need to be...


>> But then how do you determine the starting ad for each user?  Same problem, isn't it?

One option is randomizing the starting row: startRow = randRange(1,5) ;-).  That would prevent one banner from always getting more exposure.  It doesn't guarantee absolute even distribution across the ads. But neither does the db method since it could potentially skip multiple times on a busy site.  
Granted for a a site with very low concurrency, the db method would produce a more "even" rotation.

>> Either approach would give a pretty good rotation, but not perfect, depending on
>> how exact you need to be...

Yep.  The only bullet proof approach is using the application scope with an exclusive named lock

         <cflock ...>
                 <cfset application.startPos = (application.startPos = maxNum ? 1 : application.startPos + 1)>
                 <cfset request.startPos = application.startPos >
        </cflock>

Yup, agree.

Personally, I would choose either the random start session variable approach or the database sequencing approach over the application scope option.


Btw, when did this conditional format start working in CF (non-cfscript) ?  

   = (application.startPos = maxNum ? 1 : application.startPos + 1)>
SOLUTION
Avatar of _agx_
_agx_
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of MFredin

ASKER

Thanks guys, here's what I've tried...

<cfset maxPos = 7>  <!---- the number of ads ---->
      
<!--- Get Ads --->
<cfquery name="ads">
      SELECT display_position, banner_id
      FROM banners
      WHERE display_position IS NOT NULL
</cfquery>      
            
<cfoutput query="ads">
      
<cfquery>
UPDATE banners
SET display_position =  case when display_position = #maxPos# then 1 else #ads.display_position# + 1 end
WHERE banner_id = #ads.banner_id#
</cfquery>

<cfmodule template="Advertising/bannerAd.cfm" homepage_ad="1" homepage_position="#ads.display_position#">
<br />
</cfoutput>


The problem is, very once in a while I somehow end up with duplicate display position in my banners table.  It throws everything off.

Making sure the ads are rotated evenly isn't important... I just want to be sure they rotate somewhat.
ASKER CERTIFIED SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of MFredin

ASKER

Thanks gdemaria, that's perfect!  

How do you guys suggest I break up the points on this one?  gdemaria provided the best solution but agx added great insight as well.
I'd give the lion's share to gdemaria, because he provided the ultimate solution and the bulk of the code :)