Database design for compiling records from another table - c#

What's the best way to design a table that reference multiple records from another table?
For example, there is a table called diary that stores subjects, descriptions and keywords, then another table called DiaryCompilation for combining all selected records into a book by just referencing the id from the diary.
What's the best way to create the DiaryCompilation?
I was thinking of consisting it into two fields: id, references
where in all selected records are placed in references, but is it a good practice or are there better approaches?

<--- Each record is a new entry of diary --->
Diary: ID, Subject, Description, Keywords
<--- Single record per compilation for summary info --->
DiaryCompilation: ID, Title
<--- Pages of Diary Compilation --->
DiaryCompilationEntries: DiaryCompilationID, DiaryID

Diary and DiaryCompilation would have a 1 to many relationship. Just make sure that the Id in the DairyCompilation is setup as the Primary Key and put a Foreign Key constraint on it so that it ties back to an Id in the Diary table. This will prevent you from deleting a diary and orphaning a record in the DiaryCompilation table. As long as you have a normalized data model you should be fine.

Keep your fields atomic. Placing several values in references field would make it much harder to query and to enforce referential constraints.
A 1:N relationship between parent and child is modeled by migrating parent's primary key into child. In your case, this would look something like this:
COMPILATION (
COMPILATION_ID PK
-- Other fields...
)
DIARY (
DIARY_ID PK
COMPILATION_ID FK(COMPILATION)
SUBJECT
DESCRIPTION
)
-- Not a good idea to have several keywords in a single field, so we need a separate table for keywords.
DIARY_KEYWORD (
DIARY_ID PK, FK(DIARY)
KEYWORD PK
)
If you actually want N:N relationship (i.e. diary can be part of more than one compilation), you'll need a dedicated table to hold these connections, something like this:
COMPILATION (
COMPILATION_ID PK
)
DIARY_IN_COMPILATION (
COMPILATION_ID PK, FK(COMPILATION)
DIARY_ID PK, FK(DIARY)
)
DIARY (
DIARY_ID PK
SUBJECT
DESCRIPTION
)
DIARY_KEYWORD (
DIARY_ID PK, FK(DIARY)
KEYWORD PK
)

What's the best way to create the DiaryCompilation?
Sounds like the best way might be to use a view instead of a table.

Related

How to Auto-increment non-integer primary key in sql-server? [duplicate]

