Set a unique constraint only when a field is null - mysql

I have this table:
CREATE TABLE `executed_tests` (
`id` INTEGER AUTO_INCREMENT NOT NULL,
`user_id` INTEGER NOT NULL,
`test_id` INTEGER NOT NULL,
`start_date` DATE NOT NULL,
`completed_date` DATE,
PRIMARY KEY (`id`)
);
I want to set up an unique constraint on fields user_id and test_id, but only when conclusion_date is null. If conclusion_date is not null, the constraint doesn't apply.
So there will exist only one incomplete execution per user and test.
Something like this:
UNIQUE(`user_id`, `test_id`) WHEN (`completed_date` IS NULL)
How can I accomplish this on MySQL 5.6?

MySQL supports functional key parts since 8.0.13.
If your version is sufficiently recent you can define your index as:
UNIQUE(`user_id`, `test_id`, (IFNULL(`completed_date`, -1)))
(Demo on dbfiddle.uk)
Note that the above index will also prevent duplciate dates for completed executions. If those should be valid then a slightly modified index would work:
UNIQUE(`user_id`, `test_id`, (
CASE WHEN `completed_date` IS NOT NULL
THEN NULL
ELSE 0
END))
(Demo on dbfiddle.uk)
Although then it starts to feel a bit dirty ;)
If you have at least version 5.7 you can use a (virtual) generated column as workaround:
CREATE TABLE `executed_tests` (
`id` INTEGER AUTO_INCREMENT NOT NULL,
`user_id` INTEGER NOT NULL,
`test_id` INTEGER NOT NULL,
`start_date` DATE NOT NULL,
`completed_date` DATE,
`_helper` CHAR(11) AS (IFNULL(`completed_date`, -1)),
PRIMARY KEY (`id`),
UNIQUE(`user_id`, `test_id`, `_helper`)
);
(Demo on dbfiddle.uk)
If you are stuck on 5.6 then a combination of a regular (non-virtual) column and slightly modified INSERT statements would work:
CREATE TABLE `executed_tests` (
`id` INTEGER AUTO_INCREMENT NOT NULL,
`user_id` INTEGER NOT NULL,
`test_id` INTEGER NOT NULL,
`start_date` DATE NOT NULL,
`completed_date` DATE,
`is_open` BOOLEAN,
PRIMARY KEY (`id`),
UNIQUE(`user_id`, `test_id`, `is_open`)
);
In this case you would set is_open to true for incomplete executions and to NULL after completion, making use of the fact that two NULLs are treated as not equal.
(Demo on dbfiddle.uk)

Related

select all sales from specific day ? mysql , convert datetime to date

