Which update is faster using join or sequential? - mysql

This question is in sequence of my previous Question required update same table on deletion a row.
I could write two solutions using Stored Procedure instead of trigger or nested-query .
Both use a helper function my_signal(msg).
A Stored Procedure to delete employee from Employee Table.
Fist Solution: use UPDATE rows in table, without join operation:
CREATE PROCEDURE delete_employee(IN dssn varchar(64))
BEGIN
DECLARE empDesignation varchar(128);
DECLARE empSsn varchar(64);
DECLARE empMssn varchar(64);
SELECT SSN, designation, MSSN INTO empSsn, empDesignation, empMssn
FROM Employee
WHERE SSN = dssn;
IF (empSsn IS NOT NULL) THEN
CASE
WHEN empDesignation = 'OWNER' THEN
CALL my_signal('Error: OWNER can not deleted!');
WHEN empDesignation = 'WORKER' THEN
DELETE FROM Employee WHERE SSN = empSsn;
WHEN empDesignation = 'BOSS' THEN
BEGIN
UPDATE Employee
SET MSSN = empMssn
WHERE MSSN = empSsn;
DELETE FROM Employee WHERE SSN = empSsn;
END;
END CASE;
ELSE
CALL my_signal('Error: Not a valid row!');
END IF;
END//
Second solution: as I was suggested in my previous question using INNER JOIN
CREATE PROCEDURE delete_employee(IN dssn varchar(64))
BEGIN
DECLARE empDesignation varchar(128);
DECLARE empSsn varchar(64);
DECLARE empMssn varchar(64);
SELECT SSN, designation, MSSN INTO empSsn, empDesignation, empMssn
FROM Employee
WHERE SSN = dssn;
IF (empSsn IS NOT NULL) THEN
IF (empDesignation = 'OWNER') THEN
CALL my_signal('Error: OWNER can not deleted!');
END IF;
UPDATE `Employee` A INNER JOIN `Employee` B ON A.SSN= B.MSSN
SET B.MSSN = A.MSSN WHERE A.SSN = empSsn;
DELETE FROM `Employee` WHERE SSN = empSsn;
ELSE
CALL my_signal('Error: Not a valid row!');
END IF;
END//
I read here that using join is efficient for Efficient SELECT. But my problem includes only one table and I feel my solution(first) is much efficient than second because join will consume memory comparatively.
Please suggest me which is better and efficient, if Employee table is sufficiently large. Which is better for me? Reason
EDIT: I checked for small table consist of 7 rows only, and both solution take same time.
mysql> CALL delete_employee(4);
Query OK, 1 row affected (0.09 sec)
I know SQL function behaves non-deterministically because table heuristics. Which choice is better? Either if you have some idea How query can be further optimised.

After a while of thinking I am almost sure it doesn't make a difference, first solution may be even slightly slower, but in a not measurable dimension.
First intention would be, that the first solution is faster because you first fetch data by id and update only if nessesary.
But MySQL internally does nothing else in the UPDATE .. JOIN statement, just internally and as a result of this probably more efficiently as well.
Your first solution doesn't catch a default case - what happens if I neither get WORKER or BOSS?
Also your execution time (0.09s) is extremely high, which can't be explained with what I know about your database so far.
Did you set any index?
EDIT:
After looking at the table structure you've posted here
I have some improvement offers for the structure itself.
1. Use type int when you are storing integer values. The database can handle integer way more efficient
2. Why generate SSN by yourself? Using auto_increment on the PRIMARY KEY is much simpler to handle and saves you a lot of work when you add new employees
ALTER TABLE `Employee`
CHANGE `SSN` `SSN` int(11) NOT NULL AUTO_INCREMENT ,
CHANGE `MSSN` `MSSN` int(11) DEFAULT NULL,
ADD KEY `KEY_Employee_MSSN` ( `MSSN` );
3. Do you use the name for lookups? If so, it needs to be unique as well
ALTER TABLE `Employee`
ADD UNIQUE KEY `UNI_KEY_Employee` ( `name` );
4. Do you have a fixed range of designations? enum forces the input to be one of the defined values
ALTER TABLE `Employee`
CHANGE `designation` `designation` ENUM( 'BOSS', 'WORKER' ) NOT NULL DEFAULT 'WORKER',
ADD KEY `KEY_Employee_designation` ( `designation` );
Final structure
mysql> EXPLAIN `Employee`;
+-------------+-----------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+-----------------------+------+-----+---------+----------------+
| SSN | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(64) | YES | UNI | NULL | |
| designation | enum('BOSS','WORKER') | NO | MUL | WORKER | |
| MSSN | int(11) | YES | MUL | NULL | |
+-------------+-----------------------+------+-----+---------+----------------+
4 rows in set (0.00 sec)