Can I make a primary key like 'c0001, c0002' and for supplier 's0001, s0002' in one table?
The idea in database design, is to keep each data element separate. And each element has its own datatype, constraints and rules. That c0002 is not one field, but two. Same with XXXnnn or whatever. It is incorrect , and it will severely limit your ability to use the data, and use database features and facilities.
Break it up into two discrete data items:
column_1 CHAR(1)
column_2 INTEGER
Then set AUTOINCREMENT on column_2
And yes, your Primary Key can be (column_1, column_2), so you have not lost whatever meaning c0002 has for you.
Never place suppliers and customers (whatever "c" and "s" means) in the same table. If you do that, you will not have a database table, you will have a flat file. And various problems and limitations consequent to that.
That means, Normalise the data. You will end up with:
one table for Person or Organisation containing the common data (Name, Address...)
one table for Customer containing customer-specific data (CreditLimit...)
one table for Supplier containing supplier-specific data (PaymentTerms...)
no ambiguous or optional columns, therefore no Nulls
no limitations on use or SQL functions
.
And when you need to add columns, you do it only where it is required, without affecting all the other sues of the flat file. The scope of effect is limited to the scope of change.
My approach would be:
create an ID INT IDENTITY column and use that as your primary key (it's unique, narrow, static - perfect)
if you really need an ID with a letter or something, create a computed column based on that ID INT IDENTITY
Try something like this:
CREATE TABLE dbo.Demo(ID INT IDENTITY PRIMARY KEY,
IDwithChar AS 'C' + RIGHT('000000' + CAST(ID AS VARCHAR(10)), 6) PERSISTED
)
This table would contain ID values from 1, 2, 3, 4........ and the IDwithChar would be something like C000001, C000002, ....., C000042 and so forth.
With this, you have the best of both worlds:
a proper, perfectly suited primary key (and clustering key) on your table, ideally suited to be referenced from other tables
your character-based ID, properly defined, computed, always up to date.....
Yes, Actually these are two different questions,
1. Can we use varchar column as an auto increment column with unique values like roll numbers in a class
ANS: Yes, You can get it right by using below piece of code without specifying the value of ID and P_ID,
CREATE TABLE dbo.TestDemo
(ID INT IDENTITY(786,1) NOT NULL PRIMARY KEY CLUSTERED,
P_ID AS 'LFQ' + RIGHT('00000' + CAST(ID AS VARCHAR(5)), 5) PERSISTED,
Name varchar(50),
PhoneNumber varchar(50)
)
Two different increments in the same column,
ANS: No, you can't use this in one table.
I prefer artificial primary keys. Your requirements can also be implemented as unique index on a computed column:
CREATE TABLE [dbo].[AutoInc](
[ID] [int] IDENTITY(1,1) NOT NULL,
[Range] [varchar](50) NOT NULL,
[Descriptor] AS ([range]+CONVERT([varchar],[id],(0))) PERSISTED,
CONSTRAINT [PK_AutoInc] PRIMARY KEY ([ID] ASC)
)
GO
CREATE UNIQUE INDEX [UK_AutoInc] ON [dbo].[AutoInc]
(
[Descriptor] ASC
)
GO
Assigning domain meaning to the primary key is a practice that goes way, way back to the time when Cobol programmers and dinosaurs walked the earth together. The practice survives to this day most often in legacy inventory systems. It is mainly a way of eliminating one or more columns of data and embedding the data from the eliminated column(s) in the PK value.
If you want to store customer and supplier in the same table, just do it, and use an autoincrementing integer PK and add a column called ContactType or something similar, which can contain the values 'S' and 'C' or whatever. You do not need a composite primary key.
You can always concatenate these columns (PK and ContactType) on reports, e.g. C12345, S20000, (casting the integer to string) if you want to eliminate the column in order to save space (i.e. on the printed or displayed page), and everyone in your organization understands the convention that the first character of the entity id stands for the ContactType code.
This approach will leverage autoincrementing capabilities that are built into the database engine, simplify your PK and related code in the data layer, and make your program and database more robust.
First let us state that you can't do directly. If you try
create table dbo.t1 (
id varchar(10) identity,
);
the error message tells you which data types are supported directly.
Msg 2749, Level 16, State 2, Line 1
Die 'id'-Identitätsspalte muss vom
Datentyp 'int', 'bigint', 'smallint',
'tinyint' oder 'decimal' bzw.
'numeric' mit 0 Dezimalstellen sein
und darf keine NULL-Werte zulassen.
BTW: I tried to find this information in BOL or on MSDN and failed.
Now knowing that you can't do it the direct way, it is a good choice to follow #marc_s proposal using computed columns.
Instead of doing 'c0001, c0002' for customers and 's0001, s0002' for suppliers in one table, proceed in the following way:
Create one Auto-Increment field "id" of Data Type "int (10) unsigned".
Create another field "type" of Data Type "enum ('c', 's')" (where c=Customer, s=Supplier).
As "#PerformanceDBA" pointed out, you can then make the Primary Key Index for two fields "id" & "type", so that your requirement gets fulfilled with the correct methodology.
INSERT INTO Yourtable (yourvarcharID)
values('yourvarcharPrefix'+(
SELECT CAST((SELECT CAST((
SELECT Substring((
SELECT MAX(yourvarcharID) FROM [Yourtable ]),3,6)) AS int)+1)
AS VARCHAR(20))))
Here varchar column is prefixed with 'RX' then followed by 001, So I selected substring after that prefix of it and incremented the that number alone.
We can add Default Constraint Function with table definition to achieve this.
First create table -
create table temp_so (prikey varchar(100) primary key, name varchar(100))
go
Second create new User Defined Function -
create function dbo.fn_AutoIncrementPriKey_so ()
returns varchar(100)
as
begin
declare #prikey varchar(100)
set #prikey = (select top (1) left(prikey,2) + cast(cast(stuff(prikey,1,2,'') as int)+1 as varchar(100)) from temp_so order by prikey desc)
return isnull(#prikey, 'SB3000')
end
go
Third alter table definition to add default constraint -
alter table temp_so
add constraint df_temp_prikey
default dbo.[fn_AutoIncrementPriKey_so]() for prikey
go
Fourth insert new row into table without specifying value for primary column-
insert into temp_so (name) values ('Rohit')
go 4
Check out data in table now -
select * from temp_so
OUTPUT -
prikey name
SB3000 Rohit
SB3001 Rohit
SB3002 Rohit
SB3003 Rohit
you may try below code:
SET #variable1 = SUBSTR((SELECT id FROM user WHERE id = (SELECT MAX(id) FROM user)), 5, 7)+1;
SET #variable2 = CONCAT("LHPL", #variable1);
INSERT INTO `user`(`id`, `name`) VALUES (#variable2,"Jeet");
1st line to get last inserted Id by removing four character than increase one value and set to a variable1
2nd line to make complete id with four character prefix and assign to variable2
insert new value with generated new primary key = variable2
you should have minimum one data in this table to work above SQL
No. If you really need this, you will have to generate ID manually.

Connecting Three SQL Server Tables Together to Display Images

I am trying to create a Photo Album/Collection for each student in my ASP.NET website, using SQL Server tables.
Here are my table structure:
I want to store the image name within the Student_Images folder. Then each image will be linked to a Photo Collection. Each Photo Collection is linked to a group.
Then I want to display each group, any photo collections which are associated with that group, and then any images associated with that collection.
I have been able to display images related to each student, but I don't know how to add the 'Photo Collection' table between the Students and Images tables.
Here is how I would like it to display:
Here is how the tables would look populated:
Can anyone advise me as to how to go about making this connection?
There's an idea of a foreign key - where a row in a table refers to a row in another table. You definitely want to do this for a number of reasons, to ensure data integrity and to document your relationships for whoever works on the site.
You don't quite have enough information to get where you want to get. You don't have any linkage between photo albums and the images they contain. You'll need another table for that.
I kinda recommend that you use column names that don't repeat the table name. You'll find you can then see patterns more clearly in your schema. For example Photo_Collection.Photo_Collection_Id should probably be just Photo_Collection.Id. It's more concise, and it's obvious which Id you're talking about because you always use the table name when referring to that column.
So, to get you nearer to where you need to be, I'd recommend something like this:
create table dbo.Students
(
ID
int not null identity( 1, 1 )
constraint [Students.ID.PrimaryKey]
primary key clustered,
Name
nvarchar( 50 ) --> not very generous :-)
)
go
create index [Students.Name.Index] on dbo.Students( Name ) --> maybe unique?
go
create table dbo.Student_Images
(
ID
int not null identity( 1, 1 )
constraint [Student_Images.ID.PrimaryKey]
primary key clustered,
Student_ID
int not null
constraint [Student_Images.to.Student]
foreign key references dbo.Students( ID )
Filename
nvarchar( 250 ) null, --> null? really? each image should have a unique file name, dont you think?
Description
nvarchar( 250 ) null
)
go
create index [Student_Images.to.Students.Index] on dbo.Student_Images( Student_ID )
go
create table dbo.Photo_Collection
(
ID
int not null identity( 1, 1 )
constraint [Photo_Collection.ID.PrimaryKey]
primary key clustered,
Name
nvarchar( 250 ) null --> null? hmmm...could be hard to use
)
go
create index [Photo_Collection.Name.Index] on dbo.Photo_Collection( Name ) --> consider unique??
go
create table dbo.Photo_Collection_Images
(
Photo_Collection_ID
int not null
constraint [Photo_Collection_Images.to.Photo_Collection]
foreign key references dbo.Photo_Collection( ID ),
Student_Image_ID
int not null
constraint [Photo_Collection_Images.to.Student_Images]
foreign key references dbo.Student_Images( ID )
)
You didn't really describe your groups...and there are lots of questions still unanswered. For example, are Photo_Collections something that a student makes? If so, there should probably be a Student_ID in Photo_Collection with a foreign key to Students.
The table I added called Photo_Collection_Images relates your photo collections to the images. You'd include this table in any query that needs to display all the images in a given photo collection. I think that's the main missing bit for you. You'd do something similar for groups.
Also - FYI, pasting images of your text is kinda aggravating. Consider just pasting the text in and indenting it 4 spaces to get it to format correctly.
Edit:
To select, for example, all the images in a photo collection by student, you could do:
select
pc.Name PhotoCollectionName,
si.FileName FileName,
si.Description FileDescription,
s.Name StudentName
from
dbo.Photo_Collection pc
inner join
dbo.Photo_Collection_Images pci
on
pc.Id = pci.Photo_Collection_ID
inner join
dbo.Student_Image si
on
pci.Student_Image_ID = si.ID
inner join
dbo.Students s
on
si.Student_Id = s.ID
This is pretty much the base query for everything.
Make some changes in your Photo_Collection table, create an FK within your Photo_Collection of Student_ID, then you'll be able to show the photo collection of all the students because through Student_ID, you can have access to student table as well as Student_Images.
Hope it helps.

How would I create "multiple choice" columns in Local Database?

I am using local database for first time with my WPF project. I have the database setup, and I am connecting fine ETC. Ther eare some columns which I want to be multiple choice, either between a few values or a whole bunch of values. Problem is obviously human error will make typos now and then when inputting the data.
How would I go about making the data entry give the user a multiple choice? So for example, I have a column called "Category", and at the moment (this will be expanded later) I only want to allow the following options:
Bronze
Misc
I have the columns set to nvarchar(50) at present, but typing the same string manually constantly... not what I would like to be doing TBH... so... Could I set it so that there are a list of predefined values it will accept? :)
thanks :D
You can use CHECK constraint of any complexity on your table column(s). Check MSDN here
So your table definition would be as:
CREATE TABLE T
(
Category nvarchar(50) CHECK (Category in ('Bronze','Misc'))
)
If you expect your list of possible values to change in the future and you do not want to change a table definition, you can create a separate table with the list of values and use the foreign key.
CREATE TABLE Categories
(
Id int PRIMARY KEY,
CategoryName nvarchar(50)
)
INSERT INTO Categories VALUES (1, 'Bronze'), (2, 'Silver'), (3, 'Misc')
CREATE TABLE T
(
CategoryId int REFERENCES Categories
)

Persisting Object Graph to SQL Server in a single transaction

I am having an issue coming up with a solution that I think must be a common problem to be solved by anyone writing to a database. I keep thinking that there is an obvious solution that I'm overlooking and that's why I can't find an appropriate existing question here.
Imagine a situation where you need to let a user create a "Class", with "Students", and each "Student" is assigned one or more books. You have a well defined hierarchy, Class->Student->Book.
You have the following tables:
CREATE TABLE Classes (
ClassId int identity(1,1) primary key,
ClassName nvarchar(255)
)
CREATE TABLE Students (
StudentId int identity(1,1) primary key,
ClassId int,
StudentName nvarchar(255),
StudentImage image
)
CREATE TABLE StudentBooks (
StudentBookId int identity(1,1) primary key,
StudentId int,
BookName nvarchar(255)
)
With this contrived scenario, what are my options for saving this entire graph of new objects, while letting SQL server assign the identity columns, and keeping it all in one transaction? Assuming that a class has maybe 30 students, and each student has several books assigned.
I could create a transaction and make a separate call for each row in each table, returning SCOPE_IDENTITY for each new parent object so I can save each child while keeping RI intact.
I could use XML. I would like to avoid that, as the graph includes a byte array.
Any other options? I thought about passing each level of the hierarchy in a table parameter. I'm not sure how or if that would work. (Wouldn't I have to define a table type for each of my tables, essentially duplicating the schema?)
I can use SQL server 2012 for this.
Thank you!
You can use Entity Framework to achieve what you want.
There are lots of tutorials out there, but a good starting point is this one:
MSDN on getting started with Entity Framework
or the linked page
MSDN overview page on getting started
I would recommend the EDMX approach for your use-case.

