Laravel Schema: Require at least one of four columns is not null - mysql

I have a structure that looks like this:
Attribute Values Table
+----+-----------+---------------+-------------+-------------+
| id | option_id | integer_value | price_value | text_value |
+----+-----------+---------------+-------------+-------------+
| 1 | 4 | NULL | NULL | NULL |
| 2 | NULL | 24 | NULL | NULL |
| 3 | NULL | NULL | NULL | Lorem Ipsum |
| 4 | NULL | NULL | 30.50 | NULL |
+----+-----------+---------------+-------------+-------------+
(Some columns were removed for brevity)
But basically the type of the entry can either be a reference to a select option (option_id), or an integer value (integer_value), or a decimal value (price_value), or a text value (text_value). One of the omitted columns is attribute_id which corresponds to an entry that has an enum which stores which one of these four columns is it.
Is there any way to add a constraint in the Laravel schema that at least one of these four columns has to be non-null?

If you will always have only one of these column filled at any time I would recomend altering your schema to just two colums
Like
+----------+--------------+------------+
| id | value | type |
+----------+--------------+------------+
Where the value will always have a value and type will contain the 4 types that you have mentioned
ie: option_id, integer_value, price_value and text_value
This way its clean.
In laravel you cannot add the specified constraint. You must validate your input before insertion.
However if you really want to add a database level constraint you could try adding a trigger before each insert to validate your need using the DB::unprepared function on the table.
and the trigger could look like this (I have not tried this)
DB::unprepared("
DELIMITER $$
CREATE TRIGGER `foo`
BEFORE INSERT ON `table`
FOR EACH ROW
BEGIN
IF (
new.option_id IS NULL
AND new.integer_value IS NULL
AND new.price_value IS NULL
AND new.text_value IS NULL
)
THEN SIGNAL SQLSTATE '02000'
SET MESSAGE_TEXT = 'your message here';
END IF;
END$$
DELIMITER ;
");

Related

MySQL: Adding column to existing table. Have the values ready, how do I enter them altogether?

I have table with data on old game characters. I'd like to add a gender column.
If I do
ALTER TABLE characters
ADD gender ENUM('m','f') AFTER char_name
then I get a column full of NULLs. How do I get the values in?
Using an INSERT statement tries to tag them all into new rows, instead of replacing the NULLs.
Using an UPDATE statement requires a new statement for every single entry.
Is there any way to just drop a "VALUES ('m'),('f'),('f'),('m'),('f') etc" into the ALTER statement or anything else and update them all efficiently?
There is no way to fill in specific values during ALTER TABLE. The value will be NULL or else a default value you define for the column.
You may find INSERT...ON DUPLICATE KEY UPDATE is a convenient way to fill in the values.
Example:
CREATE TABLE characters (
id serial primary key,
char_name TEXT NOT NULL
);
INSERT INTO characters (char_name) VALUES
('Harry'), ('Ron'), ('Hermione');
SELECT * FROM characters;
+----+-----------+
| id | char_name |
+----+-----------+
| 1 | Harry |
| 2 | Ron |
| 3 | Hermione |
+----+-----------+
Now we add the gender column. It will add the new column with NULLs.
ALTER TABLE characters
ADD gender ENUM('m','f') AFTER char_name;
SELECT * FROM characters;
+----+-----------+--------+
| id | char_name | gender |
+----+-----------+--------+
| 1 | Harry | NULL |
| 2 | Ron | NULL |
| 3 | Hermione | NULL |
+----+-----------+--------+
Now we update the rows:
INSERT INTO characters (id, char_name, gender) VALUES
(1, '', 'm'), (2, '', 'm'), (3, '', 'f')
ON DUPLICATE KEY UPDATE gender = VALUES(gender);
It looks strange to use '' for the char_name, but it will be ignored anyway, because we don't set it in the ON DUPLICATE KEY clause. The original char_name is preserved. Specifying the value in the INSERT is necessary only because the column is defined NOT NULL and has no DEFAULT value.
SELECT * FROM characters;
+----+-----------+--------+
| id | char_name | gender |
+----+-----------+--------+
| 1 | Harry | m |
| 2 | Ron | m |
| 3 | Hermione | f |
+----+-----------+--------+
DBFiddle

on duplicate key update result affecting all the rows of the table

I have a table of this structure:
mysql> desc securities;
+-----------------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------------+-------------+------+-----+---------+-------+
| sym | varchar(19) | NO | PRI | | |
| bqn | int(11) | YES | | NULL | |
| sqn | int(11) | YES | | NULL | |
| tqn | int(11) | YES | | NULL | |
+-----------------+-------------+------+-----+---------+-------+
4 rows in set (0.01 sec)
I am trying to do a select and an update within the same query, so the reason I have chosen
insert into securities (sym, bqn, sqn , tqn) values('ANK', 50,0,1577798)
on duplicate key update bqn=bqn+50 , sqn=sqn+0 , tqn=tqn+1577798;
When I ran the above I observed it is in fact changing the values for all the other rows also.
Is this behaviour expected? I am using MySQL Database.
Your fiddle is missing the key, and the INSERT statement in the right panel (where it does not belong in the first place) is using different column names … *sigh*
Define the symbol column as PRIMARY KEY – and use the VALUES() syntax to get the values to add in the ON UPDATE part, so that you don’t have to repeat them every single time:
insert into securities
(symbol, buyerquan, sellerquan , totaltradedquan)
values('BANKBARODA', 73, 0, 4290270)
on duplicate key update
buyerquan=buyerquan+VALUES(buyerquan),
sellerquan=sellerquan+VALUES(sellerquan),
totaltradedquan=totaltradedquan+VALUES(totaltradedquan);
Works perfectly fine, result values are as to be expect from the input: http://sqlfiddle.com/#!2/21638f/1

Unexpected behaviour INT NOT NULL DEFAULT -1

I rarely work with negative numbers (except personal finances) so perhaps that's why there's a gap in my knowledge here...
Consider the following, prompted by a response to a question asked by another user in SO (How to achieve default value if column value is NULL?):
-- Mysql Version 5.5.16
-- sql_mode = ''
DROP TABLE prices;
CREATE TABLE prices (price_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,price INT SIGNED NOT NULL DEFAULT -1);
INSERT INTO prices (price) VALUES (' '),(''),(NULL);
INSERT INTO prices (price_id) VALUES (NULL);
SELECT * FROM prices;
Expected output:
+----------+-------+
| price_id | price |
+----------+-------+
| 1 | -1 |
| 2 | -1 |
| 3 | -1 |
| 4 | -1 |
+----------+-------+
Actual output:
+----------+-------+
| price_id | price |
+----------+-------+
| 1 | 0 |
| 2 | 0 |
| 3 | 0 |
| 4 | -1 |
+----------+-------+
Why?
Intermediate answer: In a nutshell, it seems that if you want to be sure of inserting the default value (when sql_mode is not set), either omit the column from the INSERT or explicitly INSERT a DEFAULT value, i.e.: INSERT INTO prices (price) VALUES(DEFAULT); To me, this goes against the spirit of a DEFAULT value !?!?
Seems like:
a.) If you provide a NULL value to a not null numeric field (not autoincrementing), the default is zero.
b.) If you dont provide a value (as in the last row), you use the given default value (-1)
http://dev.mysql.com/doc/refman/5.0/en/data-type-defaults.html
If you use Mysql STRICT MODE then the result could be different.
Currently you are providing a value NULL, the server tries to map this value to the closest INT value. The server is not using the default value of -1 because it is taking NULL as a valid value.

