MYSQL: data in one column depends on another column - mysql

I need to create a table where I have columns country and city.
If I put the country as 'USA', the city must be 'New York'. How to impose this constraint?
I tried but this affects data in other rows too:
UPDATE table1 SET city = IF(country = "USA", 'New York', '');
Also, if possible the constraint should be added while creating the table.

You can either do with the table definition like this.
CREATE TABLE table_name
(
country varchar(30),
state varchar(30),
CONSTRAINT check_state
CHECK ( state = CASE WHEN country ='usa' THEN 'new york' ELSE '' END )
);
Or you can add constraint using ADD CONSTRAINT after creating the table.
As per #Tim's comment, adding the code for trigger to achieve the same
DELIMITER |
CREATE TRIGGER `update_state`
AFTER INSERT ON `table_name` FOR EACH ROW
BEGIN
SET status = CASE WHEN new.country= 'usa' THEN 'New York' else ''
END;
DELIMITER ;

If we go by the logic of updating the table after each insert into the table, then what out need to do is :
UPDATE table1
SET city="new york"
WHERE country="usa";
Here the WHERE clause will take care that only tuples having country as usa are selected.
One of the best way to have the constraint would be using CHECK, but unfortunately CHECK constraints are ignored by MySQL as explained in a miniscule comment in the docs: CREATE TABLE
The CHECK clause is parsed but ignored by all storage engines.

Related

Struggling to figure out how to set these constraints

I am currently setting up a few different tables for which I have to use certain constraints. I have been getting on okay but I am stuck with the following:
Limiting the 'Country' column to a choice between UK, USA and Australia
Creating the 'ImageFilename' column so that each record must have an extension of .JPG
Is there a specific constraint for these examples or is it a case of thinking outside the box here? I've tried to think of ways to use the current constraints I know but I'm stumped.
Any help would be much appreciated! Thanks in advance.
edit: would it perhaps be a CHECK constraint?
As of MySQL 8.0, MySQL doesn't support CHECK constraints.
Even if MySQL were to add support for CHECK constraints, I wouldn't use that as a solution, because when you eventually add New Zealand and Canada, or if you support .PNG in addition to .JPG, you'd have to redefine the constraints.
For the countries I would create a lookup table, and a foreign key constraint to restrict your country column.
CREATE TABLE Countries ( Country VARCHAR(75) PRIMARY KEY );
ALTER TABLE DifferentTable ADD FOREIGN KEY (Country) REFERENCES Countries(Country);
This allows you to support new countries simply by INSERTing a new country name into the Countries table.
For the image extension, I can think of two alternatives:
Define a trigger that throws a SIGNAL if the filename doesn't match the right extension.
CREATE TRIGGER ImageFilenameIns BEFORE INSERT ON DifferentTable
FOR EACH ROW
BEGIN
IF (SUBSTRING_INDEX(NEW.ImageFilename, -1) <> '.JPG') THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'You must use a .JPG filename';
END IF;
END
CREATE TRIGGER ImageFilenameUpd BEFORE UPDATE ON DifferentTable
FOR EACH ROW
BEGIN
IF (SUBSTRING_INDEX(NEW.ImageFilename, -1) <> '.JPG') THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'You must use a .JPG filename';
END IF;
END
Remember to set DELIMITER if you create this trigger using the mysql client or SQL script.
Using the trigger solution requires redefining the trigger if you someday want to support .PNG or other extensions.
Define a generated column to represent the file extension, and make sure that references a lookup table of allowed file extensions.
CREATE TABLE ImageFileExtensions ( Extension VARCHAR(3) PRIMARY KEY );
INSERT INTO ImageFileExtensions (Extension) VALUES ('JPG');
ALTER TABLE DifferentTable
ADD COLUMN ImageFilenameExtension VARCHAR(3) AS (SUBSTRING_INDEX(ImageFilename, -1)) STORED,
ADD FOREIGN KEY(ImageFilenameExtension) REFERENCES ImageFileExtensions(Extension);
With the latter solution, you can add support for a new file extension simply by INSERTing a new extension into the ImageFileExtensions table.
I'm not sure what is that you try to pull off but you can use IF in mysql:
* (for Creating):
CREATE TABLE IF NOT EXISTS
:row(row_id varchar(8) NOT NULL,
:country varchar(25) NOT NULL CHECK (country IN ('USA','UK','Australia')),
PRIMARY KEY (row_id));
Test whether two strings are the same and return "YES" if they are, or "NO" if not (for Selecting):
SELECT IF(STRCMP(":country","UK") = 0, "YES", "NO");
or:
SELECT :row, CASE :Country
WHEN 'USA' THEN 1
WHEN 'UK' THEN 2
WHEN 'Australia' THEN 3
ELSE 'NO'
END AS :row1, :row2, :row3
FROM :Table
but still you can do the same thing with PHP:
$country = $_GET['country'];
switch($country){
case "UK":
case "USA":
case "Australia":
Model::DB->Post($country, "Country");
break;
default:
App::Error->Err("Invalid input - " . $country);
}