How can I Insert/Update into two related tables in one command?

A database exists with two tables
Data_t : DataID Primary Key that is
Identity 1,1. Also has another field
'LEFT' TINYINT
Data_Link_t : DataID PK and FK where
DataID MUST exist in Data_t. Also has another field 'RIGHT' SMALLINT
Coming from a microsoft access environment into C# and sql server I'm looking for a good method of importing a record into this relationship.
The record contains information that belongs on both sides of this join (Possibly inserting/updating upwards 5000 records at once). Bonus to process the entire batch in some kind of LINQ list type command but even if this is done record by record the key goal is that BOTH sides of this record should be processed in the same step.
There are countless approaches and I'm looking at too many to determine which way I should go so I thought faster to ask the general public. Is LINQ an option for inserting/updating a big list like this with LINQ to SQL? Should I go record by record? What approach should I use to add a record to normalized tables that when joined create the full record?
Sounds like a case where I'd write a small stored proc and call that from C# - e.g. as a function on my Linq-to-SQL data context object.
Something like:
CREATE PROCEDURE dbo.InsertData(#Left TINYINT, #Right SMALLINT)
AS BEGIN
DECLARE #DataID INT
INSERT INTO dbo.Data_t(Left) VALUES(#Left)
SELECT #DataID = SCOPE_IDENTITY();
INSERT INTO dbo.Data_Link_T(DataID, Right) VALUES(#DataID, #Right)
END
If you import that into your data context, you could call this something like:
using(YourDataContext ctx = new YourDataContext)
{
foreach(YourObjectType obj in YourListOfObjects)
{
ctx.InsertData(obj.Left, obj.Right)
}
}
and let the stored proc handle all the rest (all the details, like determining and using the IDENTITY from the first table in the second one) for you.
I have never tried it myself, but you might be able to do exactly what you are asking for by creating an updateable view and then inserting records into the view.
UPDATE
I just tried it, and it doesn't look like it will work.
Msg 4405, Level 16, State 1, Line 1
View or function 'Data_t_and_Data_Link_t' is not updatable because the modification affects multiple base tables.
I guess this is just one more thing for all the Relational Database Theory purists to hate about SQL Server.
ANOTHER UPDATE
Further research has found a way to do it. It can be done with a view and an "instead of" trigger.
create table Data_t
(
DataID int not null identity primary key,
[LEFT] tinyint,
)
GO
create table Data_Link_t
(
DataID int not null primary key foreign key references Data_T (DataID),
[RIGHT] smallint,
)
GO
create view Data_t_and_Data_Link_t
as
select
d.DataID,
d.[LEFT],
dl.[RIGHT]
from
Data_t d
inner join Data_Link_t dl on dl.DataID = d.DataID
GO
create trigger trgInsData_t_and_Data_Link_t on Data_t_and_Data_Link_T
instead of insert
as
insert into Data_t ([LEFT]) select [LEFT] from inserted
insert into Data_Link_t (DataID, [RIGHT]) select ##IDENTITY, [RIGHT] from inserted
go
insert into Data_t_and_Data_Link_t ([LEFT],[RIGHT]) values (1, 2)

Categories