How to model many to many relationships without circles - database

Note: Because my original question wasn't understood clearly I'm writing it completely new!
I have two tables in my database, plus a junction/join table:
Recipes:
CREATE TABLE Recipes(
id INT(11) NOT NULL AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
PRIMARY KEY (id)
)
Ingredients:
CREATE TABLE Ingredients(
id INT(11) NOT NULL AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
PRIMARY KEY (id)
)
IngredientsRecipes:
CREATE TABLE IngredientsRecipes(
id INT(11) NOT NULL AUTO_INCREMENT,
recipeId INT(11) NOT NULL,
ingredientId INT(11) NOT NULL,
PRIMARY KEY (id)
)
My Ingredient class in php code looks like this:
class Ingredient{
private $id;
private $name;
private $recipes; //In which recipes this ingredient is used
}
And this is my Recipes class:
class Recipe{
private $id;
private $name;
private $ingredients; //Ingredients used in this Recipe
}
Now when I want to populate the two lists I have the follwing problem:
The Recipe class has many Ingredients, and the Ingredients class has many Recipes. Each class contains contains the other, I hope this litte picture can illustrate the situation.
Recipe | Ingredients | Recipes using |
|used in Recipe | this Ingredient |
----------------+---------------+-----------------+
|--Noodles------|Spaghetti
|
Spaghetti-------|--Sauce--------|--Spaghetti
|
|--Cheese-------|--Spaghetti
|
|--Mac n Cheese
|--Macaroni-----|Mac n Cheese
|
Mac n Cheese----|--Cheese-------|--Spaghetti
|
|--Mac n Cheese
What is the prefered way of writing the model class for a many to many relationship?

This is usually done by a join or mapping table to hold the relationship between the two, e.g.:
CREATE TABLE recipe (
recipe_id NUMERIC PRIMARY KEY
recipe_name VARCHAR (100)
-- etc...
);
CREATE TABLE ingredient (
ingredient_id NUMERIC PRIMARY KEY
ingredient_name VARCHAR (10),
-- etc...
);
CREATE TABLE recipe_ingredient (
recipe_id NUMERIC REFERENCES recipe (recipe_id),
ingredient_id NUMERIC REFERENCES ingredient (ingredient_id),
PRIMARY KEY (recipe_id, ingredient_id)
);

Related

How to normalize three identical tables into one table?

I have three tables:
cat1:
id(PK)
name
description
cat2:
id(PK)
name
description
cat1_id1(FK)
cat3
id(PK)
name
description
cat2_id(FK)
cat1 has one-to-many cat2, and cat2 has one-to-many cat3.
How do I normalize the three tables into one table?
For example this design:
CREATE TABLE IF NOT EXISTS public."animalGroups_animalgroup"
(
id bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY ,
name text
description text COLLATE pg_catalog."default" NOT NULL,
images character varying(100) COLLATE pg_catalog."default" NOT NULL,
)
CREATE TABLE IF NOT EXISTS public.category_category
(
id bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY ,
name character varying(100) COLLATE pg_catalog."default" NOT NULL,
description text COLLATE pg_catalog."default" NOT NULL,
images character varying(100) COLLATE pg_catalog."default" NOT NULL,
animalgroup_id(FK)
)
CREATE TABLE IF NOT EXISTS public.subcategory_subcategory
(
id bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY ,
name character varying(100) COLLATE pg_catalog."default" NOT NULL,
description text COLLATE pg_catalog."default" NOT NULL,
images character varying(100) COLLATE pg_catalog."default" NOT NULL,
category_id(FK)
)
CREATE TABLE IF NOT EXISTS public.animal_animal
(
id bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY ,
name character varying(100) COLLATE pg_catalog."default" NOT NULL,
description text COLLATE pg_catalog."default" NOT NULL,
images character varying(100) COLLATE pg_catalog."default" NOT NULL,
subcategory_id(FK)
)
animalgroup can have one or more categories
category can have one or more subcategory
subcategory can have one or more animal
How it will look like:
animal group has mammals
mammals (can have more categories) has for example categories cats, dogs
cats category (can have more subcategories) has for example subcategories little cats, big cats
little cats (subcategories can have more animals) has the real cat species ragdoll
Is this design correct?
They have four of the same fields. To add one more field, for example age, then in all four tables I have to add the field age.
Ok you changed your DB design so that would like like this:
SELECT * -- should specify columns here
FROM cat1
LEFT JOIN cat2 on cat1.id = cat2.cat1_id1
LEFT JOIN cat3 on cat2.id = cat3.cat2_id
The difference in naming (cat1_id1 vs cat2_id) is strange -- I think that 1 might be a typo.
original answer below
I'm guessing your tables actually look like this
cat1:
id
cat2id
name
description
cat2:
id
cat3id
name
description
cat3
id
name
description
Where the 1 to many relationship is represented by the id they are related to in the columns I added.
In that case you can join them like this
SELECT * -- should have column list here
FROM cat1
LEFT JOIN cat2 on cat1.cat2id = cat2.id
LEFT JOIN cat3 on cat2.cat3id = cat3.id

