SQL lookup like function - mysql

I have 5 columns in SQL table
Id | activity | start date | Finish date | Predecessor activity
1 | shuttering | 1/1/2019 | 3/1/2019 |
2 | concrete | | 6/1/2019 | 1
The above is an example of my table
I need to plan my activities and all the activities are interlinked
The ID 2 succeeds activity 1
So start date of ID 2 is finish date of 1
I need all the start dates to be auto generated except first one based on predecessor activity

Based on this table
CREATE TABLE Schedule1
(`Id` int, `activity` varchar(10), `start_date` datetime
, `Finish_date` datetime, `Predecessor_activity` int)
ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO Schedule1
(`Id`, `activity`, `start_date`, `Finish_date`, `Predecessor_activity`)
VALUES
(1, 'shuttering', '2019-01-01', '2019-03-01 01:00:00', NULL),
(2, 'concrete', NULL, '2019-06-01 02:00:00', 1),
(3, 'concrete', NULL, '2019-08-01 02:00:00', 2),
(4, 'concrete', NULL, '2019-10-01 02:00:00', 3);
This sql statemenbt
UPDATE Schedule1 t2
Left JOIN Schedule1 t1 ON t2.Predecessor_activity = t1.Id
SET t2.start_date = IFNULL(t2.start_date,t1.Finish_date);
gets you
Id activity start_date Finish_date Predecessor_activity
1 shuttering 2019-01-01 00:00:00 2019-03-01 01:00:00
2 concrete 2019-03-01 01:00:00 2019-06-01 02:00:00 1
3 concrete 2019-06-01 02:00:00 2019-08-01 02:00:00 2
4 concrete 2019-08-01 02:00:00 2019-10-01 02:00:00 3
for the use of trigger like insert or update
like this
#DELIMITER //
DROP TRIGGER IF EXISTS my_update_trigger;
CREATE DEFINER=root#localhost TRIGGER my_update_trigger
AFTER update ON `schedule1`
FOR EACH ROW
BEGIN
-- Call the common procedure ran if there is an INSERT or UPDATE on `table`
-- NEW.id is an example parameter passed to the procedure but is not required
-- if you do not need to pass anything to your procedure.
CALL procedure_to_run_schedule()//
END//
DELIMITER ;
We nee a new Table, because updating the same table isn't allowed for triggers.
So we create a new table that is identical to our original
DROP TABLE IF EXISTS newschedul;
CREATE TABLE newschedule SElect * From Schedule1;
And then we add a new stored procedure.
What it does, After you inserted or updated your original table, it copies all data to the new table and uses above statement to make the new schedule.
so you can access the actual schedule by selecting rows from the. Updfating and isnert you only make in the schedule to avoid the problmes you described
DELIMITER //
DROP PROCEDURE IF EXISTS procedure_to_run_schedule//
CREATE DEFINER=root#localhost PROCEDURE procedure_to_run_schedule()
READS SQL DATA
BEGIN
SET SQL_SAFE_UPDATES = 0;
DELETE FROM newschedule;
INSERT INTO newschedule
SELECT * FROM Schedule1;
UPDATE newschedule t2
Left JOIN newschedule t1 ON t2.Predecessor_activity = t1.Id
SET t2.start_date = IFNULL(t2.start_date,t1.Finish_date) ;
SET SQL_SAFE_UPDATES = 1;
END//
DELIMITER

Related

How can I store the direction of a bus route in mysql database?