Related

CASCADE INSERT from Table A if key doesn't exist on Table B

So I have two tables which look like this:
products_tbl (product_id, product_manufacturer, product_name)
manufacturers_tbl (manufacturer_id, manufacturer_name)
Now in case a product_manufacturer is inserted into products_tbl which does NOT yet exist in manufacturers_tbl, I would like to automatically insert the respective key into manufacturers_tbl.
I already found quite a lot of old posts on the general topic of "cascading" inserts, but none of them wanted to integrate such logic.
Is it possible with mysql only, or would I have to write some more backend/php logic to accomplish this?
EDIT:
If the above is not possible, I would like to know how to add a FOREIGN KEY CONSTRAINT which throws an error if a product_manufacturer in products_tbl is added which has no equivalent manufacturer_name in manufacturers_tbl.
Easy enough with a trigger but also easy to acquire junk.
drop table if exists products_tbl,manufacturers_tbl;
create table products_tbl (product_id int, product_manufacturer varchar(10), product_name varchar(10));
create table manufacturers_tbl (manufacturer_id int, manufacturer_name varchar(10));
drop trigger if exists t;
delimiter $$
create trigger t after insert on products_tbl
for each row
begin
if not exists(select 1 from manufacturers_tbl where manufacturer_name = new.product_manufacturer) then
insert into manufacturers_tbl(manufacturer_name) values (new.product_manufacturer);
end if;
end $$
delimiter ;
insert into products_tbl values
(1,'microsoft','a'),
(2,'msoft','b'),
(3,'mcrosoft','c'),
(4,'microsoft','d');
select * from manufacturers_tbl;
+-----------------+-------------------+
| manufacturer_id | manufacturer_name |
+-----------------+-------------------+
| NULL | microsoft |
| NULL | msoft |
| NULL | mcrosoft |
+-----------------+-------------------+
3 rows in set (0.00 sec)

Select default value of column MySQL

I will not go deep in details.
I have Java program which is creating tables in Database. The thing is that when table is created one of the fields get default value. So when I am adding elements to this field I want to select default value. Now loading of all element's ID is:
SELECT distinct(codeKind) FROM [table_name];
I want to be something like this:
SELECT [default value of column codeKind] from [table_name];
I checked many of answer of other similiar questions but none of them is OK.
Thanks.
I also find solution:
select COLUMN_DEFAULT
from INFORMATION_SCHEMA.COLUMNS
where TABLE_SCHEMA='my_db' and TABLE_NAME='my_table' and COLUMN_NAME='my_column'
Maybe this
drop table if exists t;
create table t
(id int auto_increment primary key,size int, sizecaption1 int default 10, sizecaption2 int, sizecaption3 int);
insert into t (id,size) values
(1,22);
insert into t values
(2,22,22,22,22),
(3,22,22,30,20),
(4,22,1,2,3);
select * from t where sizecaption1 =
(
select column_default from information_schema.columns
where table_name = 't' and table_schema = 'sandbox' and column_default is not null
) ;
+----+------+--------------+--------------+--------------+
| id | size | sizecaption1 | sizecaption2 | sizecaption3 |
+----+------+--------------+--------------+--------------+
| 1 | 22 | 10 | NULL | NULL |
+----+------+--------------+--------------+--------------+
1 row in set (0.02 sec)
use coalesce
select coalesce(codeKind,<def_value>)
Alternatively you can set a fixed value for table's creation DDL such as
create table my_table ( my_column default 0, col2 int .... ) , if you leave the column value as blank in the insert statement, then the value is automatically populated as zero.

