3

I can implement the constraints that I want in stored procs, but I'm wondering if I can define a set of foreign key constraints which will do the job.

I have several tables, with these key relationships:

NSNs
---
Id  PK

Solicitations
----
Id            PK
NSNId         FK - NSNs

Parts
-----
Id        PK
NSNId     FK - NSNs

BaseRFQs
-------
Id      PK
NSNId   FK - NSNs

RFQs
----
Id          PK
BaseRFQId   FK - BaseRFQs

BaseRFQsSols
------------
BaseRFQId PK/FK - BaseRFQs
SolId     PK/FK - Solicitations

RFQsSolsParts
-------------
RFQId     PK/FK - RFQs
SolId     PK/FK - Solicitations
PartId    PK/FK - Parts

My questions are:

  1. Is it possible to set up a foreign key constraint on BaseRFQsSols that requires both the BaseRFQId and the SolId to reference records that have the same NSNId?
  2. Is it possible to set up a foreign key constraint on RFQsSolsParts that requires SolId and PartId to reference records that have the same NSNId, and requires RFQId to reference a BaseRFQId which has the same NSNId as the other two?

I'm using MySQL, although as I understand it (please correct me if I understand wrong) the CONSTRAINT FOREIGN KEY syntax I'm asking about is ANSI-compliant, so the solution oughtn't to vary between DBMS implementations.

EDIT: per @Drew's request, here are the table definitions as they stand now:

CREATE TABLE `nsns` (
  `ID` int(11) NOT NULL AUTO_INCREMENT,
  `NSN` char(16) NOT NULL,
  `Description` varchar(100) DEFAULT NULL,
  `ShortDesc` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`ID`),
  UNIQUE KEY `NSN_UNIQUE` (`NSN`)
) ENGINE=InnoDB AUTO_INCREMENT=42 DEFAULT CHARSET=latin7

