Denormalization - maintaining redundancy by using triggers in Oracle - plsql

I have a task for my university project where I have denormalized a table and now I have to implement a trigger in order to maintain redundancy. After following the book accordingly I'm stuck on the last step and I can't get my mind around it.
I have two tables - Supplier and Catalogue
**Supplier**
SupplierCode (primary key)
SupplierName
**Catalogue**
SupplierCode (foreign key)
CatalogueCode (both SupplierCode and CatalogueCode form a primary key)
CatalogueName
DateCreated
SupplierName (redundant column for denorm purposes)
When a new row is inserted in the Catalogue table I want that SupplierName field in the same table gets populated based on the SupplierCode which was inserted.
What I have so far:
We need to create a package as well
create or replace PACKAGE "PACK" AS
SUPPCODE NUMBER:=0;
SUPPNAME VARCHAR2(50);
END;
I have created a BEFORE INSERT TRIGGER on Catalogue table
create or replace TRIGGER CatalogueBefore
BEFORE INSERT OR UPDATE ON CATALOGUE
FOR EACH ROW
BEGIN
PACK.SUPPCODE:=:NEW.SUPPLIERCODE;
END;
I've then added an AFTER INSERT TRIGGER on Catalogue table
create or replace TRIGGER "AddNewCatalogue"
AFTER INSERT ON CATALOGUE
FOR EACH ROW
DECLARE
V_SUPPNAME SUPPLIER.SUPPLIERNAME%TYPE;
BEGIN
SELECT SUPPLIERNAME INTO V_SUPPNAME
FROM SUPPLIER
WHERE SUPPLIERCODE= PACK.SUPPCODE;
PACK.SUPPNAME:=V_SUPPNAME;
END;
After all of this I have the Supplier name held in my package in PACK.SUPPNAME but my question is how do I now use this variable when inserting a new row in the Catalogue table? I'm stuck at this for some time now and I'm probably missing something obvious. :) Any help is appreciated. Thanks.
P.S Please ignore uppercase vs. lowercase letters - I've just translated the names from my native language. :)
Thanks

I have denormalized a table and now I have to implement a trigger in order to maintain redundancy
[...]
When a new row is inserted in the Catalogue table I want that SupplierName field in the same table gets populated based on the SupplierCode which was inserted.
If those are your only requirements, all you need is one trigger to alter the record with the data from the second table. Something like that, maybe:
CREATE OR REPLACE TRIGGER CatalogueBefore
BEFORE INSERT OR UPDATE ON Catalogue
FOR EACH ROW
BEGIN
SELECT S.SupplierCode INTO :NEW.SupplierCode
FROM Supplier S
WHERE S.SupplierName = :NEW.SupplierName;
END;
That way, on update or insert into the table Catalogue, the SupplierCode will be overwritten by the code coming from Supplier table. See http://sqlfiddle.com/#!4/d79e0/1 for a live example.
Some notes though:
For this to work, Supplier.SupplierName should be an unique index too. In fact, the all thing is based on the assumption this is a natural key.
You very probably need an other trigger to deal with updates in the Supplier table. Say, when one supplier's name will change, the new name will have to be propagated to Catalogue.

Related

Sqlite using fts5 table to perform all queries instead of main table

So let's say I have two tables: users and users_fts where users_fts being the search table. So users has two fields name and surname.
Normally I would create index for both of em'. But since I have users_fts should I need to create index for name and surname in users? And is there any caveat of using users_fts to perform all queries instead of using the main table users?
SQLite provides full text search, and I am assuming that is what you are using from your table name. I will show the sample code using FTS5 but you can adapt it backward if you need to. If you have a users table something like:
CREATE TABLE users(
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
surname TEXT NOT NULL
);
then you made your full text search table using something like this:
CREATE VIRTUAL TABLE users_fts USING fts5(
name,
surname,
content='user',
content_rowid='id'
);
At this point you we have to make sure that the records in the users table get indexed for the full text search and this can be done using triggers on the users table to do it automatically. The triggers would look like:
CREATE TRIGGER users_ai AFTER INSERT ON users
BEGIN
INSERT INTO users_fts (rowid, name, surname)
VALUES (new.id, new.name, new.surname);
END;
CREATE TRIGGER users_ad AFTER DELETE ON users
BEGIN
INSERT INTO users_fts (users_fts, rowid, name, surname)
VALUES ('delete', old.id, old.name, old.surname);
END;
CREATE TRIGGER users_au AFTER UPDATE ON users
BEGIN
INSERT INTO users_fts (users_fts, rowid, name, surname)
VALUES ('delete', old.id, old.name, old.surname);
INSERT INTO users_fts (rowid, name, surname)
VALUES (new.id, new.name, new.surname);
END;
With all of this in place, you can now use the users_fts table to perform full text searches.
So how do indexes on the users table affect the users_fts table? If you are only searching using the users_fts table, then indexes on the users table do not matter. I do not know how you plan on populating the users_fts table, but if you use triggers on the users table then the proposed indexes on the users table still do not matter. If you are manually keeping the users_fts table up to date, then the answer is that indexes on the users table might affect performance. Most people I know use the trigger approach, and that is what I do which let's you forget about manually maintaining the full text search and you get the added benefit that you can ignore indexes on the source table in regards to populating the full text search. Remember though this is the scenario where you are not querying the users table at all - if you have any queries against the users table, then you may need supporting indexes.
You also asked if there are any caveats to the approach of using the users_fts table for your queries - as long as you keep the users_fts table up to date, then there is no drawback to this approach. If you need full text search features and ranking, this is a very convenient approach baked into SQLite. It will require more storage space, but you can minimize that impact by using an external content table (I showed this when I created the users_fts table). You can read some details about it in section 4.4.2 in the FTS5 extension documentation at https://www.sqlite.org/fts5.html
This approach works well for full text search capabilities and as long as you maintain the indexes, it should work well and provide you with more capabilities for searching as well as ranking. In my experience, most full text searches are faster than what you could do using standard SQL functions and operators (such as LIKE, etc.) and are far more powerful.