selecting a column from multiple tables in mysql

TABLE 1
+----+-------+-------+-------+
| uid | color | brand | model |
+----+-------+-------+-------+
| 10 | 1 | 2 | 1 |
+----+-------+-------+-------+
TABLE 2
+----+-------+-------+-------+
| uid | quantity |model |color|
+----+-------+-------+-------+
| 25 | 2 | 2 | 1 |
+----+-------+-------+-------+
I have many tables like this where the uid column is present in every table.I have a value in a variable, say var1=25. I want to check whether var1 value matches with any of the uid value of any table.If it matches I want to print the table name. Can anyone help me with this?
I tried doing this and I found
SELECT `COLUMN_NAME`
FROM `INFORMATION_SCHEMA`.`COLUMNS`
WHERE `TABLE_SCHEMA`='yourdatabasename'
AND `TABLE_NAME`='yourtablename';
But this is not giving what I want since I want to select all the tables in a database irrespective of the table name.If in future any table is added then it should also get selected.
At first, information_schema table doesn't have specific tuple data.
I suggest you to consider different design.
A. Make a meta table and use triggers(attached to base tables) to maintain meta table.
CREATE TABLE meta_table (
id INT AUTO_INCREMENT,
uid INT,
table_name VARCHAR(50)
);
# When you need to add new table (table 3)
CREATE TABLE table_3 (
uid INT,
field1 INT,
field2 INT,
field3
);
DELIMITER $$
CREATE TRIGGER table_3_insert
AFTER INSERT ON table_3
FOR EACH ROW
BEGIN
INSERT INTO meta_table (uid, table_name)
VALUE (NEW.uid, "table_3");
END$$
DELIMITER ;
# If data in `table_3` might be changed or deleted,
# then create trigger for `delete` and `update`
B. Use only one table with unstructured field and parse data field in your application
CREATE TABLE table (
uid INT,
table_type INT,
data VARCHAR(255)
);
INSERT INTO table (10, 1, '{"color":1,"brand":2,"model":1}');
INSERT INTO table (10, 2, '{"quantity":2,"model":2,"color":1}');
As you mentioned "any table can be added" often, I strongly recommend B solution. It is not good design that changing schema(creating table) often.

How can I create a query that shows children of an item recursively [duplicate]

