How to create a trigger on updating other table - mysql

I want to create table name birthrate that relates to the main table birth_t, so when I insert data to birth_t, automatically birthrate table will also be updated.
Birth_t fields:
Birth_id
Name
Birthplace
Birthdate
Sex
Height
Weight
Mother
Father
Birthrate fields:
Id
Year (from Birthdate field)
Sum (summing birth rate from the year, so we know how many babies that born in certain year)
How to create trigger for those?

You will need to get the year from Birthdate and check it against the records in the other table to update the applicable record. This is how you can do it:
delimiter |
CREATE TRIGGER birthrata_update BEFORE INSERT ON test1
FOR EACH ROW
BEGIN
insert into Birthrate(Year, Sum)
select New.Year, 0
from BirthRate
where not exists (select 1 from BirthRate BR where BR.Year = New.Year limit 0, 1 );
update Birthrate
set Sum = Sum + 1
where Birthrate.Year = YEAR(NEW.Birthdate);
END;
|
This trigger should insert a row to BirthRate if it did not exist with an initial value of 0. And then it will increment the Sum.

Related

MySQL Trigger Setting All Other Values to NULL When Run

I have two tables, Accounts and Person:
CREATE TABLE Person(
id INT NOT NULL PRIMARY KEY,
Person_Name VARCHAR(17) NOT NULL,
P_Location INT NOT NULL
);
INSERT INTO Person VALUES (1,"Adam",300),(2,"Betty",10),(3,"Louis",60);
CREATE TABLE Accounts(
Person_id INT PRIMARY KEY,
Balance INT DEFAULT 200);
INSERT INTO Accounts VALUES (1,2000),(2,1350),(3,800);
And one trigger, Bonuses:
CREATE TRIGGER Bonuses
AFTER UPDATE ON Person
FOR EACH ROW
UPDATE Accounts
SET Balance = CASE WHEN (SELECT P_Location FROM Person WHERE id = Person_id) = 3 THEN Balance - 150
WHEN (SELECT P_Location FROM Person WHERE id = Person_id) = 7 THEN Balance + 100
WHEN (SELECT P_Location FROM Person WHERE id = Person_id) = 15 THEN Balance - 30
WHEN (SELECT P_Location FROM Person WHERE id = Person_id) = 1 THEN Balance + 200
END;
And I want to make the trigger update the Accounts table according to certain instructions whenever the P_Location on the Person table changes to one of a select few values (3,7,15 and 1). However, as things are they result is incorrect. Assume I run the above code, the tables I get are:
Person
id
Player_Name
P_Location
1
Adam
300
2
Betty
10
3
Louis
60
Accounts
Person_id
Balance
1
2000
2
1350
3
800
Now if I run UPDATE Person SET P_Location = 3 WHERE id = 1; then the Accounts table should yield:
Person_id
Balance
1
1850
2
1350
3
800
However, what I get is
Person_id
Balance
1
1850
2
NULL
3
NULL
Any idea of what I'm doing wrong?
Well, that code did exactly what you said, though it wasn't what you meant!
That's the thing about UPDATE queries, EVERY row will get an update unless a WHERE clause is used to filter what actually gets modified. Nothing is found from the CASE with most records, so any of those will get assigned to NULL. To see this behavior, check this fiddle example.
However, there is good news, all that is needed in the trigger is to add a WHERE clause. Note that I simplified the CASE handling make use of the UPDATE trigger's NEW references:
CREATE TRIGGER Bonuses
AFTER UPDATE ON Person
FOR EACH ROW
UPDATE Accounts
SET Balance = CASE WHEN NEW.P_Location = 3 THEN Balance - 150
WHEN NEW.P_Location = 7 THEN Balance + 100
WHEN NEW.P_Location = 15 THEN Balance - 30
WHEN NEW.P_Location = 1 THEN Balance + 200
END
WHERE Person_id = NEW.id;
So starting with:
Then run: UPDATE Person SET P_Location = 3 WHERE id = 1;
Gives:
Example fiddle with your tables, the simplified trigger case handling, and the output examples from the update query.