I have 3 table: Route, Bus_stop, Route-Bus_stop
Route: id, weekday, weekend
Bus_stop: id, name
Route-Bus_stop: primary key(order, time), foreign key: idRoute, idBus_stop
Insert into Route(id,weekday,weekend) values(1,1,1);
Insert into Route(id,weekday,weekend) values(2,1,1);
Insert into Bus_stop(id,name) values(1,'A');
Insert into Bus_stop(id,name) values(2,'B');
Insert into Bus_stop(id,name) values(3,'C');
Insert into Route-Bus_stop(idRoute,idBus_stop,routeo_rder,stoptime) values(1,1,0,7:00);
Insert into Route-Bus_stop(idRoute,idBus_stop,route_order,stoptime) values(1,2,1,7:10);
Insert into Route-Bus_stop(idRoute,idBus_stop,route_order,stoptime) values(1,3,2,7:30);
Insert into Route-Bus_stop(idRoute,idBus_stop,route_order,stoptime) values(2,3,0,15:00);
Insert into Route-Bus_stop(idRoute,idBus_stop,route_order,stoptime) values(2,2,1,15:10);
Insert into Route-Bus_stop(idRoute,idBus_stop,route_order,stoptime) values(2,1,2,15:20);
-- this is only an example, my database is bigger.
One of the route goes A -> B -> C, the other C->B->A
If the user wants to go A to C, how can I show only one of the routes with all of the bus stations to the destination?
I've write this query, but this shows both of the routes.
select *idRoute from Route-Bus_stop where idBus_stop=1 or idBus_stop=3 order by time; // A -> C
This will show route A->C and C-> A. But I want only the A->C
First don_'t rßuse reserved words like ORDER or TIME as column names, so you spre the need to use backticks all the tiome
Also Route-Bus_stop is also not valid without Backticks.
For your query you keed a selfjoin to get starting point and end point
CREATE TABLE Route_Bus_stop (
idRoute int
,idBus_stop int
,`order` int
,`time` varchar(10))
Insert into Route_Bus_stop(idRoute,idBus_stop,`order`,`time`) values(1,1,0,'7:00');
Insert into Route_Bus_stop(idRoute,idBus_stop,`order`,`time`) values(1,2,1,'7:10');
Insert into Route_Bus_stop(idRoute,idBus_stop,`order`,`time`) values(1,3,2,'7:30');
Insert into Route_Bus_stop(idRoute,idBus_stop,`order`,`time`) values(2,3,0,'15:00');
Insert into Route_Bus_stop(idRoute,idBus_stop,`order`,`time`) values(2,2,1,'15:10');
Insert into Route_Bus_stop(idRoute,idBus_stop,`order`,`time`) values(2,1,2,'15:20');
SELECT r1.idRoute,r1.idBus_stop,r2.idBus_stop,r1.`time`, r2.`time`
FROM Route_Bus_stop r1 INNER JOIN Route_Bus_stop r2
ON r1.idRoute = r2.idRoute AND r1.`order` < r2.`order`
WHERE r1.idBus_stop = 1
AND r2.idBus_stop = 3
idRoute | idBus_stop | idBus_stop | time | time
------: | ---------: | ---------: | :--- | :---
1 | 1 | 3 | 7:00 | 7:30
db<>fiddle here

create a trigger to alter table add column based on inserted row