This question already has answers here:
Recursive MySQL Query with relational innoDB
(2 answers)
Closed 9 years ago.
I have a MySQL table which has the following format:
CREATE TABLE IF NOT EXISTS `Company` (
`CompanyId` INT UNSIGNED NOT NULL AUTO_INCREMENT ,
`Name` VARCHAR(45) NULL ,
`Address` VARCHAR(45) NULL ,
`ParentCompanyId` INT UNSIGNED NULL ,
PRIMARY KEY (`CompanyId`) ,
INDEX `fk_Company_Company_idx` (`ParentCompanyId` ASC) ,
CONSTRAINT `fk_Company_Company`
FOREIGN KEY (`ParentCompanyId` )
REFERENCES `Company` (`CompanyId` )
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB;
So to clarify, I have companies which can have a parent company. This could result in the following example table contents:
CompanyId Name Address ParentCompanyId
1 Foo Somestreet 3 NULL
2 Bar Somelane 4 1
3 McD Someway 1337 1
4 KFC Somewhere 12 2
5 Pub Someplace 2 4
Now comes my question.
I want to retrieve all children of CompanyId 2 recursive. So the following result set should appear:
CompanyId Name Address ParentCompanyId
4 KFC Somewhere 12 2
5 Pub Someplace 2 4
I thought of using the With ... AS ... statement, but it is not supported by MySQL. Another solution I thought of was using a procedure or function which returns a result set and union it with the recursive call of that function. But MySQL does only support column types as return values.
The last possible solution I thought about was to create a table with two fields: CompanyId and HasChildId. I could then write a procedure that loops recursively through the companies and fills the table with all recursive children by a companyid. In this case I could write a query which joins this table:
SELECT CompanyId, Name, Address
FROM Company C -- The child
INNER JOIN CompanyChildMappingTable M
ON M.CompanyId = C.HasChildId
INNER JOIN Company P -- The parent
ON P.CompanyId = M.CompanyId
WHERE P.CompanyId = 2;
This option should be a fast one if i'd call the procedure every 24 hours and fill the table on the fly when new records are inserted into Company. But this could be very tricky and I should do this by writing triggers on the Company table.
I would like to hear your advice.
Solution: I've built the following procedure to fill my table (now it just returns the SELECT result).
DELIMITER $$
DROP PROCEDURE IF EXISTS CompanyFillWithSubCompaniesByCompanyId$$
CREATE PROCEDURE CompanyFillWithSubCompaniesByCompanyId(IN V_CompanyId BIGINT UNSIGNED, IN V_TableName VARCHAR(100))
BEGIN
DECLARE V_CONCAT_IDS VARCHAR(9999) DEFAULT '';
DECLARE V_CURRENT_CONCAT VARCHAR(9999) DEFAULT '';
SET V_CONCAT_IDS = (SELECT GROUP_CONCAT(CompanyId) FROM Company WHERE V_CompanyId IS NULL OR ParentCompanyId = V_CompanyId);
SET V_CURRENT_CONCAT = V_CONCAT_IDS;
IF V_CompanyId IS NOT NULL THEN
companyLoop: LOOP
IF V_CURRENT_CONCAT IS NULL THEN
LEAVE companyLoop;
END IF;
SET V_CURRENT_CONCAT = (SELECT GROUP_CONCAT(CompanyId) FROM Company WHERE FIND_IN_SET(ParentCompanyId, V_CURRENT_CONCAT));
SET V_CONCAT_IDS = CONCAT_WS(',', V_CONCAT_IDS, V_CURRENT_CONCAT);
END LOOP;
END IF;
SELECT * FROM Company WHERE FIND_IN_SET(CompanyId, V_CONCAT_IDS);
END$$
Refer:
Recursive MySQL Query with relational innoDB
AND
How to find all child rows in MySQL?
It shall give a idea of how such a data structure, can be dealt in MYSQL
One quickest way to search is, use company id values in power of 2. companyId = parentId * 2 then query database like, select * from company where ((CompanyId % $parentId) == 0 )
I tried this code, it's quick but problem is it creates child's id as parentId * 2 and if depth of child goes deep, int, float may go out of range. So, I re-created my whole program.

Data change history with audit tables: Grouping changes

Lets say I want to store users and groups in a MySQL database. They have a relation n:m. To keep track of all changes each table has an audit table user_journal, group_journal and user_group_journal. MySQL triggers copy the current record to the journal table on each INSERT or UPDATE (DELETES are not supported, because I would need the information which application user has deleted the record--so there is a flag active that will be set to 0 instead of a deletion).
My question/problem is: Assuming I am adding 10 users into a group at once. When I'm later clicking through the history of that group in the user interface of the application I want to see the adding of those 10 users as one step and not as 10 independent steps. Is there a good solution to group such changes together? Maybe it is possible to have a counter that is incremented each time the trigger is ... triggered? I have never worked with triggers.
The best solution would be to put together all changes made within a transaction. So when the user updates the name of the group and adds 10 users in one step (one form controller call) this would be one step in the history. Maybe it is possible to define a random hash or increment a global counter each time a transaction is started and access this value in the trigger?
I don't want to make the table design more complex than having one journal table for each "real" table. I don't want to add a transaction hash into each database table (meaning the "real" tables, not the audit tables--there it would be okay of course). Also I would like to have a solution in the database--not in the application.
I played a bit around and now I found a very good solution:
The Database setup
# First of all I create the database and the basic table:
DROP DATABASE `mytest`;
CREATE DATABASE `mytest`;
USE `mytest`;
CREATE TABLE `test` (
`id` INT PRIMARY KEY AUTO_INCREMENT,
`something` VARCHAR(255) NOT NULL
);
# Then I add an audit table to the database:
CREATE TABLE `audit_trail_test` (
`_id` INT PRIMARY KEY AUTO_INCREMENT,
`_revision_id` VARCHAR(255) NOT NULL,
`id` INT NOT NULL,
`something` VARCHAR(255) NOT NULL
);
# I added a field _revision_id to it. This is
# the ID that groups together all changes a
# user made within a request of that web
# application (written in PHP). So we need a
# third table to store the time and the user
# that made the changes of that revision:
CREATE TABLE `audit_trail_revisions` (
`id` INT PRIMARY KEY AUTO_INCREMENT,
`user_id` INT NOT NULL,
`time` DATETIME NOT NULL
);
# Now we need a procedure that creates a
# record in the revisions table each time an
# insert or update trigger will be called.
DELIMITER $$
CREATE PROCEDURE create_revision_record()
BEGIN
IF #revision_id IS NULL THEN
INSERT INTO `audit_trail_revisions`
(user_id, `time`)
VALUES
(#user_id, #time);
SET #revision_id = LAST_INSERT_ID();
END IF;
END;
# It checks if a user defined variable
# #revision_id is set and if not it creates
# the row and stores the generated ID (auto
# increment) into that variable.
#
# Next I wrote the two triggers:
CREATE TRIGGER `test_insert` AFTER INSERT ON `test`
FOR EACH ROW BEGIN
CALL create_revision_record();
INSERT INTO `audit_trail_test`
(
id,
something,
_revision_id
)
VALUES
(
NEW.id,
NEW.something,
#revision_id
);
END;
$$
CREATE TRIGGER `test_update` AFTER UPDATE ON `test`
FOR EACH ROW BEGIN
CALL create_revision_record();
INSERT INTO `audit_trail_test`
(
id,
something,
_revision_id
)
VALUES
(
NEW.id,
NEW.something,
#revision_id
);
END;
$$
The application code (PHP)
$iUserId = 42;
$Database = new \mysqli('localhost', 'root', 'root', 'mytest');
if (!$Database->query('SET #user_id = ' . $iUserId . ', #time = NOW()'))
die($Database->error);
if (!$Database->query('INSERT INTO `test` VALUES (NULL, "foo")'))
die($Database->error);
if (!$Database->query('UPDATE `test` SET `something` = "bar"'))
die($Database->error);
// To simulate a second request we close the connection,
// sleep 2 seconds and create a second connection.
$Database->close();
sleep(2);
$Database = new \mysqli('localhost', 'root', 'root', 'mytest');
if (!$Database->query('SET #user_id = ' . $iUserId . ', #time = NOW()'))
die($Database->error);
if (!$Database->query('UPDATE `test` SET `something` = "baz"'))
die($Database->error);
And … the result
mysql> select * from test;
+----+-----------+
| id | something |
+----+-----------+
| 1 | baz |
+----+-----------+
1 row in set (0.00 sec)
mysql> select * from audit_trail_test;
+-----+--------------+----+-----------+
| _id | _revision_id | id | something |
+-----+--------------+----+-----------+
| 1 | 1 | 1 | foo |
| 2 | 1 | 1 | bar |
| 3 | 2 | 1 | baz |
+-----+--------------+----+-----------+
3 rows in set (0.00 sec)
mysql> select * from audit_trail_revisions;
+----+---------+---------------------+
| id | user_id | time |
+----+---------+---------------------+
| 1 | 42 | 2013-02-03 17:13:20 |
| 2 | 42 | 2013-02-03 17:13:22 |
+----+---------+---------------------+
2 rows in set (0.00 sec)
Please let me know if there is a point I missed. I will have to add an action column to the audit tables to be able to record deletions.
Assuming you're rate of adding a batch of users to a group is less than once a second....
I would suggest simply adding a column of type timestamp named something like added_timestamp to the user_group and user_group_journal. DO NOT MAKE THIS AN AUTO UPDATE TIMESTAMP OR DEFAULT IT TO CURRENT_TIMESTAMP, instead, in your code when you insert by batch into the user_group, calculate the current date and time, then manually set this for all the new user_group record.
You may need to tweak your setup to add the field to be copied the rest of the new user_group record into the user_group_journal table.
Then when you could create a query/view that groups on a group_id and the new added_timestamp column.
If more fidelity is needed then 1 second you could use a string column and populate it with a string representation of a more granular time (which you'd need to generate however the libraries your language of use allows).