ERD for a telephone tariffs website - is this optimal? - mysql

I have designed database tables of Mobile Phones & Tariffs.
It should support multiple mobile networks, sale type and the related affiliate.
Points (for operators) are vary depending on the selected phone, tariff, network and sale Type.
Example Price Plans:
T-Mobile: Consumer Upgrade
Samsung Galaxy S3
Tariff: Super One (Monthly Cost: $12.00)
This sale affiliate with 'Retailer One'
Total Point: 3.3
T-Mobile: Consumer New Connection
Samsung Galaxy S3
Tariff: Super One (Monthly Cost: $35.00)
This sale affiliate with 'Retailer Two'
Total Point: 7.3
AT&T: 'Sim Only Deal' (Without Phone)
Tariff: X-Deal Two (Monthly Cost: $18.00)
This sale affiliate with 'Retailer One'
Total Point: 10.0
See the tables design below, is this how it should be done? or how can it be improved?
Database Design:
CREATE TABLE IF NOT EXISTS `affiliate` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=3 ;
INSERT INTO `affiliate` (`id`, `name`) VALUES
(1, 'Retailer One'),
(2, 'Retailer Two');
CREATE TABLE IF NOT EXISTS `network` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=3 ;
INSERT INTO `network` (`id`, `name`) VALUES
(1, 'T-Mobile'),
(2, 'AT&T');
CREATE TABLE IF NOT EXISTS `network_saletype` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`network_id` int(11) NOT NULL,
`saletype_id` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=3 ;
INSERT INTO `network_saletype` (`id`, `network_id`, `saletype_id`) VALUES
(1, 1, 1),
(2, 2, 3);
CREATE TABLE IF NOT EXISTS `phone` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
`description` varchar(150) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=3 ;
INSERT INTO `phone` (`id`, `name`, `description`) VALUES
(1, 'Samsung Galaxy S3', ' Quad-core, NFC, AMOLED'),
(2, 'Apple iPhone 4S', 'A5 chip, 8MP camera, Siri voice');
CREATE TABLE IF NOT EXISTS `phone_points` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`phone_id` int(11) NOT NULL,
`tarrif_id` int(11) NOT NULL,
`affilicate_id` int(11) NOT NULL,
`point` float NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=3 ;
INSERT INTO `phone_points` (`id`, `phone_id`, `tarrif_id`, `affilicate_id`, `point`) VALUES
(1, 1, 1, 1, 3.3),
(2, 1, 2, 2, 7.3);
CREATE TABLE IF NOT EXISTS `saletype` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=4 ;
INSERT INTO `saletype` (`id`, `name`) VALUES
(1, 'Consumer Upgrade'),
(2, 'Consumer New Connection'),
(3, 'Sim-Only Deal');
CREATE TABLE IF NOT EXISTS `tariff_point` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`tarrif_id` int(11) NOT NULL,
`affilicate_id` int(11) NOT NULL,
`point` float NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=4 ;
INSERT INTO `tariff_point` (`id`, `tarrif_id`, `affilicate_id`, `point`) VALUES
(3, 3, 1, 10);
CREATE TABLE IF NOT EXISTS `tarrif` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`network_saletype_id` int(11) NOT NULL,
`name` varchar(50) NOT NULL,
`tariff_duration` int(11) NOT NULL,
`monthly_cost` decimal(6,2) NOT NULL,
`description` varchar(150) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=4 ;
INSERT INTO `tarrif` (`id`, `network_saletype_id`, `name`, `tariff_duration`, `monthly_cost`, `description`) VALUES
(1, 1, 'Super One', 12, '12.00', '200 Mins, 400 Texts, 500MB Internet'),
(2, 1, 'Super One', 12, '35.00', '200 Mins, 400 Texts, 500MB Internet'),
(3, 2, 'X-Deal Two', 12, '18.00', '');
Result
mysql> select * from network;
+----+----------+
| id | name |
+----+----------+
| 1 | T-Mobile |
| 2 | AT&T |
+----+----------+
mysql> select * from affiliate;
+----+--------------+
| id | name |
+----+--------------+
| 1 | Retailer One |
| 2 | Retailer Two |
+----+--------------+
mysql> select * from saletype;
+----+-------------------------+
| id | name |
+----+-------------------------+
| 1 | Consumer Upgrade |
| 2 | Consumer New Connection |
| 3 | Sim-Only Deal |
+----+-------------------------+
mysql> select * from network_saletype;
+----+------------+-------------+
| id | network_id | saletype_id |
+----+------------+-------------+
| 1 | 1 | 1 |
| 2 | 2 | 3 |
+----+------------+-------------+
mysql> select * from tarrif;
+----+---------------------+------------+-----------------+--------------+-------------------------------------+
| id | network_saletype_id | name | tariff_duration | monthly_cost | description |
+----+---------------------+------------+-----------------+--------------+-------------------------------------+
| 1 | 1 | Super One | 12 | 12.00 | 200 Mins, 400 Texts, 500MB Internet |
| 2 | 1 | Super One | 12 | 35.00 | 200 Mins, 400 Texts, 500MB Internet |
| 3 | 2 | X-Deal Two | 12 | 18.00 | |
+----+---------------------+------------+-----------------+--------------+-------------------------------------+
mysql> select * from tariff_point;
+----+-----------+---------------+-------+
| id | tarrif_id | affilicate_id | point |
+----+-----------+---------------+-------+
| 3 | 3 | 1 | 10 |
+----+-----------+---------------+-------+
mysql> select * from phone;
+----+-------------------+---------------------------------+
| id | name | description |
+----+-------------------+---------------------------------+
| 1 | Samsung Galaxy S3 | Quad-core, NFC, AMOLED |
| 2 | Apple iPhone 4S | A5 chip, 8MP camera, Siri voice |
+----+-------------------+---------------------------------+
mysql> select * from phone_points;
+----+----------+-----------+---------------+-------+
| id | phone_id | tarrif_id | affilicate_id | point |
+----+----------+-----------+---------------+-------+
| 1 | 1 | 1 | 1 | 3.3 |
| 2 | 1 | 2 | 2 | 7.3 |
+----+----------+-----------+---------------+-------+
Edit: My question was is the relationship design between phone, tarrif, network and saleType is ok? Basically operator can select a retailer which then select Mobile Network.. then select what type of sale (eg: consumer upgrade) and then select phone with related related tariffs. Depending what tarrifs and/or phone they have selected - operator gets number of points.

