MySql: update row that requires some logic processing - mysql

Given the following table:
id | company | names | group
-------------------------------------
0 | 1 | John | 1
1 | 1 | Doe | null //populate with preceding group number (i.e. 1)
2 | 1 | Yo | null //populate with preceding group number (i.e. 1)
3 | 1 | Zoe | null //populate with preceding group number (i.e. 1)
4 | 1 | Jack | 2
5 | 1 | Doe | null //populate with preceding group number (i.e. 2)
6 | 1 | Yo | null //populate with preceding group number (i.e. 2)
May I know how i can update only the null values to its preceding group number via sql statements? Thanks.

Try this:
UPDATE tablename t1
JOIN (
SELECT ID, #s:=IF(`group` IS NULL, #s, `group`) `group`
FROM (SELECT * FROM tablename ORDER BY ID) r,
(SELECT #s:=NULL) t
) t2
ON t1.ID = t2.ID
SET t1.`group`= t2.`group`
SQLFIDDLE DEMO

Related

Get First Non-null Value From Selected Rows For Every Column

I am trying to create a query that can get First Non-null value for selected columns from the table.
I cant fire multiple queries and union it per every column as I have so many columns. I tried to create a query using answers form some SO questions. but it doesn't work for me.
Example Table
| orders| id | default_address |
|-------|------|-----------------|
| 1 | 1 | null |
| 2 | null | null |
| 3 | 2 | 3 |
Expected Result
| id | default_address |
|----|-----------------|
| 1 | 3 |
Another Example Table
| orders| id | default_address |
|-------|------|-----------------|
| 1 | 1 | null |
| 2 | null | 5 |
| 3 | 2 | 3 |
Expected Result
| id | default_address |
|----|-----------------|
| 1 | 5 |
What i tried is here
http://sqlfiddle.com/#!9/84a5c/1
http://sqlfiddle.com/#!9/574481/2
Here is one way to do this using analytic functions, assuming you are using MySQL 8+:
WITH cte AS (
SELECT *,
COUNT(id) OVER (ORDER BY orders) cnt_id,
COUNT(default_address) OVER (ORDER BY orders) cnt_addr
FROM yourTable
)
SELECT
MAX(CASE WHEN cnt_id = 1 THEN id END) AS id,
MAX(CASE WHEN cnt_addr = 1 THEN default_address END) AS default_address
FROM cte;
This assumes that there actually exist a third column orders which generates the ordering shown in your sample data.
This trick works because the COUNT() function by default only counts non NULL values. So, used a window function with the ordering given by the orders column, it would only equal 1 at the first non NULL value.
For earlier MySQL versions:
SELECT
MAX(id) AS id,
MAX(default_address) AS default_address
FROM
(
SELECT
CASE WHEN (SELECT COUNT(t2.id) FROM yourTable t2
WHERE t2.orders <= t1.orders) = 1 THEN id END AS id,
CASE WHEN (SELECT COUNT(t2.default_address) FROM yourTable t2
WHERE t2.orders <= t1.orders) = 1 THEN default_address END AS default_address
FROM yourTable t1
) t;

Selecting best row in each group based on two columns [duplicate]

This question already has answers here:
SQL select only rows with max value on a column [duplicate]
(27 answers)
Closed 4 years ago.
Suppose we have the following table, where each row represents a submission a user made during a programming contest, id is an auto-increment primary key, probid identifies the problem the submission was made to, score is the number of points the submission earned for the problem, and date is the timestamp when the submission was made. Each user can submit as many times as they want to the same problem:
+----+----------+--------+-------+------------+
| id | username | probid | score | date |
+----+----------+--------+-------+------------+
| 1 | brian | 1 | 5 | 1542766686 |
| 2 | alex | 1 | 10 | 1542766686 |
| 3 | alex | 2 | 5 | 1542766901 |
| 4 | brian | 1 | 10 | 1542766944 |
| 5 | jacob | 2 | 10 | 1542766983 |
| 6 | jacob | 1 | 10 | 1542767053 |
| 7 | brian | 2 | 8 | 1542767271 |
| 8 | jacob | 2 | 10 | 1542767456 |
| 9 | brian | 2 | 7 | 1542767522 |
+----+----------+--------+-------+------------+
In order to rank the contestants, we need to determine the best submission each user made to each problem. The "best" submission is the one with the highest score, with ties broken by submission ID (i.e., if the user got the same score on the same problem twice, we only care about the earlier of the two submissions). This would yield a table like the following:
+----------+--------+----+-------+------------+
| username | probid | id | score | date |
+----------+--------+----+-------+------------+
| alex | 1 | 2 | 10 | 1542766686 |
| alex | 2 | 3 | 5 | 1542766901 |
| brian | 1 | 4 | 10 | 1542766944 |
| brian | 2 | 7 | 8 | 1542767271 |
| jacob | 1 | 6 | 10 | 1542767053 |
| jacob | 2 | 5 | 10 | 1542766983 |
+----------+--------+----+-------+------------+
How can I write a query to accomplish this?
SELECT username , probid , id , score , `date`
FROM tableName
ORDER BY username, score DESC, ID
Using MySQL-8.0 or MariaDB-10.2 or later:
SELECT username, probid, id, score, `date`
FROM (
SELECT username, probid, id, score, `date`,
ROW_NUMBER() over (
PARTITION BY username,probid
ORDER BY score DESC) as `rank`
FROM tablename
) as tmp
WHERE tmp.`rank` = 1
This query will work on versions of MySQL prior to 8.0 as well. The LEFT JOIN removes duplicate scores, ensuring that equal scores only have the lowest date in the result set for a given score. Then the WHERE clause ensures that we have the maximum score for a given user/problem combination:
SELECT t1.username, t1.probid, t1.id, t1.score, t1.date
FROM tablename t1
LEFT JOIN tablename t2
ON t2.username = t1.username AND
t2.probid = t1.probid AND
t2.score = t1.score AND
t2.date < t1.date
WHERE t2.id IS NULL AND
t1.score = (SELECT MAX(score) FROM tablename t3 WHERE t3.username = t1.username AND t3.probid = t1.probid)
ORDER BY t1.username, t1.probid
Update
It's almost certainly more efficient to JOIN the table to a list of maximum scores per user per problem first rather than computing the MAX value for each row in the result table. This query does that instead:
SELECT t1.username, t1.probid, t1.id, t1.score, t1.date
FROM tablename t1
JOIN (SELECT username, probid, MAX(score) AS score
FROM tablename
GROUP BY username, probid) t2
ON t2.username = t1.username AND
t2.probid = t1.probid AND
t2.score = t1.score
LEFT JOIN tablename t3
ON t3.username = t1.username AND
t3.probid = t1.probid AND
t3.score = t1.score AND
t3.date < t1.date
WHERE t3.id IS NULL
ORDER BY t1.username, t1.probid
Output (for both queries):
username probid id score date
alex 1 2 10 1542766686
alex 2 3 5 1542766901
brian 1 4 10 1542766944
brian 2 7 8 1542767271
jacob 1 6 10 1542767053
jacob 2 5 10 1542766983
Updated Demo on SQLFiddle
In pre-MySQL 8.0.2, we can emulate Row_Number() functionality using User-defined Variables. In this technique, we firstly get the data in a particular order (depends on the problem statement at hand).
In your case, within a partition of probid and username, we need to rank scores in descending order, with the row having lower timestamp value given higher priority (to break the ties). So, we will ORDER BY probid, username, score DESC, date ASC.
Now, we can use this result-set as a Derived Table, and determine the row number. It will be like a Looping technique (which we use in application code, eg: PHP). We would store the previous row values in the User-defined variables, and use conditional CASE .. WHEN expressions to check the current row's value(s) against the previous row. And, then assign row number accordingly.
Eventually, we will consider only those rows where row number is 1, and (if required), sort it by username and probid.
Query
SELECT dt2.username,
dt2.probid,
dt2.id,
dt2.score,
dt2.date
FROM (SELECT #rn := CASE
WHEN #un = dt1.username
AND #pid = dt1.probid THEN #rn + 1
ELSE 1
end AS row_no,
#un := dt1.username AS username,
#pid := dt1.probid AS probid,
dt1.id,
dt1.score,
dt1.date
FROM (SELECT id,
username,
probid,
score,
date
FROM your_table
ORDER BY username,
probid,
score DESC,
date ASC) AS dt1
CROSS JOIN (SELECT #un := '',
#pid := 0,
#rn := 0) AS user_init_vars) AS dt2
WHERE dt2.row_no = 1
ORDER BY dt2.username, dt2.probid;
Result
| username | probid | id | score | date |
| -------- | ------ | --- | ----- | ---------- |
| alex | 1 | 2 | 10 | 1542766686 |
| alex | 2 | 3 | 5 | 1542766901 |
| brian | 1 | 4 | 10 | 1542766944 |
| brian | 2 | 7 | 8 | 1542767271 |
| jacob | 1 | 6 | 10 | 1542767053 |
| jacob | 2 | 5 | 10 | 1542766983 |
View on DB Fiddle

I need to get the average for every 3 records in one table and update column in separate table

Table Mytable1
Id | Actual
1 ! 10020
2 | 12203
3 | 12312
4 | 12453
5 | 13211
6 | 12838
7 | 10l29
Using the following syntax:
SELECT AVG(Actual), CEIL((#rank:=#rank+1)/3) AS rank FROM mytable1 Group BY rank;
Produces the following type of result:
| AVG(Actual) | rank |
+-------------+------+
| 12835.5455 | 1 |
| 12523.1818 | 2 |
| 12343.3636 | 3 |
I would like to take AVG(Actual) column and UPDATE a second existing table Mytable2
Id | Predict |
1 | 11133
2 | 12312
3 | 13221
I would like to get the following where the Actual value matches the ID as RANK
Id | Predict | Actual
1 | 11133 | 12835.5455
2 | 12312 | 12523.1818
3 | 13221 | 12343.3636
IMPORTANT REQUIREMENT
I need to set an offset much like the following syntax:
SELECT #rank := #rank + 1 AS Id , Mytable2.Actual FROM Mytable LIMIT 3 OFFSET 4);
PLEASE NOTE THE AVERAGE NUMBER ARE MADE UP IN EXAMPLES
you can join your existing query in the UPDATE statement
UPDATE Table2 T2
JOIN (
SELECT AVG(Actual) as AverageValue,
CEIL((#rank:=#rank+1)/3) AS rank
FROM Table1, (select #rank:=0) t
Group BY rank )T1
on T2.id = T1.rank
SET Actual = T1.AverageValue

MySQL select from specific ID until match condition

Given this table:
+----+-----------+--------+
| id | condition | values |
+----+-----------+--------+
| 1 | a | 1 |
+----+-----------+--------+
| 2 | a | 2 |
+----+-----------+--------+
| 3 | a | 3 |
+----+-----------+--------+
| 4 | a | 4 |
+----+-----------+--------+
| 5 | b | 5 |
+----+-----------+--------+
| 6 | b | 6 |
+----+-----------+--------+
How can I get a new table that begins on id=3 (including) and goes until condition = b (excluding):
+----+-----------+--------+
| id | condition | values |
+----+-----------+--------+
| 3 | a | 3 |
+----+-----------+--------+
| 4 | a | 4 |
+----+-----------+--------+
added fiddle: http://sqlfiddle.com/#!2/9882f7
Basically I want a table between a matching first condition (over a specific column - id) and a second one (over a different column - condition)
You need to stop thinking of SQL data as having any order. Think of SQL data in sets; you have to search by values, not by positions.
SELECT t1.*
FROM t AS t1
JOIN (
SELECT MIN(id) AS id FROM t
WHERE id >= 3 AND `condition` = 'b'
) AS t2
WHERE t1.id >= 3 AND t1.id < t2.id
ORDER BY t1.id
Something like this:
select t.*
from table t
where id >= 3 and id < (select min(t2.id) from table t2 where t2.condition = 'b');
EDIT:
This query works fine on the SQL Fiddle:
select t.*
from t
where id >= 3 and id < (select min(t2.id) from t t2 where t2.condition = 'b');
If I understand what you are asking for, I believe this will work for you:
SELECT id, condition, values
FROM tableName
WHERE id > 2
AND condition != b
ORDER BY id
I hope that works for you.

update other rows in same table when one row is deleted

table Lets call it test
----------------------------
| id | catid | value |
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 2 | 1 |
| 4 | 1 | 3 |
| 5 | 1 | 4 |
| 6 | 1 | 5 |
| 7 | 2 | 2 |
----------------------------
Suppose i deleted the 2nd row from table. Now the table will become:
----------------------------
| id | catid | value |
| 1 | 1 | 1 |
| 3 | 2 | 1 |
| 4 | 1 | 3 |
| 5 | 1 | 4 |
| 6 | 1 | 5 |
| 7 | 2 | 2 |
----------------------------
So in catid1, there is no value 2. so the values will become, 1,3,4,5,.... so far so on....
Goal I need to update the values as subtracting 1 from their previous value so that the continuity is maintined (all values goes one up if the condition is met i.e. if bgger than the deleted value).
CODE
After delete, i am trying to perform an update query.
UPDATE `test`
SET `value` =
(
SELECT t.param FROM (
SELECT `value`-1 AS param
FROM `test`
WHERE
`catid` = 1 AND `value` > 2
) AS t
)
WHERE
`catid` = 1
AND `value` > 2
Now, the most inner query will return all the rows which has value >2 where as the update expects a scalar value of string or numeric.
So the question is how can i return the value in the innermost query with respect to what row is update query targeting?
e.g. If update query is trying to update the row with id 4, the innermost query will retreive the value column with the same row id only and give it back to outer query (after subtracting 1).
Is there any alternative approach in this?
JOIN that table with this subquery like this:
UPDATE test t1
INNER JOIN
(
SELECT
id, catid, value - 1 AS value
FROM test
WHERE catid = 1
AND value > 2
) AS t2 ON t1.id = t2.id
SET t1.value = t2.value;
SQL fiddle demo.
But there is no need for this subquery, you can do this directly like this:
UPDATE test t1
SET value = value - 1
WHERE catid = 1
AND value > 2;
However, You might need to fix that column id to match those values like this:
UPDATE test AS t1
INNER JOIN
(
SELECT
*,
(#rownum := #rownum + 1) AS newID
FROM test, (SELECT #rownum := 0) AS t
ORDER BY Id
) AS t2 ON t1.id = t2.id
SET t1.id = t2.newId,
t1.value = CASE
WHEN t1.catid = 1 AND t1.value > 2 THEN t2.value - 1
ELSE t1.value
END;
Updated SQL Fiddle Demo.