CREATE TABLE `solicitations` (
  `ID` int(11) NOT NULL AUTO_INCREMENT,
  `NSNId` int(11) NOT NULL,
  `UOMId` int(11) NOT NULL DEFAULT '1',
  `QUPId` int(11) NOT NULL DEFAULT '0',
  `SolicitationNo` char(16) NOT NULL,
  `Quantity` int(11) NOT NULL,
  `ReturnByDate` date NOT NULL,
  `StatusId` int(11) NOT NULL DEFAULT '1',
  `Memo` text,
  PRIMARY KEY (`ID`),
  UNIQUE KEY `SolicitationNo_UNIQUE` (`SolicitationNo`),
  KEY `NSN_idx` (`NSNId`)
  CONSTRAINT `NSNId` FOREIGN KEY (`NSNId`) REFERENCES `nsns` (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=latin7

CREATE TABLE `parts` (
  `ID` int(11) NOT NULL AUTO_INCREMENT,
  `NSNId` int(11) NOT NULL,
  `VendorId` int(11) NOT NULL,
  `UOMId` int(11) NOT NULL DEFAULT '1',
  `QUPId` int(11) NOT NULL DEFAULT '1',
  `StatusId` int(11) DEFAULT '1',
  `PartNo` varchar(45) DEFAULT NULL,
  `Memo` text,
  PRIMARY KEY (`ID`)
  KEY `NSN_idx` (`NSNId`)
  CONSTRAINT `NSNId` FOREIGN KEY (`NSNId`) REFERENCES `nsns` (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=latin7

CREATE TABLE `baserfqs` (
  `ID` int(11) NOT NULL AUTO_INCREMENT,
  `NSNId` int(11) NOT NULL,
  `BRFQNo` varchar(45) DEFAULT NULL,
  `Memo` text,
  `Finalized` bit(1) NOT NULL DEFAULT b'0',
  PRIMARY KEY (`ID`),
  UNIQUE KEY `BRFQNo_UNIQUE` (`BRFQNo`),
  KEY `NSN_idx` (`NSNId`),
  CONSTRAINT `NSNId` FOREIGN KEY (`NSNId`) REFERENCES `nsns` (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=latin7

CREATE TABLE `rfqs` (
  `ID` int(11) NOT NULL AUTO_INCREMENT,
  `BaseRFQId` int(11) NOT NULL,
  `VendorId` int(11) NOT NULL,
  `RFQNo` varchar(45) NOT NULL,
  `StatusId` int(11) NOT NULL DEFAULT '6',
  `DateSent` date DEFAULT NULL,
  `DateResponded` date DEFAULT NULL,
  `VendorNotes` text,
  `QuotedBy` varchar(45) DEFAULT NULL,
  `Title` varchar(45) DEFAULT NULL,
  `ValidityCodeId` int(11) DEFAULT '4',
  `UnitWt` decimal(10,3) DEFAULT NULL,
  `WtUOMId` int(11) DEFAULT '1',
  PRIMARY KEY (`ID`),
  UNIQUE KEY `RFQNo_UNIQUE` (`RFQNo`),
  KEY `BaseRFQId_idx` (`BaseRFQId`),
  KEY `VendorId_idx` (`VendorId`),
  KEY `StatusId_idx` (`StatusId`),
  KEY `ValidityCodeId_idx` (`ValidityCodeId`),
  KEY `WtUOMId_idx` (`WtUOMId`),
  CONSTRAINT `WtUOMId` FOREIGN KEY (`WtUOMId`) REFERENCES `wtuoms` (`ID`),
  CONSTRAINT `BaseRFQId` FOREIGN KEY (`BaseRFQId`) REFERENCES `baserfqs` (`ID`),
  CONSTRAINT `StatusId` FOREIGN KEY (`StatusId`) REFERENCES `rfqstatus` (`ID`),
  CONSTRAINT `ValidityCodeId` FOREIGN KEY (`ValidityCodeId`) REFERENCES `validitycodes` (`ID`),
  CONSTRAINT `VendorId` FOREIGN KEY (`VendorId`) REFERENCES `vendors` (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=latin7

CREATE TABLE `baserfqssols` (
  `BaseRFQId` int(11) NOT NULL,
  `SolId` int(11) NOT NULL,
  `LineItemNo` int(11) NOT NULL DEFAULT '1',
  PRIMARY KEY (`BaseRFQId`,`SolId`),
  KEY `RFQ_idx` (`BaseRFQId`),
  KEY `Solicitation_idx` (`SolId`)
) ENGINE=MyISAM DEFAULT CHARSET=latin7

CREATE TABLE `rfqssolsparts` (
  `RFQId` int(11) NOT NULL,
  `SolId` int(11) NOT NULL,
  `PartId` int(11) NOT NULL,
  `CondId` int(11) NOT NULL,
  `UOMId` int(11) NOT NULL DEFAULT '1',
  `QUPId` int(11) NOT NULL DEFAULT '1',
  `UnitPrice` decimal(10,3) NOT NULL,
  `LeadTime` int(11) DEFAULT NULL,
  `LTCId` int(11) DEFAULT NULL,
  `SplsNSNId` int(11) DEFAULT NULL,
  `SetupCostInc` bit(1) NOT NULL DEFAULT b'0',
  `CertCostInc` bit(1) NOT NULL DEFAULT b'0',
  `MfgCerts` bit(1) NOT NULL DEFAULT b'0',
  `Altered` bit(1) NOT NULL DEFAULT b'0',
  `OrigPkg` bit(1) NOT NULL DEFAULT b'1',
  `SplsContNo` varchar(45) DEFAULT NULL,
  `SplsDate` date DEFAULT NULL,
  PRIMARY KEY (`RFQId`,`SolId`,`PartId`)
) ENGINE=InnoDB DEFAULT CHARSET=latin7
Drew
  • 24,851
  • 10
  • 43
  • 78
BobRodes
  • 5,990
  • 2
  • 24
  • 26
  • why not publish the `show create table xyz` for each xyz. Several of us just pass on by to the next question. – Drew Jul 12 '16 at 22:20
  • Thanks @Drew. I'll do that. I didn't want to overcomplicate... – BobRodes Jul 12 '16 at 22:30
  • I won't say you *can't* do what you want declaratively, but I can't picture how. Were it my project, I would declare FK constraints as far as possible, then probably use triggers to do the last step of validation. – Darwin von Corax Jul 12 '16 at 22:47
  • alright, let the fun begin – Drew Jul 12 '16 at 22:51
  • @DarwinvonCorax That's kind of what I'm thinking too. – BobRodes Jul 12 '16 at 22:51
  • error 1022, table parts, dupe name, KEY `NSN_idx` (`NSNId`), .... also missing commas in a few places. Missing at least 4 tables – Drew Jul 12 '16 at 22:55
  • also, why is `baserfqssols` myisam? – Drew Jul 12 '16 at 23:00
  • for question #1. At the end of what you wrote, keep writing and say in what table(s) – Drew Jul 12 '16 at 23:07
  • You could possibly use triggers, `BEFORE INSERT` and `BEFORE UPDATE`, on the tables `BaseRFQsSols` and `RFQsSolsParts` to perform the checks and raise errors if the checks fail. Edit: You could even put triggers in the related tables to prevent NSNId value changes that would later violate the constraint. – Uueerdo Jul 12 '16 at 23:08
  • It is simply not defined in which tables these Id's are. We can assume, but oh well. I am off looking at other stuff. But #1 is quite easy. #2 first half yes. Second half most probable. – Drew Jul 12 '16 at 23:27

1 Answers1

2

I figured your solId was for solitications. I originally thought it meant solid (like a rock). So, this can be done through composition.

Note that there were a few typo errors in the schema provided. Some missing commas, some duped up index names. A MyISAM table changed to INNODB. So I did some renaming. Also, around table5 there were missing tables. So it is not as if your script would run (for table rfqs).

Similarly, the following schema will fail due to your missing tables provided, somewhere around 60 to 70 percent through it.

Tables so far:

create schema slipper;
use slipper;

CREATE TABLE `nsns` (
  `ID` int(11) NOT NULL AUTO_INCREMENT,
  `NSN` char(16) NOT NULL,
  `Description` varchar(100) DEFAULT NULL,
  `ShortDesc` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`ID`),
  UNIQUE KEY `NSN_UNIQUE` (`NSN`)
) ENGINE=InnoDB AUTO_INCREMENT=42 DEFAULT CHARSET=latin7;

drop table if exists `solicitations`;
CREATE TABLE `solicitations` (
  `ID` int(11) NOT NULL AUTO_INCREMENT,
  `NSNId` int(11) NOT NULL,
  `UOMId` int(11) NOT NULL DEFAULT '1',
  `QUPId` int(11) NOT NULL DEFAULT '0',
  `SolicitationNo` char(16) NOT NULL,
  `Quantity` int(11) NOT NULL,
  `ReturnByDate` date NOT NULL,
  `StatusId` int(11) NOT NULL DEFAULT '1',
  `Memo` text,
  PRIMARY KEY (`ID`),
  UNIQUE KEY `SolicitationNo_UNIQUE` (`SolicitationNo`),
  KEY `NSN_idx1111` (`NSNId`),
  KEY `NSN_idx1112` (`ID`,`NSNId`), -- atm an necessary evil. Revisit, perhaps collapse one
  CONSTRAINT `NSNId` FOREIGN KEY (`NSNId`) REFERENCES `nsns` (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=latin7;

drop table if exists `parts`;
CREATE TABLE `parts` (
  `ID` int(11) NOT NULL AUTO_INCREMENT,
  `NSNId` int(11) NOT NULL,
  `VendorId` int(11) NOT NULL,
  `UOMId` int(11) NOT NULL DEFAULT '1',
  `QUPId` int(11) NOT NULL DEFAULT '1',
  `StatusId` int(11) DEFAULT '1',
  `PartNo` varchar(45) DEFAULT NULL,
  `Memo` text,
  PRIMARY KEY (`ID`),
  KEY `NSN_idx2222` (`NSNId`),
  KEY `NSN_idx2223` (`ID`,`NSNId`), -- atm an necessary evil. Revisit, perhaps collapse one
  CONSTRAINT `NSNId2222` FOREIGN KEY (`NSNId`) REFERENCES `nsns` (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=latin7;

drop table if exists `baserfqs`;
CREATE TABLE `baserfqs` (
  `ID` int(11) NOT NULL AUTO_INCREMENT,
  `NSNId` int(11) NOT NULL,
  `BRFQNo` varchar(45) DEFAULT NULL,
  `Memo` text,
  `Finalized` bit(1) NOT NULL DEFAULT b'0',
  PRIMARY KEY (`ID`),
  UNIQUE KEY `BRFQNo_UNIQUE` (`BRFQNo`),
  KEY `NSN_idx4444` (`NSNId`),
  KEY `NSN_idx4445` (`ID`,`NSNId`), -- atm an necessary evil. Revisit, perhaps collapse one
  CONSTRAINT `NSNId4444` FOREIGN KEY (`NSNId`) REFERENCES `nsns` (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=latin7;

CREATE TABLE `rfqs` (
  `ID` int(11) NOT NULL AUTO_INCREMENT,
  `BaseRFQId` int(11) NOT NULL,
  `VendorId` int(11) NOT NULL,
  `RFQNo` varchar(45) NOT NULL,
  `StatusId` int(11) NOT NULL DEFAULT '6',
  `DateSent` date DEFAULT NULL,
  `DateResponded` date DEFAULT NULL,
  `VendorNotes` text,
  `QuotedBy` varchar(45) DEFAULT NULL,
  `Title` varchar(45) DEFAULT NULL,
  `ValidityCodeId` int(11) DEFAULT '4',
  `UnitWt` decimal(10,3) DEFAULT NULL,
  `WtUOMId` int(11) DEFAULT '1',
  PRIMARY KEY (`ID`),
  UNIQUE KEY `RFQNo_UNIQUE` (`RFQNo`),
  KEY `BaseRFQId_idx` (`BaseRFQId`),
  KEY `VendorId_idx` (`VendorId`),
  KEY `StatusId_idx` (`StatusId`),
  KEY `ValidityCodeId_idx` (`ValidityCodeId`),
  KEY `WtUOMId_idx` (`WtUOMId`),
  CONSTRAINT `WtUOMId` FOREIGN KEY (`WtUOMId`) REFERENCES `wtuoms` (`ID`),
  CONSTRAINT `BaseRFQId` FOREIGN KEY (`BaseRFQId`) REFERENCES `baserfqs` (`ID`),
  CONSTRAINT `StatusId` FOREIGN KEY (`StatusId`) REFERENCES `rfqstatus` (`ID`),
  CONSTRAINT `ValidityCodeId` FOREIGN KEY (`ValidityCodeId`) REFERENCES `validitycodes` (`ID`),
  CONSTRAINT `VendorId` FOREIGN KEY (`VendorId`) REFERENCES `vendors` (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=latin7;


drop table if exists `compTableX001`;
CREATE TABLE `compTableX001`
(  -- a composition table for FK's in `baserfqssols`
  `ID` int(11) AUTO_INCREMENT PRIMARY KEY,
  `BaseRFQId` int(11) NOT NULL,     -- baserfqs.ID
  `SolId` int(11) NOT NULL,         -- solicitations.ID
  `NSNId` int(11) NOT NULL,
  unique key (`BaseRFQId`,`SolId`), -- no dupes allowed  
  CONSTRAINT `tx001_base` FOREIGN KEY (`BaseRFQId`,`NSNId`) REFERENCES `baserfqs` (`ID`,`NSNId`),
  CONSTRAINT `tx001_sol` FOREIGN KEY (`SolId`,`NSNId`) REFERENCES `solicitations` (`ID`,`NSNId`)
) ENGINE=InnoDB DEFAULT CHARSET=latin7; 

drop table if exists `compTableX002`;
CREATE TABLE `compTableX002`
(  -- a composition table for FK's in `rfqssolsparts`
  `ID` int(11) AUTO_INCREMENT PRIMARY KEY,
  `BaseRFQId` int(11) NOT NULL,     -- baserfqs.ID
  `SolId` int(11) NOT NULL,         -- solicitations.ID
  `PartId` int(11) NOT NULL,        -- parts.ID
  `NSNId` int(11) NOT NULL,
  unique key (`BaseRFQId`,`SolId`,`PartId`), -- no dupes allowed 
  CONSTRAINT `tx002_base` FOREIGN KEY (`BaseRFQId`,`NSNId`) REFERENCES `baserfqs` (`ID`,`NSNId`),
  CONSTRAINT `tx002_sol` FOREIGN KEY (`SolId`,`NSNId`) REFERENCES `solicitations` (`ID`,`NSNId`),
  CONSTRAINT `tx002_part` FOREIGN KEY (`PartId`,`NSNId`) REFERENCES `parts` (`ID`,`NSNId`)
) ENGINE=InnoDB DEFAULT CHARSET=latin7; 

drop table if exists `baserfqssols`;
CREATE TABLE `baserfqssols` (
  `ID` int(11) auto_increment,
  `compId` int(11) NOT NULL,    -- composition ID `compTableX001`.`ID`
  `LineItemNo` int(11) NOT NULL DEFAULT '1',
  PRIMARY KEY (`ID`),
  CONSTRAINT `basesol_compX001` FOREIGN KEY (`compId`) REFERENCES `compTableX001` (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=latin7; -- changed engine type

-- Is it possible to set up a foreign key constraint on RFQsSolsParts that requires SolId and PartId to reference records
-- that have the same NSNId, and requires RFQId to reference a BaseRFQId which has the same NSNId as the other two?

drop table if exists `rfqssolsparts`;
CREATE TABLE `rfqssolsparts` (
  -- `RFQId` int(11) NOT NULL,      -- requirement BBBBBBBBBBBBB
  -- `SolId` int(11) NOT NULL,      -- requirement AAAAAAAAA
  -- `PartId` int(11) NOT NULL,     -- requirement AAAAAAAAA
  `ID` int(11) auto_increment,
  `compId` int(11) NOT NULL, -- composition ID `compTableX002`.`ID`
  `CondId` int(11) NOT NULL,
  `UOMId` int(11) NOT NULL DEFAULT '1',
  `QUPId` int(11) NOT NULL DEFAULT '1',
  `UnitPrice` decimal(10,3) NOT NULL,
  `LeadTime` int(11) DEFAULT NULL,
  `LTCId` int(11) DEFAULT NULL,
  `SplsNSNId` int(11) DEFAULT NULL,
  `SetupCostInc` bit(1) NOT NULL DEFAULT b'0',
  `CertCostInc` bit(1) NOT NULL DEFAULT b'0',
  `MfgCerts` bit(1) NOT NULL DEFAULT b'0',
  `Altered` bit(1) NOT NULL DEFAULT b'0',
  `OrigPkg` bit(1) NOT NULL DEFAULT b'1',
  `SplsContNo` varchar(45) DEFAULT NULL,
  `SplsDate` date DEFAULT NULL,
  -- PRIMARY KEY (`RFQId`,`SolId`,`PartId`) 
  PRIMARY KEY (`ID`),
  CONSTRAINT `triplet_compX002` FOREIGN KEY (`compId`) REFERENCES `compTableX002` (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=latin7;

compTableX001 is like a TwoParents-OneChild mini hierarchy with the name ID. So ID is the name of the parenthood. It has two parents(BaseRFQId and SolId), and one child (NSNId). The name or identifier, as ID, is the FK target of the baserfqssols row that it supports. Referenced and Referencing, respectively.

Similarly, compTableX002 appears to solve the conditions now for Question 2.

cleanup:

drop schema slipper;
Drew
  • 24,851
  • 10
  • 43
  • 78
  • Yes, there are a few tables referenced in here that I didn't include, short lookup tables and the like. Let me study this...and thank you. – BobRodes Jul 13 '16 at 00:21
  • I will continue on to question2 – Drew Jul 13 '16 at 00:22
  • Ok, the composition table is the table that glues the needed references together, then. Correct? – BobRodes Jul 13 '16 at 00:29
  • Yes. And as I see it, changing NSNId here or there will violate something in the FK chain. Setting to `NULL` bottom-up for changes (or should I say for no FK relationship) but that is a nasty hack. So if you don't do that hack, it should keep Referential Integrity as I see it, unless I screwed up somewhere. – Drew Jul 13 '16 at 00:32
  • It would be easier to test on some tiny column-count tables, but not here at the moment too easily for me. But, there would be no problem doing an `update` setting \``baserfqssols\``.\``compId\`` to a different value, if valid. – Drew Jul 13 '16 at 00:33
  • With Q2, the extra problem I see is making sure in RFQsSolsParts that you can't add a Part or Sol whose NSN differs from the RFQ, but you have to go to the BaseRFQ table to check the NSN that's associated with the RFQ. – BobRodes Jul 13 '16 at 00:40
  • I am assuming there will be a table or two to add. And that there would not necessarily be a re-use of `compTableX001 `. But if it can, certainly. – Drew Jul 13 '16 at 00:40
  • I've never thought of using intermediary tables of keys to handle complex referential integrity constraints. I've always just written stored procs to handle the CRUD work, and thrown errors. This looks like it might be faster. Thanks for the concept! I think I can see how to make it work. – BobRodes Jul 13 '16 at 01:19
  • Well we will soon see if you can poke a hole in the theory. yw. – Drew Jul 13 '16 at 01:21
  • Did you by any chance get the references flipped between baserfq and solicitation in the composition table, or am I misunderstanding something? And in the same table, should I add a constraint to NSNId referencing NSNs.ID? – BobRodes Jul 13 '16 at 01:33
  • I will take a look. It is very possible I screwed up something. Which table by the way. – Drew Jul 13 '16 at 01:45
  • Your composition table; the constraints. `CONSTRAINT tx001_base FOREIGN KEY (BaseRFQId,NSNId) REFERENCES solicitations (ID, NSNId), CONSTRAINT tx001_sol FOREIGN KEY (SolId,NSNId) REFERENCES baserfqs (ID,NSNId)` – BobRodes Jul 13 '16 at 01:47
  • Yeah I screwed that up. Thx – Drew Jul 13 '16 at 01:50
  • As for you other question, no. Because NSNId benefits from being in a table (the comp tables) that have either 2 or 3 FK's of their own back to *referenced* tables, which those themselves (all 5 of them) have FK's to the NSN table on id. So it is like cascading RI if you will. You could add it, but it would slightly slow stuff down by adding a key. – Drew Jul 13 '16 at 01:54