This table implies that it's ok for 2 or 5 or 1,847 affiliates to have the same name.
CREATE TABLE IF NOT EXISTS `affiliate` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=3 ;
These inserts will work. They probably shouldn't.
INSERT INTO `affiliate` (`id`, `name`) VALUES
(1, 'Retailer One'),
(2, 'Retailer One'),
(3, 'Retailer One'),
(4, 'Retailer One'),
(5, 'Retailer One');
The problem is that, while you've defined a meaningless (surrogate) integer as a primary key, you haven't defined what that thing is a surrogate for. All those tables should have a unique constraint on some combination of columns besides the surrogate ID number.
You've also defined no foreign keys at all. This is almost certainly a mistake.
When insert statements like this succeed, you've got work to do.
insert into phone_points values (1078, 3000, 2743, 10234, -33344.03);

Related

How do I join a many-to-many where the left table can be null OR the right table can be null?

Here is my schema and test data to create a minimum reproducible example:
CREATE TABLE `scpsl_user_id_bans` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4;
CREATE TABLE `scpsl_ip_bans` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4;
CREATE TABLE `scpsl_ban_link` (
`user_id_ban_id` bigint(20) unsigned DEFAULT NULL,
`ip_ban_id` bigint(20) unsigned DEFAULT NULL,
`start_date_skew` bigint(20) DEFAULT NULL,
`end_date_skew` bigint(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `scpsl_user_id_bans` VALUES (1, "First Ban");
INSERT INTO `scpsl_user_id_bans` VALUES (2, "Second Ban");
INSERT INTO `scpsl_user_id_bans` VALUES (3, "Third Ban");
INSERT INTO `scpsl_user_id_bans` VALUES (4, "Fourth Ban");
INSERT INTO `scpsl_ip_bans` VALUES (3, "Third Ban");
INSERT INTO `scpsl_ip_bans` VALUES (4, "Fourth Ban");
INSERT INTO `scpsl_ip_bans` VALUES (5, "Fifth Ban");
INSERT INTO `scpsl_ip_bans` VALUES (6, "Sixth Ban");
INSERT INTO `temp`.`scpsl_ban_link` VALUES ('3', '3', '0', '0');
INSERT INTO `temp`.`scpsl_ban_link` VALUES ('4', '4', '0', '0');
This is the query I want my VIEW to work with:
SELECT DISTINCT
`idbans`.`id` AS `user_id_ban_id`,
`ipbans`.`id` AS `ip_ban_id`,
`idbans`.`name` AS `user_id_ban_name`,
`ipbans`.`name` AS `ip_ban_name`,
`idbans`.`user_id` AS `user_id`,
`ipbans`.`user_id` AS `ip_address`
FROM `scpsl_ban_link` `ban_link`
LEFT OUTER JOIN `scpsl_user_id_bans` `idbans` ON (`idbans`.`id` = `ban_link`.`user_id_ban_id`)
RIGHT OUTER JOIN `scpsl_ip_bans` `ipbans` ON (`ban_link`.`ip_ban_id` = `ipbans`.`id`)
WHERE user_id_ban_id IS NULL or ip_ban_id IS NULL
ORDER BY user_id_ban_id DESC
;
And I expect to view this data:
+------+------------+----------------+-----------+-------+------------+
| id | name | user_id_ban_id | ip_ban_id | id | name |
+------+------------+----------------+-----------+-------+------------+
| 1 | First Ban | 1 | 1 | NULL | NULL |
| 2 | Second Ban | 2 | 2 | NULL | NULL |
| 3 | Third Ban | 3 | 3 | 3 | Third Ban |
| 4 | Fourth Ban | 4 | 4 | 4 | Fourth Ban |
| NULL | NULL | NULL | 5 | 5 | Fifth Ban |
| NULL | NULL | NULL | 6 | 6 | Sixth Ban |
+------+------------+----------------+-----------+-------+------------+
However, MySQL creates this SQL when I try to create a view:
CREATE
ALGORITHM = UNDEFINED
DEFINER = `scpsl`#`localhost`
SQL SECURITY DEFINER
VIEW `scpsl_print_bans` AS
SELECT DISTINCT
`idbans`.`id` AS `user_id_ban_id`,
`ipbans`.`id` AS `ip_ban_id`,
`idbans`.`name` AS `user_id_ban_name`,
`ipbans`.`name` AS `ip_ban_name`,
`idbans`.`user_id` AS `user_id`,
`ipbans`.`user_id` AS `ip_address`,
FROM_DOTNETTICKS(`idbans`.`start_date`) AS `start_date`,
FROM_DOTNETTICKS(`idbans`.`end_date`) AS `end_date`,
`idbans`.`admin` AS `admin`,
`idbans`.`reason` AS `reason`,
`idbans`.`active` AS `user_id_ban_active`,
`ipbans`.`active` AS `ip_ban_active`,
CONCAT('UPDATE scpsl_user_id_bans SET active = 0 WHERE id = ',
`idbans`.`id`) AS `unban_id`,
CONCAT('UPDATE scpsl_ip_bans SET active = 0 WHERE id = ',
`ipbans`.`id`) AS `unban_ip`
FROM
(`scpsl_ip_bans` `ipbans`
LEFT JOIN (`scpsl_ban_link` `ban_link`
LEFT JOIN `scpsl_user_id_bans` `idbans` ON ((`idbans`.`id` = `ban_link`.`user_id_ban_id`))) ON ((`ban_link`.`ip_ban_id` = `ipbans`.`id`)))
ORDER BY `user_id_ban_id` DESC
Why is the view being mangled? How do I get my data to display like this, where a ban is always present in both (scpsl_user_id_bans AND scpsl_ban_link) OR (scpsl_ban_link OR scpsl_ip_bans)?
The query that I wanted contained THREE queries, UNIONed together:
SELECT
`scpsl_user_id_bans`.`id` as `scpsl_user_id_bans_id`,
`scpsl_user_id_bans`.`name` as `scpsl_user_id_bans_name`,
`user_id_ban_id`,
`ip_ban_id`,
null as `scpsl_ip_bans_id`,
null as `scpsl_ip_bans_name`
FROM
`scpsl_user_id_bans`
LEFT OUTER JOIN `scpsl_ban_link` ON (`scpsl_ban_link`.`user_id_ban_id` = `scpsl_user_id_bans`.`id`)
WHERE `ip_ban_id` IS NULL
UNION
SELECT
`scpsl_user_id_bans`.`id` as `scpsl_user_id_bans_id`,
`scpsl_user_id_bans`.`name` as `scpsl_user_id_bans_name`,
`user_id_ban_id`,
`ip_ban_id`,
`scpsl_ip_bans`.`id` as `scpsl_ip_bans_id`,
`scpsl_ip_bans`.`name` as `scpsl_ip_bans_name`
FROM
`scpsl_user_id_bans`
LEFT JOIN `scpsl_ban_link` ON (`scpsl_ban_link`.`user_id_ban_id` = `scpsl_user_id_bans`.`id`)
LEFT JOIN `scpsl_ip_bans` ON (`scpsl_ban_link`.`ip_ban_id` = `scpsl_ip_bans`.`id`)
UNION
SELECT
null as `scpsl_user_id_bans_id`,
null as `scpsl_user_id_bans_name`,
`user_id_ban_id`,
`ip_ban_id`,
`scpsl_ip_bans`.`id` as `scpsl_ip_bans_id`,
`scpsl_ip_bans`.`name` as `scpsl_ip_bans_name`
FROM
`scpsl_ip_bans`
LEFT OUTER JOIN `scpsl_ban_link` ON (`scpsl_ban_link`.`ip_ban_id` = `scpsl_ip_bans`.`id`)
WHERE `user_id_ban_id` IS NULL
;

How are certain columns of the main table synchronized with the child tables in MySQL?

I have 3 tables. I would like to synchronize TaskDetails's task_type,task_status table with Tasks table's task_type,task_status.
I try to set task_type,task_status as foreign keys, but it seems not right. how can I do it?
User table
+-------------+----------+-----------------------+-----------+
| employee_id | password | email | phone |
+-------------+----------+-----------------------+-----------+
| Jone | password | jone356#microsoft.com | 123456789 |
+-------------+----------+-----------------------+-----------+
Tasks table
+------------+-----------+-------------+-------------+
| task_id | task_type | task_status | employee_id |
+------------+-----------+-------------+-------------+
| task_3 | type2 | status1 | Jone |
| task_one_h | type1 | status1 | Jone |
| task_t_10 | type2 | status2 | Jone |
+------------+-----------+-------------+-------------+
TaskDetails table
+------------+-----------+-------------+---------+
| task_id | task_type | task_status | user_id |
+------------+-----------+-------------+---------+
| task_one_h | type1 | status1 | Jone |
| task_3 | type2 | status1 | Jone |
| task_t_10 | type2 | status2 | Jone |
+------------+-----------+-------------+---------+
Here's the MySQL code:
CREATE TABLE Users (
`employee_id` varchar(128),
`password` varchar(128),
`email` varchar(128),
`phone` varchar(128),
PRIMARY KEY (`employee_id`)
) /*! ENGINE=InnoDB */;
CREATE TABLE Tasks (
`task_id` varchar(128),
`task_type` enum('type1', 'type2') NOT NULL,
`task_status` enum('status1', 'status2', 'status3', 'status4', 'status5') DEFAULT 'status1',
`employee_id` varchar(128) NOT NULL,
PRIMARY KEY (`task_id`),
FOREIGN KEY(`employee_id`) REFERENCES Users(`employee_id`) ON DELETE CASCADE ON UPDATE CASCADE
) /*! ENGINE=InnoDB */;
CREATE TABLE TaskDetails (
`task_id` varchar(128),
`task_type` enum('type1', 'type2') NOT NULL,
`task_status` enum('status1', 'status2', 'status3', 'status4', 'status5') DEFAULT 'status1',
`user_id` varchar(128),
FOREIGN KEY(`task_id`) REFERENCES Tasks(`task_id`) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY(`user_id`) REFERENCES Users(`employee_id`) ON DELETE CASCADE ON UPDATE CASCADE
) /*! ENGINE=InnoDB */;
-- Insert Values
INSERT INTO Users VALUES ('Jone', 'password', 'jone356#microsoft.com', '123456789');
INSERT INTO Tasks VALUES ('task_one_h', 'type1', 'status1', 'Jone');
INSERT INTO Tasks VALUES ('task_3', 'type2', 'status1', 'Jone');
INSERT INTO Tasks VALUES ('task_t_10', 'type2', 'status2', 'Jone');
INSERT INTO TaskDetails VALUES ('task_one_h', 'type1', 'status1', 'Jone');
INSERT INTO TaskDetails VALUES ('task_3', 'type2', 'status1', 'Jone');
INSERT INTO TaskDetails VALUES ('task_t_10', 'type2', 'status2', 'Jone');
You use TRIGGER for that pupose
Here is an example for an INSERT TRIGGER and an UPDATE TRIGGER
When you enter Trigger in an sql tab you need DELIMITER in front and end of the Trigger like in the link
CREATE TABLE Users (
`employee_id` varchar(128),
`password` varchar(128),
`email` varchar(128),
`phone` varchar(128),
PRIMARY KEY (`employee_id`)
) /*! ENGINE=InnoDB */;
CREATE TABLE Tasks (
`task_id` varchar(128),
`task_type` enum('type1', 'type2') NOT NULL,
`task_status` enum('status1', 'status2', 'status3', 'status4', 'status5') DEFAULT 'status1',
`employee_id` varchar(128) NOT NULL,
PRIMARY KEY (`task_id`),
FOREIGN KEY(`employee_id`) REFERENCES Users(`employee_id`) ON DELETE CASCADE ON UPDATE CASCADE
) /*! ENGINE=InnoDB */;
CREATE TABLE TaskDetails (
`task_id` varchar(128),
`task_type` enum('type1', 'type2') NOT NULL,
`task_status` enum('status1', 'status2', 'status3', 'status4', 'status5') DEFAULT 'status1',
`user_id` varchar(128),
FOREIGN KEY(`task_id`) REFERENCES Tasks(`task_id`) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY(`user_id`) REFERENCES Users(`employee_id`) ON DELETE CASCADE ON UPDATE CASCADE
) /*! ENGINE=InnoDB */;
-- Insert Values
INSERT INTO Users VALUES ('Jone', 'password', 'jone356#microsoft.com', '123456789');
CREATE TRIGGER after_update_task
AFTER UPDATE ON Tasks
FOR EACH ROW
BEGIN
UPDATE TaskDetails SET `task_type` = NEW.`task_type`,`task_status` = NEw.`task_status` WHERE `task_id` = NEW.`task_id` ;
END ;
CREATE TRIGGER iafter_insert_task
AFTER INSERT ON Tasks
FOR EACH ROW
BEGIN
INSERT INTO TaskDetails VALUES (NEW.`task_id`,NEW.`task_type`, NEw.`task_status`, NEW.`employee_id`);
END ;
INSERT INTO Tasks VALUES ('task_t_h', 'type2', 'status1', 'Jone'),
('task3', 'type2', 'status1', 'Jone'),
('task_t_10', 'type2', 'status2', 'Jone');
SELECT * FROM TaskDetails;
task_id | task_type | task_status | user_id
:-------- | :-------- | :---------- | :------
task_t_h | type2 | status1 | Jone
task3 | type2 | status1 | Jone
task_t_10 | type2 | status2 | Jone
UPDATE Tasks SET `task_status` = 'status3' WHERE `task_id` = 'task_t_10';
SELECT * FROM TaskDetails;
task_id | task_type | task_status | user_id
:-------- | :-------- | :---------- | :------
task_t_h | type2 | status1 | Jone
task3 | type2 | status1 | Jone
task_t_10 | type2 | status3 | Jone
db<>fiddle here

Filtering mysql query results by column priority

I am trying to filter out rows from joined select query results that have the same 'id_user' column value but are of lower sorting priority.
Column 'sort1' has the highest priority and 'sort4' has the lowest priority.The name of the column is what sets the priority not the value inside.
Table users:
CREATE TABLE `users` (
`user_id` int(11) NOT NULL,
`name` varchar(30) COLLATE utf8_unicode_ci NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
INSERT INTO `users` (`user_id`, `name`) VALUES
(1, 'Some Name'),
(2, 'Some Other Name');
ALTER TABLE `users`
ADD PRIMARY KEY (`user_id`);
Table items:
CREATE TABLE `items` (
`id_user` int(11) NOT NULL,
`sort1` int(11) DEFAULT NULL,
`sort2` int(11) DEFAULT NULL,
`sort3` int(11) DEFAULT NULL,
`sort4` int(11) DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
INSERT INTO `items` (`id_user`, `sort1`, `sort2`, `sort3`, `sort4`) VALUES
(1, NULL, NULL, 39, NULL),
(2, NULL, NULL, NULL, 45),
(1, NULL, 34, NULL, NULL);
I tried the following query and get more results than i needed:
SELECT
users.name,
items.id_user,
items.sort1,
items.sort2,
items.sort3,
items.sort4
FROM
items
LEFT JOIN users ON items.id_user = users.user_id
WHERE
(
items.sort1 = '' OR items.sort1 IS NULL
) AND(
items.sort2 = 34 OR items.sort2 IS NULL
) AND(
items.sort3 = 39 OR items.sort3 IS NULL
) AND(
items.sort4 = 45 OR items.sort4 IS NULL
)
Actual result:
| Name | id_user | sort1 | sort2 | sort3 | sort4 |
+-----------------+---------+-------+-------+-------+-------|
| Some Name | 1 | NULL | NULL | 39 | NULL |
| Some Other Name | 2 | NULL | NULL | NULL | 45 |
| Some Name | 1 | NULL | 34 | NULL | NULL |
The first row is extra result because the third row has higher priority(sort2).
Expected result:
| Name | id_user | sort1 | sort2 | sort3 | sort4 |
+-----------------+---------+-------+-------+-------+-------|
| Some Name | 1 | NULL | 34 | NULL | NULL |
| Some Other Name | 2 | NULL | NULL | NULL | 45 |
I also tried GROUP BY id_user and ORDER BY priority columns but i didn't get the expected result
I changed your items table, added the id field
CREATE TABLE `items` (
`id` int(11) NOT NULL,
`id_user` int(11) NOT NULL,
`sort1` int(11) DEFAULT NULL,
`sort2` int(11) DEFAULT NULL,
`sort3` int(11) DEFAULT NULL,
`sort4` int(11) DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
ALTER TABLE `items` ADD PRIMARY KEY (`id`);
INSERT INTO `items` (`id`, `id_user`, `sort1`, `sort2`, `sort3`, `sort4`) VALUES
(1, 1, NULL, NULL, 39, NULL),
(2, 2, NULL, NULL, NULL, 45),
(3, 1, NULL, 34, NULL, NULL);
So you can try request like this, I changed FROM clause from items to users
SELECT
users.name,
items.id,
items.id_user,
items.sort1,
items.sort2,
items.sort3,
items.sort4
FROM
users
LEFT JOIN
items ON items.id = (
SELECT i.id FROM items AS i WHERE i.id_user = users.user_id ORDER BY -i.sort1 DESC, -i.sort2 DESC, -i.sort3 DESC, -i.sort4 DESC LIMIT 1
)
ORDER BY -items.sort1 DESC, -items.sort2 DESC, -items.sort3 DESC, -items.sort4 DESC
Result
| name | id | id_user | sort1 | sort2 | sort3 | sort4 |
|-----------------|----|---------|--------|--------|--------|--------|
| Some Name | 3 | 1 | (null) | 34 | (null) | (null) |
| Some Other Name | 2 | 2 | (null) | (null) | (null) | 45 |

Find the gaps for given date range in mysql, having multiple joins

I have four tables:
CREATE TABLE `A` (
`AID` bigint(20) NOT NULL AUTO_INCREMENT,
`Name` varchar(150) DEFAULT NULL,
PRIMARY KEY (`AID`)
);
CREATE TABLE `B` (
`BID` bigint(20) NOT NULL AUTO_INCREMENT,
`DtStart` datetime DEFAULT NULL,
`DtStop` datetime DEFAULT NULL,
`AID` bigint(20) DEFAULT NULL,
`CID` bigint(20) DEFAULT NULL,
PRIMARY KEY (`BID`)
);
CREATE TABLE `C` (
`CID` bigint(20) NOT NULL AUTO_INCREMENT,
`FLAGS` smallint(6) DEFAULT NULL,
PRIMARY KEY (`CID`)
);
CREATE TABLE `D` (
`DID` bigint(20) NOT NULL AUTO_INCREMENT,
`CID` bigint(20) DEFAULT NULL,
`Name` varchar(150) DEFAULT NULL,
PRIMARY KEY (`DID`)
);
I'm feeding data like this,
INSERT INTO A (Name) VALUES ("First");
INSERT INTO C (FLAGS) VALUES (1);
INSERT INTO B (DtStart, DtStop, AID, CID) VALUES ("2016-09-07", "2017-09-07", 1, 1);
INSERT INTO D (CID, Name) VALUES (1, "Alan");
INSERT INTO C (FLAGS) VALUES (2);
INSERT INTO B (DtStart, DtStop, AID, CID) VALUES ("2016-09-15", "2017-09-23", 1, 2);
INSERT INTO D (CID, Name) VALUES (2, "John");
When I hit the query:
SELECT
A.Name as Object, B.DtStart, B.DtStop, C.FLAGS, D.Name as User
FROM A
LEFT JOIN B ON B.AID=A.AID
LEFT JOIN C ON C.CID=B.CID
LEFT JOIN D ON D.CID=C.CID
WHERE "2017-09-01" <= B.DtStop AND "2017-10-01" > B.DtStart;
I get the result:
+--------+---------------------+---------------------+-------+------+
| Object | DtStart | DtStop | FLAGS | User |
+--------+---------------------+---------------------+-------+------+
| First | 2016-09-07 00:00:00 | 2017-09-07 00:00:00 | 1 | Alan |
| First | 2016-09-15 00:00:00 | 2017-09-23 00:00:00 | 2 | John |
+--------+---------------------+---------------------+-------+------+
2 rows in set (0.00 sec)
How could I find all the rows which also include gaps for the given date range?
In my example, I'm looking for the report (Sep 1, 2017 to Sep 30, 2017) so I want result like this:
+--------+---------------------+---------------------+-------+------+
| Object | DtStart | DtStop | FLAGS | User |
+--------+---------------------+---------------------+-------+------+
| First | 2016-09-07 00:00:00 | 2017-09-07 00:00:00 | 1 | Alan |
| First | 2017-09-08 00:00:00 | 2017-09-14 00:00:00 | NULL | NULL |
| First | 2016-09-15 00:00:00 | 2017-09-23 00:00:00 | 2 | John |
| First | 2016-09-24 00:00:00 | 2017-09-30 00:00:00 | NULL | NULL |
+--------+---------------------+---------------------+-------+------+
4 rows in set (0.00 sec)
This is just models of my original tables, I have multiple joins over 10 tables in my query

Generate Invoice & Tracking

Company will receive an invoice on the 1st and 16th every month. (It will run via Cron Job every 2 week. It scan through the order table and then add into 'invoice' table. Is there alternative?)
There are list of customers orders in the orders table and it also indicate which company it belong to (orders.company_id)
The invoice table calculate the total cost of the orders from orders table.
I am trying to figure it out how to design reasonable invoices tracking. Sometime company will have to send me the fees or sometime I send them the fees (invoice.amount)
I need to track the invoices with the following:
when the company have sent me the amount
when did I sent the amount to the company
how much amount has been received from the company
how much amount did I sent to the company
did I receive the full amount (if not, what do I need to update on the Db?)
invoice status (Invoice Sent, Cancelled, Amount Received, Amount Sent)
Here is the database design I have came up with:
company table
mysql> select * from company;
+----+-----------+
| id | name |
+----+-----------+
| 1 | Company A |
| 2 | Company B |
+----+-----------+
Customers can select a company from my website.
orders table
mysql> select * from orders;
+----+---------+------------+------------+---------------------+-----------+
| id | user_id | company_id | total_cost | order_date | status_id |
+----+---------+------------+------------+---------------------+-----------+
| 1 | 5 | 2 | 25.00 | 2012-02-03 23:30:24 | 1 |
| 2 | 7 | 2 | 30.00 | 2012-02-13 18:06:12 | 1 |
+----+---------+------------+------------+---------------------+-----------+
two customers have ordered the products from Company B (orders.company_id = 2). I know the orders fields is not enough, just simplified for you.
orders_products table
mysql> select * from orders_products;
+----+----------+------------+--------------+-------+
| id | order_id | product_id | product_name | cost |
+----+----------+------------+--------------+-------+
| 1 | 1 | 34 | Chair | 10.00 |
| 2 | 1 | 25 | TV | 10.00 |
| 3 | 1 | 27 | Desk | 2.50 |
| 4 | 1 | 36 | Laptop | 2.50 |
| 5 | 2 | 75 | PHP Book | 25.00 |
| 6 | 2 | 74 | MySQL Book | 5.00 |
+----+----------+------------+--------------+-------+
List of products what customers have ordered.
invoice table
mysql> select * from invoice;
+----+------------+------------+---------------------+--------+-----------+
| id | company_id | invoice_no | invoice_date | amount | status_id |
+----+------------+------------+---------------------+--------+-----------+
| 7 | 2 | 123 | 2012-02-16 23:59:59 | 55.00 | 1 |
+----+------------+------------+---------------------+--------+-----------+
This is where I am quite stuck on invoice tables design. I am not sure how it should be done. Invoices will be generated every 2 weeks. From the result example invoice.amount is 55.00 because it has been calculated from orders.company_id = 2 table
If the invoice.amount is -50.00 (minus), it mean company will need to send me the fees amount.
If the invoice.amount is 50.00, it mean I need to send the company the fees.
The status_id could be: (1)Invoice Sent, (2)Cancelled, (3)Completed
Do I need to add invoice_id field in the orders table? Update the orders.invoice_id field when row has been inserted into 'invoice' table.
invoice_payment table
mysql> select * from invoice_payment;
+----+------------+-----------------+-------------+---------------------+---------------------+
| id | invoice_id | amount_received | amount_sent | date_received | date_sent |
+----+------------+-----------------+-------------+---------------------+---------------------+
| 1 | 1 | 0.00 | 55.00 | 0000-00-00 00:00:00 | 2012-02-18 22:20:53 |
+----+------------+-----------------+-------------+---------------------+---------------------+
This is where I can track and update transaction.. the payment will be made via BACS.
Is this good tables design or what do I need to improve? What fields and tables I should add?
If the invoice has been generated and later I need to make the changes in orders_products or orders tables - should it recalculate the invoice.amount field? (I will be using PHP / MySQL).
SQL Dump:
CREATE TABLE IF NOT EXISTS `company` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(25) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=3 ;
INSERT INTO `company` (`id`, `name`) VALUES
(1, 'Company A'),
(2, 'Company B');
CREATE TABLE IF NOT EXISTS `invoice` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`company_id` int(11) NOT NULL,
`invoice_no` int(11) NOT NULL,
`invoice_date` datetime NOT NULL,
`amount` decimal(6,2) NOT NULL,
`status_id` tinyint(1) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=8 ;
INSERT INTO `invoice` (`id`, `company_id`, `invoice_no`, `invoice_date`, `amount`, `status_id`) VALUES
(7, 2, 123, '2012-02-16 23:59:59', '55.00', 1);
CREATE TABLE IF NOT EXISTS `invoice_payment` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`invoice_id` int(11) NOT NULL,
`amount_received` decimal(6,2) NOT NULL,
`amount_sent` decimal(6,2) NOT NULL,
`date_received` datetime NOT NULL,
`date_sent` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=2 ;
INSERT INTO `invoice_payment` (`id`, `invoice_id`, `amount_received`, `amount_sent`, `date_received`, `date_sent`) VALUES
(1, 1, '0.00', '55.00', '0000-00-00 00:00:00', '2012-02-18 22:20:53');
CREATE TABLE IF NOT EXISTS `orders` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`company_id` int(11) NOT NULL,
`total_cost` decimal(6,2) NOT NULL,
`order_date` datetime NOT NULL,
`status_id` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=3 ;
INSERT INTO `orders` (`id`, `user_id`, `company_id`, `total_cost`, `order_date`, `status_id`) VALUES
(1, 5, 2, '25.00', '2012-02-03 23:30:24', 1),
(2, 7, 2, '30.00', '2012-02-13 18:06:12', 1);
CREATE TABLE IF NOT EXISTS `orders_products` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`order_id` int(11) NOT NULL,
`product_id` int(11) NOT NULL,
`product_name` varchar(100) NOT NULL,
`cost` decimal(6,2) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=7 ;
INSERT INTO `orders_products` (`id`, `order_id`, `product_id`, `product_name`, `cost`) VALUES
(1, 1, 34, 'Chair', '10.00'),
(2, 1, 25, 'TV', '10.00'),
(3, 1, 27, 'Desk', '2.50'),
(4, 1, 36, 'Laptop', '2.50'),
(5, 2, 75, 'PHP Book', '25.00'),
(6, 2, 74, 'MySQL Book', '5.00');
Feel free you want to updates/add tables to Answer here.
Thanks
Have a look at my add-on for Gemini - SimplyFi. It will allow you to brand your invoices accordingly, can auto email them to customers when they generated, can log payments and send reminders for payments not received (statements) and has a full REST based API you can use to integrate into your system. Also may be able to benefit off the recurring billing it features.
Where you mention negative invoice amounts, those are effectively "Credit Notes" (from what I've understood from your post). Generally, you should not be changing invoices themselves after they have been issued to a client - if you need to make amendments to an amount (ie: add on, or subtract off) then you should be issuing a new invoice (for added amount), or a credit note, for subtracted amount.
Also, I would suggest you don;t send the customer money back if they are going to receive a new invoice in a few weeks time, simply keep track of their account balance, and only issue invoices or credit notes when necessary. Moving money around costs money, and you don't need to do it if it's not necessary. Just my 2 cents