Can a primary key be empty? If yes why did this alter cause this result?

I have the following table:
mysql> DESC my_contacts;
+----------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+-------+
| id | varchar(20) | NO | PRI | | |
| location | varchar(20) | YES | | NULL | |
| city | varchar(20) | YES | | NULL | |
| state | varchar(2) | YES | | NULL | |
+----------+-------------+------+-----+---------+-------+
4 rows in set (0.01 sec)
If I do a select all I get:
mysql> SELECT * FROM my_contacts;
+----+--------------+------+-------+
| id | location | city | state |
+----+--------------+------+-------+
| 1 | Chester,NJ | NULL | NULL |
| 2 | Katy,TX | NULL | NULL |
| 3 | San Mateo,CA | NULL | NULL |
+----+--------------+------+-------+
3 rows in set (0.00 sec)
I run the following command:
INSERT INTO my_contacts (city,state)
VALUES
(SUBSTRING_INDEX(location,',',1),RIGHT(location,2));
My purpose was to populate the columns city and state with the part before the comma and the part after the comma from the location column.
But the following happened to my table:
mysql> INSERT INTO my_contacts (city,state)
-> VALUES
-> (SUBSTRING_INDEX(location,',',1),RIGHT(location,2));
Query OK, 1 row affected (0.02 sec)
mysql> SELECT * FROM my_contacts;
+----+--------------+------+-------+
| id | location | city | state |
+----+--------------+------+-------+
| | NULL | NULL | NULL |
| 1 | Chester,NJ | NULL | NULL |
| 2 | Katy,TX | NULL | NULL |
| 3 | San Mateo,CA | NULL | NULL |
+----+--------------+------+-------+
4 rows in set (0.00 sec)
I get a record and the id which is the primary key is empty. How is this possible?
I mean it is not NULL but a primary key is not supposed to be empty either right?
You defined your id field as a varchar, which is a dumb idea when you're using it to store integers. an empty field is NOT null. a zero-length string is still a valid string, and therefore a valid id value as far as your table is concerned. Try inserting ANOTHER blank string and you'll get a primary key violation:
INSERT INTO yourtable (id) VALUES (''); // will not work
The id field should be an int type. That'd disallow "empty" values.
primary keys are unique so if you alter the table, then the second row will attempt to add an empty value and fail. as a result, it will attempt the next possible value. If you want the first value not to be empty, you can set a default value.
It's not empty. It's probably an empty string. Note that the datatype is varchar(20).
Well, you didn't assign a value to the primary key field, so the default is NULL.
.
You want to modify the table so the primary key is auto_increment.
You can use a varchar as a foreign key related to another database table, but if you wish to use it as a numerical key, you should utilize a numerical data type such as int.
I know this doesn't answer the precise question regarding the primary key, but as your question does point out the fact you are also having issues parsing out the city and state from your location column, here's the query you would want to use (note you want an UPDATE to modify existing rows, not an INSERT which will add new rows rather than columns):
UPDATE my_contacts
SET
city = substr(location, 1, locate(',', location) - 1),
state = substr(location, locate(',', location) + 1);

