Change tracking in sql server 2008, senseless delete operations - sql-server-2008

I am currently testing change tracking mechanism in sql server 2008 and noticed something:
When to the base table (change tracked) I insert a new record and delete it using the same
key, select with the changes returns to me the information that it should be deleted in the remote table however that record doesn't exist at all in that table ..
Why is it functioning that way?
SAMPLE CODE:
CREATE TABLE TEST (
ID UNIQUEIDENTIFIER primary key,
value int
)
ALTER TABLE [dbo].Test
ENABLE CHANGE_TRACKING
SELECT CHANGE_TRACKING_CURRENT_VERSION()
SELECT CT.SYS_CHANGE_OPERATION, CT.ID, IV.value
FROM CHANGETABLE(CHANGES TEST, 51374) CT
LEFT JOIN TEST IV ON IV.ID = CT.ID
--zero changes now for: 51374
insert into Test VALUES ('54C1D80E-ACB0-433F-94DF-7D06FE809E22', 1)
delete from Test where id = '54C1D80E-ACB0-433F-94DF-7D06FE809E22'
select * from Test -- table is empty (insert and delete)
SELECT CT.SYS_CHANGE_OPERATION, CT.ID, IV.value
FROM CHANGETABLE(CHANGES TEST, 51374) CT
LEFT JOIN TEST IV ON IV.ID = CT.ID
--however for Anchor: 51374 it claims I should delete the record ...
My base and the remote table were in sync at 51374 anchor.
Adding and deleting the record shouldn't give me the info for deleting
of something I don't have in my remote table ...

I think Damien's answer is right. Not sure why he answered in a comment.
It's all there in the documentation, really "Only the fact that a row
has changed is required, not how many times the row has changed or the
values of any intermediate changes.", "If an application requires
information about all the changes that were made and the intermediate
values of the changed data, using change data capture, instead of
change tracking."
Imagine if there's a row with ID 1 that you know about. Then in some
period, someone goes in and deletes that row, and then adds a new row
with the same ID. Change tracking will give you an insert, even though
you already knew about a row with ID 1 and haven't seen a delete. It's
the nature of the beast - you only get the last change, and you have
to reconcile that with your version of reality. If you see a Delete
for a row you don't know about, ignore it.

Related

How to avoid race condition in MySQL

I've got a potential race condition in an application I'm developing, which I'd like to account for and avoid in my querying.
To summarise the application flow...
Create a new row in the entries table:
INSERT INTO entries ( name, email ) VALUES ( 'Foo Bar', 'foo#example.com' );
Find out if Mr Bar is a winner by checking a time-sensitive prizes table:
SELECT id FROM prizes WHERE various_time_conditions = 'met' AND id NOT IN ( SELECT prize_id FROM entries );
If he's a winner, update his entry row accordingly:
UPDATE entries SET prize_id = [prize id] WHERE id = [entry id];
As each prize can only be given out once, I need to eliminate any possibility of a race condition where another process can query the prizes table and update the entry table between steps 2 and 3 above.
I've been doing some research and have found a load of information about transactions (all my tables use InnoDB) and using MySQL's SELECT ... FOR UPDATE syntax but I'm confused as to which is the most suitable solution for me.
You're going to want to lock the prize record. So add some availability flag on the prizes table (perhaps with a default value) if you're not going to use something like a winner_id. Something like this:
SELECT id FROM prizes WHERE ... AND available = 1 FOR UPDATE
Then set the availability if you do assign the prize:
UPDATE prizes SET available = 0 WHERE id = ...
You'll need to wrap this inside a transaction of course.
Make sure that every time you check to see if the prize is available, you add AND available = 1 FOR UPDATE to the query because a SELECT without the FOR UPDATE is not going to wait for a lock.

MySQL Update set Value only if flag = 0