"CONCAT"enated column not showing up in database table

I tried concat function to combine two columns, i got the output also but
my question is why i don't see new column being added to the table. Is concatenating is just a temporary result?
SELECT CONCAT(Name,',',Continent)AS new_address FROM Country
If you want to add a column to the table, you need to alter the table:
alter table country add new_address varchar(255);
Then you can set the value using update:
update country
set new_address = concat_ws(' ', name, continent);
I prefer concat_ws() for this type of operation because it does not return NULL if one of the columns is NULL.
Note: The table has the "correct" values after the update. But, subsequent changes to the table might require that you re-run the update or that you use a trigger to maintain consistency.
On best practice is to define a view to do the calculation:
create view v_country as
select c.*, concat_ws(' ', name, continent) as new_address
from country;
When you access the data through the view, the new_address field will always be correct.
Yes this creates a column that only exists in your SELECT query.
It certainly does not alter the underlying table.
If you wanted to add this computation to the underlying table you could add a generated column as of MySQL 5.7.6.
CREATE TABLE Country
(
Name VARCHAR(100) NOT NULL,
Continent VARCHAR(100) NOT NULL
);
INSERT INTO Country
VALUES ('France', 'Europe'),
('Nigeria','Africa');
ALTER TABLE Country
ADD new_address VARCHAR(201) AS (CONCAT(Name,',',Continent));
SELECT *
FROM Country;
Online Demo

how can i modify or manipulate this trigger

I'm trying to create a trigger which will capture any event that will occur when I update any column in the table before and after updating, let's say I have 4 columns:
first_name address city country
Let's say I edited first_name lets say Jack to Henk.
It should insert in another table the command (i.e. update) ,time , description but inside the description I want it to write Jack was changed to John by current user(i.e using the current-user () function),if it is a city being updated from Mechinkova to Tostov, it should do the same do with other columns.
I know I want to have to add the concat function inside the trigger, I want it to be like this for example:
DROP TRIGGER IF EXISTS adminpanel.soft//
CREATE TRIGGER adminpanel.soft BEFORE UPDATE ON adminpanel.aggrement
FOR EACH ROW
BEGIN
INSERT INTO adminpanel.aggretrigger(cmd, time, cmd_user, last_name, city) VALUES("INSERT", NOW(), CURRENT_USER(), new.last_name, new.city);
END
//
What you are asking for is an audit trigger. It is very easy to implement.
Let us first slightly modify your main table. Let's add a field id of integer datatype as the primary key to the table, so your table would look like:
tablename
( id integer PK
, first_name varchar
, address varchar
, city varchar
, country varchar
)
Now, you will need a table, say UNIVERSAL_AUDIT_ENTRY table that will store the changes made to the data in your schema.
From what experience I have, I suggest you create this table as follows:
universal_audit_entry
( universal_audit_entryid integer PK
, table_name varchar -- captures the name of the table
, column_name varchar -- captures the name of the column
, entry_type varchar -- captures the event, e.g., 'INSERT' or 'UPDATE'
, primary_key_value integer -- captures, e.g., the value in tblename.id
, from_str varchar -- captures the value that was present before
, to_str varchar -- captures the value that was changed into
, timestamp datetime -- captures the timestamp of the event
, username varchar -- captures the name of user
)
Now with the universal_audit_entry table ready, your trigger should look somewhat like:
CREATE TRIGGER adminpanel.soft
BEFORE UPDATE ON adminpanel.aggrement
FOR EACH ROW
BEGIN
IF UPDATING(first_name) THEN
INSERT INTO universal_audit_entry VALUES
( 123 -- example for universal_audit_entryid
, 'TABLENAME'
, 'FIRST_NAME'
, 'UPDATE'
, new.id
, old.first_name
, new.first_name
, current_timestamp()
, current_user);
END IF;
END;
//
You can use similar logic to audit more columns in the same table and other tables also.
Note:
This code is not tested. I have added it here only for illustration purposes. This code for trigger is not supposed to be used directly.
new and old are the pseudo-records that are generated during an update statement. These records correspond to the rows that are being updated. :new means the row after the update statement runs and :old means the row before the update statement runs. This works in Oracle. Kindly make sure if it works in MySQL also.
EDIT
You can read more about MySQL triggers here. Read more about audit trail here and this SO question.

Using If or Case (or any other method) to increment a value based on country code