Insert data in mysql table if sum of a column is greater or equal to specific integer

I am trying to Run this Query
INSERT INTO point(description, username, _by, date_time, p)
VALUES ('Test By System','username','System',now(),if( (SELECT SUM(p) FROM point WHERE username = 'username') - 1 >=0 , - 1 ,0));
The thing i am really trying to achieve is if SUM of a column is greater or equal to 1 2 or 3 then insert a new Row in the Table.

How to INSERT a value based on the current date and a generated sequence number in MySQL?

I have this MySQL table:
CREATE TABLE bills
(
id_interess INT UNSIGNED NOT NULL,
id_bill VARCHAR(30) NULL,
PRIMARY KEY (id_interess)
) ENGINE=InnoDB;
And now I want to be able to manually insert unique integer for id_interess and automatically generate id_bill so that it consists of a current date and an integer (integer resets on a new year using trigger) like this:
id_interess |id_bill |
------------+-----------+
1 |20170912-1 |
2 |20171030-2 |
6 |20171125-3 |
10 |20171231-4 |
200 |20180101-1 |
3 |20180101-2 |
8 |20180102-3 |
If anyone has direct solution to this using only one query, I would be very glad! I only came up with a solution that uses three queries, but I still get some errors...
My newbie attempt: I created an additional column id_bill_tmp which holds integer part of id_bill like this:
CREATE TABLE bill
(
id_interess INT UNSIGNED NOT NULL,
id_bill_tmp INT UNSIGNED NULL,
id_bill VARCHAR(30) NULL,
PRIMARY KEY (id_interess)
) ENGINE=InnoDB;
Table from above would in this case look like this (note that on new year id_bill_tmp is reset to 1 and therefore I can't use AUTO_INCREMENT which can only be used on keys and keys need unique values in a column):
id_interess |id_bill_tmp |id_bill |
------------+--------------+-----------+
1 |1 |20170912-1 |
2 |2 |20171030-2 |
6 |3 |20171125-3 |
10 |4 |20171231-4 |
200 |1 |20180101-1 |
3 |2 |20180101-2 |
6 |3 |20180102-3 |
So for example to insert 1st row from the above table, table would have to be empty, and I would insert a value in three queries like this:
1st query:
INSERT INTO racuni (id_interess) VALUES (1);
I do this first because I don't know how to increment a nonexistent value for id_bill_tmp and this helped me to first get id_bill_tmp = NULL:
id_interess |id_bill_tmp |id_bill |
------------+--------------+-----------+
1 |[NULL] |[NULL] |
2nd query
Now I try to increment id_bill_tmp to become 1 - I tried two queries both fail saying:
table is specified twice both as a target for 'update' and as a separate source for data
This are the queries I tried:
UPDATE bills
SET id_bill_tmp = (SELECT IFNULL(id_bill_tmp, 0)+1 AS id_bill_tmp FROM bills)
WHERE id_interess = 1;
UPDATE bills
SET id_bill_tmp = (SELECT max(id_bill_tmp)+1 FROM bills)
WHERE id_interess = 1;
3rd query:
The final step would be to reuse id_bill_tmp as integer part of id_bill like this:
UPDATE bills
SET id_bill = concat(curdate()+0,'-',id_bill_tmp)
WHERE id_interess = 1;
so that I finally get
id_interess |id_bill_tmp |id_bill |
------------+--------------+-----------+
1 |1 |20170912-1 |
So if anyone can help me with the 2nd query or even present a solution with a single query or even without using column id_bill_tmp it would be wonderful.
Solution #1 - with the extra column
Demo
http://rextester.com/GOTPA70741
SQL
INSERT INTO bills (id_interess, id_bill_tmp, id_bill) VALUES (
1, -- (Change this value appropriately for each insert)
IF(LEFT((SELECT id_bill FROM
(SELECT MAX(CONCAT(LEFT(id_bill, 8),
LPAD(SUBSTR(id_bill, 10), 10, 0))) AS id_bill
FROM bills) b1), 4) = DATE_FORMAT(CURDATE(),'%Y'),
IFNULL(
(SELECT id_bill_tmp
FROM (SELECT id_bill_tmp
FROM bills
WHERE CONCAT(LEFT(id_bill, 8),
LPAD(SUBSTR(id_bill, 10), 10, 0)) =
(SELECT MAX(CONCAT(LEFT(id_bill, 8),
LPAD(SUBSTR(id_bill, 10), 10, 0)))
FROM bills)) b2),
0),
0)
+ 1,
CONCAT(DATE_FORMAT(CURDATE(),'%Y%m%d'), '-' , id_bill_tmp));
Notes
The query looks slightly more complicated that it actually is because of the issue that MySQL won't let you directly use a subselect from the same table that's being inserted into. This is circumvented using the method of wrapping another subselect around it as described here.
Solution #2 - without the extra column
Demo
http://rextester.com/IYES40010
SQL
INSERT INTO bills (id_interess, id_bill) VALUES (
1, -- (Change this value appropriately for each insert)
CONCAT(DATE_FORMAT(CURDATE(),'%Y%m%d'),
'-' ,
IF(LEFT((SELECT id_bill
FROM (SELECT MAX(CONCAT(LEFT(id_bill, 8),
LPAD(SUBSTR(id_bill, 10), 10, 0))) AS id_bill
FROM bills) b1), 4) = DATE_FORMAT(CURDATE(),'%Y'),
IFNULL(
(SELECT id_bill_tmp
FROM (SELECT SUBSTR(MAX(CONCAT(LEFT(id_bill, 8),
LPAD(SUBSTR(id_bill, 10), 10, 0))), 9)
AS id_bill_tmp
FROM bills) b2),
0),
0)
+ 1));
Notes
This is along the same lines as above but gets the numeric value that would have been in id_bill_tmp by extracting from the right part of id_bill from the 10th character position onwards via SUBSTR(id_bill, 10).
Step by step breakdown
CONCAT(...) assembles the string by concatenating its parts together.
DATE_FORMAT(CURDATE(),'%Y%m%d') formats the current date as yyyymmdd (e.g. 20170923).
The IF(..., <x>, <y>) is used to check whether the most recent date that is already present is for the current year: If it is then the numeric part should continue by incrementing the sequence, otherwise it is reset to 1.
LEFT(<date>, 4) gets the year from the most recent date - by extracting from the first 4 characters of id_bill.
SELECT MAX(...) AS id_bill FROM bills gets the most recent date + sequence number from id_bill and gives this an alias of id_bill. (See the notes above about why the subquery also needs to be given an alias (b1) and then wrapped in another SELECT). See the two steps below for how a string is constructed such that MAX can be used for the ordering.
CONCAT(LEFT(id_bill, 8), ...) is constructing a string that can be used for the above ordering by combining the date part with the sequence number padded with zeros. E.g. 201709230000000001.
LPAD(SUBSTR(id_bill, 10), 10, 0) pads the sequence number with zeros (e.g. 0000000001 so that MAX can be used for the ordering. (See the comment by Paul Spiegel to understand why this needs to be done - e.g. so that sequence number 10 is ordered just after 9 rather than just after 1).
DATE_FORMAT(CURDATE(),'%Y') formats the current date as a year (e.g. 2017) for the IF comparison mentioned in (3) above.
IFNULL(<x>, <y>) is used for the very first row since no existing row will be found so the result will be NULL. In this case the numeric part should begin at 1.
SELECT SUBSTR(MAX(...), 9) AS id_bill_tmp FROM bills selects the most recent date + sequence number from id_bill (as described above) and then extracts its sequence number, which is always from character position 9 onwards. Again, this subquery needs to be aliased (b2) and wrapped in another SELECT.
+ 1 increments the sequence number. (Note that this is always done since 0 is used in the cases described above where the sequence number should be set to 1).
If you are certain to be inserting in chronological order, then this will both bump the number and eliminate the need for the annual trigger:
DROP FUNCTION fcn46309431;
DELIMITER //
CREATE FUNCTION fcn46309431 (_max VARCHAR(22))
RETURNS VARCHAR(22)
DETERMINISTIC
SQL SECURITY INVOKER
BEGIN
RETURN
CONCAT(DATE_FORMAT(CURDATE(), "%Y%m%d"), '-',
IF( LEFT(_max, 4) = YEAR(CURDATE()),
SUBSTRING_INDEX(_max, '-', -1) + 1,
1 ) );
END
//
DELIMITER ;
INSERT INTO se46309431 (id_interess, id_bill)
SELECT 149, fcn46309431(MAX(id_bill)) FROM se46309431;
SELECT * FROM se46309431;
(If you might insert out of date order, then the MAX(..) can mess up.)
A similar solution is shown here: https://www.percona.com/blog/2008/04/02/stored-function-to-generate-sequences/
What you could do is to create a sequence with table, as shown there:
delimiter //
create function seq(seq_name char (20)) returns int
begin
update seq set val=last_insert_id(val+1) where name=seq_name;
return last_insert_id();
end
//
delimiter ;
CREATE TABLE `seq` (
`name` varchar(20) NOT NULL,
`val` int(10) unsigned NOT NULL,
PRIMARY KEY (`name`)
)
Then you need to populate the sequence values for each year, like so:
insert into seq values('2017',1);
insert into seq values('2018',1);
insert into seq values('2019',1);
...
(only need to do this once)
Finally, this should work:
insert into bills (id_interess, id_bill)
select
123,
concat(date_format(now(), '%Y%m%d-'), seq(date_format(now(), '%Y')));
Just replace 123 with some real/unique/dynamic id and you should be good to go.
I think you should redesign your approach to make life easier.
I would design your table as follows:
id_interess |id_counter |id_bill |
------------+--------------+-----------+
1 |1 |20170912 |
2 |2 |20171231 |
3 |1 |20180101 |
Your desired output for the first row would be "20170912-1", but you would merge id_counter and id_bill in your SQL-Query or in your application logic, not directly in a table (here is why).
Now you can write your SQL-Statements for that table.
Furthermore, I would advise not to store the counter in the table. You should only read the records' id and date from your database and calculate the id_counter in your application (or even in your SQL-Query).
You could also declare your column id_counter as auto_increment and reset it each time, see here.
One approach to do in single query would be just save the date in your table when ever you update any record. For id_bill no., generate a sequence when you want to display the records.
Schema
CREATE TABLE IF NOT EXISTS `bill` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY,
`bill_date` date NULL
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
Query
select a.id,concat(DATE_FORMAT(a.bill_date,"%Y%m%d"),'-',a.no) id_bill
from(
select b.*,count(b2.bill_date) no
from bill b
join bill b2 ON (EXTRACT(YEAR FROM b.bill_date) = EXTRACT(YEAR FROM b2.bill_date)
and b.bill_date >= b2.bill_date)
group by b.id
order by b.bill_date,no
) a
Inner query will return you the rank of each record per year by joining the same table outer query just format the data as per your desired view
DEMO
If for same date there can be more than 1 entries then in inner query the id column which is set to auto_increment can be used to handle this case
Updated Query
select a.id,concat(DATE_FORMAT(a.bill_date,"%Y%m%d"),'-',a.no) id_bill
from(
select b.*,count(b2.bill_date) no
from bill b
join bill b2 ON (EXTRACT(YEAR FROM b.bill_date) = EXTRACT(YEAR FROM b2.bill_date)
and b.id >= b2.id)
group by b.id
order by b.bill_date,no
) a
Updated Demo
The following solution requires generated (virtual) columns (available in MySQL 5.7 and MariaDB).
CREATE TABLE bills (
id_interess INT UNSIGNED NOT NULL,
bill_dt DATETIME DEFAULT CURRENT_TIMESTAMP,
bill_year YEAR AS (year(bill_dt)),
year_position INT UNSIGNED NULL,
id_bill VARCHAR(30) AS (concat(date_format(bill_dt, '%Y%m%d-'), year_position)),
PRIMARY KEY (id_interess),
INDEX (bill_year, year_position)
) ENGINE=InnoDB;
bill_year and id_bill are not stored in the table. They are derived from other columns. However - bill_year is stored in the index, which we need to get the last position for a specific year efficiently (it would also work without the index).
To insert a new row with the current timestamp:
insert into bills(id_interess, year_position)
select 1, coalesce(max(year_position), 0) + 1
from bills
where bill_year = year(now());
You can also use a custom timestamp or date:
insert into bills(id_interess, bill_dt, year_position)
select 10, '2016-01-01', coalesce(max(year_position), 0) + 1
from bills
where bill_year = year('2016-01-01')
Demo: https://www.db-fiddle.com/f/8pFKQb93LqNPNaD5UhzVwu/0
To get even simpler inserts, you can create a trigger which will calculate year_postion:
CREATE TRIGGER bills_after_insert BEFORE INSERT ON bills FOR EACH ROW
SET new.year_position = (
SELECT coalesce(max(year_position), 0) + 1
FROM bills
WHERE bill_year = year(coalesce(new.bill_dt, now()))
);
Now your insert statement would look like:
insert into bills(id_interess) values (1);
or
insert into bills(id_interess, bill_dt) values (11, '2016-02-02');
And the select statements:
select id_interess, id_bill
from bills
order by id_bill;
Demo: https://www.db-fiddle.com/f/55yqMh4E1tVxbpt9HXnBaS/0
Update
If you really, really need to keep your schema, you can try the following insert statement:
insert into bills(id_interess, id_bill)
select
#id_interess,
concat(
date_format(#date, '%Y%m%d-'),
coalesce(max(substr(id_bill, 10) + 1), 1)
)
from bills
where id_bill like concat(year(#date), '%');
Replace #id_interess and #date accordingly. For #date you can use CURDATE() but also any other date you want. There is no issue inserting dates out of order. You can even insert dates from 2016 when entries for 2017 already exist.
Demo: http://rextester.com/BXK47791
The LIKE condition in the WHERE clause can use an index on id_bill (if you define it), so the query only need to read the entries from the same year. But there is no way to determine the last counter value efficiently with this schema. The engine will need to read all rows for the cpecified year, extract the counter and search for the MAX value. Beside the complexity of the insert statement, this is one more reason to change the schema.

Adding First Values Grouped by ID from record set; report builder 3.0

I have a dataset being returned that has monthly values for different 'Goals.' The goals has unique ID's and the month/date values will always be the same for the goals. The difference is sometimes one goal doesn't have values for all the same months as the other goal because it might start at a later date, and i want to 'consolidate' the results and sum them together based on the 'First' startBalance for each goal. Example dataset would be;
goalID monthDate startBalance
1 1/1/2014 10
1 2/1/2014 15
1 3/1/2014 22
1 4/1/2014 30
2 4/1/2014 13
2 5/1/2014 29
What i want to do is display these consolidated (summed) values in a table based on the 'First' (earliest Month/Year) value for each goal. The result would look like;
Year startBalance
2014 23
This is because the 'First' value for goalID of 1 is 10 and the 'First' value for goalID of 2 is '13' but when I try to group by the
Year(Fields!MonthDate.Value)
and use the expression;
Sum(First(Fields!startBalance.Value))
I receive the error;
The Value expression for the textrun ‘StartingValue3.Paragraphs[0].TextRuns[0]’ uses a First, Last or Previous aggregate in an outer aggregate. These aggregate functions cannot be specified as nested aggregates.
Does anyone know if my grouping is incorrect, or if there's a different way i can get the 'First' value for the goalIDs summed together correctly?
You have to change
Sum(First(Fields!startBalance.Value))
to
Sum(Fields!startBalance.Value)
This is Code that you exactly want:
Copy
create table #temp
(id int,
monthDate date,
value int)
insert into #temp values(1,'1/1/2014',10)
insert into #temp values(1,'1/2/2014',15)
insert into #temp values(1,'1/3/2014',20)
insert into #temp values(2,'1/4/2014',25)
insert into #temp values(2,'1/5/2014',19)
declare #min int,#max int
select #min=MIN(ID) from #temp
select #max=MAX(ID) from #temp
select * from #temp --This is your main table
select top 0 * into #res
from #temp
while(#min<=#max)
begin
declare #minDT date
set #minDT=(select MIN(MonthDate) from #temp where id=#min)
insert into #res
select *
from #temp
where ID=#min
and Convert(Date,monthDate,103)=Convert(Date,#minDT,103)
set #min=#min+1
end
select * from #res --This is Result
drop table #res
drop table #temp

Display column name of most recently updated column

I am wondering if it is possible to display the column name of the most recently updated column in a table.
Example:
**Original Record created on 03/20/14:**
Name: John
Height
Weight
Age
Update_date: 03/20/14
Update_Column: Name
Then someone comes in and updates the height on 03/22/14:
Name: John
Height: 5'9
Weight
Age
Update_date: 03/22/14
Update_Column: Height
And the Update_date and Update_column would change again if someone came in and put a value for age. And so on.
Is this possible?
Also, if the above is possible, would it be possible to display the column name farthest right if a user updated more than one column at the same time?
Example:
User updates the below record:
Name: John
Height: 5'9
Weight
Age
Update_date: 03/22/14
Update_Column: Height
And adds in a weight and age at the same time on 03/24/14:
Name: John
Height: 5'9
Weight: 150
Age: 31
Update_date: 03/22/14
Update_Column: Height
The Update_Column would display Age because it is the farthest to the right. (Thinking of it as you read from left to right, so the column that was last updated would be the one farthest right)
So to sum things up, I need to be able to display Updated_date(which will be the current date when the record is last updated) and Updated_Col (which is the column name of the last column that was updated, and if multiple columns are updated then display the one that a value was updated last in)
Hopefully the examples help to clarify things.
Thanks,
Steven
You need to store those meta data for each row. So you'd need two new columns, the update_date and update_column. Then you can add an before update trigger to check which columns are about to change and set the update date.
update
Here's an example:
delimiter //
create table a (
id int (10) unsigned auto_increment,
a int(10),
b int(10),
update_date datetime NULL,
update_column varchar(16) NULL,
primary key (id)
)//
create trigger bu_a before update on a for each row begin
set NEW.update_date = NOW();
set NEW.update_column = NULL;
-- you need to start with the rightmost column if you want
-- that matched with the highest priority
if OLD.b != NEW.b then
set NEW.update_column = "b";
elseif OLD.a != NEW.a then
set NEW.update_column = "a";
end if;
end //
Test:
insert into a (id,a,b) values (1,1,1), (2,1,1), (3,1,1), (4,1,1)[
update a set b = 2 where id = 2;
update a set a = 2 where id = 3;
update a set a = 2 where id = 4;
update a set b = 2 where id = 4;
select * from a;
Output:
ID A B UPDATE_DATE UPDATE_COLUMN
1 1 1 (null) (null)
2 1 2 March, 24 2014 23:22:33+0000 b
3 2 1 March, 24 2014 23:22:33+0000 a
4 2 2 March, 24 2014 23:22:33+0000 b