MySQL table with key/value pairs, get keys as column names - mysql

I have a MySQL database where I can do the following query:
mysql> SELECT Name, Value from info WHERE ID = 110506;
+-------+-----------+
| Name | Value |
+-------+-----------+
| start | 228196 |
| stop | 228318 |
| fwd | 0 |
+-------+-----------+
3 rows in set (0.00 sec)
I am trying to construct a query where the result would be
+--------+--------+-----+
| start | stop | fwd |
+--------+--------+-----+
| 228196 | 228318 | 0 |
+------- +--------+-----+
1 row in set (0.00 sec)
I do not know in advance what the names in my name column will be, so I need to somehow dynamically set them from the result of my SELECTquery. How do I do that?

Try this
SELECT
MAX(CASE WHEN name = 'start' THEN value END) AS `start`,
MAX(CASE WHEN name = 'stop' THEN value END) AS `stop`,
MAX(CASE WHEN name = 'fwd' THEN value END) AS `fwd`
FROM info
WHERE id = 110506;

You can use CASE ... WHEN clause on the column values and use the same value as column name for the resulting set. And you have to apply an aggregate function to summarize the results into a single row.
Following example uses MAX aggregate function to summarise the results.
select
max( case `name` when 'start' then `value` end ) as `start`
, max( case `name` when 'stop' then `value` end ) as `stop`
, max( case `name` when 'fwd' then `value` end ) as `fwd`
from `table_name`
;

Related

How to maintain the sort at insert-select scripts?

