Do I really need a relation table in my case? - sql

Lets say I have a module. I build an interface where user can assign the module to groups.
Lets say currently I have 3 groups. In the UI the user would choose all 3 groups to assign the module to them. For examle in a multiple selectbox.
The intention of the user is to assign the module to ALL groups.
I guess I would need a many to many relation table. My source code would execute a sql query to insert 3 entries.
But wait. What if two weeks later the admin adds a new group... In the relation table are only 3 entries. And the user wonders why the module is not assigned to the new added group.
What would be an elegant solution? I need definite to update the relation table, or I make a new column -called, lets say "groups"- in my module table where I add the assigned groupsIds in this format: "1;2;7;15" or the keyword "All".
The advantage would be that with the keyword "All" I could know in my code that the module is assigned to all groups.
With the relation table I do not have this option. In addition I do not need to assign a group to a module. I just need to assign a module to groups.
In my opinion I do not need a relation table in this case.
What would you say? Or do you have another approach?

To make a many-to-many relation, you need a relation table.
To use a solution like putting comma separated values in a field to make multiple relations only works if you fetch data from separate tables in separate queries, when you need to use that to join the data in the database, it becomes very complicated very fast.
In a relation table you could use a null value to mean "all". In this example the modules 1 and 2 are members of the group 1 only, and the module 3 is a member of all groups:
ModuleId GroupId
-------- -------
1 1
2 1
3 null
To fetch data for groups using a relation like that you would use a query like:
select
g.GroupName,
m.ModuleName
from
Groups g
inner join GroupModules gm on gm.GroupId is null or gm.GroupId = g.GroupdId
inner join Modules m on m.ModuleId = gm.ModuleId
Another alternative is to use the relation table as usual, and add a property on the module that it's a member of all groups. When you add a new group, you would also add records in the relation table for all modules that are members of all groups. Example (in T-SQL):
create procedure Group_Add
#GroupName varchar(50)
as
set nocount on
declare #id int
insert into Groups (
GroupName
) values (
#GroupName
)
set #id = scope_identity()
insert in GroupModules (
GroupId,
ModuleId
)
select
#id, ModuleId
from
Modules
where
IsInAllGroups = 1

The typical database pattern for this is 3 tables, Module, Group, GroupModule. That is how many to many relationships are properly handled in database design.
How to get the data populated is a problem for your UI. You can indeed have a pull down list that includes the word all for them to use in choosing the groups a module will be associated with. It can even be the default. What you do is write code to interpret ALL to insert one record for every group.
NOw if you are adding groups as well as modules, you also need a process to make sure that when a group is added all those which shoudl have all groups get added to the new group. Personally I would put an IS_ALL flag on the Module table to make this easier. Then you know which moduels have been selected for all groups. You will need to make sure that if someone goes back and changes the module to specificgroups instead of all that this field is updated.

Related

Is it possible to inherit data from another table across schemas?

I'm trying to create a custom list of houses that a user can plot on a map. Each user can create houses, or edit the houses they've created. However there is a common set of houses that all users must have, and that can not be edited or deleted.
The users are divided into one schema per user, with a user.houses table listing all the custom houses for a single user.
Similarly, the public.houses table holds the houses available to every user. The structure of these tables are identical.
Is there a way to not duplicate the public.houses table data across all the user.houses tables?
I was able to use inheritance to ensure that the user.houses table exactly matches the public.houses table structure. Now I want to do this with the data as well.
INSERT INTO public.houses id VALUES (1), (2) ;
INSERT INTO user_a.houses id VALUES (3) ;
INSERT INTO user_b.houses id VALUES (4) ;
SELECT id FROM user_b.houses ;
I expect the output to be ids: [1, 2, 4]
This is too long for a comment.
You may have already made a bad decision by having one schema per user. There are some valid reasons for doing this -- security would be the number one reason. If you had a strong requirement that the data be separated. Another reason would be if the users were actually using different database versions (their own installations).
That said, it is usually better to support multiple users within a single databases/schema. The tables would identify the user for each entity (where it is important). Some tables, such as reference tables would be shared without ownership.
One thing that you can do is create views. The view:
create view v_user_houses as
select p.*
from public.houses p
union all
select u.*
from user.houses u;
You would need a separate view for each user.
An alternative would be to copy the publish houses into all the users table. When anything changes in the pubic houses, then you would propagate those changes to the users houses tables.
Try this
SELECT array_agg(a.id) FROM (
select id from public.houses
union all
select id from user_a.houses
union all
select id from user_b.houses ) AS a

