difficult constraint for a mysql-table - mysql

I need a constraint for a mysql-table. The table has the fields 'id', 'ref', 'from' and 'to'. The constraint schould guarantee that there are no datasets with the same 'ref' and a time overlapping (fields 'from' and 'to').
In sql: The following statement should always return '0'.
select count(*)
from `TABLE` d1 inner join `TABLE` d2 on
d1.`ref` = d2.`ref` and d1.`id` <> d2.`id` and
d1.`to` >= d2.`from` and d1.`from`<=d2.`to`
Is there a way to handle this with constrains?

Now I have the following triggers. Thanks for your help!
DELIMITER $$
USE `devtestplandb`$$
CREATE
TRIGGER `db`.`trig1`
BEFORE INSERT ON `db`.`TABLE`
FOR EACH ROW
BEGIN
SET #CNT = (
select count(*)
from `TABLE` d
where d.`ref` = NEW.`ref` and
d.`to` >= NEW.`from` and
d.`from` <= NEW.`to`);
IF #CNT != 0 THEN
CALL error_001();
END IF;
END$$
CREATE
TRIGGER `db`.`trig2`
BEFORE UPDATE ON `db`.`TABLE`
FOR EACH ROW
BEGIN
SET #CNT = (
select count(*)
from `TABLE` d
where d.`ref` = NEW.`ref` and
d.`ID` <> NEW.`ID` and
d.`to` >= NEW.`from` and
d.`from` <= NEW.`to`);
IF #CNT != 0 THEN
CALL error_002();
END IF;
END$$

"Is there a way to handle this with constrains?"
Yes, SQL Standard 2011 supports this kind of scenarios in readable declarative manner:
unique constraint definition
<without overlap specification> ::=
<application time period name> WITHOUT OVERLAPS
In your example:
CREATE TABLE tab (
id INT AUTO_INCREMENT PRIMARY KEY,
ref VARCHAR(100),
from_date DATE,
end_date DATE,
PERIOD FOR ref_period(from_date, end_date),
UNIQUE (ref, ref_period WITHOUT OVERLAPS)
);
And sample inserts:
INSERT INTO tab(ref, from_date, end_date) VALUES ('a', '2020-01-01','2020-03-01');
-- OK
INSERT INTO tab(ref, from_date, end_date) VALUES ('a', '2020-03-01','2020-05-01');
-- OK
INSERT INTO tab(ref, from_date, end_date) VALUES ('a', '2020-04-01','2020-07-01')
-- Duplicate entry 'a-2020-07-01-2020-04-01' for key 'ref
SELECT * FROM tab;
db<>fiddle demo - Maria DB 10.5

Related

Custom auto-increment column