We have a table called tblINUser, which has many records and occupies a vast amount of space. In an attempt to reduce the amount of used space, we create a table called tblINUserSortByFilter which contains all the possible text values of this field and we create a foreign key in tblINUser that numerically references this value. We have several databases, because this database is sharded, so it would be great to sort the values similarly accross databases. This was the first attempt:
CREATE TABLE MC.tblINUserSortByFilterType(
pkINUserSortByFilterTypeID SMALLINT(6) PRIMARY KEY AUTO_INCREMENT,
SortByFilter varchar(45) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'first',
INDEX(SortByFilter)
);
INSERT INTO MC.tblINUserSortByFilterType(SortByFilter)
SELECT DISTINCT SortByFilter
FROM MC.tblINUser
ORDER BY SortByFilter = 'first';
ALTER TABLE MC.tblINUser
ADD COLUMN fkINUserSortByFilterTypeID SMALLINT(6) DEFAULT 1,
ADD INDEX (fkINUserSortByFilterTypeID);
UPDATE MC.tblINUser INUser
JOIN MC.tblINUserSortByFilterType INUserSortByFilterType
ON INUser.SortByFilter = INUserSortByFilterType.SortByFilter
SET INUser.fkINUserSortByFilterTypeID = INUserSortByFilterType.pkINUserSortByFilterTypeID;
ALTER TABLE MC.tblINUser
DROP COLUMN SortByFilter;
You may argue, correctly that the sort has the only criteria, which is ORDER BY SortByFilter = 'first' and a clause of ORDER BY SortByFilter = 'first', SortByFilter would be an obvious improvement. This would be a correct criticism, yet, even though we may have a chaotic behavior starting from the second record, it would be reasonable to expect that the very first inserted record would be first, yet, unfortunately, this is not the case. Running select * from MC.tblINUserSortByFilterType; yields
+----------------------------+----------------------------+
| pkINUserSortByFilterTypeID | SortByFilter |
+----------------------------+----------------------------+
| 5 | first |
| 4 | first-ASC |
| 3 | last |
| 1 | none |
| 2 | StatTeacher.IsActive DESC |
+----------------------------+----------------------------+
as we can see, not even this expectation is met, since first has an id of 5. An improvement is achieved by changing the inserts to
INSERT INTO MC.tblINUserSortByFilterType(SortByFilter)
SELECT DISTINCT SortByFilter
FROM MC.tblINUser
WHERE SortByFilter = 'first';
INSERT INTO MC.tblINUserSortByFilterType(SortByFilter)
SELECT DISTINCT SortByFilter
FROM MC.tblINUser
WHERE SortByFilter <> 'first';
and then the result of the same selection we get this result:
+----------------------------+----------------------------+
| pkINUserSortByFilterTypeID | SortByFilter |
+----------------------------+----------------------------+
| 1 | first |
| 3 | first-ASC |
| 4 | last |
| 2 | none |
| 5 | StatTeacher.IsActive DESC |
+----------------------------+----------------------------+
5 rows in set (0.00 sec)
as we can see, first is correctly receiving a value of 1. Yet, it seems that if we run the same inserts over different copies of the database, the order of subsequent rows might be unreliable. So, how could we ensure that the records would be inserted in the exact order that the following query yields?
SELECT DISTINCT SortByFilter
FROM MC.tblINUser
WHERE SortByFilter = 'first', SortByFilter;
I know that we can solve this by
using a cursor for the insert
looping the records received
inserting them individually
But that would have as many insert statements as the number of records the above query yields. Is there a way to achieve the same with a single command?
it would be reasonable to expect that the very first inserted record would be first
I don't think so. You used ORDER BY SortByFilter = 'first' which returns 0 for all values except 'first', followed by 1 for 'first'. The value 1 sorts after the value 0, so the entry 'first' ends up being last. The other values end up sorting more or less randomly.
Demo:
mysql> create table mytable (SortByFilter varchar(64));
Query OK, 0 rows affected (0.02 sec)
mysql> insert into mytable values ('first'), ('first-ASC'),
('last'), ('none'), ('StatTeacher.IsActive DESC');
Query OK, 5 rows affected (0.01 sec)
Records: 5 Duplicates: 0 Warnings: 0
mysql> select SortByFilter='first', SortByFilter from mytable
order by SortByFilter = 'first';
+----------------------+---------------------------+
| SortByFilter='first' | SortByFilter |
+----------------------+---------------------------+
| 0 | first-ASC |
| 0 | last |
| 0 | none |
| 0 | StatTeacher.IsActive DESC |
| 1 | first |
+----------------------+---------------------------+
I suggest do not rely on automatic sorting. Be specific about the sort order of every value. Here's one way to do it:
mysql> select field(SortByFilter, 'first', 'first-ASC',
'none', 'StatTeacher.IsActive DESC', 'last') AS SortOrder,
SortByFilter
from mytable order by SortOrder;
+-----------+---------------------------+
| SortOrder | SortByFilter |
+-----------+---------------------------+
| 1 | first |
| 2 | first-ASC |
| 3 | none |
| 4 | StatTeacher.IsActive DESC |
| 5 | last |
+-----------+---------------------------+
To get the rows in a particular order, you must use an ORDER BY. That is straightforward to do if the object of the ORDER BY is a string and you want alphabetical order, or it is numeric and you want it in numeric order. Ditto for the reverse by using DESC.
For for some abnormal ordering, here is one trick:
ORDER BY FIND_IN_SET(my_column, "first,second,third,fourth")
Another:
ORDER BY my_column != 'first', my_column
That will list 'first' first, then do the rest in alphabetic order. (I am assuming my_column is a VARCHAR.)
ORDER BY my_column = 'last', my_column
Note that a boolean expression evaluates to 0 (for false) or 1 (for true); I am then depending on the sort order of 0 and 1.

Does a similar mysql function like coalesce exists for field data which errors out during insert?

