I am developing multitenant SaaS based application and going with Shared Database for storing all the tenant records with help of TenantId column.
Now the problem is i have some list of lookup records that needs to be shared for all the tenants. For example list of games.
GamesTable
Id
GameName
Also have another table used for storing only tenant specific records
TenantGames
Id
TenantId
GameName
The basic need is i want to use both table data and get the necessary details (Game_Name) while joining with another transaction table like UserGames. How can i achive this with this design? Here Game_Name can be either referred from Games Shared table or TenantSpecificGames table
Is there any other DB design which allows me to do mix both common master data and tenant master data with JOIN?
Basic requirement is keep common data and allow customization for the tenants if they want to add any new items.
This is the design I would then use.
Games
Id
GameName
IsTenantSpecific
SomeGameSpecificColumn
TenantGames
GameId
TenantId
SomeTenantSpecificColumn
AnotherTenantSpecificColumn
Then you can query that table in a Join with:
...
FROM
Games
INNER JOIN UserGames ON
UserGames.GameId = Games.Id
LEFT JOIN TenantGames ON
TenantGames.GameId = Games.Id
WHERE
TenantGames.TenantId = #tenantId OR
(
TenantGames.TenantId IS NULL AND
IsTenantSpecific = 0
)
Game specific fields can be put in the Games table. Tenant specific fields can be added to the TenantGames table, and those fields will be NULL if it is not a tenant specific customization.
We have a saas based database and we keep common data and tenant data in the same table.
Concept
GamesTable
Id NOT NULL
TenantId NULL
GameName NOT NULL
Add a unique key for TenantId and GameName
if TenantId is NULL you know it is common data
if TenantId is NOT NULL you know it belongs to a specific tenant and who exactly.
"Is there any other DB design which allows me to do mix both common
master data and tenant master data with JOIN?"
Yes
SELECT *
FROM GamesTable where TenantId = 'your tenant id'
UNION
SELECT *
FROM GamesTable where TenantId IS NULL -- common
This is a classic example of "many to many".
Table: Games
------------
GameID
GameName
IsMasterGame
TennantGames
------------------
GameID
TennantID
Tennants
------------
TennantID
...
To get the games for a given tennant, you would run a query like:
select *
from Games
where isMasterGame = true
union
select *
from Games g,
TennantGames tg
where g.GameID = tg.GameID
and isMasterGame = false
and tg.TennantID = $currentTennant
(Apologies for archaic join syntax)
The union allows you to ask two questions: which games apply to everyone (isMasterGame = true), and secondly which games apply to the current tennant (tg.TennantID = $currentTennant). Logically, tennant games cannot also be master games.
You can merge the tables leaving TenantId as NULL for records you wish to not be Tenant specific.
Games
Id
TenantId
GameName
The you can query that table in a Join with:
...
FROM
Games
INNER JOIN UserGames ON
UserGames.GameId = Games.Id
WHERE
Games.TenantId = #tenantId OR
Games.TenantId IS NULL
This will save you the trouble of ensuring that the Id is unique between the tables, unless you are using a UNIQUEIDENTIFIER for the Id.
Related
I have the following table designed already.
Now the new requirement is i need to show some Category for all the
tenants. Also each tenant should able to add their new Category into
the Master Category. So they can see all master + their specific
categories
Tenant Table
TenantId
Name
Group Table
GroupId
Name
Category Table
CategoryId
Name
TenantXCategory
TenantId
CategoryId
What changes i can do in above tables to achieve it? I tried this below
Modified Category table to below.
Category Table
CategoryId
Name
TenantId NULL // This indicates tenant specific category
Add a unique key for TenantId and Name
Then queried
SELECT *
FROM Category where TenantId = 1
UNION
SELECT *
FROM Category where TenantId IS NULL
But the problem is if two tenant only want to see a particular
Category, I need to add a new row with other TenantId in Category
table. This mean i am creating duplicate entry in a lookup table. Any
suggestion to achieve the new requirement?
So, a tenant A may see:
(1) master categories,
(2) categories that belong to tenant A,
(3) categories which belong to another tenant and which the other tenant explicitly allowed tenant A to see
Your present schema seems to satisfy the requirements. In particular, the first two rules can indeed be implemented using a nullable TenantId column in the Category table, where NULL would stand for a master category and a non-NULL value would reference the creator/owner of the category and thus signify a tenant-specific category. (I might rename the column to something like OwnerTenantId for better clarity, but that might be just me.)
To retrieve only master categories or those that belong to the specified tenant, you could use the query you've posted in your question or this one (which will probably yield an identical execution plan to your query's):
SELECT
CategoryId,
CategoryName,
CASE
WHEN TenantId = #TenantId THEN 'Mine'
WHEN TenantId IS NULL THEN 'Master'
END AS Ownership
FROM Category
WHERE TenantId = #TenantId
OR TenantId IS NULL
;
To implement the third rule, you could use your TenantXCategory table to store categories available to tenants in addition to those that are accessible using the first two rules. That is, if tenant M permits tenant N to see some of tenant M's categories, the categories' Ids would be inserted into TenantXCategory along with tenant N's Id.
So, to query all categories available to a particular tenant, you could do something along the lines of the following:
SELECT
c.CategoryId,
c.CategoryName,
CASE
WHEN c.TenantId = #TenantId THEN 'Mine'
WHEN c.TenantId IS NULL THEN 'Master'
WHEN tc.CategoryId IS NOT NULL THEN 'Someone else''s'
END AS Ownership
FROM Category c
LEFT JOIN TenantXCategory tc
ON tc.CategoryId = c.CategoryId AND tc.TenandId = #TenantId
WHERE c.TenantId = #TenantId
OR c.TenantId IS NULL
OR tc.CategoryId IS NOT NULL
;
If you're going to be adding additional categories, you should probably have an identifier to distinguish between master and user-defined categories.
CREATE TABLE CategoryType (
[CategoryTypeID] int primary key identity,
[Description]
)
CREATE TABLE Category (
[CategoryID] int primary key identity,
[CategoryName] nvarchar(max)
[CategoryTypeID] int
)
/* CategoryTypes
1 Master
2 User-defined */
Show categories for a specific tenant
SELECT *
FROM TenantXCategory txc
JOIN Tenant t
ON t.TenantID = txc.TenantID
JOIN Category c
ON c.CategoryID = txc.CateoryID
WHERE t.TenantName = N'user1909604'
-- AND c.CategoryTypeID = 1 -- Only show master categories
-- AND c.CategoryTypeID = 2 -- Only show user-defined categories
-- AND c.CategoryID in (1, 2) -- Only show specific categories
To add a category for a user, store the category in your cross reference table. If the category doesn't exist, you'll have to add it to the category table first.
INSERT TenantXCategory (TenantID, CategoryID)
SELECT #TenantID, #CategoryID
--
Unless you're storing who created the category (for auditing purposes), this should work. If not, I misunderstood your question and you should clarify what you're trying to do.
I am creating a small playlist program on VB, which contains adduser, deleteuser and also user can modify its playlist.
My stupid question is, how do I manage user playlist? Consider I am using database, where should I add user?
As a new table in Database?
As a new Entry in some kind of Table which contains userID, Name and its undefined number of choices?
If I select option 2, what kind of datatype handles a integer set of undefined size?
Thank you.
You would create 3 tables:
Users table
-----------
userID
email
password
name
Playlist table
--------------
playlistID
userID
trackID
Tracks table
------------
trackID
trackName
You would then create relationship between the tables:
Users.userID 1-* Playlist.userID (1 to many)
Tracks.trackID 1-* Playlist.trackID (1 to many)
Then you would store the users choices in the playlist table.
To see a users tracks you could do:
SELECT Playlist.trackID, Tracks.trackName
FROM Playlist
JOIN Tracks ON Playlist.trackID=Tracks.trackID
WHERE Playlist.userID = 12
ORDER BY Tracks.trackName
This is the basics of relational database system and normalisation of data.
For more information see:
http://www.dreamincode.net/forums/topic/179103-relational-database-design-normalization/
Due to non-disclosure at my work, I have created an analogy of the situation. Please try to focus on the problem and not "Why don't you rename this table, m,erge those tables etc". Because the actual problem is much more complex.
Heres the deal,
Lets say I have a "Employee Pay Rise" record that has to be approved.
There is a table with single "Users".
There are tables that group Users together, forexample, "Managers", "Executives", "Payroll", "Finance". These groupings are different types with different properties.
When creating a "PayRise" record, the user who is creating the record also selects both a number of these groups (managers, executives etc) and/or single users who can 'approve' the pay rise.
What is the best way to relate a single "EmployeePayRise" record to 0 or more user records, and 0 or more of each of the groupings.
I would assume that the users are linked to the groups? If so in this case I would just link the employeePayRise record to one user that it applies to and the user that can approve. So basically you'd have two columns representing this. The EmployeePayRise.employeeId and EmployeePayRise.approvalById columns. If you need to get to groups, you'd join the EmployeePayRise.employeeId = Employee.id records. Keep it simple without over-complicating your design.
My first thought was to create a table that relates individual approvers to pay rise rows.
create table pay_rise_approvers (
pay_rise_id integer not null references some_other_pay_rise_table (pay_rise_id),
pay_rise_approver_id integer not null references users (user_id),
primary key (pay_rise_id, pay_rise_approver_id)
);
You can't have good foreign keys that reference managers sometimes, and reference payroll some other times. Users seems the logical target for the foreign key.
If the person creating the pay rise rows (not shown) chooses managers, then the user interface is responsible for inserting one row per manager into this table. That part's easy.
A person that appears in more than one group might be a problem. I can imagine a vice-president appearing in both "Executive" and "Finance" groups. I don't think that's particularly hard to handle, but it does require some forethought. Suppose the person who entered the data changed her mind, and decided to remove all the executives from the table. Should an executive who's also in finance be removed?
Another problem is that there's a pretty good chance that not every user should be allowed to approve a pay rise. I'd give some thought to that before implementing any solution.
I know it looks ugly but I think somethimes the solution can be to have the table_name in the table and a union query
create table approve_pay_rise (
rise_proposal varchar2(10) -- foreign key to payrise table
, approver varchar2(10) -- key of record in table named in other_table
, other_table varchar2(15) );
insert into approve_pay_rise values ('prop000001', 'e0009999', 'USERS');
insert into approve_pay_rise values ('prop000001', 'm0002200', 'MANAGERS');
Then either in code a case statement, repeated statements for each other_table value (select ... where other_table = '' .. select ... where other_table = '') or a union select.
I have to admit I shudder when I encounter it and I'll now go wash my hands after typing a recomendation to do it, but it works.
Sounds like you'd might need two tables ("ApprovalUsers" and "ApprovalGroups"). The SELECT statement(s) would be a UNION of UserIds from the "ApprovalUsers" and the UserIDs from any other groups of users that are the "ApprovalGroups" related to the PayRiseId.
SELECT UserID
INTO #TempApprovers
FROM ApprovalUsers
WHERE PayRiseId = 12345
IF EXISTS (SELECT GroupName FROM ApprovalGroups WHERE GroupName = "Executives" and PayRiseId = 12345)
BEGIN
SELECT UserID
INTO #TempApprovers
FROM Executives
END
....
EDIT: this would/could duplicate UserIds, so you would probably want to GROUP BY UserID (i.e. SELECT UserID FROM #TempApprovers GROUP BY UserID)
Currently I am designing a small twitter/facebook kind of system, where in a user should be able to see his friends latest activities.
I am using ASP.NET with MySQL database.
My Friendships table is as follows:
|Friendshipid|friend1|Friend2|confirmed|
Friend1 and Friend2 in the above table are userids.
User activities table design following:
|activityId|userid|activity|Dated|
Now, I am looking for best way to query the latest 50 friend activities for a user.
For example, let's say if Tom logs into the system, he should be able to see latest 50 activities among all his friends.
Any pointers on the best practices, a query or any information is appreciated.
It largely depends on what data is stored in the Friendships table. For example, what order are the Friend1 and Friend2 fields stored in? If, for the fields (friend1, friend2) the tuple (1, 2) exists, will (2, 1) exist also?
If this is not the case, then this should work:
SELECT activities.*
FROM Activities
INNER JOIN Friendships ON userid = friend1 OR userid = friend2
WHERE activity.userid != [my own id]
AND confirmed = TRUE
LIMIT 50;
If you have database performance concern, you may redefine the friendship table as following:
friendshipid, userid, friendid, confirmed
When you query the latest 50 activities, the SQL would be:
SELECT act.*
FROM Activities AS act
INNER JOIN
Friendships AS fs
ON fs.friendid = act.userid
AND fs.user_id = 'logon_user_id'
AND confirmed = TRUE
ORDER BY act.dated DESC
LIMIT 50;
And if there is a index on Friendships(userid) column, it would give the database the chance to optimize the query.
The friendship table redefined needs to create two tuples when a friendship occur, but it still obey the rule of business, and, has performance benefit when you need it.
Web app is being written in classic ASP with a MSSQL backend. On this particular page, the admin can select 1 or any/all of the employees to assign the project to. I'm trying to figure out a simple way to store the employee IDs of the people assigned to it in one column.
The list of employees is generated from another table and can be dynamic (firing or hiring) so I want the program to be flexible enough to change based on these table changes.
Basically need to know how to assign multiple people to a project that can later be called up on a differen page or from a different query.
Sorry for the n00bish question, but thanks!
Don't store multiple ID's in one column! Create another table with the primary key of your existing table and a single ID that you want to store. You can then insert multiple rows into this new table, creating a 1:m (one to many) relationship. For example, let's look at an order table:
order:
order_id
order_date
and I have a product table...
product:
product_id
product_name
Now, you could go down the road of adding a column to order that let you list the products in the order, but that would be bad form. What you want instead is something like..
order_item:
order_item_id
order_id
product_id
quantity
unit_price
You can then perform a join to get all of the products for a particular order...
select
product.*
from orders
inner join order_item on order_item.order_id = order.order_id
inner join product on product.product_id = order_item.product_id
where orders.order_id = 5
Here's an example order_id of 5, and this will get all of the products in that order.
You need to create another table that stores these values such as. So this new table would store one row for each ID, and then link back to the original record with the original records ID.