I have to add a coupon table to my db. There are 3 types of coupons : percentage, amount or 2 for 1.
So far I've come up with a coupon table that contains these 3 fields. If there's a percentage value not set to null then it's this kind of coupon.
I feel it's not the proper way to do it. Should I create a CouponType table and how would you see it? Where would you store these values?
Any help or cue appreciated!
Thanks,
Teebot
You're correct, I think a CouponType table would be fit for your problem.
Two tables: Coupons and CouponTypes. Store the CouponTypeId inside the Coupons table.
So for an example, you'll have a Coupon record called "50% off", if would reference the percent off CouponType record and from there you could determine the logic to take 50% off the cost of the item.
So now you can create unlimited coupons, if it's a dollar amount coupon type it will take the "amount" column and treat it as a dollar amount. If it's a percent off it will treat it as a percentage and if it's an "x for 1" deal, it will treat the value as x.
- Table Coupons
- ID
- name
- coupon_type_id # (or whatever fits your style guidelines)
- amount # Example: 10.00 (treated as $10 off for amount type, treated as
# 10% for percent type or 10 for 1 with the final type)
- expiration_date
- Table CouponTypes
- ID
- type # (amount, percent, <whatever you decided to call the 2 for 1> :))
In the future you might have much more different coupon types. You could also have different business logic associated with them - you never know. It's always useful to do the things right in this case, so yes, definitely, create a coupon type field and an associated dictionary table to go with it.
I would definitely create a CouponType lookup table. That way you avoid all the NULL's and allow for more coupon types in the future.
Coupon
coupon_id INT
name VARCHAR
coupon_type_id INT <- Foreign Key
CouponType
coupon_type_id INT
type_description VARCHAR
...
Or I suppose you could have a coupon type column in your coupon table CHAR(1)
Related
I want to know the best database design for storing multi-currency money values in database.
For example I have one Entity that has two money fields. These fields may have different currency types for each record.
Example:
Table A:
-------------
Id
Name
Amount1
Amount2
Sample records:
Id Name Amount1 Amount2
1 aaa 12$ 15£
2 bbb 30€ 17$
I cannot store values in one currency. For example I want to lend somebody money. I store its data in a table. The type of money can be different. When I lend someone 10€ I should take back 10€ and I cannot store all values in one currency like dollar.
I want to know what the best and more efficient design for storing these values in database is.
Should I store amount and currency sign in one column with string data type or it is better to define Money and Currency tables as separate tables for storing money values?
Or any other design?
Without commenting too much on the actual structure of your table (2 or more monies in a single table is OK, as long as they have business meaning—like subtotal, shippingFee and total), I'll focus on how to store them:
Storing the monies
You should store the amount in one column, and the currency code in another.
Currency
You'll typically deal with ISO 4217, which comes with 2 codes for each currency:
a 3-letter alpha code, such as EUR or USD
a 3-digit numeric code, such as 978 or 840
Which one you use is up to you. You can save one byte per record by using numeric codes. On the other hand, alpha codes are easier to read and remember for humans, and could make it easier to use custom currencies (crypto), that may have a de-facto standard alpha code but no numeric code.
Amount
Because currencies may have different numbers of decimal places (2 for EUR, USD etc., but 0 for JPY, 3 for TND...), I always advise to store amounts in minor currency units (cents) as an integer.
Your table would therefore look like:
amount INT NOT NULL,
currencyCode CHAR(3) NOT NULL
If you want to store 12.34 USD, you'll actually store (1234, 'USD').
Multiple amounts, single currency
If your table holds several monies that will always be in a single currency (like the subtotal/shippingFee/total example above), all 3 amounts can share a single currency code:
subtotal INT NOT NULL,
shippingFee INT NOT NULL,
total INT NOT NULL,
currencyCode CHAR(3) NOT NULL
In this case, you probably don't need separate subtotalCurrencyCode, shippingFeeCurrencyCode and totalCurrencyCode, although there are other business cases where amounts will be in different currencies. The decision is yours.
Retrieving the monies
Of course dealing with integer amounts is not very easy, so you'll need to use a money library that supports converting from/to minor amounts.
For example in PHP, you can use brick/money (disclaimer: I'm the author):
$money = Money::ofMinor(1234, 'USD');
$money->getAmount(); // 12.34
$money->getMinorAmount(); // 1234
Never ever store multiple values in the same column!!
The currecny and the amount need to be separated. Also, if you have column names with numbers then you most probably do something wrong. This is one way to do it:
table products
--------------
id
name
table prices
------------
id
product_id
amount
currency_id
table currencies
----------------
id
name
abbreviation_sign
Then to get the Dollar price of a product you can do
select pri.amount
from prices pri
join products prod on prod.id = pri.product_id
join currencies c on c.id = pri.currency_id
where prod.id = 123
and c.name = 'Dollar'
With proper indexes this is really fast to query.
I have three tables, one dim table called "ISO_ccy" only showing the ISO acronyms of currencies, one dim table showing the "home currency" of an entity ("entities") and another (fact) table ("trades") showing foreign exchange (FX) trades. The thing with FX trades is, they always have two currencies (ccy) involved, hence the latter table has two columns with currency ISO codes (and corresponding amounts). The two dim tables both only have one column with ISO ccy codes (the table "ISO_ccy" having distinct values only).
I now have one (active) relationship for currency 1 (ccy1) and one inactive for currency 2 (ccy2) between the "ISO_ccy" and the "trades" table. There is also an active relationship between "ISO_ccy" and "entities" tables.
I need to calculate the sum for each currency and each entity where the currency is not equal to the "home currency" of that entity.
Seems to be pretty straight forward for the ccy with an active relationship (ccy1):
Sum_Hedges_activeRelation:=
CALCULATE(
SUM([Amount_ccy1]);
FILTER(trades;trades[ccy1]>LOOKUPVALUE(entities[ccy];entities[name];trades[name]))
)
The filter expression ensures that only amounts are shown where the ccy of a trade is not equal to the "home" ccy of the entity.
Here, I'm getting the desired result.
Now I need to do the same with the inactive relation (ccy2).
This is what I tried:
Sum_Hedges_in-activeRelation:=
CALCULATE(
SUM([Amount_ccy2]);
USERELATIONSHIP(trades[ccy2];ISO_ccy[ccy]);
FILTER(trades;trades[ccy2]<>LOOKUPVALUE(entities[ccy];entities[name];trades[name]))
)
However, I only get an "empty" result.
I also tried to add "ALL(trades)" to the CALCULATE function. No results there as well.
So, I am a bit at a loss now, how I can make this work. Can you please help?
UPDATE 08 April 2019 with a solution:
I found a solution to my problem here:
sqlbi: USERELATIONSHIP in a Measure
Now my forumlar looks like this:
Sum_Hedges_in-activeRelation:=
CALCULATE(
CALCULATE(
SUM([Amount_ccy2]);
FILTER(trades;trades[ccy2]
<>LOOKUPVALUE(entities[ccy];entities[name];trades[name]))
);
USERELATIONSHIP(trades[ccy2];ISO_ccy[ccy])
)
This is slightly different from the solution provided (for a column related context) in the referenced article, as I omitted the ALL() instruction in the outer CALCULATE(). I could not explain, though...
I'm using ssas tabular (powerpivot) and need to design a data-model and write some DAX.
I have 4 tables in my relational database-model:
Orders(order_id, order_name, order_type)
Spots (spot_id,order_id, spot_name, spot_time, spot_price)
SpotDiscount (spot_id, discount_id, discount_value)
Discounts (discount_id, discount_name)
One order can include multiple spots but one spot (spot_id 1) can only belong to one order.
One spot can include different discounts and every discount have one discount_value.
Ex:
Order_1 has spot_1 (spot_price 10), spot_2 (spot_price 20)
Spot_1 has discount_name_1(discount_value 10) and discount_name_2 (discount_value 20)
Spot_2 has discount_name_1(discount_value 15) and discount_name_3 (discount_value 30)
I need to write two measures: price(sum) and discount_value(average)
How do I correctly design a star schema with fact table (or maybe two fact tables) so that I in my powerpivot cube can get:
If i choose discount_name_1 I should get
order_1 with spot_1 and spot_2 and price on order_1 level will have value 50 and discount_value = 12,5
If I choose discount_name_3 I should get
order_1 with only spot_2 and price on order level = 20 and discount_value = 30
Fact(OrderKey, SpotKey, DiscountKey, DateKey, TimeKey Spot_Price, Discount_Value,...)
DimOrder, DimSpot, DimDiscount, etc....
TotalPrice:=
SUMX(
SUMMARIZE(
Fact
,Fact[OrderKey]
,Fact[SpotKey]
,Fact[Spot_Price]
)
,Fact[Spot_Price]
)
AverageDiscount:=
AVERAGE(Fact[Discount_Value])
Fact table is denormalized and you end up with the simplest star schema you can have.
First measure deserves some explanation. [Spot_Price] is duplicated for any spot with multiple discounts, and we would get wrong results with a simple SUM(). SUMMARIZE() does a group by on all the columns passed to it, following relationships (if necessary, we're looking at a single table here so nothing to follow).
SUMX() iterates over this table and accumulates the value of the expression in its second argument. The SUMMARIZE() has removed our duplicate [Spot_Price]s so we accumulate the unique ones (per unique combination of [OrderKey] and [SpotKey]) in a sum.
You say
One order can include multiple spots but one spot (spot_id 1) can only
belong to one order.
That's is not supported in the table definitions you give just above that statement. In the table definitions, one order has only one spot but (unless you've added a unique index to Orders on spot_id) each Spot can have multiple orders. Each Spot can also have multiple discounts.
If you want to have the relationship described in your words, the table definitions should be:
Orders(order_id, order_name, order_type)
OrderSpot(order_id, spot_id) -- with a Unique index on spot_id)
Spots (spot_id, spot_name, spot_time, price)
or:
Orders(order_id, order_name, order_type)
Spots (spot_id, spot_name, spot_time, order_id, price)
You can create the ssas cube with Order as the fact table, with one dimention in the Spot Table. If you then add the SpotDiscount and Discount tables with their relations (SpotDiscount to Spot, Discount to SpotDiscount) you have a 1 dimentional.
EDIT as per comments
Well, the Fact table would have order_id, order_name, order_type
The Dimension would be made up of the other 3 tables and have the columns you're interested in: probably spot_name, spot_time, spot_price, discount_name, discount_value.
I have a Client Dimension and a Fact table which tracks Sessions with Clients, these have the following columns:
Code:
[DimClient]
----------
PK_ClientKey
ClientNumber
EmailAddress
Postcode
PostcodeLongitude
PostcodeLatitude
DateOfBirth
Gender *
Sexuality *
CulturalIdentity *
LanguageSpokenAtHome *
CountryOfBirth
UsualAccommodation *
LivingWith *
OccupationStatus *
HighestLevelOfSchooling *
RegistrationDate
LastLoginDate
Status
[FactSession]
-------------
PK_SessionKey
FK_ClientKey
...
My first requirement was to start grouping the age of the Clients at a specific Session (FactSession), the best way to approach this was to create a Age Group dimension and create a foreign key (FK_AgeGroupKey) in the FactSession to the DimAgeGroup dimension.
Now I'm thinking it would be good to track all the columns with an * (above). These could (not yet proven) have a high correlation against Sessions. Reading through the DWH Toolkit it seems a Mini Dimension to accomodate all the * columns along with the Age Group would suit best, so I put together the following structure:
Code:
[DimClient]
----------
PK_ClientKey
ClientNumber
...
Status
[DimDemographic]
-----------------
PK_DemographicKey
AgeGroup
Gender
Sexuality
...
HighestLevelOfSchooling
[FactSession]
-------------
PK_SessionKey
FK_ClientKey
FK_DemographicKey
The DimDemographic table would need to utilize a SCD Type 2 to be able to track the changes over time. Would this be the best approach to my requirements?
Additionally, I have RegistrationDate and LastLoginDate columns on my Client Dimension, in the case where a Client registers but never logs in what would be the best value to put in the LastLoginDate field? Something like '1900-01-01' or NULL?
Sorry for the long post but hopefully I have given enough information Thanks in advance!
I would add a field to your client dimensions to indicate the user has never logged in. Something like:
select * form DimClient where HasUserLoggedIn = 'NO';
Its very human readable and you won't have to teach your business users about nulls. Traditionally nulls are bad in a Data Warehouse except in the case of numeric fact values, due to the complexities of null != null.
Yes, the above solution should work fine. It supports your need to track changes over time, otherwise you can have included the DimDemographic linkage directly in DimClient.
Regarding the date question, I believe you should use NULL, it means that there is no value because there was no login. Also, identifying non-logged-in would be:
select * from DimClient where LastLoginDate IS NULL
For me this reads much better than a query that uses an artificial date.
I have an application where the database back-end has around 15 lookup tables. For instance there is a table for Counties like this:
CountyID(PK) County
49001 Beaver
49005 Cache
49007 Carbon
49009 Daggett
49011 Davis
49015 Emery
49029 Morgan
49031 Piute
49033 Rich
49035 Salt Lake
49037 San Juan
49041 Sevier
49043 Summit
49045 Tooele
49049 Utah
49051 Wasatch
49057 Weber
The UI for this app has a number of combo boxes in various places for these lookup tables, and my client has asked that the boxes list in this case:
CountyID(PK) County
49035 Salt Lake
49049 Utah
49011 Davis
49057 Weber
49045 Tooele
'The Rest Alphabetically
The best plan I have for accomplishing this is to add a column to each lookup table for SortOrder(numeric). I had a colleague tell me he thought that would cause the tables to violate 3rd-Normal-Form, but I think the sort order still depends on the key and only the key (even though the rest of the list is alphabetical).
Is adding the SortOrder column the best way to do this, or is there a better way I am just not seeing?
I agree with #cletus that a sort order column is a good way to go and it does not violate 3NF (because, as you said, the sort order column entries are functionally dependent on the candidate keys of the table).
I'm not sure I agree that alphanumeric is better than numeric. In the specific case of counties, there are seldom new ones created. But there is no requirement that the numbers assigned are sequential; you can allocate them with numbers that are a multiple of a hundred, for example, leaving ample room for insertions.
Yes I agree a sort order column is the best solution when the requirements call for a custom sort order like the one you cite. I wouldn't go with a numeric column however. If the data is alphanumeric, the sort order should be alphanumeric. That way you can seed the value with whatever is in the county field.
If you use a numeric field you'll have to resequence the entire table (potentially) whenever you add a new entry. So:
Columns: ID, County, SortOrder
Seed:
UPADTE County SET SortOrder = CONCAT('M-', County)
and for the special cases:
UPDATE County
SET SortOrder = CONCAT('E-' . County)
WHERE County IN ('Salt Lake', 'Utah', 'Davis', 'Weber', 'Tooele')
Arguably you may want to put another marker column in to indicate those entries are special.
I went with numeric and large multiples.
Even with the CONCAT('E-'.. example, I don't get the required sort order. That would give me Davis, SL, Tooele... and Salt Lake needs to be first.
I ended up using multiples of 10 and assigned the non-special-sort entries a value like 10000. That way the view for each lookup can have
ORDER BY SortOrder ASC, OtherField ASC
Another programmer suggested using DECODE in Oracle, or CASE statements in SQL Server, but this is a more general solution. YMMV.