How to make a lookup query to another table filter out records based on existing relationships between tables without macros?

Given those relationships, how do I limit the choice of Leader in a given record in GroupResults to only those StudentResults.IDs, which have Class&Group set to the same value as in the ID field of that record without creating forms and using VBA?
If I assign SELECT StudentResults.ID, StudentResults.FullName FROM StudentResults; to the Row Source in [Leader], like this ,
I get all the records in the table to choose from, regardless of the [Class&Group] field value, like this .
How do I restrict the assignable records to only those that belong to the corresponding group?
I'd spent a very long time trying to find a way to run a parametrised SQL query to pass the [Class&Group] to the WHERE clause, but eventually had to give up.
Thank you very much for your help!
P.S. I do realise that this may or may not be more of an ms-access, rather than SQL question.
Tables are not designed to be user interfaces. Conditional comboboxes, validation, etc. work best on forms. Comboxbox lookup dropdowns are more an Access GUI convenience to show parent table indicators for key number values.
When queries are then run from such tables, these drop downs fields show to help us humans who naturally understand names and indicators rather than integer primary/foreign keys. So instead of Student: 1, we see Student: John Doe. In fact, such table field drop downs even helps generate the same comboboxes on Access forms and reports in advance to avoid the designer in building them upon clicking the form icons on ribbon.
However, for your needs consider adjusting combobox by showing the [Class&Group] field so the user can see or match the group of specific Leader with appropriate one for current record in Class column. See adjusted query and column count/heads.
Row Source: SELECT s.ID, s.[Class&Group], s.FullName FROM StudentResults s
Bound Column: 1
Column Count: 3
Column Heads: Yes
Also, if you want the Leader name to always show when table or query is opened instead of ID, reverse the order in query and change bound column:
Row Source: SELECT s.FullName, s.[Class&Group], s.ID FROM StudentResults
Bound Column: 3
Column Count: 3
Column Heads: Yes

Storing list of strings in MySql column

I have a users table which contains data for registered users, each row represents a user. One column in particular should contain a list of groups the user is part of, at the moment that column is of TEXT type and I'm storing that list as a string where groups are separated with a semicolon, something like:
admin;moderators;devteam
And I was wondering: "Is this a good idea?", is there a better/safer way to do this that doesn't require a lot of effort to implement or is this "ok"?
Here is a pic of the table as of now:
And I was wondering: "Is this a good idea?"
Short answer: probably not.
Why
If you will ever need to do any manipulation on that column, you will find yourself in big trouble. Simply selecting all users in a group will require some operations on a string (usually not performance-friendly). Same will hold true for sorting, joining and all the other operations SQL is great for.
Solution
What you describe is a typical example of N:N relationship, where each user can belong to multiple groups and each group can have multiple users in it.
The 'standard' way of modeling this relationship is creating a new table, where each row will represent a user belonging to a group. The columns will be group and userID.
With data from your example
userID | group
--------|----------
1 | admin
1 | moderator
1 | test
This allows to have one row for each user in the users table, and getting the groups of a specific user is as simple as
select group
from user_groups
where userID = '1'

SQL: Showing a Link Option Using Conditions