Efficient insertion of row and foreign table row if it does not exist

Similar to this question and this solution for PostgreSQL (in particular "INSERT missing FK rows at the same time"):
Suppose I am making an address book with a "Groups" table and a "Contact" table. When I create a new Contact, I may want to place them into a Group at the same time. So I could do:
INSERT INTO Contact VALUES (
"Bob",
(SELECT group_id FROM Groups WHERE name = "Friends")
)
But what if the "Friends" Group doesn't exist yet? Can we insert this new Group efficiently?
The obvious thing is to do a SELECT to test if the Group exists already; if not do an INSERT. Then do an INSERT into Contacts with the sub-SELECT above.
Or I can constrain Group.name to be UNIQUE, do an INSERT OR IGNORE, then INSERT into Contacts with the sub-SELECT.
I can also keep my own cache of which Groups exist, but that seems like I'm duplicating functionality of the database in the first place.
My guess is that there is no way to do this in one query, since INSERT does not return anything and cannot be used in a subquery. Is that intuition correct? What is the best practice here?
My guess is that there is no way to do this in one query, since INSERT
does not return anything and cannot be used in a subquery. Is that
intuition correct?
You could use a Trigger and a little modification of the tables and then you could do it with a single query.
For example consider the folowing
Purely for convenience of producing the demo:-
DROP TRIGGER IF EXISTS add_group_if_not_exists;
DROP TABLE IF EXISTS contact;
DROP TABLE IF EXISTS groups;
One-time setup SQL :-
CREATE TABLE IF NOT EXISTS groups (id INTEGER PRIMARY KEY, group_name TEXT UNIQUE);
INSERT INTO groups VALUES(-1,'NOTASSIGNED');
CREATE TABLE IF NOT EXISTS contact (id INTEGER PRIMARY KEY, contact TEXT, group_to_use TEXT, group_reference TEXT DEFAULT -1 REFERENCES groups(id));
CREATE TRIGGER IF NOT EXISTS add_group_if_not_exists
AFTER INSERT ON contact
BEGIN
INSERT OR IGNORE INTO groups (group_name) VALUES(new.group_to_use);
UPDATE contact SET group_reference = (SELECT id FROM groups WHERE group_name = new.group_to_use), group_to_use = NULL WHERE id = new.id;
END;
SQL that would be used on an ongoing basis :-
INSERT INTO contact (contact,group_to_use) VALUES
('Fred','Friends'),
('Mary','Family'),
('Ivan','Enemies'),
('Sue','Work colleagues'),
('Arthur','Fellow Rulers'),
('Amy','Work colleagues'),
('Henry','Fellow Rulers'),
('Canute','Fellow Ruler')
;
The number of values and the actual values would vary.
SQL Just for demonstration of the result
SELECT * FROM groups;
SELECT contact,group_name FROM contact JOIN groups ON group_reference = groups.id;
Results
This results in :-
1) The groups (noting that the group "NOTASSIGNED", is intrinsic to the working of the above and hence added initially) :-
have to be careful regard mistakes like (Fellow Ruler instead of Fellow Rulers)
-1 used because it would not be a normal value automatically generated.
2) The contacts with the respective group :-
Efficient insertion
That could likely be debated from here to eternity so I leave it for the fence sitters/destroyers to decide :). However, some considerations:-
It works and appears to do what is wanted.
It's a little wasteful due to the additional wasted column.
It tries to minimise the waste by changing the column to an empty string (NULL may be even more efficient, but for some can be confusing)
There will obviously be an overhead BUT in comparison to the alternatives probably negligible (perhaps important if you were extracting every Facebook user) but if it's user input driven likely irrelevant.
What is the best practice here?
Fences again. :)
Note Hopefully obvious, but the DROP statements are purely for convenience and that all other SQL up until the INSERT is run once
to setup the tables and triggers in preparation for the single INSERT
that adds a group if necessary.

