Live schema:

Businesses:

FieldValueImportant or not
id1Yes
network_id1Yes
client_id1Yes
business_code1397Yes
timezoneAmerica/Los_AngelesConditional
nameGrowthzilla Inc.Yes
phone5623623456No
emailsupport@growthzilla.comNo
address4842 Wiggin St.No
cityOak ParkYes
stateCAYes
zipcode91377Yes
latitudeEmptyConditional
longitudeEmptyConditional
place_idEmptyNo
office_hours{“enabled”:false,“hours”:[{“day”:“sunday”,“start”:“08:00”,“end”:“20:00”,“closed”:false},{“day”:“monday”,“start”:“08:00”,“end”:“20:00”,“closed”:false},…]}No
logohttps://growthzilla.com/img/growthzilla-logo.pngNo
descriptionEmptyNo
mod_waitlist1No
mod_timesheet1No
mod_payment1Yes
active2Yes
passwordEmptyNo
saltEmptyNo
statusACTIVEYes
loginattempts0No
invoice_itemsEmptyConditional
reportsettingsEmptyNo
stripe_idEmptyConditional
subscription_idEmptyConditional
created_atNovember 21, 2014, 6:15 AMYes
updated_atAugust 17, 2022, 3:49 PMYes
websites[“https://growthzilla.com”]No
suspended_afterEmptyNo
countryEmptyYes

Checkins:

FieldValueImportant or Not
id8Yes
subscriber_id3Yes
business_id3Yes
network_id3Yes
employee_id12Yes
queue_idEmptyConditional
billamount1,000Yes
paidamount0Yes
cashamount0Yes
creditamount0Yes
giftamount0Conditional
tipamount0Yes
cashtipamount0Yes
cashbackamount0Conditional
guestamount0Conditional
taxamount0Yes
feesamount0Yes
discountamount0Yes
servicesEmptyConditional
paidforothersEmptyConditional
payeeEmptyNo
split0Conditional
points_earned1Yes
manual_points0Conditional
points_redeemed0Yes
withcouponEmptyConditional
withmultiplecouponsEmptyNo
withfeesEmptyNo
refund_idEmptyConditional
checkout_dayNovember 25, 2014Yes
orig_checkout_dayEmptyNo
prepaid0Yes
isnew1Yes
created_atNovember 26, 2014, 5:51 AMYes
updated_atEmptyYes
deleted_atEmptyYes

Saleslogs:

FieldValueImportant or Not
id1Yes
business_id68Yes
checkin_id458,824Yes
product_idEmptyConditional
service_id2,153Yes
price15,600Yes
commissionpriceEmptyConditional
discountpriceEmptyConditional
discount0Yes
adjustment0Conditional
quantity1Yes
itempropertiesEmptyNo
employees[358]Yes
created_atMarch 31, 2016, 5:56 AMYes
updated_atMarch 31, 2016, 5:56 AMYes
prepaidsEmptyConditional
campaign_idEmptyConditional
cashpriceEmptyConditional
cashdiscountEmptyConditional

Note: Both tables handle financial aspects but from different angles. Checkins focus on the overall transaction (total bill, discounts, payments), wile Saleslogs dive into specifics of each sale (service or product sold, price, discounts applied)

There is a possibility that a customer checks in but no sales are recorded with that checkin. This situation can occur for several reasons, reflecting the diverse purposes of check-ins across different business models.

Appointments:

FieldValueImportant or Not
id00003912-1bc2-4b41-8923-103356cd4386Yes
appointment_bid491d5d34-63fd-4e63-a120-98d7ec195cbcNo
business_id150Yes
queue_idEmptyConditional
appointment_dayAugust 19, 2020Yes
profile_idEmptyConditional
phone18185552462No
emailsbashyalcfg@example.comNo
nameShishir BashyalNo
self0No
order_idEmptyNo
appointmenttypeNORMALYes
statusCONFIRMEDYes
dueamount899Yes
discount0Yes
couponEmptyConditional
textok0No
repeat0No
repeatsettingsEmptyNo
starttimeAugust 19, 2020, 9:15 PMYes
endtimeAugust 19, 2020, 9:30 PMYes
taskendtimeAugust 19, 2020, 9:30 PMNo
duration15Yes
sourceipsEmptyNo
confirmationtypeemailNo
sendconfirmation0No
appEmptyNo
desktopapp0No
kb0No
domainEmptyNo
sourceEmptyConditional
scheduledbyselfNo
remindertimeEmptyNo
reminderstatusEmptyNo
nopreference1No
price_adjustment0Conditional
time_adjustment0No
invitation_idEmptyNo
created_dayAugust 18, 2020Yes
created_atAugust 18, 2020, 7:04 PMYes
updated_atAugust 18, 2020, 7:04 PMYes
deleted_atEmptyYes

| | | |

Fact transactions:

Column NameSource TableSource column name
transaction_idcheckinsiddone
business_idBusinessesdone
network_idBusinessesdone
subscriber_idcheckinssubscriber_iddone
cashier_idcheckinsemployee_iddone
employee_idsaleslogsdone
appointment_idappointments
saleslog_idsaleslogsdone
service_idsaleslogsdone
product_idsaleslogsdone
checkout_daycheckinscheckout_daydone
booked_atappointments
queue_idcheckinsqueue_id
prepaid_idsaleslogsdoes not exist
coupon_idsaleslogsdoes not exist
paidamountcheckinspaidamountdone
cashamountcheckinscashamountdone
creditamountcheckinscreditamountdone
giftamountcheckinsgiftamountdone
guestamountcheckinsguestamountdone
cashbackamountcheckinscashbackamountdone
cashtipamountcheckinscashtipamountdone
taxamountcheckinstaxamountdone
feesamountcheckinsfeesamountdone
discountamountcheckinsdiscountamountdone
cashpricesaleslogsdone
cashdiscountsaleslogsdone
quantitysaleslogsdone
tipamountcheckinstipamountdone
points_earnedcheckinspoints_earneddone
points_redeemedcheckinspoints_redeemeddone
manual_pointscheckinsmanual_oints
payee_idcheckinspayee
guest_subscriber_idcheckinspaidforothers
is_prepaidcheckinsprepaiddone
is_newcheckinsisnewdone
refund_idcheckinsrefund_id
created_at(multiple)
updated_at(multiple)

Facts Table

FACT1: Transactions

  • transaction_id checkins.id

  • checkout_day chcekins.checkout_day

  • queue_id

  • subscriber_id

  • business_id b.business_code

  • network_id b.network_id

  • saleslog_id saleslogs.id

  • service_id saleslogs.service_id

  • product_id saleslogs.product_id

  • employe_id saleslogs.employees

  • cashier_id checkins.employee_id

  • coupon_id salelogs.campaing_id (/coupon_id)

  • prepaid_id sales.log

  • paidamount (all these from checkins)

  • cashamount

  • creditamount

  • giftamount

  • guestamount

  • cashbackamount

  • cashtipamount

  • taxamount

  • feesamount

  • discountamount

  • cashprice

  • cashdiscount

  • quantity

  • tipamount

  • points_earned

  • points_redeemed

  • manual_points

  • payee_id

  • guest_subscriber_id checkins.paidforothers

  • is_prepaid

  • is_new

  • refund_id

  • appointment_id appointments.id

  • booked_at appointsments.booked_date

  • booked_source appointmens…

  • appointment ??

  • created_at

  • updated_at

select
    -- -- --
    c.id as transaction_id,
    -- -- --
    b.business_code as business_id,
    b.network_id,
    -- -- --
    c.subscriber_id,
    -- -- --
    c.employee_id as cashier_id,
    sl.employees,
    -- -- --
    sl.id AS saleslog_id,
    sl.service_id,
    c.services,
    
    sl.product_id,
    sl.price,
    sl.quantity,
 
    sl.discount,
    sl.discountprice,
    
    sl.cashprice,
    sl.cashdiscount,
    
    sl.commissionprice,
    
    sl.prepaids,
    
    sl.created_at,
    sl.updated_at,
    -- -- --
    c.billamount,
    c.paidamount,
    c.cashamount,
    c.creditamount,
    
    c.tipamount,
    c.cashtipamount,
    
    c.giftamount,
    
    c.guestamount,
    
    c.feesamount,
    
    c.discountamount,
    c.cashbackamount,
    c.points_earned,
    c.points_redeemed,
    
    c.taxamount,
    
    c.prepaid,
    
    c.isnew,
    
    c.checkout_day
    -- -- --
from
    checkins c
join
    businesses b ON b.id = c.business_id
left join
    saleslogs sl ON sl.checkin_id = c.id
left join
    appointments ap ON ap.queue_id = c.queue_id
order by
    c.id desc
limit
    100;

Dimensions Tier

  1. Business
  • Network
  • Region
  • Management
  • User
  1. Customer
  • Customer ? (Parent/Family)
  1. Day
  • Periods
  1. Service - Category

  2. Product - Category - Brand

  3. Prepaid

  4. Coupon/Campaign

  5. Staff

FACT2: Appointments

…appointments

FACT3: Staff

…employees + timesheets

FACT4: Operation

…queues + appointments

FACT5: Membership+Prepaid

…giftcards…membership

(check schema in dbdiagram)

The retail schema tells us in exquisite detail what was purchased at each store and under what conditions. However, the schema doesn’t allow us to very easily analyze which products were sold in the same market basket together. This notion of analyzing the combination of products that sell together is known by data miners as affinity grouping but more popularly is called market basket analysis. Market basket analysis gives the retailer insights about how to merchandise various combinations of items. If frozen pasta dinners sell well with cola products, then these two products perhaps should be located near each other or marketed with complementary pricing. The concept of market basket analysis can be extended easily to other situations. In the manufacturing environment, it is useful to see what products are ordered together because we may want to offer product bundles with package pricing.

The retail sales fact table cannot be used easily to perform market basket analyses because SQL was never designed to constrain and group across line item fact rows. Data mining tools and some OLAP products can assist with market basket analysis, but in the absence of these tools, we’ll describe a more direct approach below. Be forewarned that this is a rather advanced technique; if you are not doing market basket analysis today, simply skim this section to get a general sense of the techniques involved.

We illustrate a market basket fact table that was derived from retail sales transactions. The market basket fact table is a periodic snapshot representing the pairs of products purchased together during a specified time period. The facts include the total number of baskets (customer tickets) that included products A and B, the total number of product A dollars and units in this subset of purchases, and the total number of product B dollars and units purchased.

The basket count is a semiadditive fact. For example, if a customer ticket contains line items for pasta, soft drinks, and peanut butter in the market basket fact table, this single order is counted once on the pasta-soft drinks fact row, once on the row for the pasta-peanut butter combination, and so on. Obviously, care must be taken to avoid summarizing purchase counts for more than one product.

WITH transactions AS (
    SELECT *
    FROM saleslogs
    ORDER BY checkin_id DESC
    LIMIT 1000
), basket_fact AS (
    SELECT
        t1.service_id AS service_A,
        t2.service_id AS service_B
    FROM
        transactions t1
        INNER JOIN transactions t2 ON t1.checkin_id = t2.checkin_id
    WHERE
        t1.service_id IS NOT NULL AND t2.service_id IS NOT NULL
), service_pairs AS (
    SELECT
        service_A,
        service_B,
        COUNT(*) AS pair_count
    FROM
        basket_fact
    GROUP BY
        service_A, service_B
), total_service_A AS (
    SELECT
        service_id AS service_A,
        COUNT(DISTINCT checkin_id) AS total_service_A
    FROM
        transactions
    GROUP BY
        service_id
)


SELECT
  sp.service_A,
  sp.service_B,
  sp.pair_count,
  sp.pair_count * 100.0 / (SELECT COUNT(DISTINCT checkin_id) FROM transactions) AS support,
  sp.pair_count * 100.0 / tsa.total_service_A AS confidence
FROM
  service_pairs sp
JOIN
  total_service_A tsa ON sp.service_A = tsa.service_A;