<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>SQLAndy &#187; Database Design</title>
	<atom:link href="http://www.sqlandy.com/archive/tag/database-design/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.sqlandy.com</link>
	<description>A work in progress!</description>
	<lastBuildDate>Thu, 09 Sep 2010 15:56:01 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>Building a PASS Voting Solution &#8211; Part 4</title>
		<link>http://www.sqlandy.com/archive/building-a-pass-voting-solution-part-4/</link>
		<comments>http://www.sqlandy.com/archive/building-a-pass-voting-solution-part-4/#comments</comments>
		<pubDate>Tue, 22 Jun 2010 05:55:00 +0000</pubDate>
		<dc:creator>Andy</dc:creator>
				<category><![CDATA[SQL Community]]></category>
		<category><![CDATA[Database Design]]></category>
		<category><![CDATA[PASS]]></category>
		<category><![CDATA[Voting Booth]]></category>

		<guid isPermaLink="false">http://www.sqlandy.com/archive/building-a-pass-voting-solution-part-4/</guid>
		<description><![CDATA[We’re almost at the end of the SQL side of things, just needing to send out notifications/reminders. I was torn here, I already had a proc to send a link to a single user, but using it here would mean a loop. Even if it was slow it wouldn’t matter, we do a handful of [...]]]></description>
			<content:encoded><![CDATA[<p>We’re almost at the end of the SQL side of things, just needing to send out notifications/reminders. I was torn here, I already had a proc to send a link to a single user, but using it here would mean a loop. Even if it was slow it wouldn’t matter, we do a handful of votes in a year, but just couldn’t do it! I get good reuse out of MergeBallotInfo. Note that I’m passing in a schedule ID, will show you that next.</p>
<blockquote><p>ALTER proc [Voting].[NotifyVotersOfBallot] @ScheduleID uniqueidentifier </p>
<p>as </p>
<p>declare @Rows int     <br />declare @Template varchar(max) </p>
<p>set nocount on     <br />set xact_abort on </p>
<p>&#8211;make sure we don&#8217;t send twice     <br />if not exists (select * from Voting.NotificationSchedule where ScheduleID = @ScheduleID and DateSent is null)      <br />&#160;&#160;&#160; begin      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; raiserror (&#8216;This schedule item has already been processed. Add a new schedule if you want to send again&#8217;, 16, 1)      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; return      <br />&#160;&#160;&#160; end </p>
<p>&#8211;get the template once     <br />select      <br />&#160;&#160;&#160; @Template = TemplateHTML      <br />from Voting.Templates      <br />where      <br />&#160;&#160;&#160; TemplateName = &#8216;BallotEmail&#8217; </p>
<p>begin transaction </p>
<p>insert into Voting.Email (     <br />&#160;&#160;&#160; MsgFrom,      <br />&#160;&#160;&#160; MsgTo,      <br />&#160;&#160;&#160; Subject,      <br />&#160;&#160;&#160; Message,      <br />&#160;&#160;&#160; Priority,      <br />&#160;&#160;&#160; DBName)      <br />select      <br />&#160;&#160;&#160; &#8216;hq@sqlpass.org&#8217;,      <br />&#160;&#160;&#160; EV.EmailAddress,      <br />&#160;&#160;&#160; &#8216;PASS Vote: &#8216; + B.Title,      <br />&#160;&#160;&#160; Voting.MergeBallotInfo (@Template, B.BallotID, EV.VoterID, B.Title, B.Description, B.ClosingDate),      <br />&#160;&#160;&#160; 2,      <br />&#160;&#160;&#160; db_name()      <br />from Voting.EligibleVoters EV inner join Voting.Ballot B on EV.BallotID = B.BallotID      <br />inner join Voting.NotificationSchedule NS on B.BallotID = NS.BallotID      <br />where      <br />&#160;&#160;&#160; EV.HasVoted = 0      <br />&#160;&#160;&#160; and B.ClosingDate &gt; GetUTCDate()      <br />&#160;&#160;&#160; and NS.ScheduleID = @ScheduleID      <br />set @Rows = @@Rowcount </p>
<p>&#8211;mark as done     <br />update Voting.NotificationSchedule set      <br />&#160;&#160;&#160; DateSent = getutcdate(),      <br />&#160;&#160;&#160; NumberSent = @Rows      <br />where      <br />&#160;&#160;&#160; ScheduleId = @ScheduleID </p>
<p>commit transaction</p>
</blockquote>
<p>I wanted to make this as set and forget as possible, and it’s not as easy as saying each ballot gets 2 reminders. I created this table which can be populated with as many rows as needed, but I’d guess the average will be 2-3. I logged the number of emails sent which may be overkill, but it’s a nice way to know that things happened as expected.</p>
<p>&#160;</p>
<p><a href="http://www.sqlandy.com/wp-content/uploads/2010/06/image3.png"><img style="border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px" title="image" border="0" alt="image" src="http://www.sqlandy.com/wp-content/uploads/2010/06/image_thumb3.png" width="334" height="134" /></a> </p>
<p>I didn’t avoid loops entirely. I could have written the above to handle all notifications that were due at once (rarely more than one), but I hate not having the ability to just send <em>one</em> if I need to – say a quick resend if something has failed. I’m using a read only cursor, and it’s really a just in case solution, doesn’t cost much to code or execute in this context.</p>
<blockquote><p>ALTER proc [Voting].[VotingProcessSchedule] </p>
<p>as </p>
<p>declare @ScheduleId uniqueidentifier </p>
<p>set nocount on </p>
<p>DECLARE curSchedule CURSOR     <br />READ_ONLY      <br />FOR SELECT scheduleid from Voting.NotificationSchedule      <br />where      <br />&#160;&#160;&#160; Datesent is null      <br />&#160;&#160;&#160; and DateToSend &gt; GetUTCDate() </p>
<p>OPEN curSchedule </p>
<p>FETCH NEXT FROM curSchedule INTO @ScheduleId     <br />WHILE (@@fetch_status &lt;&gt; -1)      <br />BEGIN      <br />&#160;&#160;&#160; IF (@@fetch_status &lt;&gt; -2)      <br />&#160;&#160;&#160; BEGIN      <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; exec Voting.NotifyVotersOfBallot @ScheduleID </p>
<p>&#160;&#160;&#160; END     <br />&#160;&#160;&#160; FETCH NEXT FROM curSchedule INTO @ScheduleId      <br />END </p>
<p>CLOSE curSchedule     <br />DEALLOCATE curSchedule </p>
</blockquote>
<p>Finally, we’ll have to do some reporting, which for now consists of selecting from the view, but not a big thing to add a simple RS report to finish things up:</p>
<blockquote><p>CREATE view [Voting].[BallotResults] </p>
<p>as </p>
<p>select     <br />&#160;&#160;&#160; b.BallotID,      <br />&#160;&#160;&#160; b.Title,      <br />&#160;&#160;&#160; bd.Title as BallotOption,      <br />&#160;&#160;&#160; isnull(a.VotesCast,0) as VotesCast      <br />from Voting.Ballot B inner join Voting.BallotDetail BD       <br />on B.BallotID = BD.BallotID      <br />left join      <br />(      <br />select      <br />&#160;&#160;&#160; BallotDetailID,      <br />&#160;&#160;&#160; count(*) as VotesCast      <br />from Voting.VotesCast      <br />group by      <br />&#160;&#160;&#160; BallotDetailID)      <br />a on BD.BallotDetailID = A.BallotDetailID</p>
</p>
</blockquote>
<p>For security there is a role called Voter, and I’ve granted execute on the procs that can be called from the web app. Not much else to it. Next post will look at some of the UI pieces.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.sqlandy.com/archive/building-a-pass-voting-solution-part-4/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Building a PASS Voting Solution &#8211; Part 3</title>
		<link>http://www.sqlandy.com/archive/building-a-pass-voting-solution-part-3/</link>
		<comments>http://www.sqlandy.com/archive/building-a-pass-voting-solution-part-3/#comments</comments>
		<pubDate>Mon, 21 Jun 2010 05:40:00 +0000</pubDate>
		<dc:creator>Andy</dc:creator>
				<category><![CDATA[SQL Community]]></category>
		<category><![CDATA[Database Design]]></category>
		<category><![CDATA[PASS]]></category>
		<category><![CDATA[Voting Booth]]></category>

		<guid isPermaLink="false">http://www.sqlandy.com/archive/building-a-pass-voting-solution-part-3/</guid>
		<description><![CDATA[Today we’ll look at the server side code portion of the solution. I’m definitely in favor of stored procs, so that’s what you’ll see here. I typically only handle errors within a proc if I have the ability to do something there to fix the problem, otherwise I raise them back to the application. I [...]]]></description>
			<content:encoded><![CDATA[<p>Today we’ll look at the server side code portion of the solution. I’m definitely in favor of stored procs, so that’s what you’ll see here. I typically only handle errors within a proc if I have the ability to do something there to fix the problem, otherwise I raise them back to the application. I was trying for the minimum amount of code, both to save time writing and in testing.</p>
<p>We start with the main proc we call every time someone hits the voting page. We may or may not get ballotID and VoterID, and you can see I’m doing something you probably don’t see often, returning both output parameters and a resultset.</p>
<blockquote><p>ALTER proc [Voting].[GetBallotDetails]<br />
    @BallotID uniqueidentifier,<br />
    @VoterID uniqueidentifier,<br />
    @IsEligibleToVote bit output,<br />
    @HasVoted bit output</p>
<p>as</p>
<p>set nocount on<br />
set xact_abort on</p>
<p>select @IsEligibleToVote = voting.IsEligibleToVote(@BallotID, @VoterID)<br />
select @HasVoted = voting.HasVoted(@BallotID, @VoterID)</p>
<p>select<br />
    B.BallotID,<br />
    B.Title as ItemTitle,<br />
    B.MaxSelection,<br />
    B.MinSelection,<br />
    B.Description,<br />
    B.ClosingDate,<br />
    BD.BallotDetailID,<br />
    BD.Title as DetailTitle<br />
from voting.BallotDetail BD Inner join voting.Ballot B on B.BallotID = BD.BallotID<br />
where<br />
    B.BallotID = @BallotID<br />
    and B.ClosingDate &gt; getutcdate()</p></blockquote>
<p>You’ll notice I’m calling two functions. I thought I’d be able to reuse them and it made it easy to follow:</p>
<blockquote><p>ALTER FUNCTION [Voting].[HasVoted]<br />
(<br />
    @BallotID uniqueidentifier,<br />
    @VoterID uniqueidentifier<br />
)<br />
RETURNS bit<br />
AS<br />
BEGIN<br />
    &#8212; Declare the return variable here<br />
    DECLARE @Result bit</p>
<p>    if exists (select * from Voting.EligibleVoters where BallotID = @BallotID and VoterID = @VoterID and HasVoted = 1)<br />
        set @Result = 1<br />
    else<br />
        set @Result  = 0</p>
<p>    &#8212; Return the result of the function<br />
    RETURN @Result</p>
<p>END</p></blockquote>
<blockquote><p>ALTER FUNCTION [Voting].[IsEligibleToVote]<br />
(<br />
    @BallotID uniqueidentifier,<br />
    @VoterID uniqueidentifier<br />
)<br />
RETURNS bit<br />
AS<br />
BEGIN<br />
    &#8212; Declare the return variable here<br />
    DECLARE @Result bit</p>
<p>    if exists (select * from Voting.EligibleVoters where BallotID = @BallotID and VoterID = @VoterID)<br />
        set @Result = 1<br />
    else<br />
        set @Result  = 0</p>
<p>    &#8212; Return the result of the function<br />
    RETURN @Result</p>
<p>END</p></blockquote>
<p>It worked out ok. It was one of those cases where in hindsight I wonder if programming by exception (throwing an error if anything didn’t match) might have been cleaner on the app side, but it’s not a technique I normally use and am biased against.</p>
<p>Casting a vote is a big deal of course, and hopefully I got this right. Here I don’t just rely on what I “know” in the UI, I want to make sure I enforce the voting rules. You can see I did reuse my functions. I cheated here a little by only allowing voters to vote once, that means I don’t have to check for scenarios such as picking one option and submitting, then picking a second option and submitting. Restricting to one call means I can just check all the conditions then, with little complexity.</p>
<blockquote><p>USE [Vote]<br />
GO<br />
/****** Object:  StoredProcedure [Voting].[CastVote]    Script Date: 06/11/2010 09:33:59 ******/<br />
SET ANSI_NULLS ON<br />
GO<br />
SET QUOTED_IDENTIFIER ON<br />
GO<br />
ALTER proc [Voting].[CastVote]<br />
    @Details varchar(8000),<br />
    @BallotID uniqueidentifier,<br />
    @VoterID uniqueidentifier<br />
as</p>
<p>declare @MaxVotes int<br />
declare @MinVotes int<br />
declare @ActualCount int<br />
declare @Temp table (DetailID uniqueidentifier)</p>
<p>set xact_abort on<br />
set nocount on</p>
<p>&#8211;check that they are eligible to vote<br />
if Voting.IsEligibleToVote(@BallotID, @VoterID) = 0<br />
    begin<br />
        raiserror (&#8216;You are not an eligible voter for this ballot.&#8217;, 16, 1)<br />
        return<br />
    end</p>
<p>&#8211;see if user has already voted on this ballot<br />
if Voting.HasVoted(@BallotID, @VoterID) = 1<br />
    begin<br />
        raiserror (&#8216;You have already voted on this ballot.&#8217;, 16, 1)<br />
        return<br />
    end</p>
<p>&#8211;get the max number of selections allowed for this ballot<br />
select<br />
    @MaxVotes = MaxSelection,<br />
    @MinVotes = MinSelection<br />
from Ballot B<br />
where<br />
    BallotID = @BallotID</p>
<p>&#8211;convert list of votes to a table<br />
insert into @Temp (DetailID) select convert(uniqueidentifier, Param) from voting.SplitCommaDelimitedString (@Details)</p>
<p>&#8211;get count of votes<br />
select @ActualCount = count(*) from @Temp</p>
<p>&#8211;make sure no nulls<br />
if @ActualCount is null<br />
    set @ActualCount = 0</p>
<p>&#8211;check that #votes don&#8217;t exceed max<br />
if @ActualCount &gt; @MaxVotes<br />
    begin<br />
        raiserror (&#8216;Selected more choices than are allowed on this ballot.&#8217;, 16, 1)<br />
        return<br />
    end</p>
<p>&#8211;and min<br />
if @ActualCount &lt; @MinVotes<br />
    begin<br />
        raiserror (&#8216;Selected fewer than the required number of choices on this ballot.&#8217;, 16, 1)<br />
        return<br />
    end</p>
<p>begin transaction</p>
<p>&#8211;record the vote  <br />
insert into voting.VotesCast (<br />
    BallotDetailID,<br />
    VoterID)<br />
select<br />
    DetailID,<br />
    @VoterID<br />
from @Temp</p>
<p>&#8211;mark as voted<br />
update voting.EligibleVoters set<br />
    HasVoted = 1<br />
where<br />
    BallotID = @BallotID<br />
    and VoterID = @VoterID<br />
commit transaction</p></blockquote>
<p>To provide a voting link to a voter, we need to build an email message, which here we do by inserting a row into the Voting.Email synonym, which points to a different db and a process that converts the row into the final SMTP message. The work of populating the template is done by a function also shown below:</p>
<blockquote><p>ALTER proc [Voting].[SendVoterLink] @BallotID uniqueidentifier, @EmailAddress varchar(150)</p>
<p>as</p>
<p>declare @Template varchar(max)<br />
declare @VoterId uniqueidentifier</p>
<p>set nocount on<br />
set xact_abort on</p>
<p>&#8211;log the request<br />
insert into Voting.VoterInquiries (<br />
    BallotID,<br />
    EmailAddress)<br />
values (<br />
    @BallotID,<br />
    @EmailAddress)</p>
<p>&#8211;see if possible<br />
if not exists (select VoterID from voting.EligibleVoters where EmailAddress = @Emailaddress and ballotID = @ballotID)<br />
    begin<br />
        raiserror (&#8216;You are not listed as an eligible voter for this ballot using the provided email address&#8217;, 16, 1)<br />
        return<br />
    end    </p>
<p>&#8211;get the template once<br />
select<br />
    @Template = TemplateHTML<br />
from Voting.Templates<br />
where<br />
    TemplateName = &#8216;BallotEmail&#8217;</p>
<p>insert into Voting.Email (<br />
    MsgFrom,<br />
    MsgTo,<br />
    Subject,<br />
    Message,<br />
    Priority,<br />
    DBName)<br />
select<br />
    &#8216;hq@sqlpass.org&#8217;,<br />
    EV.EmailAddress,<br />
    &#8216;PASS Vote: &#8216; + B.Title,<br />
    Voting.MergeBallotInfo (@Template, @BallotID, EV.VoterID, B.Title, B.Description, B.ClosingDate),<br />
    2,<br />
    db_name()<br />
from Voting.EligibleVoters EV inner join Voting.Ballot B on EV.BallotID = B.BallotID<br />
where<br />
    EV.HasVoted = 0<br />
    and EV.BallotID = @BallotID<br />
    and B.ClosingDate &gt; GetUTCDate()<br />
    and Ev.EmailAddress = @EmailAddress</p></blockquote>
<p> </p>
<blockquote><p>USE [Vote]<br />
GO<br />
/****** Object:  UserDefinedFunction [Voting].[MergeBallotInfo]    Script Date: 06/11/2010 09:37:58 ******/<br />
SET ANSI_NULLS ON<br />
GO<br />
SET QUOTED_IDENTIFIER ON<br />
GO<br />
ALTER FUNCTION [Voting].[MergeBallotInfo]<br />
(<br />
    @Template varchar(max),<br />
    @ballotID uniqueidentifier,<br />
    @voterid uniqueidentifier,<br />
    @Title varchar(50),<br />
    @Description varchar(4000),<br />
    @ClosingDate datetime<br />
)<br />
RETURNS varchar(max)<br />
AS<br />
BEGIN</p>
<p>    declare @Temp varchar(max)</p>
<p>    set @Temp = Replace(@Template, &#8216;{BALLOTID}&#8217;, @BallotID)<br />
    set @Temp = Replace(@Temp, &#8216;{VOTERID}&#8217;, @VoterID)<br />
    set @Temp = Replace(@Temp, &#8216;{TITLE}&#8217;, @Title)<br />
    set @Temp = Replace(@Temp, &#8216;{DESCRIPTION}&#8217;, @Description)<br />
    set @Temp = Replace(@Temp, &#8216;{CLOSINGDATE}&#8217;, @ClosingDate)</p>
<p>    RETURN @Temp</p>
<p>END</p></blockquote>
<p>Tomorrow we’ll cover sending out the notifications to the entire eligible voter list and the view used for reporting.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.sqlandy.com/archive/building-a-pass-voting-solution-part-3/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Building a PASS Voting Solution &#8211; Part 2</title>
		<link>http://www.sqlandy.com/archive/building-a-pass-voting-solution-part-2/</link>
		<comments>http://www.sqlandy.com/archive/building-a-pass-voting-solution-part-2/#comments</comments>
		<pubDate>Fri, 18 Jun 2010 05:26:00 +0000</pubDate>
		<dc:creator>Andy</dc:creator>
				<category><![CDATA[SQL Community]]></category>
		<category><![CDATA[Database Design]]></category>
		<category><![CDATA[PASS]]></category>
		<category><![CDATA[Voting Booth]]></category>

		<guid isPermaLink="false">http://www.sqlandy.com/archive/building-a-pass-voting-solution-part-2/</guid>
		<description><![CDATA[Yesterday I wrote about deciding to build a quick and dirty voting solution. Or to try to at least! Should be interesting to see how I fare with you all:-)
Here are the tables I ended up with:
&#160;
 
As you might guess Ballot holds the overall description of what is being voted on, BallotDetail holds the [...]]]></description>
			<content:encoded><![CDATA[<p>Yesterday I wrote about deciding to build a quick and dirty voting solution. Or to try to at least! Should be interesting to see how I fare with you all:-)</p>
<p>Here are the tables I ended up with:</p>
<p>&#160;</p>
<p><a href="http://www.sqlandy.com/wp-content/uploads/2010/06/image.png"><img style="border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px" title="image" border="0" alt="image" src="http://www.sqlandy.com/wp-content/uploads/2010/06/image_thumb.png" width="275" height="164" /></a> </p>
<p>As you might guess Ballot holds the overall description of what is being voted on, BallotDetail holds the options that voters can pick from. Here’s the DDL for those two:</p>
<blockquote><p>CREATE TABLE [Voting].[Ballot](     <br />&#160;&#160;&#160; [BallotID] [uniqueidentifier] NOT NULL,      <br />&#160;&#160;&#160; [DateAdded] [datetime] NOT NULL,      <br />&#160;&#160;&#160; [Title] [varchar](50) NOT NULL,      <br />&#160;&#160;&#160; [Description] [varchar](4000) NOT NULL,      <br />&#160;&#160;&#160; [OpeningDate] [datetime] NULL,      <br />&#160;&#160;&#160; [ClosingDate] [datetime] NOT NULL,      <br />&#160;&#160;&#160; [MinSelection] [tinyint] NOT NULL,      <br />&#160;&#160;&#160; [MaxSelection] [tinyint] NOT NULL,      <br /> CONSTRAINT [PK_Election] PRIMARY KEY CLUSTERED       <br />(      <br />&#160;&#160;&#160; [BallotID] ASC      <br />)WITH (PAD_INDEX&#160; = OFF, STATISTICS_NORECOMPUTE&#160; = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS&#160; = ON, ALLOW_PAGE_LOCKS&#160; = ON) ON [PRIMARY]      <br />) ON [PRIMARY]</p>
<p>CREATE TABLE [Voting].[BallotDetail](     <br />&#160;&#160;&#160; [BallotDetailID] [uniqueidentifier] NOT NULL,      <br />&#160;&#160;&#160; [DateAdded] [datetime] NOT NULL,      <br />&#160;&#160;&#160; [Title] [varchar](75) NOT NULL,      <br />&#160;&#160;&#160; [BallotID] [uniqueidentifier] NOT NULL,      <br /> CONSTRAINT [PK_BallotDetail] PRIMARY KEY CLUSTERED       <br />(      <br />&#160;&#160;&#160; [BallotDetailID] ASC      <br />)WITH (PAD_INDEX&#160; = OFF, STATISTICS_NORECOMPUTE&#160; = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS&#160; = ON, ALLOW_PAGE_LOCKS&#160; = ON) ON [PRIMARY]      <br />) ON [PRIMARY] </p>
<p>GO </p>
<p>SET ANSI_PADDING OFF     <br />GO </p>
<p>ALTER TABLE [Voting].[BallotDetail]&#160; WITH CHECK ADD&#160; CONSTRAINT [FK_BallotDetail_Ballot] FOREIGN KEY([BallotID])     <br />REFERENCES [Voting].[Ballot] ([BallotID])      <br />GO </p>
<p>ALTER TABLE [Voting].[BallotDetail] CHECK CONSTRAINT [FK_BallotDetail_Ballot]     <br />GO </p>
<p>ALTER TABLE [Voting].[BallotDetail] ADD&#160; CONSTRAINT [DF_BallotDetail_BallotDetailID]&#160; DEFAULT (newid()) FOR [BallotDetailID]     <br />GO </p>
<p>ALTER TABLE [Voting].[BallotDetail] ADD&#160; CONSTRAINT [DF_BallotDetail_DateAdded]&#160; DEFAULT (getutcdate()) FOR [DateAdded]     <br />GO</p>
</p>
</blockquote>
<p>HQ would load those two tables first, then load the list of eligible voters into Voting.EligibleVoters where they get assigned a “voterid” in the form of a uniqueidentifier. Eventually we will send a email to each eligible voter where the url looks something like “pass.org/vote.aspx?ballotid=X&amp;voterid=Y. You’ll notice I cheated some, adding a HasVoted flag here rather than just determining it based on whether they have actually cast a vote:</p>
<p><a href="http://www.sqlandy.com/wp-content/uploads/2010/06/image1.png"><img style="border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px" title="image" border="0" alt="image" src="http://www.sqlandy.com/wp-content/uploads/2010/06/image_thumb1.png" width="325" height="114" /></a> </p>
<p>From there votes get inserted into VotesCast:</p>
<p><a href="http://www.sqlandy.com/wp-content/uploads/2010/06/image2.png"><img style="border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px" title="image" border="0" alt="image" src="http://www.sqlandy.com/wp-content/uploads/2010/06/image_thumb2.png" width="344" height="99" /></a> </p>
<p>For the remaining tables, VoterInquiries is used to log when voters arrive at the site only knowing a ballotid, giving us the ability to help out by providing a generic link like “pass.org/vote.aspx?ballotid=x” and then they can enter their email address – if it matches we send them a full voting link.</p>
<p>VotingTemplates has only one row for now, the HTML template we send to voters. It contains tokens that we fill in to show the ballot name, URL, etc. Finally, the NotificationSchedule table allows us to set up a series of emails to remind voters for each ballot to vote.</p>
<p>I can’t say I agonized over the design. I used uniqueidentifiers because I planned to pass them around in the URL querystring (which ruled out using newsequentialid()) and I used a schema other than dbo (Voting) so that if we like the solution we can just add it to the main PASS db rather than having a separate Voting db. I tried to set reasonable primary keys and foreign keys, but haven’t done any load testing (that time thing again!). While I might be missing an index or two, given the low volume of data and low number of concurrent users, I’m not expecting any tough performance issues.</p>
<p>I’ve uploaded scripts for all the tables <a href="http://www.sqlandy.com/wp-content/uploads/2010/06/VotingBooth.zip">here</a>. In the next post we’ll look at the data access pieces on the server.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.sqlandy.com/archive/building-a-pass-voting-solution-part-2/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>SSC Editorial &#8211; Bad Database Design</title>
		<link>http://www.sqlandy.com/archive/ssc-editorial-bad-database-design/</link>
		<comments>http://www.sqlandy.com/archive/ssc-editorial-bad-database-design/#comments</comments>
		<pubDate>Mon, 14 Jun 2010 05:27:00 +0000</pubDate>
		<dc:creator>Andy</dc:creator>
				<category><![CDATA[SQL Community]]></category>
		<category><![CDATA[Database Design]]></category>

		<guid isPermaLink="false">http://www.sqlandy.com/archive/ssc-editorial-bad-database-design/</guid>
		<description><![CDATA[I wrote this editorial about bad design based on some real world experiences. It’s a fun story, maybe&#160; even borderline ridiculous, but the truth is the database design wasn’t as big a problem as you might expect. We got rid of some of the duplicate data bulk caused by the ‘seach columns’ (an upper case [...]]]></description>
			<content:encoded><![CDATA[<p>I wrote this editorial about <a href="http://www.sqlservercentral.com/articles/Editorial/70422/">bad design</a> based on some real world experiences. It’s a fun story, maybe&#160; even borderline ridiculous, but the truth is the database design wasn’t as big a problem as you might expect. We got rid of some of the duplicate data bulk caused by the ‘seach columns’ (an upper case copy of the actual data) and yes, it required more horsepower than it should have, but only because of the api cursor usage. When we later built a similar application on the same data structure using better data access (procs), it ran better and the remaining problems were usually more about how we accomplished a goal than the design itself.</p>
<p>Ten years later those core tables are still there, still working.</p>
<p>Good design is worth doing, but a lot of times what we call ‘bad design’ doesn’t matter as much as we think it does. I’d take a good normalized design with ugly primary keys over a highly denormalized design with integer keys any day.</p>
<p>On any given day we do the best we can. Sometimes that means fixing or work around problems created by those with less knowledge or different styles than we have. I try to neither complain nor gloat in such cases, they made something work, now they are asking me to make it work better – that’s what I do!</p>
]]></content:encoded>
			<wfw:commentRss>http://www.sqlandy.com/archive/ssc-editorial-bad-database-design/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Buying a Second Seat on the Plane</title>
		<link>http://www.sqlandy.com/archive/buying-a-second-seat-on-the-plane/</link>
		<comments>http://www.sqlandy.com/archive/buying-a-second-seat-on-the-plane/#comments</comments>
		<pubDate>Tue, 09 Mar 2010 14:14:00 +0000</pubDate>
		<dc:creator>Andy</dc:creator>
				<category><![CDATA[SQL Community]]></category>
		<category><![CDATA[Database Design]]></category>

		<guid isPermaLink="false">http://www.sqlandy.com/archive/buying-a-second-seat-on-the-plane/</guid>
		<description><![CDATA[Saw this article about more elbow room, the premise being that is it is often cheaper to buy a second seat than it is to move to business or first class. Strangely, some airlines don’t let you do this, and almost all of them don’t make it obvious that you can do it. Which, being [...]]]></description>
			<content:encoded><![CDATA[<p>Saw this article about <a href="http://www.bing.com/travel/content/search?q=The+Middle+Seat%3a+How+to+Buy+Yourself+More+Elbow+Room+in+Coach&amp;cid=msn1131830&amp;gt1=41000">more elbow room</a>, the premise being that is it is often cheaper to buy a second seat than it is to move to business or first class. Strangely, some airlines don’t let you do this, and almost all of them don’t make it obvious that you can do it. Which, being curious, makes me wonder why? They save some fuel, 3 peanuts, and half a soda, what is the downside?</p>
<p>But being a data guy, it was the airlines solution to tracking this that caught my attention. How would you design your database to handle the situation where someone buys two tickets for one person? It’s important to know the number of ‘souls on board’ in case of a crash at the very least. How did the big guys do it?</p>
<ul>
<li>Southwest – makes the 2nd seat in the same name, but the middle name is “XS”, for extra seat</li>
<li>Continental – changes the first name on the 2nd seat to “Extra Seat”</li>
<li>American – just blocks the seat from being sold</li>
</ul>
<p>It kinda feels like maybe they don’t like selling the extra seat because it’s hard to keep track of!</p>
<p>It’s one of those data modeling riddles that doesn’t have a perfect answer. I think I’d lean toward a ‘ExtraSeat’ column on the seat assignment record that pointed to the master/parent seat. Easy to identify which seats are vacant in flight, and now we avoid the weirdness of the person or two that enters their name as Extra Seat!</p>
<p>I might do it differently than they would, but their works, and it’s an edge case, so good enough is good enough!</p>
]]></content:encoded>
			<wfw:commentRss>http://www.sqlandy.com/archive/buying-a-second-seat-on-the-plane/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
