I have 2 table Product and Supplier
create table Product(
ProductCode int not null primary key,
Name varchar(50) not null ,
PurchasePrice numeric(20,3) not null,
SellPrice numeric(20,3) not null ,
Type varchar(50) not null,
SupplierCode int not null
)
go
create table Supplier(
SupplierCode int not null primary key,
SupplierName varchar(50) not null ,
Address varchar(50) not null
)
I want : A product of Samsung must be television, mobile or tablet.
Help me.
database enter image description here
I want "SupplierCode<>4" because Supplier supply 'food'
You can't achieve it this way do something like this in check constraint because value depends on different tables.
The most straightforward way would be to create a trigger. This one is after insert. It just deletes rows that are not acceptable. You can experiment and make it instead of insert instead
CREATE TRIGGER insertProduct
ON Sales.Product
AFTER INSERT, UPDATE
AS
delete from inserted a
join Supplier b on a.SupplierCode = b.SupplierCode
where
(b.Supplier = 'Samsung' and a.Type not in ('phone', whatever you need...))
or (b.Supplier = 'different' and a.Type not in (...))
--here you put any additional logic for different suppliers
if ##ROWCOUNT > 0
RAISERROR ('Couldn't insert into table', 16, 10);
GO
Depending on your case what you can also do is make inserting to the tables available only via stored procedures. Then put the checks in the stored procedure instead.
If I understand you correct, you want to define for each suppliers what kind of products are allowed in your tables.
I think you need a table that defines what producttypes are allowed for what Suppliers, and than you can force this in a trigger or in your client.
First you need a table to define the kind of products
table ProductType (ID, Name)
this holds info like
(1, 'Television'), (2, 'Tablet'), (3, 'Mobi'), (4, 'Food'), and so on...
Then replace the field Type by ProductTypeID in your Product table
Now you can have a table that defines what product types each supplier may have
table AllowedProducts (ID, SupplierCode, ProductTypeID)
and finally you can check this in your client, or in a trigger if you want to keep this rule in your database
When inserting data you can check if the ProductTypeID of the selected Product is present in table AllowedProducts for the selected ´Supplier` and if not reject the insert.
Related
If I want to do something relatively complicated - something usually done by a stored procedure. Is it possible to make it automatic using a VIEW?
My specific case:
I want output table = input table A + some rows input table B. In a stored procedure, I can make a copy of table A and then INSERT INTO it, but it's not allowed in a view.
Simplified example:
input table is [test_album], and output table = input table + singer Prince.
--create test data
IF OBJECT_ID('[dbo].[test_album]', 'U') IS NOT NULL
DROP TABLE [dbo].[test_album]
CREATE TABLE [test_album] (
id int not null identity(1, 1) primary key,
singer VARCHAR(50) NULL,
album_title VARCHAR(100) NULL
)
INSERT INTO [test_album] (singer, album_title)
VALUES ('Adale', '19'),
('Michael Jaskson', 'Thriller')
--this can be executed as sql code or in stored proc
SELECT *
INTO [result_table]
FROM [test_album]
INSERT INTO [result_table] ([singer])
VALUES ('Prince')
select *
from [result_table]
--id singer album_title
--1 Adale 19
--2 Michael Jaskson Thriller
--3 Prince NULL
----as expected
But I can do this INSERT INTO inside a view.
Real-life case:
additional singers are in a table [extra_singers]
[test_album] may have many other columns (or schema may change) so it's ideal not to type all column names in the code.
--create test data
IF OBJECT_ID('[dbo].[test_album]', 'U') IS NOT NULL
DROP TABLE [dbo].[test_album]
IF OBJECT_ID('[dbo].[extra_singers]', 'U') IS NOT NULL
DROP TABLE [dbo].[extra_singers]
IF OBJECT_ID('[dbo].[result_table]', 'U') IS NOT NULL
DROP TABLE [dbo].[result_table]
CREATE TABLE [test_album] (
id int not null identity(1, 1) primary key,
singer VARCHAR(50) NULL,
album_title VARCHAR(100) NULL,
many_other_columns VARCHAR(100) NULL
)
INSERT INTO [test_album] (singer, album_title)
VALUES ('Adale', '19'),
('Michael Jaskson', 'Thriller')
CREATE TABLE [extra_singers] (
[id] int not null identity(1, 1) primary key,
[name] VARCHAR(50) NULL )
INSERT INTO [extra_singers] ([name])
VALUES ('Prince'),
('Taylor Swift')
--append [extra_singers] to [test_album]
--this can be executed as sql code or in stored proc
SELECT *
INTO [result_table]
FROM [test_album]
INSERT INTO [result_table] ([singer])
SELECT [name]
FROM [extra_singers]
Is there an alternative to this (that is automatic)?
any help's appreciated. Thank u-
a partial solution I can think of:
create view test_view as
select *
from [test_album]
union all
select 3 as id,
'Prince' as singer,
NULL as album_title
but you have to know all the column names in [test_album] and you can't let column [id] do auto-increment
So you may be misunderstanding what a view does, or what an insert is. A view is simply a wrapper around a single select query. It contains exactly one select statement, and nothing else. An insert permanently adds a row of data to a persisted table. The example you gave where you just union the row you want seems valid enough. And certainly if it's the same row you want every time, you would not want to be inserting (or even trying to insert) that row into the underlying table each time
This raises a couple questions though.
If you're always going to be unioning the same single row every time, why not jut add that row to the table?
If, lets say, you don't want that row in the underlying table, cool. But if it's always the same static values, why do you need to include it in the view? Can't it just be assumed it's there?
If it can't be assume to always be the same, you certainly don't want to be changing the VIEW body every time you need it to change. So if it is going to change and you don't want to insert it into the base table, maybe make a second table containing the values you want appended to the base table in the view. Then union the base table and the "extra values" table together instead of a single, hard coded row constructor.
I've created a table called ProductsAudit that is meant to hold values from the MyGuitarShop Products table after they have been updated with a trigger, but I can't seem to get it to work. I'm doing this in SQL Server.
So what I need to happen is when Products is updated, it stores the old value in to the ProductsAudit table.
CREATE TABLE ProductsAudit
(
AuditID int NOT NULL,
CategoryID int,
ProductCode varchar(10) NOT NULL,
ProductName varchar(255) NOT NULL,
ListPrice money NOT NULL,
DiscountPercent money NOT NULL,
DateUpdated datetime
PRIMARY KEY (AuditID)
FOREIGN KEY (CategoryID) REFERENCES Categories(CategoryID)
)
CREATE TRIGGER Products_UPDATE2
ON Products
AFTER UPDATE
AS
BEGIN;
ROLLBACK TRAN
INSERT INTO ProductsAudit
SELECT CategoryID, ProductCode, ProductName, ListPrice, DiscountPercent
FROM Deleted
WHERE CategoryID = (SELECT CategoryID FROM Inserted);
PRINT 'Old data sent to ProductsAudit'
END;
UPDATE Products
SET ListPrice = 79.43
WHERE ProductID = 3;
CREATE TABLE ProductsAudit
(
AuditID int NOT NULL IDENTITY(1,1),
CategoryID int,
ProductCode varchar(10) NOT NULL,
ProductName varchar(255) NOT NULL,
ListPrice money NOT NULL,
DiscountPercent money NOT NULL,
DateUpdated datetime DEFAULT (GETDATE()),
PRIMARY KEY (AuditID),
FOREIGN KEY (CategoryID) REFERENCES Categories(CategoryID)
)
CREATE TRIGGER Products_UPDATE2
ON Products
AFTER UPDATE
AS
BEGIN;
INSERT INTO ProductsAudit (CategoryID, ProductCode, ProductName, ListPrice, DiscountPercent, DateUpdated)
SELECT
d.CategoryID
,d.ProductCode
,d.ProductName
,d.ListPrice
,d.DiscountPercent
,DateUpdated = GETDATE() -- may consider adding default to the table to handle this part
FROM
deleted d
LEFT JOIN inserted i
ON d.CategoryID= i.CategoryID --is this the Product PrimaryKey Column?
WHERE
ISNULL(d.CategoryID,-1) <> ISNULL(i.CategoryID,-1)
OR ISNULL(d.ProductCode,'') <> ISNULL(i.ProductCode,'')
OR ISNULL(d.ProductName,'') <> ISNULL(i.ProductName,'')
OR ISNULL(d.ListPrice,-1) <> ISNULL(i.ListPrice,-1)
OR ISNULL(d.DiscountPercent,-1000) <> ISNULL(i.DiscountPercent,-1000)
PRINT 'Old data sent to ProductsAudit'
END;
UPDATE Products
SET ListPrice = 79.43
WHERE ProductID = 3;
So you seem to have a few issues going on, and I will see if I can walk you through them and 1 way of fixing. First, you say you want ProductAudit to hold values "AFTER" they have been updated but your code and the term Audit suggests you want to store the OLD values (deleted) into an Audit Table not the new values so that is the assumption I worked from.
2 main issues with the code you showed.
ROLLBACK TRAN - this is sort of like an undo button that would roll back all statements in the transactions which could be more than just the update you are wanting to track. This would mean your data wouldn't actually get updated.
(SELECT CategoryId FROM Inserted) - Triggers are evaluated on a set based operation NOT a scalar value so the table inserted would have more than 1 CateogryId if more than 1 row is updated. So it would have picked the last row in the data set and only that row would have been inserted into your Audit Table.
Note you can use the deleted and the inserted tables to get to the information you want. technically you only need the deleted if you want to store a record no matter what. I am showing how to use both to detect if there was actually a change to the record and then only log if there was one.
I beginning to learn how to write trigger with this basic database.
I'm also making my very 1st database.
Schema
Team:
TeamID int PK (TeamID int IDENTITY(0,1) CONSTRAINT TeamID_PK PRIMARY KEY)
TeamName nvarchar(100)
History:
HistoryID int PK (HistoryID int IDENTITY(0,1) CONSTRAINT HistoryID_PK PRIMARY KEY)
TeamID int FK REF Team(TeamID)
WinCount int
LoseCount int
My trigger: when a new team is inserted, it should insert a new history row with that team id
CREATE TRIGGER after_insert_Player
ON Team
FOR INSERT
AS
BEGIN
INSERT INTO History (TeamID, WinCount, LoseCount)
SELECT DISTINCT i.TeamID
FROM Inserted i
LEFT JOIN History h ON h.TeamID = i.TeamID
AND h.WinCount = 0 AND h.LoseCount = 0
END
Executed it returns
The select list for the INSERT statement contains fewer items than the insert list. The number of SELECT values must match the number of INSERT columns.
Please help thank. I'm using SQL Server
The error text is the best guide, it is so clear ..
You try inserting one value from i.TeamID into three columns (TeamID,WinCount,LoseCount)
consider these WinCount and LoseCount while inserting.
Note: I Think the structure of History table need to revisit, you should select WinCount and LoseCount as Expressions not as actual columns.
When you specify insert columns, you say which columns you will be filling. But in your case, right after insert you select only one column (team id).
You either have to modify the insert to contain only one column, or select, to retrieve 3 fields as in insert.
If you mention the columns where values have to be inserted(Using INSERT-SELECT).
The SELECT Statement has to contain the same number of columns that have been specified to be inserted. Also, ensure they are of the same data type.(You might face some issues otherwise)
I have a sample table like below where Course Completion Status of a Student is being stored:
Create Table StudentCourseCompletionStatus
(
CourseCompletionID int primary key identity(1,1),
StudentID int not null,
AlgorithmCourseStatus nvarchar(30),
DatabaseCourseStatus nvarchar(30),
NetworkingCourseStatus nvarchar(30),
MathematicsCourseStatus nvarchar(30),
ProgrammingCourseStatus nvarchar(30)
)
Insert into StudentCourseCompletionStatus Values (1, 'In Progress', 'In Progress', 'Not Started', 'Completed', 'Completed')
Insert into StudentCourseCompletionStatus Values (2, 'Not Started', 'In Progress', 'Not Started', 'Not Applicable', 'Completed')
Now as part of normalizing the schema I have created two other tables - CourseStatusType and Status for storing the Course Status names and Status.
Create Table CourseStatusType
(
CourseStatusTypeID int primary key identity(1,1),
CourseStatusType nvarchar(100) not null
)
Insert into CourseStatusType Values ('AlgorithmCourseStatus')
Insert into CourseStatusType Values ('DatabaseCourseStatus')
Insert into CourseStatusType Values ('NetworkingCourseStatus')
Insert into CourseStatusType Values ('MathematicsCourseStatus')
Insert into CourseStatusType Values ('ProgrammingCourseStatus')
Insert into CourseStatusType Values ('OperatingSystemsCourseStatus')
Insert into CourseStatusType Values ('CompilerCourseStatus')
Create Table Status
(
StatusID int primary key identity(1,1),
StatusName nvarchar (100) not null
)
Insert into Status Values ('Completed')
Insert into Status Values ('Not Started')
Insert into Status Values ('In Progress')
Insert into Status Values ('Not Applicable')
The modified table is as below:
Create Table StudentCourseCompletionStatus1
(
CourseCompletionID int primary key identity(1,1),
StudentID int not null,
CourseStatusTypeID int not null CONSTRAINT [FK_StudentCourseCompletionStatus1_CourseStatusType] FOREIGN KEY (CourseStatusTypeID) REFERENCES dbo.CourseStatusType (CourseStatusTypeID),
StatusID int not null CONSTRAINT [FK_StudentCourseCompletionStatus1_Status] FOREIGN KEY (StatusID) REFERENCES Status (StatusID),
)
I have few question on this:
Is this the correct way to normalize it ? The old table was very helpful to get data easily - I can store a student's course status in a single row, but now 5 rows are required. Is there a better way to do it?
Moving the data from the old table to this new table seems to be not an easy task. Can I achieve this using a query or I have to manually to do this ?
Any help is appreciated.
vou could also consider storing results in flat table like this:
studentID,courseID,status
1,1,"completed"
1,2,"not started"
2,1,"not started"
2,3,"in progress"
you will also need additional Courses table like this
courserId,courseName
1, math
2, programming
3, networking
and a students table
students
1 "john smith"
2 "perry clam"
3 "john deere"
etc..you could also optionally create a status table to store the distinct statusstrings statusstings and refer to their PK instead ofthestrings
studentID,courseID,status
1,1,1
1,2,2
2,1,2
2,3,3
... etc
and status table
id,status
1,"completed"
2,"not started"
3,"in progress"
the beauty of this representation is: it is quite easy to filter and aggregate data , i.e it is easy to query which subjects a particular person have completed, how many subjects are completed by an average student, etc. this things are much more difficult in the columnar design like you had. you can also easily add new subjects without the need to adapt your tables or even queries they,will just work.
you can also always usin SQLs PIVOT query to get it to a familiar columnar presentation like
name,mathstatus,programmingstatus,networkingstatus,etc..
but now 5 rows are required
No, it's still just one row. That row simply contains identifiers for values stored in other tables.
There are pros and cons to this. One of the main reasons to normalize in this way is to protect the integrity of the data. If a column is just a string then anything can be stored there. But if there's a foreign key relationship to a table containing a finite set of values then only one of those options can be stored there. Additionally, if you ever want to change the text of an option or add/remove options, you do it in a centralized place.
Moving the data from the old table to this new table seems to be not an easy task.
No problem at all. Create your new numeric columns on the data table and populate them with the identifiers of the lookup table records associated with each data table record. If they're nullable, you can make them foreign keys right away. If they're not nullable then you need to populate them before you can make them foreign keys. Once you've verified that the data is correct, remove the old de-normalized columns. Done.
In StudentCourseCompletionStatus1 you still need 2 associations to Status and CourseStatusType. So I think you should consider following variant of normalization:
It means, that your StudentCourseCompletionStatus would hold only one CourseStatusID and another table CourseStatus would hold the associations to CourseType and Status.
To move your data you can surely use a query.
I have only done databases without relations, but now I need to do something more serious and correct.
Here is my database design:
Kunde = Customer
Vare = Product
Ordre = Order (Read: I want to make an order)
VareGruppe = ehm..type? (Read: Car, chair, closet etc.)
VareOrdre = Product_Orders
Here is my SQL (SQLite) schema:
CREATE TABLE Post (
Postnr INTEGER NOT NULL PRIMARY KEY,
Bynavn VARCHAR(50) NOT NULL
);
CREATE TABLE Kunde (
CPR INTEGER NOT NULL PRIMARY KEY,
Navn VARCHAR(50) NOT NULL,
Tlf INTEGER NOT NULL,
Adresse VARCHAR(50) NOT NULL,
Postnr INTEGER NOT NULL
CONSTRAINT fk_postnr_post REFERENCES Post(Postnr)
);
CREATE TABLE Varegruppe (
VGnr INTEGER PRIMARY KEY,
Typenavn VARCHAR(50) NOT NULL
);
CREATE TABLE Vare (
Vnr INTEGER PRIMARY KEY,
Navn VARCHAR(50) NOT NULL,
Pris DEC NOT NULL,
Beholdning INTEGER NOT NULL,
VGnr INTEGER NOT NULL
CONSTRAINT fk_varegruppevgnr_vgnr REFERENCES Varegruppe(VGnr)
);
CREATE TABLE Ordre (
Onr INTEGER PRIMARY KEY,
CPR INTEGER NOT NULL
CONSTRAINT fk_kundecpr_cpr REFERENCES Kunde(CPR),
Dato DATETIME NOT NULL,
SamletPris DEC NOT NULL
);
CREATE TABLE VareOrdre (
VareOrdreID INTEGER PRIMARY KEY,
Onr INTEGER NOT NULL
CONSTRAINT fk_ordrenr_onr REFERENCES Ordre(Onr),
Vnr INTEGER NOT NULL
CONSTRAINT fk_varevnr_vnr REFERENCES Vare(Vnr),
Antal INTEGER NOT NULL
);
It should work correctly.
But I am confused about Product_Orders.
How do I create an order? For example, 2 products using SQL INSERT INTO?
I can get nothing to work.
So far:
Only when I manually insert products and data into Product_Orders and then add that data to Orders = which makes it complete. Or the other way around (create an order in with 1 SQL, then manually inserting products into Product_orders - 1 SQL for each entry)
You should first create an order and then insert products in the table Product_Orders. This is necessary because you need an actual order with an id to associate it with the table Product_Orders.
You always should create a record in the foreign-key table before being able to create one in your current table. That way you should create a "Post", customer, type, product, order and product_order.
Try this ...
first you have to insert a customer
insert into kunde values(1, 'navn', 1, 'adresse', 1)
then you insert a type
insert into VareGruppe values(1, 'Type1')
then you insert a product
insert into vare values(1, 'product1', '10.0', 1, 1)
then you add an order
insert into ordre values(1, 1, '20090101', '10.0')
then you insert a register to the product_orders table
insert into VareOrdre values (1, 1, 1, 1)
I think this is it. :-)
As the primary keys are autoincrement, don't add them to the insert and specify the columns like this
insert into vare(Nav, Pris, Beholdning, VGnr) values('product1', '10.0', 1, 1)
Use Select ##identity to see the onr value
I think you already have the hang of what needs to happen. But what I think you are getting at is how to ensure data integrity.
This is where Transactions become important.
http://www.sqlteam.com/article/introduction-to-transactions
Is it the SalesPrice (I'm guessing that's what SamletPris means) that's causing the issue? I can see that being a problem here. One common design solution is to have 2 tables: Order and OrderLine. The Order is a header table - it will have the foreign key relationship to the Customer table, and any other 'top level' data. The OrderLine table has FK relationships to the Order table and to the Product table, along with quantity, unit price, etc. that are unique to an order's line item. Now, to get the sales price for an order, you sum the (unit price * quantity) of the OrderLine table for that order. Storing the SalesPrice for a whole order is likely to cause big issues down the line.
A note just in case this is MySQL: If you're using MyISAM, the MySQL server ignores the foreign keys completely. You have to set the engine to InnoDB if you want any kind of integrity actually enforced on the database end instead of just in your logic. This isn't your question but it is something to be aware of.
fbinder got the question right :)