How to create one-to-zero-or-one relationship programmatically - MySQL? - mysql

I'm busy with a project and one of the stumbling blocks I've come across has been the following:
I have a bookings table, which may or may not result in an invoice being issued (because of some irrelevant stuff such as cancellations). How would I enforce a one (on the bookings side) to zero-or-one (on the invoice side) relationship? Here's what I have thus far:
CREATE TABLE IF NOT EXISTS `booking` (
`booking_id` int(11) NOT NULL AUTO_INCREMENT,
`voucher_id` int(11) NOT NULL,
`pickup_date_time` datetime NOT NULL, ...
PRIMARY KEY (`booking_id`,`voucher_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
And then, later on:
CREATE TABLE IF NOT EXISTS `invoice` (
`booking_id` int(11) NOT NULL,
`voucher_id` int(11) NOT NULL,
`invoice_number` int(11) NOT NULL,
`paid` tinyint(1) NOT NULL,
PRIMARY KEY (`booking_id`,`voucher_id`),
UNIQUE KEY `invoice_number` (`invoice_number`),
KEY `voucher_id` (`voucher_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
voucher_id is just something else I use in the system. The invoice_number is also generated in the PHP, so this is irrelevant.
Any help would be greatly appreciated!

This is more-less just a systematization of what #thaJeztah already suggested in his comments, but here you go anyway...
CREATE TABLE voucher (
voucher_id int(11) PRIMARY KEY
-- Etc...
);
CREATE TABLE booking (
booking_id int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
voucher_id int(11) REFERENCES voucher (voucher_id),
pickup_date_time datetime NOT NULL
-- Etc...
);
CREATE TABLE invoice (
invoice_number int(11) NOT NULL PRIMARY KEY,
booking_id int(11) NOT NULL UNIQUE REFERENCES booking (booking_id),
paid tinyint(1) NOT NULL
-- Etc...
);
Minimal cardinality: There can be a booking without an invoice. There cannot, however, be an invoice without the booking (due to the FK on the non-NULL field invoice.booking_id).
Maximal cardinality: A booking cannot be connected to multiple invoices due to the UNIQUE constraint on invoice.booking_id. An invoice cannot be connected to multiple bookings, simply because one field (in one row) cannot contain multiple values.
So, the resulting relationship between booking and invoice is "one to zero or one".
Alternatively, put everything in just one table with NULL-able fields that get progressively filled as the booking advances.

Related

Correct database's structure for table's relations (MySQL)

I have a table payouts with column reciever_id which by idea must links to primary keys of another Two tables: workers or doctors, which is determined by payouts.reciever_type.
As I understood this structure is a bit wrong.
Would be better to make payouts like this ?
....
`reciever_worker` INT(10) UNSIGNED DEFAULT NULL,
`reciever_doctor` INT(10) UNSIGNED DEFAULT NULL
....
Or merge tables workers and doctors to one table.
Table's structure:
TABLE `payouts`
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`date_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`reciever_type` VARCHAR(25) NOT NULL,
`reciever_id` INT(10) UNSIGNED NOT NULL,
`sum` DOUBLE NOT NULL,
`description` TEXT NOT NULL,
PRIMARY KEY (`id`)
TABLE `workers`
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(40) NOT NULL
PRIMARY KEY (`id`)
TABLE `doctors`
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(40) NOT NULL,
`doctor_fields_1` TEXT ...,
`doctor_fields_2` ...
PRIMARY KEY (`id`)
How to realize it correctly and standardly?
Note: this is valid only if 'receiver_id` is always either a doctor or a worker.
If you rename those doctor fields in the doctors table to something more generic, then it would make perfect sense to have just 1 table for both workers and doctors, with an additional type column that would flag them as one or another.
Then, from the payouts table you'd use a single column with a FK pointing to this one table with workers and doctors. Also, receiver_type would be redundant in payouts table.
Having two separate columns in payouts table would induce permanent checking if one is empty (NULL) or not, or check the type and use either one or another table, and so on. In short, it'd pretty much complicate your life without any benefit.

Is it a good practice to have clientid as first field in the table, thus making a composite key in lieu of faster fetch

My Scenario is to have multiple clients and have their data segregated by client id. To me it appears having client id as the first field in almost all the tables will make the primary index and the data segregation faster.
Sample SQL(MySQL)
CREATE TABLE IF NOT EXISTS client (
client_id BIGINT(20) NOT NULL AUTO_INCREMENT,
client_name VARCHAR(50) NOT NULL UNIQUE,
created DATETIME NOT NULL,
modified TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (client_id)
)ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE IF NOT EXISTS staff (
client_id BIGINT(20) NOT NULL,
staff_id BIGINT(20) NOT NULL UNIQUE AUTO_INCREMENT,
created DATETIME NOT NULL,
modified TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (client_id, staff_id),
FOREIGN KEY (client_id) REFERENCES client(client_id)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
bill_master etc.
In this cases you can define index on your client_id field and with this approach it will be fast in MySQL
ALTER TABLE `staff` ADD INDEX(`client_id`);

Table "Products" with predefined products, user can customize the price. How to avoid data redundancy?

I've been thinking on this problem for fews days and I still can't find a way to do what I want.
Below is how my database is currently designed (it's where I'm stuck) :
This is what I want :
a User can create multiple PriceSheets. A User can give a PriceSheet any name he wants. There are two PriceSheets types : "Lab Fulfillment", or "Self Fulfillment".
if the User chooses "Lab Fulfillment", he can import all or part of the Products of one of the predefined Labs. (I rephrase : there are few Labs that come with a predefined list of Products). The User will only be able to customize the price. He can't add custom products to this PriceSheet.
if the User chooses "Self Fulfillment", he can add his own products, and can personalize each field (name, cost, price, dimension_h, dimension_l).
I don't know how to link the tables between them. If I put the predefined Products in the Products table and set a Many-to-Many relationship between PriceSheets and Product, the default price of a predefined Product will be overwritten when a User customizes it, which is not what I want.
Also, I want the default values of my predefined Products to be only once in my database. If 100 users uses the predefined Products, I don't want the default cost to be in my database 100 times.
Don't hesitate to ask for precisions, I had trouble making this question clear and I think it's still not totaly clear.
Thanks in advance for your help
OK, database normalization 101. Lots of ways to do this, would take me a day to really optimize all this, this should help:
User
Lab
Product
id name cost dimension .....
1 a
2 b
3 c
4 d
So those three tables are fine. All your products will go in the Product table. No foreign keys in any of those tables.
PriceSheet
user_id custom_price product_id type
1 1.99 1 lab-fulfillment
0 NULL 2 self-fulfillment
1 5.99 3 lab-fulfillment
So a user can have as many price sheets as they want, and they can only adjust the price of a product. This can actually be normalized further if you so wish:
PriceSheet (composite key on id, user_id, FK user_id)
id user_id
0 0
1 1
2 1
LabPriceSheet (you could add an id, might be better, or you could use a composite key, stricter)
PriceSheet_id custom_price lab_product_id
0 1.99 0
2 5.99 1
CustomPriceSheet
PriceSheet_id custom_product_id
1 0
With foreign keys as appropriate. This now makes MySQL restrict the custom_price, rather than in PHP (although you would still have to deal with ensuring correct INSERT!).
Now, to deal with who adds the products:
CustomProduct
id user_id product_id timestamp
0 3 2 ...
LabProduct
id lab_id product_id timestamp
0 0 1 ...
1 0 3 ...
So let's double check:
This is what I want :
a User can create multiple PriceSheets. check A User can give a PriceSheet
any name he wants. check There are two PriceSheets types : "Lab
Fulfillment", or "Self Fulfillment". check
if the User chooses "Lab Fulfillment", he can import all or part of the Products of one of the predefined Labs. (I rephrase : there are few Labs that come with a predefined list of Products). The User will only be able to customize the price. He can't add custom products to this PriceSheet.
Yup, because he would create a LabPriceSheet that can only add lab_product_id. Custom price is there too, that overrides the default price in product table.
if the User chooses "Self Fulfillment", he can add his own products, and can personalize each field (name, cost, price, dimension_h, dimension_l).
Yup, he would add a product (you would need to check if a similar one exists, else return the id of the existing product in the product table), and then that would also be an entry in CustomProduct.
I don't know how to link the tables between them. If I put the predefined Products in the Products table and set a Many-to-Many relationship between PriceSheets and Product, the default price of a predefined Product will be overwritten when a User customizes it, which is not what I want.
Yeah that won't happen :) Never (very very rarely) implement many-many rels.
Also, I want the default values of my predefined Products to be only
once in my database. If 100 users uses the predefined Products, I
don't want the default cost to be in my database 100 times.
Of course.
Let me know if you want the MySQL code, I assume you're good! Remember to use InnoDB and properly configure your MySQL configuration!
EDIT
I felt like helping you out with a copy and paste thing. I like copy and paste things. Also, there's a redundant user_id column in the blurb above which I fixed in an earlier edit.
SET GLOBAL innodb_file_per_table = 1;
SET GLOBAL general_log = 'OFF';
SET FOREIGN_KEY_CHECKS=1;
SET GLOBAL character_set_server = utf8mb4;
SET NAMES utf8mb4;
CREATE DATABASE SO; USE SO;
ALTER DATABASE SO CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
CREATE TABLE `User` (
`id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
`email` VARCHAR(555) NOT NULL,
`password` VARBINARY(200) NOT NULL,
`username` VARCHAR(100) NOT NULL,
`role` INT(2) NOT NULL,
`active` TINYINT(1) NOT NULL,
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`modified` DATETIME ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `Lab` (
`id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(1000) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `Product` (
`id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(1000) NOT NULL,
`password` VARBINARY(200) NOT NULL,
`cost` DECIMAL(10, 2) NOT NULL,
`price` DECIMAL(10, 2) NOT NULL,
`height` DECIMAL(15, 5) NOT NULL,
`length` DECIMAL(15, 5) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `CustomProduct` (
`id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
`user` BIGINT(20) UNSIGNED NOT NULL,
`product` BIGINT(20) UNSIGNED NOT NULL,
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`user`) REFERENCES `User`(`id`),
FOREIGN KEY (`product`) REFERENCES `Product`(`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `LabProduct` (
`id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
`lab` BIGINT(20) UNSIGNED NOT NULL,
`product` BIGINT(20) UNSIGNED NOT NULL,
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`lab`) REFERENCES `Lab`(`id`),
FOREIGN KEY (`product`) REFERENCES `Product`(`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `PriceSheet` (
`id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(1000) NOT NULL,
`user` BIGINT(20) UNSIGNED NOT NULL,
PRIMARY KEY (`id`,`user`),
FOREIGN KEY (`user`) REFERENCES `User`(`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `LabPriceSheet` (
`id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
`price_sheet` BIGINT(20) UNSIGNED NOT NULL,
`lab_product` BIGINT(20) UNSIGNED NOT NULL,
`custom_price` DECIMAL(10, 2) NOT NULL,
PRIMARY KEY (`id`),
FOREIGN KEY (`price_sheet`) REFERENCES `PriceSheet`(`id`),
FOREIGN KEY (`lab_product`) REFERENCES `LabProduct`(`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `CustomPriceSheet` (
`id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
`price_sheet` BIGINT(20) UNSIGNED NOT NULL,
`custom_product` BIGINT(20) UNSIGNED NOT NULL,
PRIMARY KEY (`id`),
FOREIGN KEY (`price_sheet`) REFERENCES `PriceSheet`(`id`),
FOREIGN KEY (`custom_product`) REFERENCES `CustomProduct`(`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

How do I structure a Database with a Sales Table MySql

I have a sales table that I want to record all the sales done by the employee.
The problem i'm having is that I can only store one ProductId that comes from a Products table. What is wrong is that a sale has multiples products and with my current structure I can only store one ProductId. I know my approach is wrong but I just don't know how to properly fix it. The question I have is how do I store multiples products in the Sales Table.
This is my Sales Table columns.
CREATE TABLE `Sales` (
`SaleId` int(11) unsigned NOT NULL AUTO_INCREMENT,
`EmployeeId` int(11) unsigned NOT NULL,
`ProductId` int(11) unsigned NOT NULL,
PRIMARY KEY (`SaleId`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Your table and approach is ok.
You just have to insert one row in your sales table per product-id.
The unique key for the sales is the SaleId,
the foreign keys for Employee and Product are also there.
The only thing that's missing in your table is the sale quantity and amount.
It's the typical n:m-issue which is solved by the V-structure.
You need a second table, a Sales-Product table:
CREATE TABLE `SaleProduct` (
`SaleId` int(11) unsigned NOT NULL,
`ProductId` int(11) unsigned NOT NULL,
`Quantity` int(11) unsigned NOT NULL,
PRIMARY KEY (`SaleId`, `ProductId`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
and change your Sales Table:
CREATE TABLE `Sales` (
`SaleId` int(11) unsigned NOT NULL AUTO_INCREMENT,
`EmployeeId` int(11) unsigned NOT NULL,
PRIMARY KEY (`SaleId`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
The second way I suggest is more "standard" but since I have no idea how do you query the table and the size of it, the first method might(?) be useful
concatenate all prod_id into a single string, get them using regexp
example: prod_id = 13_07_255_23_11
SELECT * FROM Sales WHERE ProductId REGEXP '(^|)23(|$)';
split a single sale into multiple entries, have sales_id and product_id be your combined unique/primary key

Which columns should I add to a PRIMARY KEY

I want to create a table and avoid duplicated entries, by creating a PRIMARY KEY. The problem is I don't know which columns I should add to this KEY. Consider the next table:
CREATE TABLE `customers` (
`id_c` int(11) unsigned NOT NULL,
`lang` tinyint(2) unsigned NOT NULL,
`name` varchar(80) collate utf8_unicode_ci NOT NULL,
`franchise` int(11) unsigned NOT NULL,
KEY `id_c` (`id_c`),
KEY `lang` (`lang`),
KEY `franchise` (`franchise`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
id_c: Id of customer. It can be an enterprise. Suppose McDonald's
lang: Contact language.
boss: Boss' name
franchise: If not zero, it is a franchise. McDonald's in Rome, Paris, London...
As you can see, each ENTERPRISE can have different central "shops" in each country (contact language), but also different franchises in each city (where boss' name would be different).
I want to be able to INSERT new rows where the id_c, lang can be not-distinct (many franchises in same country). But name has to be distinct only if (id_c,lang) is the same (for other id_c,lang combination... name could be the same). And franchise can be the same too only if it has not been assigned in the same (id_c,lang) pair.
I was thinking about a PRIMARY KEY (lang,name), but it might not be the best way. Is this table structure just too complex?
you need to create a multiple column UNIQUE constraint,
CONSTRAINT tb_uq UNIQUE (id_c,lang, name)
or set them as the primary key,
CREATE TABLE `customers`
(
`id_c` int(11) unsigned NOT NULL,
`lang` tinyint(2) unsigned NOT NULL,
`name` varchar(80) collate utf8_unicode_ci NOT NULL,
`franchise` int(11) unsigned NOT NULL,
KEY `id_c` (`id_c`),
KEY `lang` (`lang`),
KEY `franchise` (`franchise`),
CONSTRAINT tb_PK PRIMARY KEY (id_c,lang, name) --- <<== compound PK
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
If i get your question right...u are asking which columns to choose...and not HOW to do it?Correct?
So i'd guess that franchise number is not a boolean(YES/NO) thing but holds a number unique for each shop?...each country? If thats the case then go with id_c and franchise.
If not you can choose all 4 of them to be the key...but i think thats not a good practise.In that case i'd say that you should add one more column(trueID for example - autoincrement integer) and use this one as your primary key.
Just give Id as primary key. Because using Id_c you can get other column values. As you see the best advice is to create your Primary id should be in first column.