I'm trying to make a menu option available to users depending on conditions using 2 tables.
We have a directory website for different towns and each town administrator is able to add subcategories to main categories in order to customise their directory structure, but it's currently allowing them to add subcategories to further subcategories that are already populated with adverts - which it shouldn't do.
I would like to only show the option to 'Add a subcatgory' to another subcategory when there are no adverts already added to it.
For example, there is a subcategory called 'Accountants' in all town directories. It is populated with client adverts in the 'Brentwood' directory, but not in the 'Shrewsbury' directory.
I want to only show the option to 'Add a subcategory', if the following three conditions are met using data from the following tables and columns:
'Directories' Table: It needs to be the current franchise town (using the 'FranchiseGID' column)
'Directories' Table: It must apply to each individual subcategory (GID column)
'Clients' Table: No Client have registered to be listed in the subcategory (GID column)
So using a combination of these table columns, using the 'iAdvertCount' as the counter and 'ClientGID' to check whether there are advert subscriptions in that particular subcategory; also that it's only being applied to the current franchise website (FranchiseGID).
I can select information from one table, but not multiple - so would love some help on how to select the reqiured info from the above columns and tables.
Here is how far I've got with the 'Directories' table, but need to add in the Client GID condition from 'Clients' too:
Dim iAdvertCount
SQLCommand = "SELECT COUNT(*) AS Counter FROM Directories WHERE GID is not null AND FranchiseGID is not null"
rsTemp.Open SQLCommand, objConn, adOpenStatic, adLockReadOnly
iSubscriptionCount = rsTemp("Counter")
rsTemp.Close
if iSubscriptionCount = 0 then
%><% = GetIcon("Add", "Add Sub Directory", 25, True) %><%
end if
So far I am able to hide the 'Add a subcategory' option on any website franchise (e.g. Brentwood) directory that contains a ClientGID, but it's being applied to all subcategories regardless of whether they contain Client Subscriptions/Adverts or not.
I am hoping someone can help and indeed make sense of what I wrote to assist in some way as it would help a lot!
I'm hoping this is more than a proverbial shot in the dark
I believe you need to use an exist operator.
The exist operator is used in the where clause to determine if subquery returns results or not.
So your query might look like:
SELECT COUNT(*) AS Counter
FROM Directories
WHERE GID is not null
AND FranchiseGID is not null
AND NOT EXISTS(SELECT 'ARBITRARY VALUE'
FROM clients
WHERE clients.gid = directories.gid)
The exists operator takes advantage of a subquery that's why we're able to refer to the directories table even though it's not in the subquery's from clause. So if the subquery returns anything it will not be returned to be counted by the count function
You'll probably need to change the subquery's where clause in order to get it to work correctly since I'm not sure how your database is structured

What is the preferred way of saving dynamic lists in database?