I have this table built like this, which assigns a unique articleId starting with zero for each user.
CREATE TABLE `articles` (
`artcId` INT(10) NOT NULL AUTO_INCREMENT,
`artcUserId` INT(10) NOT NULL DEFAULT '0',
`artcStackId` INT(10) NOT NULL DEFAULT '0',
`artcTitle` VARCHAR(200) NULL DEFAULT NULL,
PRIMARY KEY (`artcUserId`, `artcId`)
)
COLLATE='utf8_general_ci'
ENGINE=MyISAM;
There also is a part which assigns a running serial number (artcStackId) to each article. You'll see in the code.
Thing is, is it somehow possible to assign a artcStackId based on the country of the user? That country code will come from php.
Eg: If it's US then start from 10001+1, if its UK start from 20001+1, if its CA then start from 30001+1 and on & on.
Will it be possible to do this?
My current SQL query goes as follows:
insert into articles (artcUserId, artcStackId, artcCountry, artcTitle)
select 4,IFNULL((MAX(artcStackId)+1) ,0),'US','Hello World'
FROM articles;
But the way I want it it'll have to go like (this is just an example sql):
insert into articles (artcUserId, artcStackId, artcCountry, artcTitle)
select 4, IF artcCountry = 'US' then (selct MAX(artcStackId)+1
where artcCountry = 'US'),
'US','Hello World'
FROM articles;
I hope you get the idea.
I would suggest you to use a TRIGGER:
CREATE TRIGGER ins_articles
BEFORE INSERT ON articles
FOR EACH ROW
SET new.artcStackId=coalesce(
(select max(artcStackId)
from articles
where artcCountry = new.artcCountry)+1,1)
;
or something like this:
CREATE TRIGGER ins_articles
BEFORE INSERT ON articles
FOR EACH ROW
SET new.artcStackId=coalesce(
(select max(artcStackId)
from articles
where artcCountry = new.artcCountry)+1,
case when new.artcCountry = 'US' then 10001
when new.artcCountry = 'UK' then 20001
when new.artcCountry = 'CA' then 30001
end)
;
see it working here. Then you can insert without specifying the column artcStackId, since it will automatically be calculated.
Technically this will work, but you might run into problems with duplicate values for artcStackId during heavy load. It's also not likely very efficient.
INSERT INTO articles (artcUserId,artcStackId,artcCountry,artcTitle)
SELECT 4,MAX(artcStackId)+1,'US','Hello World'
FROM articles
WHERE artcCountry = 'US' LIMIT 1
Also, this assumes that the articles table already has at least one row for that country.
A better option might be to have a country table with a nextStackId value on it. In a transaction, update the next stack id in that table, select the updated value, use that for your insert, then commit the transaction. That should be much safer.
What happen if you create a new table named Cities, with the idCity and numberBeg as fields, and with a relation you will know from which number your begin to count

MySQL - Select where fields exist, rows, if not insert

I want to do all the following in ONE QUERY:
Given some data Address, City, State, Zip. I want to see if a User already exists with that data, and if not insert a new record. If so, just update the date modified on it.
SELECT user_id FROM userInfo WHERE Address = '123 Main ST' AND City = 'New York' AND State='NY' AND Zip = '12345'
If count is greater than 0...
UPDATE userInfo SET modifiedDate = '' WHERE user_id = SELECTED user_id
Else If count is 0
INSERT INTO userInfo (Address, City, State, Zip) VALUES ('123 Main ST', 'New York', 'NY', '12345')
I'd like to point out that the Address and City and State and Zip fields are NOT keys of any type, so REPLACE will not work. Plus, there is additional data I'd like to add in a different query if it does exist.
In that case you cannot. REPLACE is used for inserting or updating and it's the only statement that can do both. But it needs unique keys.
If your filter is not a unique key, you cannot know which of the matching rows should be updated, even if you would write separate statements.
If it is a unique key, you should add a unique index on it, and you will be able to use REPLACE.
Long story short: Determine your key and update your indexes.
To have the option of running two different queries, you'll need to use a Stored Procedure. Try this:
CREATE PROCEDURE sp_UpdateIfNotExists()
BEGIN
DECLARE the_user_id INT; /* or whatever your data type is */
SELECT user_id
INTO the_user_id
FROM userInfo
WHERE Address = '123 Main ST' AND City = 'New York' AND State='NY' AND Zip = '12345'
CASE
WHEN the_user_id IS NULL INSERT INTO userInfo (Address, City, State, Zip) VALUES ('123 Main ST', 'New York', 'NY', '12345');
ELSE UPDATE userInfo SET modifiedDate = '' WHERE user_id = the_user_id
END
Then just execute the stored procedure (with CALL sp_UpdateIfNotExists). You could customize this with parameters and so on, I use these in PHP web applications.
Even for the SELECT to be efficient you will need an index on (State, City, Address). For the data consistency the index should apply UNIQUE constraint.
For partial updates of existing rows there is MySQL specific extention: INSERT ... ON DUPLICATE KEY UPDATE
To know the id of the affected existing row you can use the LAST_INSERT_ID(expr) in the UPDATE clause of the INSERT, and then read the value of the usual LAST_INSERT_ID() or its equivalent.