Since some of the datetime values are future dates. My mysql sql insert is failing with the error below.
ERROR 1292 (22007): Incorrect datetime value: '2038-01-28 14:13:01' for column 'column_date_time' at row 3
Is there a mysql function similar to coalesce but for errors, that way when this error occurs, it grabs the value of now() instead?
Here is the sql code:
INSERT INTO rl (id, ad_id, leased_date_time)
SELECT 0, unit_id, max_end_time FROM
(
SELECT
0,
sps.units_id as unit_id,
max(sps.publish_schedule_endtime) as max_end_time
FROM publish_schedule as sps
join units as su on(su.units_id = sps.units_id)
join properties as sp on(sp.properties_id = su.properties_id)
join owners as so on(so.owners_id = sp.owners_id)
WHERE
DATE(sps.publish_schedule_endtime) > date(date_sub(now(), interval 12 month))
AND DATE(sps.publish_schedule_begintime) <> DATE(sps.publish_schedule_endtime)
AND so.owners_email like '%searchstr%'
group by su.units_id
) leased_ads
ON DUPLICATE KEY UPDATE leased_date_time=(now());
And the destination table structure:
mysql> desc rl;
+------------------+-------------------+------+-----+-------------------+-------------------+
| Field | Type | Null | Key | Default | Extra |
+------------------+-------------------+------+-----+-------------------+-------------------+
| id | smallint unsigned | NO | PRI | NULL | auto_increment |
| ad_id | smallint unsigned | NO | UNI | 0 | |
| leased_date_time | timestamp | NO | | CURRENT_TIMESTAMP | DEFAULT_GENERATED |
+------------------+-------------------+------+-----+-------------------+-------------------+
3 rows in set (0.01 sec)
Thanks
I seem to have got it working by using an IF statement in the SELECT statement. Here is the code that worked:
INSERT INTO rl (id, ad_id, leased_date_time)
SELECT 0, unit_id, IF(max_end_time > NOW(), NOW(), max_end_time) FROM
(
SELECT
0,
sps.units_id as unit_id,
max(sps.publish_schedule_endtime) as max_end_time
FROM publish_schedule as sps
join units as su on(su.units_id = sps.units_id)
join properties as sp on(sp.properties_id = su.properties_id)
join owners as so on(so.owners_id = sp.owners_id)
WHERE
DATE(sps.publish_schedule_endtime) > date(date_sub(now(), interval 12 month))
AND DATE(sps.publish_schedule_begintime) <> DATE(sps.publish_schedule_endtime)
AND so.owners_email like '%searchstr%'
group by su.units_id
) leased_ads
ON DUPLICATE KEY UPDATE leased_date_time=(now());

Updating a column with one query in MYSQL

To update a column in the database i use the following query
UPDATE table_name
SET column1 = value1
WHERE condition;
The problem with this query is that i end up updating one column at a time
i want to update a column by setting a condition and updating a whole column that meets the condition set
An example of what i have tried:
UPDATE adggtnz1.lng01_rpt_animrec
SET origin = 'New'
WHERE origin = NULL;
and the result of this query is
0 row(s) affected Rows matched: 0 Changed: 0 Warnings: 0 0
picture of sample data:
Why does NULL = NULL evaluate to false in SQL server
NO
WHERE origin = NULL;
YES
WHERE origin is NULL;
create table `lng01_rpt_animrec`
(
`origin` varchar(10)
)
insert into `lng01_rpt_animrec` (`origin`) values
('EADD'),
(null),
('EADD'),
(null),
(null),
('EADD'),
(null),
('EADD'),
('EADD');
UPDATE
lng01_rpt_animrec
SET
origin = 'New'
WHERE
origin is null;
select * from `lng01_rpt_animrec`
| origin |
| :----- |
| EADD |
| New |
| EADD |
| New |
| New |
| EADD |
| New |
| EADD |
| EADD |
db<>fiddle here
use case when clause for conditional update below query be an example for that
UPDATE table_name
SET column1 = case when 1<2 then value1 else value2 end
WHERE condition;

MySQL Group By Sum by Day