Delete duplicate rows GROUP BY with LIKE

I have a string in database (mysql) which is like:
{"StateId":73,"CallTime":"\/Date(1336365498912+0500)\/","CallId":"1336365489.14157","Target":"agi://127.0.0.1"}},"Profile":{"$type":"DataWriter.DbProfile, DataWriterObjects","Name":"DataService","Provider":"mssql","ConnectionString":"Data Source=localhost\\mydb; Database=mydb; User Id=sa; Password=admin;"}}
The string is a JSON object which contains multiple fields. The problem is that I have multiple duplicate rows which I want to remove from the database. A row is considered a duplicate if the CallId and StateId is same but the CallTime is different. So first I want to get list of the duplicates (GROUP BY) of those rows which have CallId same and ignore the difference in CallTime. The below record has different CallTime from the first one but same CallId, hence it is considered a duplicate (basically need not to consider CallTime for duplicate)
{"StateId":73,"CallTime":"\/Date(1336365498913+0500)\/","CallId":"1336365489.14157","Target":"agi://127.0.0.1"}},"Profile":{"$type":"DataWriter.DbProfile, DataWriterObjects","Name":"DataService","Provider":"mssql","ConnectionString":"Data Source=localhost\\mydb; Database=mydb; User Id=sa; Password=admin;"}}
So how do I do a GROUP BY? Basically everything in the GROUP BY should be matched ignoring the CallTime value.
The table structure is
mysql> describe Statements;
+------------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+-------------+------+-----+---------+----------------+
| SequenceId | bigint(10) | NO | PRI | NULL | auto_increment |
| Profile | varchar(32) | YES | MUL | NULL | |
| CacheItem | text | NO | | NULL | |
+------------+-------------+------+-----+---------+----------------+
After that I want to delete the duplicates. Anyone help me out?
I think your database is not atomic enough, you may have to split out your JSON string into separate fields