how to convert the DateTime column to date?
I need something like the select * from sales where date = = "2022-09-18";
I need to get all sales from a specific day
I tried this How can I convert datetime to date, truncating the times, leaving me the dates?
and many more solutions but nothing is working.
i tried this SELECT DATE(datatime) AS date_of_booking FROM sales; " ;
but it gave me this
I tried also this but it gave me error this
this is a table named Sales
if there is anybody that can help me understand how do I get this thing to work please i really appreciate that! a lot .
code for generating the database if you want
DROP DATABASE IF EXISTS ice_cream_store;
CREATE DATABASE IF NOT EXISTS ice_cream_store;
USE ice_cream_store;
CREATE TABLE `Tastes` (
`tid` INTEGER NOT NULL AUTO_INCREMENT primary key,
`name` VARCHAR(20) NOT NULL,
UNIQUE (name)
);
CREATE TABLE `Toppings` (
`topid` INTEGER NOT NULL AUTO_INCREMENT primary key,
`name` VARCHAR(20) NOT NULL,
UNIQUE (name)
);
CREATE TABLE `Receptacles` (
`rid` INTEGER NOT NULL AUTO_INCREMENT primary key,
`name` VARCHAR(20) NOT NULL,
`price` int NOT NULL,
UNIQUE (name)
);
CREATE TABLE `Sales` (
`sid` INTEGER NOT NULL AUTO_INCREMENT primary key,
`rid` integer not null,
foreign key (`rid`) references Receptacles(`rid`),
`datetime` datetime not null,
`completed` bool not null,
`paid` bool not null,
`total_price` INTEGER NOT NULL
);
CREATE TABLE `Tastes_Sales` (
`sid` integer not null,
foreign key (`sid`) references Sales(`sid`),
`tid` integer not null,
foreign key (`tid`) references Tastes(`tid`),
PRIMARY KEY (`sid` , `tid`),
`quantity` integer not null
);
CREATE TABLE `Toppings_Sales` (
`sid` integer not null,
foreign key (`sid`) references Sales(`sid`),
`topid` integer not null,
foreign key (`topid`) references Toppings(`topid`),
PRIMARY KEY (`sid` , `topid`)
);
SELECT
DATE(`datatime`)
AS date_of_booking
FROM sales;
select * from tastes;
select * from Receptacles;
select * from Toppings;
select * from sales;
select * from Toppings_Sales;
select * from Tastes_Sales;
Use the extract function
Example:
SELECT EXTRACT(MONTH FROM "2017-06-15 09:34:21");
outputs 6
From there you just do:
select * from sales where EXTRACT(DAY FROM date) = 18;
Now you just add AND operators to do the same for month and year
I think this is a simple enough solution. Hope this works for you.
SELECT * from sales WHERE DATE(datetime) = '2022-09-15'; this is working for me #RiggsFolly thank you very much!!

Correct structure and index for a Weekly table

I need a table to store text every week for each user.
So I thought two alternatives:
1) Using composite primary key:
CREATE TABLE `WeeklyTxt` (
`Year` YEAR(4) NOT NULL ,
`Week` ENUM('1','2','3','4', ... ,'51','52','53') NOT NULL ,
`UserId` BIGINT NOT NULL ,
`WeekTxt` TEXT NOT NULL,
PRIMARY KEY (`Year`, `Week`, `UserId`)
) ENGINE = InnoDB;
2) Using autoincrement primary key
CREATE TABLE `WeeklyTxt_2` (
`WeekTxtId` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
`Year` YEAR(4) NOT NULL ,
`Week` ENUM('1','2','3','4', ... ,'51','52','53') NOT NULL ,
`UserId` BIGINT NOT NULL ,
`WeekTxt` TEXT NOT NULL
) ENGINE = InnoDB;
I can't figure out what could be the better choice (and why)
It depends of the search in the table that you will usually do!
Tipically I would use a Simple PRIMARY KEY, and I will add another KEY like your KEY: (Year, Week, UserId)

MySQL - Using PRIMARY KEY - UNIQUE INDEX related table many to many

happy day.
I am creating a database of movies, all movies will receive one vote for each user, valuing as it seemed the movie, "Good, good or fair" These votes of these stored in a table [movies_has_rating] that have the ID of the user who made the vote, with the type of vote, and the creation date, I need to ensure and prevent a user can participate twice in a movie, as only one vote per user is allowed, although this already I have done through PHP and MySQL queries, there is still the possibility of adding it manually from MySQL, and would also be able to establish this same default MySQL, my question is:
1) If the fields are defined [vote id] and [user_id] as primary keys, you can avoid having two evaluations of a user for the same film, example.
CREATE TABLE `user_has_rating` (
`movie_id` int UNSIGNED NOT NULL ,
`vote_id` int UNSIGNED NOT NULL ,
`user_id` int UNSIGNED NOT NULL ,
`create_at` datetime NOT NULL ,
PRIMARY KEY (`vote_id`, `user_id`)
)
2) It is necessary to add to the fields UNIQUE INDEX [vote id] and [user_id], when they are already defined as primary keys, which has advantages and differences using UNIQUE INDEX when we defininas primary keys.
CREATE TABLE `user_has_rating` (
`movie_id` int UNSIGNED NOT NULL ,
`vote_id` int UNSIGNED NOT NULL ,
`user_id` int UNSIGNED NOT NULL ,
`create_at` datetime NOT NULL ,
PRIMARY KEY (`vote_id`, `user_id`),
UNIQUE INDEX `vote_id` (`vote_id`) ,
UNIQUE INDEX `user_id` (`user_id`)
);
3) It is necessary to specify the type of method the index, such as "BTREE or HASH"
CREATE TABLE `user_has_rating` (
`movie_id` int UNSIGNED NOT NULL ,
`vote_id` int UNSIGNED NOT NULL ,
`user_id` int UNSIGNED NOT NULL ,
`create_at` datetime NOT NULL ,
PRIMARY KEY (`vote_id`, `user_id`),
UNIQUE INDEX `vote_id` (`vote_id`) USING BTREE ,
UNIQUE INDEX `user_id` (`user_id`) USING BTREE
);
Much appreciate your help, thank you very much!