I am making a C# project in which I need help to generate and insert fields like below on MySQL database.
161013001
Where:
16 is Year,
10 is Month,
13 is day
and 001 is auto-increment numbers that reset each days.
Eg.
161012-001
161012-002
161012-002
161013-001
161013-002
161014-001
161014-002
161014-003
161014-004
161014-005
161015-001
please guide me how to make this that ID reset each day and start from 1 after every day.
Here both MySQL & SQL Server Implementation are added,
MySQL:
DROP TEMPORARY TABLE TempDate;
CREATE TEMPORARY TABLE TempDate(
Id VARCHAR(50),
Comments VARCHAR(50)
);
INSERT INTO TempDate(Id,Comments)
SELECT '190630-001', '1'
UNION
SELECT '190630-002', '2'
UNION
SELECT '190701-001', '1'
UNION
SELECT '190701-002', '2'
UNION
SELECT '190701-003', '3';
SET #v_ToDay = '';
SET #v_ToDay = (SELECT date_format(current_date(),'%y%m%d'));
SET #v_TotalByDay = '' ;
SET #v_TotalByDay =(
SELECT CONCAT('000',CAST(CASE WHEN COUNT(1) = 0 THEN 1 ELSE COUNT(1)+1 END as CHAR))
FROM TempDate
WHERE LEFT(Id,6) = #v_ToDay);
SELECT CONCAT(#v_ToDay, '-', CASE WHEN CHAR_LENGTH(RTRIM(#v_TotalByDay)) > 3 THEN RIGHT(#v_TotalByDay,3) ELSE #v_TotalByDay END) as NewIdColumn
SQL Server:
DECLARE #TempDate TABLE(
Id NVARCHAR(50),
Comments NVARCHAR(MAX)
)
INSERT INTO #TempDate(Id,Comments)
SELECT '190630-001', '1'
UNION
SELECT '190630-002', '2'
UNION
SELECT '190701-001', '1'
UNION
SELECT '190701-002', '2'
UNION
SELECT '190701-003', '3'
DECLARE #ToDay NVARCHAR(20) = (SELECT CONVERT(NVARCHAR(6), GETDATE(), 12))
DECLARE #TotalByDay NVARCHAR(20) = ''
SELECT #TotalByDay = '000' + CAST(CASE WHEN COUNT(1) = 0 THEN 1 ELSE COUNT(1)+1 END as NVARCHAR(20) )
FROM #TempDate
WHERE LEFT(Id,6) = #ToDay
SELECT #ToDay + '-' + CASE WHEN LEN(#TotalByDay) > 3 THEN RIGHT(#TotalByDay,3) ELSE #TotalByDay END as NewIdColumn
my situation is little more different then this question ....
i have a legacy database (it's not operational only use for reporting
purpose..)
in this DB transaction table was a auto increment tranx id column.
like 1, 2, 3 ...... but now our new report need meaningful tranx id
(yyMMDD<count of that day>) like this question. so actually i need a
select query to solve this problem.
with the help of #Khairul 's logic i solve my problem ....
i share my solution for other's help....
SELECT
trnx_id, account_id, pay_amount,counter_id, trantime, trandate
FROM(
SELECT
#id:=IF(#prev != t.trandate, #rownum:=1, #rownum:=#rownum+1)
,#prev:=t.trandate
,CONCAT(
SUBSTR(YEAR(t.`trandate`),3) -- year
,IF(LENGTH(MONTH(t.`trandate`))=1,CONCAT('0',MONTH(t.`trandate`)),MONTH(t.`trandate`)) -- month
,IF(LENGTH(DAY(t.`trandate`))=1,CONCAT('0',DAY(t.`trandate`)),DAY(t.`trandate`)) -- day
,IF(LENGTH(#id)=1,CONCAT('000',#id),IF(LENGTH(#id)=2,CONCAT('00',#id),IF(LENGTH(#id)=3,CONCAT('0',#id),#id))) -- count
) AS trnx_id
,t.*
FROM tax_info t ORDER BY t.`trandate`, t.`trantime`
) AS te
and my query result is ..........
After solving my problem i try to solve this question .......
for this i use a trigger for input auto increment custom column ...
my code is below , here my payment column has a custom tranx id ....
DELIMITER $$
DROP TRIGGER tranxidGeneration$$
CREATE
TRIGGER tranxidGeneration BEFORE INSERT ON payment
FOR EACH ROW BEGIN
DECLARE v_tranx_id_on INT;
-- count total row of that day
select IFNULL(COUNT(tranx_id),0)+1 Into v_tranx_id_on from payment where SUBSTR(tranx_id,1,6) = DATE_FORMAT(NOW(), "%y%m%d");
-- set custom generate id into tranx_id column
SET NEW.tranx_id := CONCAT(DATE_FORMAT(NOW(), "%y%m%d"),LPAD(v_tranx_id_on,4,0)) ;
END;
$$
DELIMITER ;

Create a trigger to insert the old data to a new table

Here is the table I created.
USE my_guitar_shop;
DROP TABLE IF EXISTS Products_Audit;
CREATE TABLE Products_Audit (
audit_id INT PRIMARY KEY,
category_id INT REFERENCES categories(category_id),
product_code VARCHAR ( 10 ) NOT NULL UNIQUE ,
product_name VARCHAR ( 255 ) NOT NULL,
list_price INT NOT NULL,
discount_percent INT NOT NULL DEFAULT 0.00 ,
date_updated DATETIME NULL);
Create a trigger named products_after_update. This trigger should insert the old data about the product into the Products_Audit table after the row is updated. Then, test this trigger with an appropriate UPDATE statement.
Here is the trigger I created but the data is not showing up in the Products_Audit table it is showing all null.
USE my_guitar_shop;
DROP TRIGGER IF EXISTS products_after_update;
DELIMITER $$
CREATE TRIGGER products_after_update
BEFORE UPDATE ON products
FOR EACH ROW
BEGIN
INSERT INTO products_audit (audit_id, product_id, category_id, product_code,
product_name, list_price, discount_percent, date_updated)
SELECT audit_id, products.product_id, products.category_id, products.product_code,
products.product_name,products.list_price, products.discount_percent, date_updated
FROM products JOIN products_audit
ON products_audit.audit_id = (SELECT audit_id FROM inserted);
END $$
DELIMITER ;
EDIT with the INSERT INTO
USE my_guitar_shop;
DROP TRIGGER IF EXISTS products_after_update;
DELIMITER $$
CREATE TRIGGER products_after_update
BEFORE UPDATE ON products
FOR EACH ROW
BEGIN
INSERT INTO products_audit (audit_id, product_id, category_id,product_code,
product_name, list_price, discount_percent, date_updated)
VALUES (OLD.audit_id, OLD.product_id, OLD.category_id, OLD.product_code,
OLD.product_name, OLD.list_price, OLD.discount_percent, OLD.date_updated)
DELIMITER ;
You are overcomplicating the insert. As mysql documentation on triggers says:
In an UPDATE trigger, you can use OLD.col_name to refer to the columns
of a row before it is updated and NEW.col_name to refer to the columns
of the row after it is updated.
Therfore, use the OLD.column_name format in the insert. Also, I would set the audit_id field to auto increment and leave it out of the insert:
INSERT INTO products_audit (product_id, category_id, product_code,
product_name, list_price, discount_percent, date_updated)
VALUES (OLD.product_id, OLD.category_id, OLD.product_code,
OLD.product_name, OLD.list_price, OLD.discount_percent, OLD.date_updated)
Here's an example of how I do it:
CREATE OR REPLACE EDITIONABLE TRIGGER E_TABLE_TRG
before insert or update or delete on e_table
for each row
declare
l_seq number;
begin
-- Get a unique sequence value to use as the primary key
select s_seq.nextval
into l_seq
from dual;
if inserting then
:new.date_opened := sysdate;
:new.last_txn_date := null;
:new.status := 'A';
end if;
if inserting then
insert into e_table_history
(
t_seq,
user_id,
date_opened,
last_txn_date,
status,
insert_update_delete,
insert_update_delete_date
)
values
(
l_seq,
:new.user_id,
:new.date_opened,
:new.last_txn_date,
:new.status,
'I',
sysdate
);
elsif updating then
insert into e_table_history
(
t_seq,
date_opened,
last_txn_date,
status,
insert_update_delete,
insert_update_delete_date
)
values
(
l_seq,
:new.date_opened,
:new.last_txn_date,
:new.status,
'U',
sysdate
);
else
insert into e_table_history
(
t_seq,
date_opened,
last_txn_date,
status,
insert_update_delete,
insert_update_delete_date
)
values
(
l_seq,
:old.date_opened,
:old.last_txn_date,
:old.status,
'D',
sysdate
);
end if;
end;
/
ALTER TRIGGER E_TABLE_TRG ENABLE;
/

Get primary key column value of last inserted record in mysql

I would like to capture the primary key value of column based on the last inserted record. Below is the table structure:
create table test
(
id varchar(100) not null primary key,
rmain varchar(100),
rpart bigint
);
Stored Procedure:
Delimiter $$
DROP PROCEDURE IF EXISTS insTest$$
Create Procedure insTest()
Begin
Set #rmain := (select trim(concat('DNB', DATE_FORMAT(CURRENT_DATE(), '%y'), DATE_FORMAT(CURRENT_DATE(), '%m'))));
IF ((trim(DATE_FORMAT(CURRENT_DATE(),'%m')) = 01) OR (trim(DATE_FORMAT(CURRENT_DATE(),'%m')) = 1)) THEN
Set #rpart = 1;
END IF;
IF ((trim(DATE_FORMAT(CURRENT_DATE(),'%m')) != 01) OR (trim(DATE_FORMAT(CURRENT_DATE(),'%m')) != 1)) THEN
Set #rpart := (select coalesce(max(rpart),0) from test) + 1;
END IF;
insert into Test (ID, rmain, rpart) values (concat(#rmain,#rpart),#rmain,#rpart);
End$$
DELIMITER ;
Please advice. I checked on last_insert_ID() but it works for primary key column with auto_increment setting only. Thanks in advance...
Why? What if you get select max(id) or if you get select id from tbl1 order by id desc limit 1?
See Transaction In MySQL. Also, set the transaction isolation level to READ COMMITTED
declare last_id INT;
START TRANSACTION;
INSERT INTO tbl1(id,col1,col2) values(1001,'test','test');
SELECT last_id = id FROM tbl1 ORDER BY id DESC LIMIT 1
COMMIT;
INSERT INTO test (a,b,c) values (1,2,3);
SELECT LAST_INSERT_ID();
this way you can access the last inserted id

Mysql triggers - capture each column change

I am trying to create trigger, that capture changes in database after update.
Table my_table I am watching:
Table my_table_log where I am writing changes to log them
And here is trigger so far:
CREATE TRIGGER `log_update`
AFTER UPDATE ON `my_table`
FOR EACH ROW
BEGIN
INSERT INTO
`my_table_log`
(
`id`,
`action`,
`column_name`,
`value_before`,
`value_after`,
`who`,
`ts`
)
VALUES
(
NEW.id,
'u',
'name',
OLD.name,
NEW.name,
user(),
NOW()
);
END
Question: How to log each change of column ?
Problem: I am curently watching only if column name changed in my_table. And I have another trigger for column age. How to set trigger for each row and each column that was changed?
Thank you for your suggestions/code/inspirations
You might use ifs for every column you'd like to watch in your trigger:
create trigger `log_update`
after update on `my_table`
for each row
begin
if (old.name <> new.name) then
insert into `my_table_log`
(
`id`,
`action`,
`column_name`,
`value_before`,
`value_after`,
`who`,
`ts`
)
values
(
new.id,
'u',
'name',
old.name,
new.name,
user(),
now()
);
end if;
if (old.age <> new.age) then
insert into `my_table_log`
(
`id`,
`action`,
`column_name`,
`value_before`,
`value_after`,
`who`,
`ts`
)
values
(
new.id,
'u',
'age',
old.age,
old.age,
user(),
now()
);
end if;
end
But better make the insert a stored procedure to avoid redudancy:
create procedure `log_insert`
(
id int(11),
`action` char,
column_name varchar(255),
value_before varchar(255),
value_after varchar(255)
)
begin
insert into `my_table_log`
(
`id`,
`action`,
`column_name`,
`value_before`,
`value_after`,
`who`,
`ts`
)
values
(
id,
`action`,
column_name,
value_before,
value_after,
user(),
now()
);
end
And call it in your trigger:
create trigger `log_update`
after update on `my_table`
for each row
begin
if (old.name <> new.name) then
call log_insert
(
new.id,
'u',
'name',
old.name,
new.name
);
end if;
if (old.age <> new.age) then
call log_insert
(
new.id,
'u',
'age',
old.age,
new.age
);
end if;
end
You can re-use the stored procedure to log events in your insert and delete triggers.
Make shure to use a composite primary key in your my_table_log to allow updates over several columns. I'd use at least:
primary key(id,column_name,who,ts).
Or use dedicated single column primary key to avoid varchars in your primary key for better performance.
One alternative is to just log the new values together with user() and now():
create table my_table_log
( id ...
, name ...
, age ...
, action ...
, who ...
, ts ... )
To determine what was changed, compare with the previous row.
It is however rather expensive to determine what a row looked like at a certain point in time, you will have to find the last version before that point in time. Another model that makes this a lot easier is to keep track of begin_ts and end_ts for each row:
create table my_table_log
( id ...
, name ...
, age ...
, action ...
, who ...
, begin_ts ...
, end_ts ...)
The insert trigger adds a copy of the row with begin_ts = now() and end_ts = null. The update trigger updates end_ts = now() where end_ts is null and inserts a row like the insert trigger. The delete trigger updates end_ts and might add a copy together with who deleted the row. Determining what a row looked like at ts t is just a matter of where t between start_ts and end_ts

MySQL Stored Procedure, One INSERT not functioning with different field name?

This is quite a strange problem, so first I will post the procedure:
DELIMITER $$
USE `blahblahblah`$$
DROP PROCEDURE IF EXISTS `duplicateTradeIn`$$
CREATE DEFINER=`blahblahblah` PROCEDURE `duplicateTradeIn`(duplicate_claim INT(12))
BEGIN
DECLARE valuation INT(12) DEFAULT 0;
DECLARE claim INT(12) DEFAULT 0;
DECLARE serialNumber VARCHAR(255);
DECLARE duplicates INT(12);
DECLARE i INT(12) DEFAULT 1;
DECLARE claimID INT(12);
SET #claimID = duplicate_claim;
SET #duplicates = (SELECT COUNT(*) FROM `duplicates`);
SET #i = 1;
WHILE #i <= #duplicates DO
SET #serialNumber = (SELECT `serial` FROM `duplicates` WHERE id = #i);
INSERT INTO valuations (`pID`,`boughtproduct`,`valuationType`,`valuationAmount`,`working`,`accessories`,`age`,`brand`,`qty`,`created`,`valuationStatus`)
SELECT `pID`,`boughtproduct`,`valuationType`,`valuationAmount`,`working`,`accessories`,`age`,`brand`,`qty`,`created`,`valuationStatus`
FROM valuations WHERE vID = (SELECT vID FROM claims WHERE cID = #claimID);
SET #valuation = (SELECT LAST_INSERT_ID());
INSERT INTO claims (`vID`, `pID`, `email`, `title`, `firstname`, `lastname`, `customerType`, `company`, `position`, `address1`,`address2`,`town`,`county`,`postCode`,`telephone`,`mobile`,`emailBusiness`,`emailConsumer`,`contactPost`,`contactTelephone`,`contactMobile`,`contactEmail`,`invoiceNum`,`invoiceDate`,`invoiceInc`,`reseller`,`dateOfOrder`,`heardAbout`,`salesPerson`,`method`,`cleanseCert`,`blanccoCert`,`created`,`modified`,`noInvEmailDated`,`lateRejEmailSent`,`invAddressEmailSent`,`revalueEmailSent`,`signed`,`dated`,`sessionID`,`received`,`receiptSent`,`processedDate`,`validatedDate`,`rejectedDate`,`rejectReason`,`notes`,`claimStatus`,`origin`,`invoiceexported`)
SELECT #valuation, `pID`, `email`, `title`, `firstname`, `lastname`, `customerType`, `company`, `position`, `address1`,`address2`,`town`,`county`,`postCode`,`telephone`,`mobile`,`emailBusiness`,`emailConsumer`,`contactPost`,`contactTelephone`,`contactMobile`,`contactEmail`,`invoiceNum`,`invoiceDate`,`invoiceInc`,`reseller`,`dateOfOrder`,`heardAbout`,`salesPerson`,`method`,`cleanseCert`,`blanccoCert`,`created`,`modified`,`noInvEmailDated`,`lateRejEmailSent`,`invAddressEmailSent`,`revalueEmailSent`,`signed`,`dated`,`sessionID`,`received`,`receiptSent`,`processedDate`,`validatedDate`,`rejectedDate`,`rejectReason`,`notes`,`claimStatus`,`origin`,`invoiceexported`
FROM claims WHERE cID = #claimID;
SET #claim = (SELECT LAST_INSERT_ID());
INSERT INTO documents (`claimid`,`serial`,`filename`,`filenameOrig`,`filenameTemp`,`filetype`,`filesize`)
SELECT #claim,`serial`,`filename`,`filenameOrig`,`filenameTemp`,`filetype`,`filesize`
FROM documents WHERE claimid = #claimID;
INSERT INTO redemptions (`cID`,`pID`,`prID`,`bundleNo`,`serialNum`,`price`,`cashback`,`created`,`modified`,`claimStatus`)
SELECT #claim,`pID`,`prID`,`bundleNo`,#serialNumber,`price`,`cashback`,`created`,`modified`,`claimStatus`
FROM redemptions WHERE cID = #claimID;
INSERT INTO tradeins (`vID`,`cID`,`valuationType`,`valuationAmount`,`working`,`accessories`,`age`,`created`,`brand`,`claimStatus`)
SELECT #valuation,#claim,`valuationType`,`valuationAmount`,`working`,`accessories`,`age`,`created`,`brand`,`claimStatus`
FROM tradeins WHERE cID = #claimID;
SET #i = (#i + 1);
END WHILE;
END$$
DELIMITER ;
EDIT: Sorry!
The problem is when it tries to insert into documents (So refer to the documents table insert). If I rename the column (claimid) in the WHERE clause and INSERT to cID (by altering the table and query) it works, but otherwise it refuses to insert the new rows when it is named claimid not cID. If you have any ideas or insight into this it would be much appreciated.
To try and clarify:
Documents table with field and query as cID not claimid works.
Documents table with field and query as claimid does not insert rows.
All of the other queries seem to work fine and it only occurred to me to try cID in the documents table as that was the only difference.
Again sorry for the vagueness before and I hope the question is now clearer.
Thanks!
Just to clear this up. It is as I stated and maybe a bug in mySQL? If there is a table with a field where the ID name differs. In this example cID and claimid then it will not recognise the second declared field claimid and not perform the query.
In order for this to work I had to revise the documents table from claimID to cID.