I have two tables in a database named as follows:
1. state_master
+---------------+-------------------+
| state_id | state_name |
+---------------+-------------------+
| 1 | new |
| 2 | assigned |
| 3 | in_progress |
| 4 | on_hold |
| 5 | closed |
+---------------+-------------------+
2. store_complaint_state_count
+----------+-----+--------+------------+-------+------+
| store_id | new |assigned| in_progress|on_hold|closed|
| 101 | 1 |2 | 2 |0 |0 |
| 102 | 5 |4 | 1 |0 |2 |
+----------+-----+--------+------------+-------+------+
Now I want to add another row in state_master state_id =6 and state_name=reopen.
I want to create a trigger which can alter table store_complaint_state_count and add column reopen in it.
I've created a procedure:
CREATE DEFINER=`root`#`localhost` PROCEDURE `alterTablestorewisecomplaintcount`(in state int )
BEGIN
alter table storewisecomplaintcount
add column state INT UNSIGNED ZEROFILL NOT NULL DEFAULT 0;
END
And a trigger:
CREATE DEFINER = CURRENT_USER
TRIGGER `nxtlife_sfcms_db_v2`.`complaint_state_AFTER_INSERT`
AFTER INSERT ON `complaint_state`
FOR EACH ROW
BEGIN
call alterTablestorewisecomplaintcount(new.state_value);
END
But it throws an error at the time of insertion:
ERROR 1422: 1422: Explicit or implicit commit is not allowed in stored function or trigger.
you are not allowed to do ALTER or DROP operation on triggers. these are the implicit commits that you see in the error message (see more details here)
Even if it would be possible by some workaround, this is wrong design. If you could do what you wanted- the first insert to the table would add the column, and from then on- all other inserts would have failed since the column already exists
Trigger shouldn't be used that way; instead of every time altering the able and adding a column; you should use a single column say Status which will contain all the different status values like
store_complaint_state_count: Status varchar(20);
Status
new
assigned
in_progress
on_hold
closed
reopen
Then you can modify your trigger code to populate that value to store_complaint_state_count table
CREATE DEFINER = CURRENT_USER
TRIGGER `nxtlife_sfcms_db_v2`.`complaint_state_AFTER_INSERT`
AFTER INSERT ON `complaint_state`
FOR EACH ROW
BEGIN
INSERT INTO store_complaint_state_count (store_id, `Status`) VALUES(105, new.state_name);
END
Hi you can create table and log all events in table.
create table dbLOG (Id Int Identity(1,1),PostTime
VARCHAR(50),ServerName VARCHAR(25),UserName VARCHAR(15),CommandText
VARCHAR(MAX))
go
CREATE TRIGGER [db_LOG]
ON DATABASE
FOR create_table,alter_table,drop_table
,create_PROCEDURE, alter_PROCEDURE,drop_PROCEDURE
,ALTER_function,create_function,drop_function
,ALTER_trigger,create_trigger,drop_trigger
AS
SET NOCOUNT ON
DECLARE #xEvent XML
SET #xEvent = eventdata() --capture eventdata regarding SQL statement user have fired
INSERT INTO dbLOG VALUES(
REPLACE(CONVERT(VARCHAR(50), #xEvent.query('data(/EVENT_INSTANCE/PostTime)')),'T', ' '),
CONVERT(VARCHAR(25), #xEvent.query('data(/EVENT_INSTANCE/ServerName)')),
CONVERT(VARCHAR(15), #xEvent.query('data(/EVENT_INSTANCE/UserName)')),
CONVERT(VARCHAR(MAX), #xEvent.query('data(/EVENT_INSTANCE/TSQLCommand/CommandText)'))
)

how do I write an SQL INSERT query that will avoid duplicating data that are in a range on a row

id | user_id | job_range | start_date | client_name| job_type | job_no | job_quan
1 | 4 | 1-3000 | 2014-05-13 | kenny's | coloured | t50000 | n4500
2 | 2 | 3001-4500 | 2014-05-13 | kenny's | coloured | t50000 | n4500
3 | 3 | 1-2000 | 2014-05-15 | fredy's | plain | t42100 | n5000
4 | 4 |2001-5000 | 2014-05-15 | fredy's | plain | t42100 | n5000
I want to write a query that uses range of numbers already inputted into the database (job_range) and matches it with a job_no to avoid another user from putting jobs in the job range into the database.
the number cannot be within the lower range already given. for any specific job, if there is a range of 1-2000 then you cannot add another row to the table for that job with the range starting between the 1-2000, only jobs from 2001 to 5000 can be picked. so user can only chose to do jobs btw range 2001 -5000
You have to make the column UNIQUE. You can do that by running this query (you'll need to delete duplicate entries first):
ALTER TABLE jobs ADD UNIQUE (desc)
Then when you try to run a query and the desc column already exists, it will return an error.
CREATE TABLE test_dup (id number, dup_from number,dup_to number);
create table dup_val_range (range_values NUMBER);
ALTER TABLE dup_val_range
ADD CONSTRAINT rng_unique unique (range_values);
CREATE SEQUENCE rng_seq
INCREMENT BY 1
START WITH 1
MINVALUE 1
MAXVALUE 9999999999999999999
NOCYCLE
NOCACHE
NOORDER;
create or replace procedure ins_rng(p_from IN NUMBER, p_to IN NUMBER)
AS
v_indx1 NUMBER;
BEGIN
IF p_from> p_to THEN
RAISE_APPLICATION_ERROR(-20001, 'from is Higher tham To');
END IF;
-- check for nulls
FOR i IN p_from..p_to
LOOP
BEGIN
insert into dup_val_range (range_values)
values (i);
EXCEPTION WHEN DUP_VAL_ON_INDEX THEN
rollback;
RAISE_APPLICATION_ERROR(-20001, 'Range ir taken!');
END;
END LOOP;
insert into test_dup
values (rng_seq.nextval,p_from,p_to);
END;
exec ins_rng(p_from=>1, p_to=>19);
exec ins_rng(p_from=>5, p_to=>20);
ORA-20001: Range ir taken!
select * from dup_val_range
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
select * from test_dup
3 1 19
But, if values wont be inserted through this proc ir will be added with hand this wont help..
As Marcell says, it isn't possible to use WHERE on an INSERT.
I'd be inclined to set a UNIQUE key on 'desc' based on your table layout and desired action, and then do a standard INSERT. If 1-300 has been inserted already, then the INSERT will fail with a duplicate key error that you can catch in your application.
ALTER TABLE `jobs` ADD UNIQUE (desc);
If you specify ON DUPLICATE KEY UPDATE, and a row is inserted that would cause a duplicate value in a UNIQUE index or PRIMARY KEY, an UPDATE of the old row is performed. For example, if column a is declared as UNIQUE and contains the value 1, the following two statements have identical effect:
INSERT INTO table (a,b,c) VALUES (1,2,3) ON DUPLICATE KEY UPDATE c=c+1;
UPDATE table SET c=c+1 WHERE a=1;
Here is the more detailed explanation and usage of ON DUPLICATE KEY
http://dev.mysql.com/doc/refman/5.0/en/insert-on-duplicate.html
You want your INSERT to fail if some condition on the two values in your desc field isn't met.
First, as user2879235 mentions, the condition would be much easier to formulate if the desc field were split into two separate columns, let's call them start and end.
Another issue is with the meaning of the values in desc. Sometimes you only have 1 value, sometimes you have 2.
Is 1000 just a shorthand for 1000-1000 or is it something else? I will assume that it is.
And do 100-1000 and 1000-1100 conflict or do they not? I will assume that they do.
With these assumptions, I think your constraint can be formulated as (please verify this!)
NOT EXISTS (
SELECT j FROM jobs WHERE (j.start <= new.start AND j.end >= new.start)
OR (j.start <= new.end AND j.end >= new.end)
)
where new is the newly created tuple.
You want this constraint to be checked upon trying to insert, but MySQL doesn't support checking CHECK constraints, so you'll need to use a trigger instead in which you can raise a SIGNAL when the opposite of this constraint is met, which should roll back (i.e. undo) the INSERT.
one way you needed to split column (desc to desc_from and desc_to) or(substr string to get value from ,to) and you need check that from can't be greater than to and could use something like this..
CREATE TABLE test_dup (id number, dup_from number,dup_to number);
select * from test_dup;
ALTER TABLE test_dup
ADD CONSTRAINT chk_dup CHECK (dup_from<=dup_to);
insert into test_dup
select 1,2,300 from dual where not exists (select 1 from test_dup where 2<=dup_to AND 300>=dup_from);
insert into test_dup
select 2,4,399 from dual where not exists (select 1 from test_dup where 4<=dup_to AND 399>=dup_from);
insert into test_dup
select 3,1,500 from dual where not exists (select 1 from test_dup where 1<=dup_to AND 500>=dup_from);
insert into test_dup
select 4,301,304 from dual where not exists (select 1 from test_dup where 301<=dup_to AND 304>=dup_from);
insert into test_dup
select 4,200,200 from dual where not exists (select 1 from test_dup where 200<=dup_to AND 200>=dup_from);
insert into test_dup
select 4,555,555 from dual where not exists (select 1 from test_dup where 555<=dup_to AND 555>=dup_from);
select * from test_dup;
1 2 300
4 301 304
4 555 555
else ?you could use function based constraint where you put in same check, or not?
or one more you could create new check table and add unique counstraint. before you insert on real table you put all range of data into check table later catch error on dupval if there is
insert into test_dup
select 1,2,300 from dual where not exists (select 1 from test_dup where 2<=dup_to AND 300>=dup_from AND dup_from<dup_to);
one more
create or replace procedure insert_rng (p_id IN NUMBER, p_from IN NUMBER, p_to IN NUMBER)
AS
BEGIN
insert into test_dup
select p_id,p_from,p_to from dual where not exists (select 1 from test_dup where p_from<=dup_to AND p_to>=dup_from AND dup_from<=dup_to);
IF sql%notfound THEN
RAISE_APPLICATION_ERROR(-20001, 'range taken!');
END IF;
END;
exec insert_rng (p_id=>1, p_from=>1, p_to=>99);

MYSQL Triggers - how to store the result of a calculated field

I am using MySQL 5.5. I need to add a Trigger to my table using mysql trigger syntax: http://dev.mysql.com/doc/refman/5.0/en/trigger-syntax.html
The example they have given doesn't explain how I can go about doing this -
I have a table - table(a INT, b INT, c INT);. field a and b are numbers, while field c should be a + b. Now i'm sure you are wondering why not just slap this in a view and be done with it, or why not put this in my code. The reason is because I am working with a client that needs the convenience of an auto calc'ed field, with the ability to modify the value incase it needs variation. They are an auditing company and massaging the numbers is often required because of companies missing audit dates etc.
So how can I create a trigger that will:
on insert:
make `c` the value of `a` + `b`.
on update:
if the value of NEW.`c`==OLD.`c` THEN
make `c` the value of `a` + `b`.
ELSE
no change
The reason for the update not changing if the new value is different to the old value is because that would mean they want to modify the number to be slightly different to what the actual sum is.
Please feel free to change my logic - my aim is to preserve the value of c if it has been entered manually and to blast it if it hasn't been touched manually.
Thanks!
I know this is an old question, but if the answer is still needed here it is.
First of all an id column has been added to the table for example's sake to have more direct updates.
CREATE TABLE table1
(
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
a INT, b INT, c INT
);
Now in INSERT trigger the logic is changed to allow an insert of a pre-calculated value to C column.
CREATE TRIGGER tg_table1_before_insert
BEFORE INSERT ON table1
FOR EACH ROW
SET NEW.c = IF(NEW.c IS NULL, NEW.a + NEW.b, NEW.c);
An update trigger implements the logic per your requirements
CREATE TRIGGER tg_table1_before_update
BEFORE UPDATE ON table1
FOR EACH ROW
SET NEW.c = IF(NEW.c <=> OLD.c, NEW.a + NEW.b, NEW.c);
Now lets do some inserts and updates
INSERT INTO table1 (a, b) VALUES (1, 2), (3, 4);
INSERT INTO table1 (a, b, c) VALUES (5, 6, 0), (7, 8, 100);
UPDATE table1 SET c = 25 WHERE id = 2;
UPDATE table1 SET c = c WHERE id = 3;
As a result we have
| ID | A | B | C |
--------------------
| 1 | 1 | 2 | 3 | -- calculated on insert
| 2 | 3 | 4 | 25 | -- explicitly set on update
| 3 | 5 | 6 | 11 | -- re-calculated on update
| 4 | 7 | 8 | 100 | -- explicitly set on insert
Here is SQLFiddle demo

Move rows from TableA into Table-Archive

Is it possible to move rows that are 3 days old into an other table called "Table_Archive" automatically in mysql ones a week?
tableA ex:
ID | stringvalue | Timestamp
1 | abc | 2011-10-01
2 | abc2 | 2011-10-02
3 | abc3 | 2011-10-05
4 | abc4 | 2011-10-10
5 | abc5 | 2011-10-11
After the move
tableA:
ID | stringvalue | Timestamp
4 | abc4 | 2011-10-10
5 | abc5 | 2011-10-11
Table_Archive:
ID | stringvalue | Timestamp
1 | abc | 2011-10-01
2 | abc2 | 2011-10-02
3 | abc3 | 2011-10-05
And when new input comes into tableA it wont be any problems with ID (PK) in the next move?
What Ive got:
CREATE PROCEDURE clean_tables ()
BEGIN
BEGIN TRANSACTION;
DECLARE _now DATETIME;
SET _now := NOW();
INSERT
INTO Table_Archive
SELECT *
FROM TableA
WHERE timestamp < _now - 3;
FOR UPDATE;
DELETE
FROM TableA
WHERE timestamp < _now - 3;
COMMIT;
END
How do I change _now to be the date 3 days ago?
Personally, I would make use of the MySQL Event Scheduler. This is a built in event scheduler rather like CRON in Linux.
You can specify it to call a procedure, procedures or functions or run a bit of SQL at designated intervals.
Read the MySQL docs but an example would be:
CREATE EVENT mydatabase.myevent
ON SCHEDULE EVERY 1 WEEK STARTS CURRENT_TIMESTAMP + INTERVAL 10 MINUTE
DO
call clean_tables();
So this is saying "call clean_tables() once a week and make the first call in 10 minutes' time"
One gotcha is that the event scheduler is (I think) disabled by default. To turn it on run:
SET GLOBAL event_scheduler = ON;
You can then run:
SHOW PROCESSLIST;
To see whether the event scheduler thread is running.
As for preserving your Table A ID column (if you must). I would keep the ID on Table_Archive as unique to that table i.e make it the primary key & auto_increment and then have a 'Original_TableA_ID' column in which to store the TableA ID. You can put a unique index on this if you want.
So Table_Archive would be like:
create table `Table_Archive` (
ID int unsigned primary key auto_increment, -- < primary key auto increment
tableAId unsigned int not null, -- < id column from TableA
stringValue varchar(100),
timestamp datetime,
UNIQUE KEY `archiveUidx1` (`tableAId`) -- < maintain uniqueness of TableA.ID column in Archive table
);
Nobody seems to have answered your original question "How do I change _now to be the date 3 days ago?". You do that using INTERVAL:
DELIMITER $
CREATE PROCEDURE clean_tables ()
BEGIN
BEGIN TRANSACTION;
DECLARE _now DATETIME;
SET _now := NOW();
INSERT
INTO Table_Archive
SELECT *
FROM TableA
WHERE timestamp < _now - interval 3 day;
FOR UPDATE;
DELETE
FROM TableA
WHERE timestamp < _now - interval 3 day;
COMMIT;
END$
DELIMITER ;
One final point is that you should consider creating an index on the timestamp column on TableA to improve the performance of you clean_tables() procedure.
You may need to have a look into cron jobs if you want that script/query to be executed automatically.
If you are using cpanel have a look into http://www.siteground.com/tutorials/cpanel/cron_jobs.htm
Adding to the best answer (imo) by Tom Mac regarding the event scheduler - be aware that when backing up the schema, you have to specify that you want the events backed up with it via the --events=TRUE flag.
If you're exporting manually in the workbench, the latest version has a checkbox on the main 'Export To Disk' tab - older versions hide it away in the Advanced Export Options tab.
It is possible, MySQL will execute query automatically at specific time using MySQL Event Scheduler. Check this link for more details.
https://dev.mysql.com/doc/refman/5.7/en/event-scheduler.html