simple select query optimise

I have a table defined as follows:
CREATE TABLE IF NOT EXISTS `cards` (
`ID` int(11) NOT NULL,
`Name` varchar(200) NOT NULL,
`WorkerID` varchar(20) NOT NULL,
`pic` varchar(200) NOT NULL,
`expDate` bigint(20) NOT NULL,
`reminderSent` tinyint(4) NOT NULL,
`regNum` varchar(8) NOT NULL,
`cardType` varchar(200) NOT NULL
) ENGINE=MyISAM AUTO_INCREMENT=92 DEFAULT CHARSET=latin1;
ALTER TABLE `cards`
ADD PRIMARY KEY (`ID`), ADD KEY `cardsWorkerID_idx` (`WorkerID`);
But running:
explain
SELECT pic, expDate, Name, ID, cardType, regNum FROM cards WHERE workerID= 18
tells me it is scanning the entire table, even though I added an index to the workerID field. Can anyone explain what I'm missing?
The use of indexes depends on the size of the data. It also depends on the types used for the comparison. If you have a small table, then the SQL engine might decide that a scan is more efficient than using the index. This is particularly true if the table fits on a single data page.
In your case, though, the problem is might be data conversion. Use the appropriate typed constant for the comparison:
SELECT pic, expDate, Name, ID, cardType, regNum
FROM cards
WHERE workerID = '18';

Which column(s) to index in MySQL

I'm trying to optimize the following table, according to phpMyAdmin several stats regarding Table Scans are high and indices do not exist or are not being used. (Handler read rnd next 5.7 M)
1.
$query = "
SELECT * FROM apps_discrep
WHERE discrep_station = '$station'
AND discrep_date = '$date'
ORDER BY discrep_timestart";
2.
$query = "
SELECT * FROM apps_discrep
WHERE discrep_date BETWEEN '$keyword' AND '$keyword3'
AND (discrep_station like '$keyword2%') ORDER BY discrep_date";
Would it be correct to Index discrep_station, discrep_date, and discrep_timestart?
There currently only exist the Primary Unique Index on the auto-increment ID.
-- Table structure
`index` int(11) NOT NULL AUTO_INCREMENT,
discrep_station varchar(5) NOT NULL,
discrep_timestart time NOT NULL,
discrep_timestop time NOT NULL,
discrep_date date NOT NULL,
discrep_datetime timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
discrep_show varchar(31) NOT NULL,
discrep_text text NOT NULL,
discrep_by varchar(11) NOT NULL,
discrep_opr varchar(11) NOT NULL,
email_traffic varchar(3) NOT NULL,
email_techs varchar(3) NOT NULL,
email_promos varchar(3) NOT NULL,
email_spots varchar(3) NOT NULL,
eas_row varchar(11) NOT NULL,
PRIMARY KEY (`index`)
ENGINE=MyISAM DEFAULT CHARSET=utf8;
It looks to me like you can get both queries with the same BTREE index, since that allows you to use the left-most tuples as a separate index.
Consider this MySQL doc page as a reference.
ALTER TABLE xxx ADD KEY `key1` (`discrep_station`, `discrep_date`, `discrep_timestart`) USING BTREE;
Your first query will use all 3 fields in the index. The second query will only use the first 2 fields in the index.