SQL to get teams with no power forward

I store data about basketball teams in my Teams table that looks like this:
CREATE TABLE Teams (
Id varchar(5) NOT NULL PRIMARY KEY,
TeamName varchar(50) NOT NULL
);
And I keep team members in TeamMembers table that looks like this:
CREATE TABLE TeamMembers (
Id int NOT NULL PRIMARY KEY,
TeamId VARCHAR(5) FOREIGN KEY REFERENCES Teams(Id),
LastName varchar(50) NOT NULL,
FirstName varchar(50) NOT NULL,
PositionId int NOT NULL
);
Positions are in another table with INT ID's. For example, Guard: 1, Center: 2 and Power Forward: 3 in this exercise.
I want to get a list of basketball teams with NO power forward.
Something like:
select *
from Teams
where Id not in
(
select TeamId
from TeamMembers
where PositionID = 4
)
When checking if a row doesn't exist, use a NOT EXISTS!.
SELECT
T.*
FROM
Teams AS T
WHERE
NOT EXISTS (
SELECT
'team has no power forward member'
FROM
TeamMembers AS M
WHERE
M.TeamID = T.ID AND
M.PositionID = 3) -- 3: Power Forward

Auto-increment Id based on composite primary key

Note: Using Sql Azure & Entity Framework 6
Say I have the following table of a store's invoices (there are multiple stores in the DB)...
CREATE TABLE [dbo].[Invoice] (
[InvoiceId] INTEGER NOT NULL,
[StoreId] UNIQUEIDENTIFIER NOT NULL,
CONSTRAINT [PK_Invoice] PRIMARY KEY CLUSTERED ([InvoiceId] ASC, [StoreId] ASC)
);
Ideally, I would like the InvoiceId to increment consecutively for each StoreId rather than independent of each store...
InvoiceId | StoreId
-------------------
1 | 'A'
2 | 'A'
3 | 'A'
1 | 'B'
2 | 'B'
Question: What is the best way to get the [InvoiceId] to increment based on the [StoreId]?
Possible options:
a) Ideally a [InvoiceId] INTEGER NOT NULL IDENTITY_BASED_ON([StoreId]) parameter of some kind would be really helpful, but I doubt this exists...
b) A way to set the default from the return of a function based on another column? (AFAIK, you can't reference another column in a default)
CREATE FUNCTION [dbo].[NextInvoiceId]
(
#storeId UNIQUEIDENTIFIER
)
RETURNS INT
AS
BEGIN
DECLARE #nextId INT;
SELECT #nextId = MAX([InvoiceId])+1 FROM [Invoice] WHERE [StoreId] = #storeId;
IF (#nextId IS NULL)
RETURN 1;
RETURN #nextId;
END
CREATE TABLE [dbo].[Invoice] (
[InvoiceId] INTEGER NOT NULL DEFAULT NextInvoiceId([StoreId]),
[StoreId] UNIQUEIDENTIFIER NOT NULL,
CONSTRAINT [PK_Invoice] PRIMARY KEY CLUSTERED ([InvoiceId] ASC, [StoreId] ASC)
);
c) A way to handle this in Entity Framework (code first w/o migration) using DbContext.SaveChangesAsync override or by setting a custom insert query?
Note: I realize I could do it with a stored procedure to insert the invoice, but I'd prefer avoid that unless its the only option.
You should stick to an auto-incrementing integer primary key, this is much simpler than dealing with a composite primary key especially when relating things back to an Invoice.
In order to generate an InvoiceNumber for the sake of a user, which increments per-store, you can use a ROW_NUMBER function partitioned by StoreId and ordered by your auto-incrementing primary key.
This is demonstrated with the example below:
WITH TestData(InvoiceId, StoreId) AS
(
SELECT 1,'A'
UNION SELECT 2,'A'
UNION SELECT 3,'A'
UNION SELECT 4,'B'
UNION SELECT 5,'B'
)
Select InvoiceId,
StoreId,
ROW_NUMBER() OVER(PARTITION BY StoreId ORDER BY InvoiceId) AS InvoiceNumber
FROM TestData
Result:
InvoiceId | StoreId | InvoiceNumber
1 | A | 1
2 | A | 2
3 | A | 3
4 | B | 1
5 | B | 2
After playing around with the answer provided by #Jamiec in my solution I instead decided to go the TRIGGER route in order to persist the invoice number and better work with Entity Framework. Additionally, since ROW_NUMBER doesn't work in an INSERT (AFAIK) I am instead using MAX([InvoiceNumber])+1.
CREATE TABLE [dbo].[Invoice] (
[InvoiceId] INTEGER NOT NULL,
[StoreId] UNIQUEIDENTIFIER NOT NULL,
[InvoiceNumber] INTEGER NOT NULL,
CONSTRAINT [PK_Invoice] PRIMARY KEY CLUSTERED ([InvoiceId] ASC)
);
CREATE TRIGGER TGR_InvoiceNumber
ON [Invoice]
INSTEAD OF INSERT
AS
BEGIN
INSERT INTO [Invoice] ([InvoiceId], [StoreId], [InvoiceNumber])
SELECT [InvoiceId],
[StoreId],
ISNULL((SELECT MAX([InvoiceNumber]) + 1 FROM [Invoice] AS inv2 WHERE inv2.[StoreId] = inv1.[StoreId]), 1)
FROM inserted as inv1;
END;
This allows me to set up my EF class like:
public class Invoice
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int InvoiceId { get; set; }
public Guid StoreId { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public int InvoiceNumber { get; set; }
}

How to implement tag following in database?

I'm making a micro-blogging website where users can follow tags. Like in twitter, users can follow other users.. in my project they can follow tags as well. What should be the database design to implement tag following? User following is easy.
One way is to have like 5 tag ID columns in the table containing posts:
Table: Posts
Columns: PostID, AuthorID, TimeStamp, Content, Tag1,Tag2...Tag5
I will make two comma separated lists: One is for the users the given user is following and the other for the tags the given user is following: $UserFollowingList and $TagFollowingList
and the select query can be like:
select ... from `Posts` where ($Condition1) or ($Condition2) order by `PostID` desc ...
$Condition1 = "`AuthorID` in $UserFollowingList"
$Condition2 = " ( `Tag1` in $TagFollowingList ) or ( `Tag2` in $TagFollowingList ) ... or ( `Tag5` in $TagFollowingList )"
Please suggest a better way? And what if I don't want to limit to 5 tags? I've an idea but want to know what will experience developers like you will do?
you can use one table for who is following who like
CREATE TABLE `followers` (
`targetID` INT(10) UNSIGNED NOT NULL,
`targetType` ENUM('user','tag') NOT NULL,
`userID` INT(10) UNSIGNED NOT NULL,
PRIMARY KEY (`targetID`, `targetType`),
INDEX `userID` (`userID`)
)
and another one for the tags in each post like
CREATE TABLE `postTags` (
`postID` INT(10) UNSIGNED NOT NULL,
`tag` INT(10) NOT NULL,
PRIMARY KEY (`postID`, `tag`)
)
edit: sorry didn't think about it the 1-st time.
To avoid using a string as targetID there must be a tags table too
CREATE TABLE `tags` (
`tagID` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`tag` VARCHAR(255) NOT NULL DEFAULT '',
PRIMARY KEY (`tagID`)
)
this will give you the posts $currentUser is following
SELECT
p.*
FROM posts p
JOIN posttags pt on pt.postID = p.postID
JOIN followers f ON f.targetType = 'tag' AND f.targetID = pt.tagID
WHERE
f.followerID = $currentUserID
UNION
SELECT
p.*
FROM posts p
JOIN followers f ON f.targetID = p.authorID AND f.targetType = 'user'
WHERE
f.followerID = $currentUserID

database design for many questions with different answer datatypes

I have a problem with my database design.
I have two options in mind but none of them really solves my problem:
Option 1:
FB (ID, Year, Question, Value)
1 | 2004| Q1 | hello
1 | 2004| Q2500 | 15.2.12
1 | 2004| Q2 | 56€
1 | 2003| Q1 | bye
2 | 2003| Q2 | 55€
The problem with Option 1 is that the data type of field “Value” can be really everything! To solve that problem I thought of
creating a table for each datatype or
changing the table to FB (ID, Year, Question, Valueint, Valuestring,….etc.)
Neither 1. nor 2. seems right to me.
Option 2:
FB (ID, Year, Q1, Q2, …., Q2500)
1| 2004 | hello| 56€ |,....,| 15.2.12
1| 2003 | bye | …...|,….., |…..
2| 2003 | salut| 55€ |, …..,|…..
The number of Questions (Q1-QX ) may vary a lot.
Any suggestions are appreciated! Thanks...
I'd go for
CREATE TABLE Questions (
QuestionID varchar(5) not null primary key,
AnswerType varchar(10) not null,
constraint CK_Question_Types CHECK (AnswerType in ('INT','CHAR','BOOL')), --Add more appropriate type names
constraint UQ_Questions_TypeCheck UNIQUE (QuestionID,AnswerType)
)
and:
CREATE TABLE Answers (
ID int not null,
Year int not null,
QuestionID varchar(5) not null,
AnswerType varchar(10) not null,
IntAnswer int null,
CharAnswer varchar(max) null,
BoolAnswer bit null,
constraint FK_Answers_Questions FOREIGN KEY (QuestionID) references Questions,
constraint FK_Answers_Question_TypeCheck FOREIGN KEY (QuestionID,AnswerType) references Questions (QuestionID,AnswerType),
constraint CK_Answer_Types CHECK (
(IntAnswer is null or AnswerType='INT') and
(CharAnswer is null or AnswerType='CHAR') and
(BoolAnswer is null or AnswerType='BOOL') and
(IntAnswer is not null or CharAnswer is not null or BoolAnswer is not null)
)
)
This lets you ensure that each answer is of the correct type, and not null, whilst ensuring no extraneous data ends up in the table.
The use of two foreign keys isn't really required (you could remove FK_Answers_Questions), but I prefer to document that the real FK reference is on QuestionID, whilst we want to enforce a constraint across the tables, using the second one and the new CHECK constraint.
I would create two tables:
Create Table tblQuestions (
ID int IDENTITY(1,1) NOT NULL,
Year varchar(4) Default '',
QuestionText varchar(4000) Default '',
AnswerDataType varchar(50) Default '' )
Create Table tblAnswers (
ID int IDENTITY(1,1) NOT NULL,
tblQuestions_ID int Default 0,
Answer varchar(255) Default '' )
Then I would create a function or sproc to validate that an answer was given in the correct data type and return TRUE or FALSE. Unfortunately I don't have time to write that code.

Resources