In our application user can create different lists (like sharepoint) for example a user can create a list of cars (name, model, brand) and a list of students (name, dob, address, nationality), e.t.c.
Our application should be able to query on different columns of the list so we can't just serialize each row and save it in one row.
Should I create a new table at runtime for each newly created list? If this was the best solution then probably Microsoft SharePoint would have done it as well I suppose?
Should I use the following schema
Lists (Id, Name)
ListColumns (Id, ListId, Name)
ListRows (Id, ListId)
ListData(RowId, ColumnId, Value)
Though a single row will create as many rows in list data table as there are columns in the list, this just doesn't feel right.
Have you dealt with this situation? How did you handle it in database?
what you did is called EAV (Entity-Attribute-Value Model).
For a list with 3 columns and 1000 entries:
1 record in Lists
3 records in ListColumns
and 3000 Entries in ListData
This is fine. I'm not a fan of creating tables on-the-fly because it could mess up your database and you would have to "generate" your SQL queries dynamically. I would get a strange feeling when users could CREATE/DROP/ALTER Tables in my database!
Another nice feature of the EAV model is that you could merge two lists easily without droping and altering a table.
Edit:
I think you need another table called ListRows that tells you which ListData records belong together in a row!
Well I've experienced something like this before - I don't want to share the actual table schema so lets do some thought exercises using some of the suggested table structures:
Lets have a lists table containing a list of all my lists
Lets also have a columns table containing the metadata (column names)
Now we need a values table which contains the column values
We also need a rows table which contains a list of all the rows, otherwise it gets very difficult to work out how many rows there actually are
To keep things simple lets just make everything a string (VARCAHR) and have a go at coming up with some queries:
Counting all the rows in a table
SELECT COUNT(*) FROM [rows]
JOIN [lists]
ON [rows].list_id = [Lists].id
WHERE [Lists].name = 'Cars'
Hmm, not too bad, compared to:
SELECT * FROM [Cars]
Inserting a row into a table
BEGIN TRANSACTION
DECLARE #row_id INT
DECLARE #list_id INT
SELECT #list_id = id FROM [lists] WHERE name = 'Cars'
INSERT INTO [rows] (list_id) VALUES (#list_id)
SELECT #row_id = ##IDENTITY
DECLARE #column_id INT
-- === Need one of these for each column ===
SELECT #column_id = id FROM [columns]
WHERE name = 'Make'
AND list_id = #list_id
INSERT INTO [values] (column_id, row_id, value)
VALUES (#column_id, #row_id, 'Rover')
-- === Need one of these for each column ===
SELECT #column_id = id FROM [columns]
WHERE name = 'Model'
AND list_id = #list_id
INSERT INTO [values] (column_id, row_id, value)
VALUES (#column_id, #row_id, 'Metro')
COMMIT TRANSACTION
Um, starting to get a little bit hairy compared to:
INSERT INTO [Cars] ([Make], [Model}) VALUES ('Rover', 'Metro')
Simple queries
I'm now getting bored of constructing tediously complex SQL statements so maybe you can have a go at coming up with equivalent queries for the followng statements:
SELECT [Model] FROM [Cars] WHRE [Make] = 'Rover'
SELECT [Cars].[Make], [Cars].[Model], [Owners].[Name] FROM [Cars]
JOIN [Owners] ON [Owners].id = [Cars].owner_id
WHERE [Owners].Age > 50
SELECT [Cars].[Make], [Cars].[Model], [Owners].[Name] FROM [Cars]
JOIN [Owners] ON [Owners].id = [Cars].owner_id
JOIN [Addresses] ON [Addresses].id = [Owners].address_id
WHERE [Addresses].City = 'London'
I hope you are beginning to get the idea...
In short - I've experienced this before and I can assure you that creating a database inside a database in this way is definitely a Bad Thing.
If you need to do anything but the most basic querying on these lists (and literally I mean "Can I have all the items in this list please?"), you should try and find an alternative.
As long as each user pretty much has their own database I'll definitely recommend the CREATE TABLE approach. Even if they don't I'd still recommend that you at least consider it.
Perhaps a potential solution would be the creating of lists can involve CREATE TABLE statements for those entities/lists?
It sounds like the db structure or schema can change at runtime, or at the user's command, so perhaps something like this might help?
User wants to create a new list of an entity never seen before. Call it Computer.
User defines the attributes (screensize, CpuSpeed, AmountRAM, NumberOfCores)
System allows user to create in the UI
system generally lets them all be strings, unless can tell when all supplied values are indeed dates or numbers.
build the CREATE scripts, execute them against the DB.
insert the data that the user defined into that new table.
Properly coded, we're working with the requirements given: let users create new entities. There was no mention of scale here. Of course, this requires all input to be sanitized, queries parameterized, actions logged, etc.
The negative comment below doesn't actually give any good reasons, but creates a bit of FUD. I'd be interested in addressing any concerns with this potential solution. We haven't heard about scale, security, performance, or usage (internal LAN vs. internet).
You should absolutely not dynamically create tables when your users create lists. That isn't how databases are meant to work.
Your schema is correct, and the pluralization is, in my opinion, also correct, though I would remove the camel case and call them lists, list_columns, list_rows and list_data.
I would further improve upon your schema by skipping rows and columns tables, they serve no purpose. Simply have a row/column number attached to each cell, and keep things sparse: Don't bother holding empty cells in the database. You retain the ability to query/sort based on row/column, your queries will be (potentially very much) faster because the number of list_cells will be reduced, and you won't have to do any crazy joining to link your data back to its table.
Here is the complete schema:
create table lists (
id int primary key,
name varchar(25) not null
);
create table list_cells (
id int primary key,
list_id int not null references lists(id)
on delete cascade on update cascade,
row int not null,
col int not null,
data varchar(25) not null
);
It sounds like you might have Sharepoint already deployed in your environment.
Consider integrating your application with Sharepoint, and have it be your datastore. No need to recreate all the things you like about Sharepoint, when you could leverage it.
It'd take a bit of configuring, but you could call SP web services to CRUD your list data for you.
inserting list data into Sharepoint via web services
reading SP lists via web services
Sharepoint 2010 can also expose lists via OData, which would be simple to consume from any application.