PL/SQL Trigger exercise

I have this question that I can see what it's asking but just can't wrap my head around how to write it specifically:
When a new tuple is inserted into the sales order detail, your trigger should insert a corresponding raw material tuple in the Inventory Report table for the Report date (i.e., beginning inventory level, consumption quantity, same-day order quantity, next-day order quantity)
Assumptions I gathered from the notes:
itemID = itemID from RAWMATERIALS table
reportDate = dueDate from SALESORDERS table
begInv = inventoryLevels from RAWMATERIALS table
consumpQty = itemID from FINISHEDGOODS * rmQuantity (raw material required to make each finished good) from FINISHED GOODS
orderNextDay = If consumpQty <= begInv then orderNextDay = consumpQty
orderSameDay = 0
orderSameDay = If consumpQty > begInv then orderNextDay = begInv
orderSameDay = consumpQty - begInv
I thought the trigger might go something like:
CREATE OR REPLACE TRIGGER inv_report
BEFORE INSERT
ON SALESORDERDETAIL
FOR EACH ROW
DECLARE
item_id RAWMATERIALS.ITEMID%TYPE
reportDate SALESORDERS.DUEDATE%TYPE
begInv RAWMATERIALS.INVENTORYLEVEL%TYPE
And then I understand we would probably need to INSERT into INVENTORYREPORT table (which currently has no data in it) and maybe do an IF ELSEIF statement to satisfy the orderNextDay and orderSameDay columns but i'm still not familiar enough with PL/SQL to know exactly how to tackle this.
Can anyone help me out with this? Thanks!
I'll try to find some time to write up some code for you on this, but my first recommendation is to NOT do this in a trigger. Instead, create a procedure "insert_so_detail" that inserts into the the SALESORDERDETAIL and then does the related insert into INVENTORYREPORT.
That procedure is then always called to insert into the S-O-D table. Executing DML inside triggers is a tricky thing to do because the database might decide it needs to restart the DML and then possibly do another insert into the I-R table. Check out this AskTOM thread for more details.
You are always better off putting your DML into PL/SQL package APIs, then only allow DML on the table through the API (grant execute on the package, do NOT grant insert or update or delete on the table directly to a user).

MS Access 2010 Triggers/data macros

I am developing a database to do a annual inventory count with 32 tables in it, 33 including the Master.
We currently have 4000 SKU's so the master table needed to be broken down into smaller tables so I can hand out a realistic amount of work to my counters.
What I am trying to achive is when my counters enter data in the smaller tables using the UI it would automatically populate the fields in the master table.
Any help would be greatly appreciated.
Michael
In Access, there is no way to apply a trigger to a table. What you can do is create a form that implements a grid. Have an After-Update event fire that does what you need. You can make the form look like a table by using the datasheet view.
While you can create a data macro* to update a table from an update on another, why would you want to do it in this case? You can either include the quantity field in the sub table and validate the data against the main table before running an update query, or the sub table (note, table, the employee ID will be sufficient to divide the data) could consist only of an employee id and an SKU, the sub table can then be joined to the main table by SKU and all updates use the quantity field from the main table:
SELECT Mytable1.SKU, MyTable.Quantity
FROM MyTable1
INNER JOIN MyTable
ON MyTable1.SKU = MyTable.SKU
WHERE EmployeeID = [Enter ID: ]
*Data Macro

Not Updating primary key but still got error using entity framework

I have a table called "orderDetails" which contains 4 fields:
OrderID // (primary key of Orders table)
ItemID // (primary key of Items table)
Amount
IsImportant
the primary key of orderDetails table is composed by the first two fields.
I have in my asp.net site a gridview which shows the order details of a selected order.
I'm trying to update a row in the gridview. the user can update only the Amount, IsImportant fileds.
For all the rows except the first one i'm getting this error when trying to update a row:
The property 'ItemID' is part of the object's key information and
cannot be modified.
I read that it is not possible to update the primary key but this is weird because
I'm not trying to update the primary key, only the rest of the fields
updating the first row in the gridview does succeed.
Thanks!
I'm not sure this is what you're looking for, but try to set the column of the problematic key to visible = false. Because when you do update it basically tries to updates ALL fields which appears on that row.
But it's still weird why the first row's update did succeed.

Resources