I have a table with
id int pk auto_inc | created int(11) | amount int | user_id int
I want to create a list of rows grouped by day totalling the amount field.
I have tried this:
SELECT created, sum(amount) as amount, id FROM total_log WHERE user_id = $this->user_id GROUP BY DAY(created)
This doesn't give the right results. They are getting grouped into one row.
The date is saved from dd/mm/yyyy format to unix time stamp like 1349046000
SELECT
DATE(FROM_UNIXTIME(created)) as d,
sum(amount) as amount
FROM total_log
WHERE user_id = $this->user_id
GROUP BY d
MySQL doesn't like mixing day and int columns:
mysql> select day(1349046000);
+-----------------+
| day(1349046000) |
+-----------------+
| NULL |
+-----------------+
1 row in set, 1 warning (0.00 sec)
mysql> show warnings;
+---------+------+----------------------------------------+
| Level | Code | Message |
+---------+------+----------------------------------------+
| Warning | 1292 | Incorrect datetime value: '1349046000' |
+---------+------+----------------------------------------+
1 row in set (0.00 sec)
So all of your rows will have NULL for day(some_int_value), and they'll all be in the same group.
I would suggest using a date or datetime type for that column instead.
Also, columns not in the group by clause should not be referenced in the select statement, unless an aggregating function is used on them.
try
SELECT
DAY(DATE(FROM_UNIXTIME(created))),
sum(amount) as amount
FROM total_log
WHERE user_id = $this->user_id
GROUP BY DAY(DATE(FROM_UNIXTIME(created)))

How do I combine two UPDATE statements in one MySQL query?