I have this Query:
INSERT INTO `items` (`id`,`image`)
VALUES(112,'asdf.jpg'),(113,'foobar.png')
ON DUPLICATE KEY UPDATE
`id`VALUES(`id`),
`image` = IF(image_uploaded = 0, VALUES(`image`),image);
The worse: its properly working but not as i want.
What i want: The images should only be updated if the field "image_uploaded" is set to 0.
Any ideas?
The Background: I have a DB-Table with data. Now each night a cronjob calls an API-Fn to get new Data from another DB and write it down to my table. This function is getting all items from the second DB so its currently just overwriting my existing data. Now my Application allows changes on data i got from the 2nd DB and overwrites the changes in my own Table. So the Problem is: I need the ability to edit Data via my App AND update Data via API without colliding. The User may change the "image" but all other things should be updated from the 2nd DB. The Image should only be overwritten if it wasn't uploaded manually.
Without playing around with ON DUPLICATE KEY... I'm not sure, if it can handle this situation. I'd work around it by using another (temporary) table.
It's the same structure as your target_table plus a column (in the example sid) to indicate if the entry exists in your target_table or not.
CREATE TEMPORARY TABLE tmp_whatever (
sid int,
id int, image varchar(50)
);
Now we insert the data you want to insert into your target_table into the newly created table first and check with coalesce() and left joining, if the entry already exists. I'm assuming here, that id is your primary key.
INSERT INTO tmp_whatever (sid, id, image)
SELECT
COALESCE(t.id, 0),
id, image
FROM (
SELECT 112 AS id,'asdf.jpg' AS image
UNION ALL
SELECT 113,'foobar.png'
) your_values v
LEFT JOIN your_target_table t ON v.id = t.id;
Then we update the target_table...
UPDATE your_target_table t INNER JOIN tmp_whatever w ON t.id = w.id AND w.sid <> 0
SET t.image = w.image
WHERE t.image_uploaded = 0;
And finally we insert the rows not already existing...
INSERT INTO your_target_table (id, image)
SELECT
id, image
FROM tmp_whatever
WHERE sid = 0;
While I was writing this down, it came to my mind, that I might have had wrong assumptions on what your problem is. This
The worse: its properly working but not as i want.
is definitely not the way to ask a question or describe a problem. I answered because I have a good day :) (and it's the reason why you get downvotes, btw)
Anyway, another cause of "not as i want" could be, that you're missing a unique index in your table. Though a primary key is a unique key, afaik/iirc ON DUPLICATE KEY relies on a unique index on another column to work correctly.

Automatic Update for database rows based on changes in another table

I have a scenario that requires a value in a row of a table to be updated automatically whenever a row has been added or deleted in another table. I'm not sure how to do it.BTW I'm using phpmyadmin in order to manage my database. Thanks in advance.
pages Table
------------
page_no
no_of_choices
choices Table
-------------
page_no
choice_no
When I add a choice with choice number 1 and page_no, then the table page which has the row, page_no=1 should be updated with no_of_choices=no_of_choices+1
You can use triggers.
For example:
CREATE TRIGGER `test1`
AFTER INSERT ON `tbl1`
FOR EACH ROW SET NEW.upd_fld = new_value
Similarly could be done for delete.
You can also create triggers from phpMyAdmin
TABLE A: page_no, no_of_choices
TABLE B: page_no, choice_no...
With a relational database you very rarely want to have duplicate data. If something breaks at some point, you won't know which to trust - the rows in Table B, or the no_of_choices in Table A. A better solution is to do one of the following (depending on which table you are querying):
SELECT COUNT(no_of_choices) FROM B WHERE page_no = 1
or
SELECT A.*, COUNT(choice_no) AS choice_no FROM A LEFT JOIN B USING(page_no)
You get the same result, but now you have one record to go off of, so you won't have inconsistent data.

mysql divide a table into 2 with different column names

I have a mysql table which I need to split into two. Currently the table contains info about two loosely related entities.
For example the entities can be Employee of a firm and the firm's laptop he is tagged to.
The following table explains my example. I have a table employee which has the following columns
employee_id,employee_name,employee_detail,join_date,laptop_id,laptop_type,laptop_tagged_date,laptop_details,laptop_make.
I need to split this table into 2 as shown below.
employee table without the laptop columns
employee_id,employee_name,employee_detail,join_date.
A new laptop table with employee_id as the key.
employee_id,laptop_id,laptop_type,laptop_tagged_date,laptop_details,laptop_make
The below statements are true for the current database design
The employee table is heavily used by the back end code. The back
end code is written in java and php.
employee table cannot be renamed.Implies I do not want to create 2
new tables.I want to retain the employee table, but remove all the
laptop info from it.
New rows are added/existing rows are updated on a daily basis.
My question is
Is there a design approach which I can follow for a smooth
transition from the current design of a single table to the newly
suggested design?
Are there any best practices to follow to ensure a smooth
transition.
Could you suggest/re-direct me the steps to complete this task.
Backup your existing database:
mysqldump my_db > backup.sql
Create a new, empty table laptop (I'm not defining indexes or foreign key constraints in my example below, but you should do whatever is appropriate to your data structure):
CREATE TABLE laptop
SELECT employee_id,
laptop_id,
laptop_type,
laptop_tagged_date,
laptop_details,
laptop_make
FROM employee
WHERE FALSE;
Define triggers on the original table to forward each type of write operation (insert/update/delete) to the new table:
CREATE TRIGGER employee_insert AFTER INSERT ON employee FOR EACH ROW
INSERT INTO laptop VALUES (
NEW.employee_id,
NEW.laptop_id,
NEW.laptop_type,
NEW.laptop_tagged_date,
NEW.laptop_details,
NEW.laptop_make
);
CREATE TRIGGER employee_update AFTER UPDATE ON employee FOR EACH ROW
UPDATE laptop SET
employee_id = NEW.employee_id,
laptop_id = NEW.laptop_id,
laptop_type = NEW.laptop_type,
laptop_tagged_date = NEW.laptop_tagged_date,
laptop_details = NEW.laptop_details,
laptop_make = NEW.laptop_make
WHERE
employee_id <=> OLD.employee_id,
laptop_id <=> OLD.laptop_id,
laptop_type <=> OLD.laptop_type,
laptop_tagged_date <=> OLD.laptop_tagged_date,
laptop_details <=> OLD.laptop_details,
laptop_make <=> OLD.laptop_make;
CREATE TRIGGER employee_delete AFTER DELETE ON employee FOR EACH ROW
DELETE FROM laptop WHERE
employee_id <=> OLD.employee_id,
laptop_id <=> OLD.laptop_id,
laptop_type <=> OLD.laptop_type,
laptop_tagged_date <=> OLD.laptop_tagged_date,
laptop_details <=> OLD.laptop_details,
laptop_make <=> OLD.laptop_make;
Empty the new table (of anything the trigger has since inserted) and then, within the same transaction, use INSERT ... SELECT to copy into it all incumbent data from the original table:
START TRANSACTION;
DELETE FROM laptop;
INSERT INTO laptop
SELECT employee_id,
laptop_id,
laptop_type,
laptop_tagged_date,
laptop_details,
laptop_make
FROM employee;
COMMIT;
Thoroughly search your codebase (including database-stored programs) for all operations that access the laptop columns in the original table. Note whether each operation:
only reads from those columns;
only writes to those columns; or
both reads from and writes to those columns (e.g. UPDATE employee SET laptop_tagged_date = laptop_tagged_date + INTERVAL 1 WEEK WHERE ...).
Modify read operations to use the new table, splitting operations that both read and write into separate steps (e.g. UPDATE employee JOIN laptop ON ... SET employee.laptop_tagged_date = laptop.laptop_tagged_date + INTERVAL 1 WEEK WHERE ...).
This change does not need to be effected atomically, as the original and new tables will be kept in sync by the triggers: some parts of your application can therefore read from the new table whilst others continue using the original table.
Do not proceed to the next step until you are satisfied that this step has been completed, as the next step will result in the tables becoming desynchronised. You could even use MySQL user permissions to prevent writing to the new table (except by the triggers) until you are satisfied that this step is complete.
Modify write operations to use the new table.
This change does not need to be effected atomically, as any writes to the original table will be forwarded to the new one by the triggers: some parts of your application can therefore write to the new table whilst others continue writing to the original table.
Drop the columns from the original table:
ALTER TABLE employee
DROP COLUMN laptop_id,
DROP COLUMN laptop_type,
DROP COLUMN laptop_tagged_date,
DROP COLUMN laptop_details,
DROP COLUMN laptop_make;
if you want to have 2 physical different tables you can adapt Sebastien M. answer as such :
create a laptop table with corresponding data to externalize them
CREATE TABLE laptop AS
SELECT DISTINCT employee_id,laptop_id,laptop_type,laptop_tagged_date,laptop_details,laptop_make
FROM employee
WHERE ...
provide a employee_laptop view to imitate the behaviour of employee and get backward compatibility
create view employee_laptop as
select employee_id, e.employee_name,e.employee_detail,e.join_date,
l.laptop_id,l.laptop_type,l.laptop_tagged_date,l.laptop_details,l.laptop_make
from employee e join laptop l using(employee_id);
then you have all the latitude to drop the unnecessary columns from the employee table
I can suggest you a possible way , when you are creating laptop table use the following query type :-
create table laptop select employee_id,laptop_id,laptop_type,
laptop_tagged_date,laptop_details,laptop_make from
employee;
After creating the laptop table in this above process, you can remove the specified columns from employee table for getting your new employee table with relevant fields.
Delete column from employee table.
alter table employee
drop column laptop_id,laptop_type,laptop_tagged_date,laptop_details,laptop_make;
Now the new employee table has following fields :
employee_id,employee_name,employee_detail,join_date
Now the laptop table has following fields :
mployee_id,laptop_id,laptop_type,
laptop_tagged_date,laptop_details,laptop_make
Hope it will help you.

Using a trigger to alter an existing row when a row with a duplicate key is added in MySQL

I have a table called jobs in a MySQL database.
One user adds data by importing an Excel file into a linked table in Access.
This works fine, except occasionally, there is a row with a duplicate id.
What I would like to do when this happens is take the data from one column (comments) of the new row and insert it into the existing row with the identical id.
For example, the original table:
id customer product comments
1 Jeff Widget Hello
2 Fred Sprocket Important comment here
Some imported data:
id customer product comments
2 Fred A new and different comment
3 Jerry Widget More comments
The table after importing the above data:
id customer product comments
1 Jeff Widget Hello
2 Fred Sprocket A new and different comment
3 Jerry Widget More comments
I'm at a loss as to how to build a trigger that would achieve this goal.
Any advice?
EDIT: Opted to use a PHP script to clean up the duplicates after the fact. Thanks for the input, all.
From my experience with Access linked tables, this can only be done with code or a two-step query process. First inserting the new records, and then performing an update operation with the remainder of the data set (you could do it in the opposite order as well). Is this something you need to do on an ongoing basis, or just a one time deal?
I think this may be of help.
As I understand it, you run an and INSERT statement and tell MySQL what to UPDATE when a duplicate key is inserted.
So, your table insert statement would look something like this:
INSERT INTO mysqlTable (id,customer,product,comment)
SELECT id,customer,product,comment FROM linkedTable
WHERE x = y
ON DUPLICATE KEY UPDATE comment = linkedTable.comment;
I would suggest using REPLACE INTO. But also, you have to be careful with REPLACE INTO as it actually deletes the previous entry and starts a new row.
http://dev.mysql.com/doc/refman/5.0/en/replace.html
I did this recently using mssql. I ran an update first. After that I ran the insert on what was left. I have never worked with access and quite frankly don't want to.
update table1 set comments = (select comments from table2 where table2.id = table1.id) where table1.id in (select table2.id from table2);
insert into table1 (customer,product,comments) select customer, product, comments from table2 where table2.id not in (select table1.id from table1);
Not sure if you can do joins on the insert and update like you can in mssql/t-sql
Hope it helps