Best way to update/translate the values of a field mysql - mysql

So, I need to update/translate a field called status_id on my mysql table, basically following this:
WHERE | SHOULD BE
0 | 0
1 | 1
2 | 4
3 | 8
4 | 9
5 | 10
6 | 6
7 | 2
8 | 11
I've though of a few methods, but i'm not sure which one would be the best
The first one:
Use some transition elements, in this case, the desired final value+100
UPDATE myTable
SET status_id = 100
WHERE status_id = 0;
-- ...
UPDATE myTable
SET status_id = 111
WHERE status_id = 8;
-- ...
UPDATE myTable
SET status_id = 0
WHERE status_id = 100;
-- ...
UPDATE myTable
SET status_id = 11
WHERE status_id = 111;
The second one:
use CASE
UPDATE myTable SET status_id =
CASE
WHEN status_id = 0 THEN 0
-- ...
WHEN status_id = 8 THEN 11
ELSE status_id
END
This will only be done once, i just want to make sure i do not mess this up (I have backups, but it's always good to not need them)

For MySql 8.0+ you can create a CTE that returns each pair of current and new status_id and join it to the table in the UPDATE statement:
WITH cte(status_id, new_status_id) AS (VALUES
ROW(0, 0),
ROW(1, 1),
ROW(2, 4),
ROW(3, 8),
ROW(4, 9),
ROW(5, 10),
ROW(6, 6),
ROW(7, 2),
ROW(8, 11)
)
UPDATE myTable t
INNER JOIN cte c ON c.status_id = t.status_id
SET t.status_id = c.new_status_id
See the demo.
For prior versions join a subquery to the table:
UPDATE myTable t
INNER JOIN (
SELECT 0 status_id, 0 new_status_id UNION ALL
SELECT 1, 1 UNION ALL
SELECT 2, 4 UNION ALL
SELECT 3, 8 UNION ALL
SELECT 4, 9 UNION ALL
SELECT 5, 10 UNION ALL
SELECT 6, 6 UNION ALL
SELECT 7, 2 UNION ALL
SELECT 8, 11
) c ON c.status_id = t.status_id
SET t.status_id = c.new_status_id
See the demo.
Note that I don't see any reason to keep where both values are equal, but MySql will not perform update in such a case.

If you want to have some extra fun, you can use MySQL's JSON capabilities:
UPDATE myTable SET status_id = JSON_EXTRACT(
CAST('
{
"0": 0,
"1": 1,
"2": 4,
"3": 8,
"4": 9,
"5": 10,
"6": 6,
"7": 2,
"8": 11
}'
AS JSON
),
CONCAT('$."', status_id, '"')
)
It can actually be more convenient to write and maintain (and in some cases also cleaner and simpler to construct automatically in code).

You can put your values in a "holder table".
CREATE TEMPORARY TABLE my_temp_table (before_value int, after_value int) ENGINE=MEMORY;
/* populate your my_temp_table with your cross-walk values */
INSERT INTO my_temp_table (before_value, after_value)
SELECT 101, 201 FROM DUAL
UNION ALL SELECT 102, 202 FROM DUAL;
(etc, etc, i'm giving example values, put in your own real values.)
Update myTable
Set status_id = my_temp_table.after_value
from myTable , my_temp_table
Where
myTable.status_id = my_temp_table.before_value
That means you have "matches" that are not ambiguous.
The above is (one) "set based" approach.
If you have to do it sequentially (aka, you can't do the second update until the first update has been completed), you have to update one by one (also known as Row by Agonizing Row) RBAR.
REFERENCE:
https://dev.mysql.com/doc/refman/8.0/en/create-temporary-table.html

UPDATE myTable
SET status_id = FIND_IN_SET(status_id, '1,7,x,2,x,6,x,3,4,5,8')
WHERE status_id BETWEEN 0 AND 8;
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=5dd617361fcf6a3d2d669625dc809db1
PS. This is a special case which allows such special solution.

Related

Custom query with group by and then count

I am using events.I would like to know how to calculate sum in event or using single query
http://sqlfiddle.com/#!9/ad6d1c/1
DDL for question:
CREATE TABLE `table1` (
`id` int(11) NOT NULL,
`group_id` int(11) NOT NULL DEFAULT '0',
`in_use` tinyint(1) NOT NULL DEFAULT '1' COMMENT '0->in_use,1->not_in_use',
`auto_assign` tinyint(1) NOT NULL DEFAULT '0' COMMENT '0->Yes,1->No'
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
ALTER TABLE `table1`
ADD PRIMARY KEY (`id`);
ALTER TABLE `table1`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
INSERT INTO `table1` (`id`, `group_id`, `in_use`, `auto_assign`) VALUES
(1, 3, 1, 0),(2, 2, 0,1),(3, 1, 1, 1),(4, 3, 1, 0),(5, 3, 0, 0),(6, 3, 0, 1),
(7, 3, 1, 0),(8, 3, 0, 1),(9, 3, 0, 1),(10, 3, 0, 1),(11, 3, 0, 1),(12, 3, 1, 1),
(13, 3, 1, 0),(14, 3, 0, 0),(15, 3, 0, 0),(16, 3, 0, 0),(17, 3, 0, 0),(18, 3, 1, 1),
(19, 3, 0, 0),(20, 3, 0, 0)
Expected Output :
| count | in_use | auto_assign | sum | check_count |
|-------|--------|-------------|------|------------ |
| 7 | 0 | 0 | 11 | 5 |
| 5 | 0 | 1 | 07 | 3 |
| 4 | 1 | 0 | 11 | 5 |
| 2 | 1 | 1 | 07 | 3 |
Here we can see that auto_assign=0 have total 11 count(7+4) and
auto_assign=1 have 7 count(5+2) this count should be stored into new column sum.
check_count column is percentage value of sum column.Percentage will be predefined.
Lets take 50%, So count 11(sum column value) ->50% = 5.5 = ROUND(5.5) == 5(In integer). Same way count 7(sum column value)->50% = 3.5 =ROUND(3.5)=3(Integer)
Here 5 > 4(auto_assign=0 and in_use=1 ).So have to insert record into another table(table2). if not then not.
Same way, If 3 >2 then also need to insert record into another table(table2).if not then not.
Note : This logic I would like to implement in event
This is bit complicated, but please suggest me how to do this in event.
Detail clarification :
here percentage_Value is 5 for auto_assign =0.But auto_assign=0 and in_use=1 have count is 4 which less than 5 ,then have to insert record into table 2.
suppose,if we get count is 6 for auto_assign=0 and in_use=1 ,Then no need to insert record into table2.
Same way,
here percentage_Value is 3 for auto_assign =1.But auto_assign=1 and in_use=1 have count is 2 which less than 3 ,then have to insert record into table 2.
suppose,if we get count is 4 for auto_assign=1 and in_use=1 ,Then no need to insert record into table2.
Insert query into table2:
Insert into table2(cli_group_id,auto_assign,percentage_value,result_value) values(3,0,5,4)
DEMO Fiddle
Break the problem down: we need a count of the records by auto_Assigns; so we generate a derived table (B) with that value and join back to your base table on auto_Assign. This then gives us the column we need for some and we use the truncate function and a division model to get the check_count
SELECT count(*), in_use, A.Auto_Assign, B.SumC, truncate(B.SumC/2,0) as check_Count
FROM table1 A
INNER JOIN (Select Auto_Assign, count(*) sumC
from table1
where Group_ID = 3
Group by Auto_Assign) B
on A.Auto_Assign = B.Auto_Assign
WHERE GROUP_ID = 3
Group by in_use, A.Auto_Assign
we can eliminate the double where clause by joining on it:
SELECT count(*), in_use, A.Auto_Assign, B.SumC, truncate(B.SumC/2,0) as check_Count
FROM table1 A
INNER JOIN (Select Auto_Assign, count(*) sumC, Group_ID
from table1
where Group_ID = 3
Group by Auto_Assign, Group_ID) B
on A.Auto_Assign = B.Auto_Assign
and A.Group_ID = B.Group_ID
Group by in_use, A.Auto_Assign
I'd need clarification on the rest of the question: I'm not sure what 5 > 4 your'e looking at and I see no 3 other than the check count but that's not "the same way" so I'm not sure what you're after.
Here 5 > 4(auto_assign=0 and in_use=1 ).So have to insert record into another table(table2). if not then not.
Same way, If 3 >2 then also need to insert record into another table(table2).if not then not.
Note : This logic I would like to implement in event
This is bit complicated, but please suggest me how to do this in event.
So to create the event: DOCS
Which results in:
CREATE EVENT myevent
ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 6 Minutes
DO
INSERT INTO table2
SELECT count(*) as mCount
, in_use
, A.Auto_Assign
, B.SumC, truncate(B.SumC/2,0) as check_Count
FROM table1 A
INNER JOIN (SELECT Auto_Assign, count(*) sumC, Group_ID
FROM table1
WHERE Group_ID = 3
GROUP BY Auto_Assign, Group_ID) B
ON A.Auto_Assign = B.Auto_Assign
AND A.Group_ID = B.Group_ID
GROUP BY in_use, A.Auto_Assign

MySQL query row/column difference

I'm trying to write a MySQL query that does the following:
I have a table that looks like this
Id t
-----------
1 1
2 4
1 6
2 9
1 12
2 14
I need to find the sum of the t column for each Id of 2, and subtract from it the sum of the t column for each Id of 1.
So for this example, the sum of Id 1 is 19, and the sum of Id 2 is 27.
I would want the output to then be 8.
I would imagine the statement would look similar to:
SELECT sum(t) WHERE Id = 2 - sum(t) WHERE Id = 1;
But this obviously isn't proper syntax.
And I apologize for the poorly drawn table, I'm still new to stackoverflow.
You could use a CASE statement:
SELECT
SUM(CASE
WHEN Id = 2 THEN t
WHEN Id = 1 THEN 0 - t
ELSE 0
END) AS mySum
FROM myTable
Hopefully that works as-is... I only have SQL Server to test on, but the syntax should be the same for MySQL.
SELECT SUM(IF(`id` = 2, t, 0)) - SUM(IF(`id` = 1, t, 0)) as `result` FROM `table`
Depends on how big is your table. If it is small or no indexes you can do:
select sum(if( Id=2,t,if(Id=1,-t,0)))
from data;
If you have plenty of rows and have an index in column Id:
select sum(id2)-sum(id1)
from (
select 0 as 'id1', sum(t) as 'id2'
from data
where id=2
union
select sum(t) as 'id1', 0 as 'id2'
from data
where id=1
) as d;

How to chain a series of numbers together in a MySQL DB with two columns defining the series connections?

I have a MySQL DB that defines series of numbers within sets as such:
set item1 item2
1 1 2
1 2 3
1 3 4
1 4 5
1 5 6
I want to write a query (or queries) that returns to me the fact that set 1 is a series of numbers that spans from 1 to 6. Is this possible?
Please note that the real DB I'm dealing with contains hundreds of sets and that each set can contain a series of items that can be somewhat long as well (up to 50 items per set, I'm guessing). Also, I'm not totally sure, but the DB might also have cases where the series of numbers split. Using the example above, there may be instances like the following:
set item1 item2
1 1 2
1 2 3
1 3 4
1 4 5
1 5 6
1 3 7
1 7 8
1 8 9
In which case, I'd want to know that set 1 has two series of numbers: [1, 2, 3, 4, 5, 6] and [1, 2, 3, 7, 8, 9]. Is this possible with hopefully one query (or if necessary, multiple queries)?
Edit: Please note that I used the numbers 1-9 in sequential order to make the question easier to understand. The real data is much more mixed up and not that orderly.
As you are aware, MySQL cannot handle recursion 'out-of-the-box', so options include:
writing a stored procedure
switching from an adjacency list to an alternative model (e.g. nested set)
joining the table to itself as often as could be required
handling the recursion in application level code (e.g. a bit of PHP)
Here is an example using option 3, but it could be easily adapted to suit option 4...
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(
family_id INT NOT NULL,
item_id INT NOT NULL,
parent_id INT NULL,
PRIMARY KEY(family_id, item_id)
);
INSERT INTO my_table
VALUES (101, 1, null), (101, 2, 1), (101, 3, 2), (101, 4, 3),
(101, 5, 4), (101, 6, 5), (101, 7, 3), (101, 8, 7), (101, 9, 8);
SELECT CONCAT_WS(','
, a.item_id
, b.item_id
, c.item_id
, d.item_id
, e.item_id
, f.item_id
, g.item_id
, h.item_id
, i.item_id
) series
FROM
my_table a
LEFT JOIN
my_table b ON b.parent_id = a.item_id AND b.family_id = a.family_id
LEFT JOIN
my_table c ON c.parent_id = b.item_id AND c.family_id = b.family_id
LEFT JOIN
my_table d ON d.parent_id = c.item_id AND d.family_id = c.family_id
LEFT JOIN
my_table e ON e.parent_id = d.item_id AND e.family_id = d.family_id
LEFT JOIN
my_table f ON f.parent_id = e.item_id AND f.family_id = e.family_id
LEFT JOIN
my_table g ON g.parent_id = f.item_id AND g.family_id = f.family_id
LEFT JOIN
my_table h ON h.parent_id = g.item_id AND h.family_id = g.family_id
LEFT JOIN
my_table i ON i.parent_id = h.item_id AND i.family_id = h.family_id
WHERE
a.parent_id IS NULL;
+-------------+
| series |
+-------------+
| 1,2,3,4,5,6 |
| 1,2,3,7,8,9 |
+-------------+
I can solve the first problem.
Note that "set" is a keyword, so I renamed the first column to "sset"
You can see the result in http://sqlfiddle.com/#!9/ef6360/5
Create table and insert data:
create table test
(
sset int not null
, item1 int not null
, item2 int not null
) engine=InnoDB;
insert into test
values
(1, 1, 2)
, (1, 2, 3)
, (1, 3, 4)
, (1, 4, 5)
, (1, 5, 6)
Run the query:
select
sset
, group_concat(distinct item1or2 order by item1or2 asc)
from
(
select
sset
, item1 as item1or2
from test
union all
select
sset
, item2 as item1or2
from test
) u;
The output is:
1,2,3,4,5,6

join with union of multiple tables in SQL

I have the following data structure:
a table entries with a column entry_id
a table data_int with columns entry_id, question and data
a table data_text with columns entry_id, question and data
a table questions with columns question_id
Now I would like to make a MySQL query that does the following: for a given entry_id (say 222) it should select all question_id q from that table for which there is no row with (entry_id=222 AND question_id=q) in data_int, and also no such row in data_text. Is this possible in a single query, and if so how should I do this?
A sample data set would be
entries:
1
2
data_int:
1, 1, 4
1, 2, 56
1, 6, 43
1, 7, -1
data_text:
1, 3, 'hello'
1, 5, 'world'
questions:
1
2
3
4
5
6
7
8
9
10
Then for entry_id=1, the return value should be 4, 8, 9, 10, since these don't appear in either data_ table for entry_id=1.
For entry_id=2, the return value should be 1,2,3,4,5,6,7,8,9,10 since nothing appears in any of the data_ tables.
There are a couple ways to do this. The more efficient way with mysql is probably using multiple outer join / null checks.
select q.*
from questions q
left join data_int di on q.questionid = di.questionid and di.entryid = 1
left join data_text dt on q.questionid = dt.questionid and dt.entryid = 1
where di.entryid is null and dt.entryid is null

Return the k rows that appear the most

Lets say I got this table
photo_id user_id tag
0 0 Car
0 0 Bridge
0 0 Sky
20 1 Car
20 1 Bridge
2 2 Bridge
2 2 Cat
1 3 Cat
I need to return the k tags that appear the most, WITHOUT USING LIMIT.
tie breaker for tags that appear the same number of times will be the lexicographically order (smallest will have the highest score).
I will need for each tag the number of tags he appeared as well.
for example, for the table above with k=2 the output should be:
Tag #
Bridge 3
Car 2
and for k=4:
Tag #
Bridge 3
Car 2
Cat 2
Sky 1
Try this :
SELECT t1.tag, COUNT(*) as mycount FROM table t1
GROUP BY t1.tag
ORDER BY mycount DESC
LIMIT 2;
Replace the limit ammount for your k var.
Inserting data into table:
INSERT INTO new_table VALUES
(0,0,'Car'),
(0,0,'Bridge'),
(0,0,'Sky'),
(20,1,'Car'),
(20,1,'Bridge'),
(0,0,'bottle');
To query:
SELECT tag, COUNT(1) FROM new_table
GROUP BY tag HAVING COUNT(1) = (
SELECT MIN(c) FROM
(
SELECT COUNT(1) AS c FROM new_table GROUP BY tag
) AS temp
)
Output:
+--------+----------+
| tag | count(1) |
+--------+----------+
| bottle | 1 |
| Sky | 1 |
+--------+----------+
Note : Get smallest count tag
Although this is homework and we are not supposed to answer such questions (not till you've proved that attempted to solve it and not getting desired result), I got a little curious about not using LIMIT in this question, so I am posting here.
The idea is to rank the result and then select only rows whose rank are less than or equal to value k (as in your case). The rank column is like adding a S.No. (serial number) column to your result and selecting till desired number.
DDL statements:
CREATE TABLE new_table(
photo_id INTEGER,
user_id INTEGER,
tag VARCHAR(10)
);
INSERT INTO new_table VALUES
(0, 0, 'Car'),
(0, 0, 'Bridge'),
(0, 0, 'Sky'),
(20, 1, 'Car'),
(20, 1, 'Bridge'),
(2, 2, 'Bridge'),
(2, 2, 'Cat'),
(1, 3, 'Cat');
Query:
SELECT
tag, tag_count,
#k := #k + 1 AS k
FROM (
SELECT
tag,
COUNT(*) AS tag_count
FROM new_table
GROUP BY tag
ORDER BY tag_count DESC
) AS temp, (SELECT #k := 0) AS k
WHERE #k < 2;
Check this SQLFiddle.