Greetings,
How would one go about performing two UPDATE statements in one query, for example:
UPDATE albums SET isFeatured = '0' WHERE isFeatured = '1'
combined with
UPDATE albums SET isFeatured = '1' WHERE id = '$id'
Basically, when a new album is featured, the previously featured album is switched back to normal and the newly featured one is set to active.
Thanks!
Try this:
UPDATE albums SET isFeatured = IF(id!='$id', '0','1')
When you have to do this sort of thing it is an indicator that your data model is wrong and could do with some fixing.
So, I'd recommend to add a seperate table featured_albums (FK: int id_album) and use that to determine if the album is featured.
Your update becomes
DELETE FROM featured_album; INSERT INTO featured_album SET id_album = $id;
When selecting join the tables
SELECT album.id,
album.name,
( id_album IS NOT NULL ) AS isfeatured
FROM album
LEFT JOIN featured_album ON id_album = album.id
As requested to expand on the above basically I'm suggesting adding a table that will contain a row indicating the currently selected album. This is a 1 to 1 relationship, i.e. one record in the album table has one related record in the feature_albums table. See Types of Relationship.
You remove the isFeatured field from the album table and add a new table.
CREATE TABLE `featured_album` (
`id_album` INTEGER NOT NULL,
FOREIGN KEY (id_album) REFERENCES `album` (`id`)
);
The DELETE FROM .. INSERT INTO line sets the featured album by creating an entry in the table.
The SELECT statement with the LEFT JOIN will pull in the records from the album table and join those that match from the featured_album table, in our case only one record will match so as there is one field in the featured_album table it will return NULL for all records except the featured album.
So if we did
SELECT album.id, album.name, featured_album.id_album as isFeatured0
FROM album
LEFT JOIN featured_album ON id_album = album.id
We'd get something like the following:
+----+----------------+------------+
| id | name | isFeatured |
+----+----------------+------------+
| 1 | Rumours | NULL |
| 2 | Snowblind | NULL |
| 3 | Telegraph road | 3 |
+----+----------------+------------+
i.e. a NULL for isFeatured or an ID.
By adding the ( id_album IS NOT NULL ) AS isfeatured and using the first query we get
+----+----------------+------------+
| id | name | isfeatured |
+----+----------------+------------+
| 1 | Rumours | 0 |
| 2 | Snowblind | 0 |
| 3 | Telegraph road | 1 |
+----+----------------+------------+
i.e. 0/1 for isfeatured which makes things more readable, although if you're processing the results in PHP it won't make a difference to your code.
You can use CASE WHEN statement and remember to set original value where necessary (ELSE clause below) and order CASE conditions as required (in statement below isFeatured will be 0 if row having requested id also has isFeatured = 1, to change it swap WHEN clauses).
UPDATE albums
SET isFeatured = CASE
WHEN isFeatured = '1' THEN '0'
WHEN id = '$id' THEN '1'
ELSE isFeatured
END
You just can't. You can only select one group of records that should be updated and can then only perform one operation on all of them. It's not possible to do
UPDATE x SET col1 = 1 WHERE col1 = 0 AND col1 = 0 WHERE col1 = 1;
Be careful when using functions to work around this, as they need to be evaluated for every row and this can become really expensive.
MySQL is unable to use the index when it is inside an if function:
You need an index on the function which is not possible in MySQL.
see also: How does one create an index on the date part of DATETIME field in MySql
I am using the employee test database http://dev.mysql.com/doc/employee/en/employee.html
mysql> describe employees;
+------------+---------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------+---------------+------+-----+---------+-------+
| emp_no | int(11) | NO | PRI | NULL | |
| birth_date | date | NO | | NULL | |
| first_name | varchar(14) | NO | | NULL | |
| last_name | varchar(16) | NO | | NULL | |
| gender | enum('M','F') | NO | | NULL | |
| hire_date | date | NO | | NULL | |
+------------+---------------+------+-----+---------+-------+
6 rows in set (0.01 sec)
mysql> select count(*) from employees;
+----------+
| count(*) |
+----------+
| 300024 |
+----------+
1 row in set (0.37 sec)
Set all genders to male so it mimics the question.
mysql> update employees set gender = 'M';
Query OK, 1 row affected (9.11 sec)
Rows matched: 300024 Changed: 1 Warnings: 0
mysql> select emp_no, gender from employees order by emp_no limit 2;
+--------+--------+
| emp_no | gender |
+--------+--------+
| 10001 | M |
| 10002 | M |
+--------+--------+
2 rows in set (0.00 sec)
Set one employee to female.
(Notice it uses the index and is almost instant.)
mysql> update employees set gender = 'F' where emp_no = 10001;
Query OK, 1 row affected (0.14 sec)
Rows matched: 1 Changed: 1 Warnings: 0
Now we use the suggested answer. (Notice it does not use the index and touches every row.)
mysql> update employees set gender = if(emp_no=10002, 'F', 'M');
Query OK, 2 rows affected (10.67 sec)
Rows matched: 300024 Changed: 2 Warnings: 0
Will an index help?
> mysql> create index employees_gender_idx on employees(gender);
Query OK, 300024 rows affected (21.61 sec)
Records: 300024 Duplicates: 0 Warnings: 0
> mysql> update employees set gender = if(emp_no=10001, 'F', 'M');
Query OK, 2 rows affected (9.02 sec)
Rows matched: 300024 Changed: 2 Warnings: 0
Nope.
It was also said that MySQL is only going to look at the rows that need to be changed.
mysql> update employees set gender = 'M';
Query OK, 1 row affected (8.78 sec)
Rows matched: 300024 Changed: 1 Warnings: 0
Guess not. What if use a WHERE clause?
mysql> update employees set gender = 'M' where gender ='F';
Query OK, 0 rows affected (0.03 sec)
Rows matched: 0 Changed: 0 Warnings: 0
Gee that fast, now it used the index.
Mysql has no idea what the IF function will return and must do a full table scan. Notice that WHERE really does mean where and SET really does mean set. You can't expect the DB to just arrange all your clauses to get good performance.
The correct solution is to issue two updates (which if use indexes will be almost instant.)
Notice, it was said elsewhere that MySQL will magically know only update the rows it needs to change.
Adding an alternate method to the excellent answer provided by
#too where instead of CASE IF statement is used -
UPDATE album
-> SET isFeatured = IF (
-> isFeatured = '1', '0', IF (
-> id = '$id', '1', isFeatured
-> ));
I don't think you can, or at least not in a neat or practical way.
If you're wanting to do one call from php/whatever then you can seperate them with semicolons thus:
UPDATE albums SET isFeatured = '0' WHERE isFeatured = '1';UPDATE albums SET isFeatured = '1' WHERE id = '$id';