i have a table called 'tblDive' with columns:
create table tblDive (
DiveNumber int
InstructorNumber int
ClubNumber int
InstructorSigniture date
)
and another table:
create table tblWorksAt (
InstructorNumber int
ClubNumber int
StartWorkingDate date
EndWorkingDate date
)
the table 'tblWorksAt' has this record:
InstructorNumber | ClubNumber | StartWorkingDate | EndWorkingDate
1 2 1.1.2000 1.1.2005
i want to create a trigger that checks when inserting a new dive, if the instructor really worked at this club in the same time of signing on the dive.
so for example if i insert a new dive:
insert into tblDive (DiveNumber InstructorNumber ClubNumber InstructorSigniture)
values 111, 1, 2, 1.1.2009
i won't be able to insert this record because instructor number 1 stopped working at club number 2 in 1.1.2005
An alternative to using a trigger is to use a check constraint and a user defined function.
A function that checks is the instructor is employed at the right club at the right time:
CREATE FUNCTION CheckEmployment(#InstructorNumber int, #ClubNumber int, #checkdate date)
RETURNS int
AS
BEGIN
DECLARE #retval int
SELECT #retval = COUNT(*)
FROM tblWorksAt
WHERE InstructorNumber = #InstructorNumber
AND ClubNumber = #ClubNumber
AND (EndWorkingDate IS NULL OR EndWorkingDate > #checkdate)
RETURN #retval
END;
GO
And a check constraint using it:
ALTER TABLE tblDive
ADD CONSTRAINT chkEmployed
CHECK (dbo.CheckEmployment(InstructorNumber, ClubNumber, InstructorSigniture) != 0);
This might not be the most efficient way, but it should get the job done. The logic in the function might need improvement too, I might have missed something.
Sample SQL Fiddle showing it in action.
What I'll do is give you some hints and not-so-obvious information about triggers that may help you write the trigger, but you need to write it.
The inserted table
In SQL Server triggers, there are 2 pseudo-tables that you can reference: inserted and deleted tables. The names are somewhat deceiving, particularly if you are doing an UPDATE. The thing to remember is that under the hood, an UPDATE is a delete plus insert.
So essentially, inserted is the new (or updated) rows and deleted is the deleted rows and/or previous rows from an UPDATE before the changes were applied.
For a straight INSERT statement, the deleted table ought to be empty.
Therefore, you want to look for rows in inserted that meet a certain set of criteria. There are two logical ways to approach this:
Check if all rows meet this condition
Look for any rows that do not meet the condition.
Join
If you join inserted to tblWorksAt, you now have all the data you need. Something like this to join the tables and find rows that pass your business rules:
select 1
from inserted i
inner join tblWorksAt wa on wa.InstructorNumber = i.InstructorNumber and i.ClubNumber = wa.ClubNumber
where i.InstructorSignature between wa.StartWorkingDate and wa.EndWorkingDate
What to do with the query
Like I said before:
Check if all rows meet this condition
Look for any rows that do not meet the condition.
To check if all rows pass that criteria, you could:
Check if the count of rows matching that query equals the number of total rows in inserted, without a join.
Loop over each row in inserted which I will tell you right now, is almost never a good idea in a trigger.
To check if at least one row fails to pass the criteria, you could:
Change the between to not between and wrap this query around if (exists(...)).
Change the inner join to left outer join, move the where clause to the join, and then add a new WHERE clause that says tblWorksAt.InstructorNumber is null, then wrap this query around if (exists(...)).
Throwing an error
Now you know how to find rows that pass or fail. Now you just need to throw an error to prevent the statement from completing and prevent the data from persisting. I will leave that as an exercise to you. It should be easy to research.
Related
I am trying to build an API and one of the endpoints will return a random row from my database. In the database I have a table in which I want a "views" column to be updated every time I run a SELECT query on a row.
My table looks something like this:
CREATE TABLE `movies` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(256) NOT NULL,
`description` text,
`views` int(11) NOT NULL DEFAULT 0,
PRIMARY KEY (`id`)
);
The row is selected by ordering the table with rand() and then limiting the result by 1, like so:
SELECT * FROM table ORDER BY rand() LIMIT 1;
Is something like this below possible?
SELECT * FROM table ORDER BY rand() LIMIT 1
UPDATE table SET views = +1 WHERE (selected row?);
I'm new to SQL queries, so I don't know if this is the best way or even possible at all. Should I run a new query after this one has completed that updates the value instead?
Usually, every table has a Primary Key, i.e. a unique ID of every single row. Since you have a result of your SELECT query and it's only 1 row, you always can make a consequent update query like UPDATE table SET views = views + 1 WHERE id = <returned_record_id>. Here we assume that the column id is a Primary Key column. This pair of queries need to be issued by the application code. If you want to achieve SELECT + UPDATE functionality as a single SQL statement, consider using stored procedures.
While the aforementioned approach is technically possible, it might have a few performance problems. First of, ORDER BY rand() often has a poor performance. Also, having an update on each select could have bad performance implications.
No what you want is not possible .as, select and update commands can not be used togethor in a single transaction.
You can do it seperately
You need to create a procedure for this in your database like:
CREATE PROCEDURE `procedure_name`()
BEGIN
SELECT * FROM table ORDER BY rand() LIMIT 1 ;
UPDATE table SET views = +1 WHERE (selected row?) ;
END
and then call it
call procedure_name();
You can check only as there are many ways to write a procedure.
Thanks
Unfortunately, what you want to do is not possible, at least not without a lot of work. SQL in general -- and MySQL in particular -- offer a capability called triggers.
Triggers allow you to do take actions when something happens in the database. For instance, if you want to check that values are correct, you can write an insert/update trigger to check the values and reject improper ones. Or, if you want to stash deleted records into an audit table, a trigger is the way to go.
What you are describing could be implemented using a trigger on a "select". Such a beast does not exist.
What are your options? Well, the simplest is to do this in your application. When a movie is selected, then you can update views. Of course, that only increments the views where you have the code.
You can move this code into a stored procedure. This simplifies the application code. It just has to "know" to use the stored procedure. But, there is no enforcement mechanism.
You can make this more enforceable by using permissions. Basically, don't allow access to the underlying table except through the stored procedure. This is closest to what you want.
If I have a table that has these rows:
animal (primary)
-------
man
dog
cow
and I want to delete all the rows and insert my new rows (that may contain some of the same data), such as:
animal (primary)
-------
dog
chicken
wolf
I could simply do something like:
delete from animal;
and then insert the new rows.
But when I do that, for a split second, 'dog' won't be accessible through the SELECT statement.
I could simply insert ignore the new data and then delete the rest, one by one, but that doesn't feel like the right solution when I have a lot of rows.
Is there a way to insert the new data and then have MySQL automatically delete the rest afterward?
I have a program that selects data from this table every 5 minutes (and the code I'm writing now will be updating this table once every 30 minutes), so I would like to be as accurate as possible at all times, and I would rather have too many rows for a split second than too few rows for the same time.
Note: I know that this may seem like it is unnecessary but I just feel like if I leave too many of those unlikely possibilities in different places, there will be times where things go wrong.
You may want to use TRUNCATE instead of DELETE here. TRUNCATE is faster than DELETE and resets the table back to its empty state (meaning IDENTITY columns are reset to original values as well).
Not sure why you're having problems with selecting a value that was deleted and re-added, maybe I'm missing some context. But if you're wiping the table clean, you might want to use truncate instead.
You could add another column timestamp and change the select statement to accommodate this scenario where it needs to check for the latest value.
If this is for school, I would argue that you need a timestamp and that is what your professor is looking for. You shouldn't need to truncate a table to get the latest values, you need to adjust the thinking behind the table and how you are querying data. Hope this helps!
Check out these:
How to make a mysql table with date and time columns?
Why not update values instead?
My other questions would be:
How are you loading this into the table?
What does that code look like?
Can you change the way you Select from the table?
What values are being "updated" and change in such a way that you need to truncate the entire table?
If you don't want to add new column, there is an other method.
1. At first step, update table in any way that mark all existing rows for deletion in future. For example:
UPDATE `table_name` SET `animal`=CONCAT('MUST_BE_DELETED_', `animal`)
At second step, insert new rows.
On final step, remove all marked rows:
DELETE FROM `table_name` WHERE `animal` LIKE 'MUST_BE_DELETED_%'
You could implement this by having the updated_on column as timestamp and you may even utilize some default values, but let's go with an example without them.
I presume the table would look something like this:
CREATE TABLE `new_table` (
`animal` varchar(255) NOT NULL,
`updated_on` timestamp,
PRIMARY KEY (`animal`)
) ENGINE=InnoDB
This is just a dummy table example. What's important are the two queries later on.
You would simply perform a query to insert the data, such as:
insert into my_table(animal)
select animal from my_view where animal = 'dogs'
on duplicate key update
updated_on = current_timestamp;
Please notice that my_view is your table/view/query by which you supply the values to insert into your table. Also notice that you need to have primary/unique key constraint on your animal column in this example, in order to work.
Then, you proceed with the following query, to "purge" (delete) the old values:
delete from my_table
where updated_on < (
select *
from (
select max(updated_on) from my_table
) as max_date
);
Please notice that you could make a separate view in order to obtain this max_date value for updated_on entry. This entry should indicate the timestamp for your last updated/inserted values in a previous query, so you could proceed with utilizing it in a where clause in order to issue deletion of old records that you don't want/need anymore.
IMPORTANT NOTE:
Since you are doing multiple queries and it's supposed to be a single operation, I'd advise you to utilize it within a single trancations and to utilize a proper rollback on various potential outcomes (i.e. in case of mysql exceptions). You might wish to utilize a proper stored procedure for that.
This may seem like a dumb question. I am wanting to set up an SQL db with records containing numbers. I would like to run an enquiry to select a group of records, then take the values in that group, do some basic arithmetic on the numbers and then save the results to a different table but still have them linked with a foreign key to the original record. Is that possible to do in SQL without taking the data to another application and then importing it back? If so, what is the basic function/procedure to complete this action?
I'm coming from an excel/macro/basic python background and want to investigate if it's worth the switch to SQL.
PS. I'm wanting to stay open source.
A tiny example using postgresql (9.6)
-- Create tables
CREATE TABLE initialValues(
id serial PRIMARY KEY,
value int
);
CREATE TABLE addOne(
id serial,
id_init_val int REFERENCES initialValues(id),
value int
);
-- Init values
INSERT INTO initialValues(value)
SELECT a.n
FROM generate_series(1, 100) as a(n);
-- Insert values in the second table by selecting the ones from the
-- First one .
WITH init_val as (SELECT i.id,i.value FROM initialValues i)
INSERT INTO addOne(id_init_val,value)
(SELECT id,value+1 FROM init_val);
In MySQL you can use CREATE TABLE ... SELECT (https://dev.mysql.com/doc/refman/8.0/en/create-table-select.html)
I've got a bit of a stupid question. The thing is my program has to have the function to delete data from my database. Yay, not really the problem. But how can I delete data without the danger that others can see, that there has been something deleted.
User Table:
U_ID U_NAME
1 Chris
2 Peter
OTHER TABLE
ID TIMESTAMP FK_U_D
1 2012-12-01 1
2 2012-12-02 1
Sooooo the ID's are AUTO_INCREMENT, so if I delete one of them there's a gap. Furthermore, the timestamp is also bigger than the row before, so ascending.
I want to let the data with ID 1 disappear from the user's profile (U_ID 1).
If I delete it, there is a gap. If I just change the FK_U_ID to 2 (Peter) it's obvious, because when I insert data, there are 20 or 30 data rows with the same U_ID...so it's obvious that there has been a modification.
If I set the FK_U_ID NULL --> same sh** like when I change it to another U_ID.
Is there any solution to get this work? I know that if nobody but me has access to the database, it's just no problem. But just in case, if somebody controls my program it should not be obvious that there has been modifications.
So here we go.
For the ID gaps issue you can use GUIDs as #SLaks suggests, but then you can't use the native RDBMS auto_increment which means you have to create the GUID and insert it along with the rest of the record data upon creation. Of course, you don't really need the ID to be globally unique, you could just store a random string of 20 characters or something, but then you have to do a DB read to see if that ID is taken and repeat (recursively) that process until you find an unused ID... could be quite taxing.
It's not at all clear why you would want to "hide" evidence that a delete was performed. That sounds like a really bad idea. I'm not a fan of promulgating misinformation.
Two of the characteristics of an ideal primary key are:
- anonymous (be void of any useful information, doesn't matter what it's set to)
- immutable (once assigned, it will never be changed.)
But, if we set that whole discussion aside...
I can answer a slightly different question (an answer you might find helpful to your particular situation)
The only way to eliminate a "gap" in the values in a column with an AUTO_INCREMENT would be to change the column values from their current values to a contiguous sequence of new values. If there are any foreign keys that reference that column, the values in those columns would need to be updated as well, to preserve the relationship. That will likely leave the current auto_increment value of the table higher than the largest value of the id column, so I'd want to reset that as well, to avoid a "gap" on the next insert.
(I have done re-sequencing of auto_increment values in development and test environments, to "cleanup" lookup tables, and to move the id values of some tables to ranges that are distinct from ranges in other tables... that let's me test SQL to make sure the SQL join predicates aren't inadvertently referencing the wrong table, and returning rows that look correct by accident... those are some reasons I've done reassignment if auto_increment values)
Note that the database can "automagically" update foreign key values (for InnnoDB tables) when you change the primary key value, as long as the foreign key constraint is defined with ON UPDATE CASCADE, and FOREIGN_KEY_CHECKS is not disabled.
If there are no foreign keys to deal with, and assuming that all of the current values of id are positive integers, then I've been able to do something like this: (with appropriate backups in place, so I can recover if things don't work right)
UPDATE mytable t
JOIN (
SELECT s.id AS old_id
, #i := #i + 1 AS new_id
FROM mytable s
CROSS
JOIN (SELECT #i := 0) i
ORDER BY s.id
) c
ON t.id = c.old_id
SET t.id = c.new_id
WHERE t.id <> c.new_id
To reset the table AUTO_INCREMENT back down to the largest id value in the table:
ALTER TABLE mytable AUTO_INCREMENT = 1;
Typically, I will create a table and populate it from that query in the inline view (aliased as c) above. I can then use that table to update both foreign key columns and the primary key column, first disabling the FOREIGN_KEY_CHECKS and then re-enabling it. (In a concurrent environment, where other processes might be inserting/updating/deleting rows from one of the tables, I would of course first obtain an exclusive lock on all of the tables to be updated.)
Taking up again, the discussion I set aside earlier... this type of "administrative" function can be useful in a test environment, when setting up test cases. But it is NOT a function that is ever performed in a production environment, with live data.
How to get the primary key (assuming know his name by looking show keys) resulting from an insert into?
How to get the primary keys of rows affected by an update? (as in the previous case, independent of the key name).
How to get the primary keys returned from a select query (in the query even if the key is not one of the fields surveyed).
I need to SQLs commands I run after the inserts, updates and selects in my application to obtain such information, it is possible?
My database is MySQL.
I need only sqls because i am making a logic of cache queries to aplicate in many applications (java and php) and i wish that the logic be independent of language.
example:
select name from people
i need that a query executed after this return the pk of these people
SELECT LAST_INSERT_ID();
And seriously, putting "primary key from insert mysql" into Google gets you a Stack Overflow answer as the first result.
EDIT: more discussion based on comments.
If you want to see what rows are affected by an update, just do a SELECT with the same WHERE clause and JOIN criteria as the UPDATE statement, e.g.:
UPDATE foo SET a = 5 WHERE b > 10;
SELECT id FROM foo WHERE b > 10;
If you are INSERTing into a table that does not have an auto-increment primary key, you don't need to do anything special. You already know what the new primary key is, because you set it yourself in the INSERT statement. If you want code that can handle INSERT statements coming from outside of the code that will be tracking PK changes, then you'll either need to parse the INSERT statement, or have the calling code provide information about the primary key.