Tables Design and store proc transfer For Data

  • Hi, This is a follow on form the previous post. Its related but not directly so I have created a separate posting for this question.

    I want to create a structure such that the user input directly into a table a row that records a series of client appointments with a ClientID, StartDate, ApptTime, Duration, Frequency (i.e. 1.daily or 2. weekly, 3. Biweekly, 4.once off) and an EndDate (or if it is to run indefinitely leave blank)

    Called TableClientPOC.

    Once entered I then need a procedure to 'transpose' the data into another table called TblAppointments and create a seperate entry for each appointment for up to 12 weeks in advance of today date.

    So if its a daily appointment then there will be 7 * 12 = 84 entries.

    If its a weekly appointment then it will be 1* 12 entries = 12 entries

    and it its 2 weekly there will be 1 * 6 entries = 6 entries and for a once off appointment there will be only 1 entry! (Thats all provided the Appt 'EndDate' is after the 12 weeks or indefinite.

    If 'EndDate' its less than the 12 week then all entries must cease at the End Date!

    My thinking here is that it is easier for user to enter a range of dates and then populate a much larger table with the actual appointment, than the user having to repeatedly enter each day.

    Once in the TblAppointments I then have a third table where I can allocate/deallocate the staff attending each appointment, the number of staff can be 1 ,2 or 3 and thus I am storing that information a table called TblApptStaff.

    The reason for this is that I can quickly retrieve the time each member of staff has spent with a client for billing and payroll purposes.

    Structure are as follows;

    CREATE TABLE [TblClientPOC](

    [ID] [int] IDENTITY(1,1) NOT NULL,

    [ClientID] [nchar](10) NULL,

    [StartDate] [date] NULL,

    [ApptTime] [time](7) NULL,

    [Duration] [int] NULL,

    [Frequency] [int] NULL,

    [EndDate] [date] NULL)

    INSERT INTO [TblClientPOC]

    ([ClientID],[StartDate],[ApptTime],[Duration],[Frequency])

    VALUES

    (2528,'2015-01-20', '10:00',30,1),

    (2528,'2015-01-20', '17:00',30,1)

    (2528,'2015-01-25', '17:30',30,2)

    (2528,'2015-01-30', '17:30',30,4)

    CREATE TABLE [dbo].[TblAppointments](

    [ID] [int] NOT NULL,

    [OriginalApptID] [int] NULL,

    [ClientID] [int] NULL,

    [ApptDate] [date] NULL,

    [ApptTime] [time](7) NULL,

    [Duration] [int] NULL,

    [Approved] [bit] NULL,

    [ApprovedBy] [nvarchar](50) NULL,

    [ApprovedWhen] [datetime] NULL,

    So I am asking is there a better way to structure this and what is the best way yo transpose the data in bulk to enter into the new table?

    I was thinking using a tally table fro the dates and then bulk select non empty date and insert into new table!

    All suggestions appreciated

    kind regards

  • I think the structure is good overall.

    Here are some possible adjustments to consider:

    TblClientPOC

    1) Add a description / comments column

    2) I'd get rid of the identity column here -- contrary to popular myth, there is no db rule which requires every table to have an identity column

    3) Presumably no need for the clientid to be unicode, unless you assign special chars to your own id (??)

    4) Can't imagine why you'd need to schedule to the fraction of a second -- time(0) should be fine.

    5) Can't imagine duration needing to be more than smallint.

    6) Can't imagine frequency needing to be more than tinyint.

    SQL DBA,SQL Server MVP(07, 08, 09) "It's a dog-eat-dog world, and I'm wearing Milk-Bone underwear." "Norm", on "Cheers". Also from "Cheers", from "Carla": "You need to know 3 things about Tortelli men: Tortelli men draw women like flies; Tortelli men treat women like flies; Tortelli men's brains are in their flies".

  • I'd suggest some changes to the design:

    CREATE TABLE [ClientApptSeries](

    [ID] [int] IDENTITY(1,1) NOT NULL, -- it's good to have a unique ID to use it in JOINs

    [ClientID] int NOT NULL, -- do not use char data typed for ID columns. They're horribly slow in JOINs comparing to integers

    [StartDate] [DATE] NOT NULL, -- do not allow NULLs where they should not be allowed

    [ApptTime] [time](7) NOT NULL,

    [Duration] [int] NULL, -- may be null if you have some kind of "default" duration

    [Frequency] [int] NOT NULL,

    [EndDate] [date] NULL

    PRIMARY KEY NONCLUSTERED (ID),

    CONSTRAINT FK_ClientApptSeries_Client FOREIGN KEY (ClientID) REFERENCES dbo.Client(ID) -- I assume you have such a table

    )

    CREATE CLUSTERED INDEX CX_ClientApptSeries ON [ClientApptSeries](StartDate, ClientID)

    CREATE TABLE [dbo].[Appointment](

    [ID] [int] IDENTITY(1,1) NOT NULL,

    [ClientApptSeriesID] [int] NOT NULL,

    --[ClientID] [int] NULL, not needed, you know it from [ClientApptSeries]

    [ApptDateTime] [datetime2] NOT NULL, -- must be [datetime], not separate date and time; you'll thank me later

    [Duration] [INT] NOT NULL, -- I'd rather use datetime, but it's a matter of personal preferences

    PRIMARY KEY NONCLUSTERED (ID),

    UNIQUE CLUSTERED (ClientApptSeriesID, [ApptDateTime]) -- cannot have 2 appointments for the same client scheduled for the same time

    FOREIGN KEY ([ClientApptSeriesID]) REFERENCES [ClientApptSeries](ID)

    )

    /*

    "Approved" is not the only status you'll need to apply to appointments.

    So you better move that part into a separate table:

    */

    CREATE TABLE dbo.Appointment_Status (

    AppointmentID INT NOT NULL,

    TimeRecorded DATETIME2 not null DEFAULT(GETDATE()),

    StatusID SMALLINT NOT NULL, -- refers to statuses like "Approved", "Scheduled", "Committed", "Finished", etc.

    UserID INT NOT NULL, -- entry from a "User" table, must be a staff member (?)

    PRIMARY KEY (AppointmentID, TimeRecorded),

    UNIQUE KEY (TimeRecorded, AppointmentID),

    FOREIGN KEY (StatusID) REFERENCES dbo.Status(ID)

    )

    It's just a draft.

    Use of Tally table to populate Appointments from ApptSeries seems quite appropriate.

    And you must consider an option to change an appointment without changing the whole series, or change the whole schedule of appointments which results in removing and re-populating of the whole set of correlated appointments.

    _____________
    Code for TallyGenerator

  • Hi Guys,

    Thank you so much for the replies its a great help. Your right I have also been thinking how to delete/amend a series of appointments or just 1 appt.

    I have the so code to produce the three tally tables I think I will require.

    First to give me all dates between 2 dates for daily appointments.

    The second to give me all dates a week apart between 2 dates for weekly appointments.

    Thirdly two weekly apart dates between 2dates.

    For one off appointments there is no tally table required.

    I will post the code tomorrow morning for your perusal and advice.

    Kind regards

  • Hi, As promised here is the code I have prepared to give me the 3 tally tables I may need.

    The first Tally example uses a number of cte's that (as far as I can tell!) allows the possible growth to 10,000 rows, which I think is some kind of sql limit!) to create all the dates I need.

    Example 1

    Declare

    @SDate Date = '2015-06-01',

    @EDate Date = '2015-12-31';

    WITH

    E(n) AS(

    SELECT n FROM (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0))E(n)

    ),

    E2(n) AS(

    SELECT a.n FROM E a, E b

    ),

    E4(n) AS(

    SELECT a.n FROM E2 a, E2 b

    )

    SELECT TOP(DATEDIFF( dd, @SDate, @EDate) + 1)

    DATEADD( dd, ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) - 1, @SDate) calDate

    FROM E4

    END

    But here the same output with shorter code

    Example 2

    DECLARE @dt1 Datetime='2015-06-01'

    DECLARE @dt2 Datetime='2015-12-31';

    WITH

    DailyDates

    AS

    (SELECT [Dates]= @dt1

    UNION ALL

    SELECT [dates] + 1

    FROM DailyDates

    WHERE [dates] + 1 <= @dt2)

    SELECT CONVERT(date,[dates]) AS [Daily Dates]

    FROM DailyDates

    OPTION (maxrecursion 0)

    END

    Output for both are the same, but which one should I use? I like the look of the last one as its easier to read for me anyways!

    For Tally table 2 for all dates a week apart I have the same dilemma as above. I can use Example 1 and change 'dd' to 'ww', or Example 2 and

    But for Tally table3 where I need 2 weeks apart (known as a fortnight here) the DATEADD function does not do 2 weeks so I opted for

    DECLARE @dt1 Datetime='2015-06-15'

    DECLARE @dt2 Datetime='2015-12-31';

    WITH

    TwoWeeklyDates

    AS

    (SELECT [Dates]= @dt1

    UNION ALL

    SELECT [dates] + 14

    FROM TwoWeeklyDates

    WHERE [dates] + 14 <= @dt2)

    SELECT CONVERT(date,[dates]) AS [Two Weekly Dates]

    FROM TwoWeeklyDates

    OPTION (maxrecursion 0)

    END

    I am not quite sure which one of the 2 examples to use and why so any advice is appreciated.

    Obviously once I have my Tally tables I left join with my TblClientPOC table and INSERT into TblAppointments!

  • My advise - create a static Calendar table:

    N int,

    [Date] date,

    DayOfWeek tinyint,

    WeekNumber,

    LastWeekdayOfMonth tinyint

    etc.

    Whatever property of a date you may need in your Scheduling tool.

    Populate it once, using Tally table.

    Calculate and store all the properties.

    Use whatever slow and ineffective code you wish, loops, cursors, correlated subqueries, does not matter - you need to do it only once.

    64k rows of Calendar table cover 176 years and occupy about 2MB of disk space.

    Create as many indexes as you want - the tabe is not gonna be updated ever.

    Even yoyu create 20 different covering indexes on every occasion they will take other 5MB, OK, may be 10MB of disk space.

    Anyway - it's nothing.

    And forget about those tricky CTE's once and forever.

    _____________
    Code for TallyGenerator

  • Hi SSCarpal,

    I love CTE they make perfect sense to me and I have all of my sp done that way already...so there's no going back...

    But thanks for commenting anyways...

  • Tallboy (12/24/2015)


    Hi SSCarpal,

    I love CTE they make perfect sense to me and I have all of my sp done that way already...so there's no going back...

    But thanks for commenting anyways...

    Tallboy,

    For some reason you've chosen the worst option of them all.

    You use recursive CTE's which are the worst performers amongst all possible options. Even a WHILE loop does better.

    They have nothing to do with Tally table.

    Having them in every procedure is definitely a path to killing the server.

    Before it's too late - go back to the drawing board, learn how to do calendars and schedules in an effective way and redesign your solutions.

    Before it's too late...

    _____________
    Code for TallyGenerator

  • Tallboy (12/23/2015)


    But for Tally table3 where I need 2 weeks apart (known as a fortnight here) the DATEADD function does not do 2 weeks so I opted for

    DECLARE @dt1 Datetime='2015-06-15'

    DECLARE @dt2 Datetime='2015-12-31';

    WITH

    TwoWeeklyDates

    AS

    (SELECT [Dates]= @dt1

    UNION ALL

    SELECT [dates] + 14

    FROM TwoWeeklyDates

    WHERE [dates] + 14 <= @dt2)

    SELECT CONVERT(date,[dates]) AS [Two Weekly Dates]

    FROM TwoWeeklyDates

    OPTION (maxrecursion 0)

    END

    I am not quite sure which one of the 2 examples to use and why so any advice is appreciated.

    How about this?

    SELECT DATEADD(dd, 14*N, @d1) FortnightDays

    From Tally

    Where N>= 0

    And N<=DATEDIFF(dd, @d1,@d2)/14

    The code looks a bit shorter to me.

    _____________
    Code for TallyGenerator

Viewing 9 posts - 1 through 8 (of 8 total)

You must be logged in to reply to this topic. Login to reply