Query for looping values in column - mysql

I need to make a query that moves values of only one column one row up ↑ at a time:
+------------+----------------+
| anotherCOL | values_to_loop |
+------------+----------------+
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
| 4 | 4 |
| 5 | 5 |
| 6 | 6 |
| 7 | 7 |
| 8 | 8 |
| 9 | 9 |
| 10 | 10 |
+------------+----------------+
So, the next time i run the query, it should look like this
+------------+----------------+
| anotherCOL | values_to_loop |
+------------+----------------+
| 1 | 2 |
| 2 | 3 |
| 3 | 4 |
| 4 | 5 |
| 5 | 6 |
| 6 | 7 |
| 7 | 8 |
| 8 | 9 |
| 9 | 10 |
| 10 | 1 |
+------------+----------------+
I need to loop the values of only one MYSQL COLUMN, as in move the values one ROW UP ↑ each time I run the query.
Notice: Tables provided are just illustrative, the data is different.

Here's how you can do it within a single UPDATE query:
UPDATE tbl a
INNER JOIN (
SELECT values_to_loop
FROM (SELECT * FROM tbl) c
ORDER BY anotherCOL
LIMIT 1
) b ON 1 = 1
SET a.values_to_loop =
IFNULL(
(SELECT values_to_loop
FROM (SELECT * FROM tbl) c
WHERE c.anotherCOL > a.anotherCOL
ORDER BY c.anotherCOL
LIMIT 1),
b.values_to_loop
)
It works as follows:
Updates all records from tbl
Joins with a temporary table to retrieve the top value of values_to_loop (the one that will go to the bottom)
Set the new value for values_to_loop to the corresponding value from the next row (c.anotherCOL > a.anotherCOL ... LIMIT 1)
Notes:
This works even if there are gaps in anotherCOL (eg: 1, 2, 3, 6, 9, 15)
It is required to use (SELECT * FROM tbl) instead of tbl because you're not allowed to use the table that you're updating in the update query
Faster query when there are no gaps in anotherCOL
If there are no gaps for values in anotherCOL you can use the query below that should work quite fast if you have an index on anotherCOL:
UPDATE tbl a
LEFT JOIN tbl b on b.anotherCOL = a.anotherCOL + 1
LEFT JOIN (
SELECT values_to_loop
FROM tbl
WHERE anotherCOL = (select min(anotherCOL) from tbl)
) c ON 1 = 1
SET a.values_to_loop = ifnull(
b.values_to_loop,
c.values_to_loop
)

I`ve created a sample table and added both a select to get the looped values and update to loop the values in the table. Also, using a #start_value variable to know the "1" which might be other. Try this:
CREATE TEMPORARY TABLE IF NOT EXISTS temp_table
(other_col INT, loop_col int);
INSERT INTO temp_table (other_col, loop_col) VALUES (1,1);
INSERT INTO temp_table (other_col, loop_col) VALUES (2,2);
INSERT INTO temp_table (other_col, loop_col) VALUES (3,3);
INSERT INTO temp_table (other_col, loop_col) VALUES (4,4);
INSERT INTO temp_table (other_col, loop_col) VALUES (5,5);
DECLARE start_value INT;
SELECT start_value = MIN(loop_col) FROM temp_table;
SELECT T1.other_col, ISNULL(T2.loop_col, start_value)
FROM temp_table T1
LEFT JOIN temp_table T2
ON T1.loop_col = T2.loop_col - 1;
UPDATE T1 SET
T1.loop_col = ISNULL(T2.loop_col, #start_value)
FROM temp_table T1
LEFT JOIN temp_table T2
ON T1.loop_col = T2.loop_col - 1;
SELECT *
FROM temp_table;
Let me know if it works for you.
Step by step:
1 - created a temp_table with values 1 to 5
2 - declared a start_value which will keep the lowest value for the column you to need to loop through
3 - select all rows from temp_table self left join with same temp_table. join condition is on loop_col - 1 so it can shift the rows up
4 - the same self left join, but this time update the values in place too.
please note that in case i get a null value, it should be the start_value there, because it cannot match

Perhaps these are what you had in mind:
update T
set values_to_loop = mod(values_to_loop, 10) + 1
update T
set values_to_loop =
coalesce(
(
select min(t2.values_to_loop) from T t2
where t2.values_to_loop > T.values_to_loop
),
(
select min(values_to_loop) from T
)
)

Related

Select duplicated values from one coloum and get there value in one row

i have table with 2 columns like below
+----------+----------+
| Column A | Column B |
+----------+----------+
| 123 | ABC |
| 123 | XYC |
| 123 | FGH |
| 145 | QWE |
| 147 | YUI |
+----------+----------+
I want to select all values from table but view it like below:
+----------+---------+---------+----------+
| Column A | value 1 | value 2 | value 3 |
+----------+---------+---------+----------+
| 123 | ABC | XYC | FGH |
| 145 | QWE | | |
| 147 | YUI | | |
+----------+---------+---------+----------+
If you're not trying to create extra columns in your output, you can simply use GROUP_CONCAT with the separator of your choice. For example:
SELECT `Column A`,
GROUP_CONCAT(`Column B` SEPARATOR ' | ') AS `Values`
FROM table1
GROUP BY `Column A`
Output:
Column A Values
123 ABC | XYC | FGH
145 QWE
147 YUI
Demo on dbfiddle
I'm not sure how are you going to execute the query? but if you can manage to create dynamic SQL query string to find all duplicates rows and insert each row into a temp table and other values (unique) into a separate temp table. Then create another query to join all temp tables (with duplicate) value into a new data set, union all of them with the (unique) data set.
It may be a long and not a good solution but here's my experiment:
Insert all duplicates rows into #temp tables (3 rows= 3 #temp tables)
SELECT Id,Name
INTO #temp1
FROM TestTable
WHERE Name='ABC'
SELECT Id,Name
INTO #temp2
FROM TestTable
WHERE Name='XYC'
SELECT Id,Name
INTO #temp3
FROM TestTable
WHERE Name='FGH'
Insert all unique rows into single #temptable
SELECT Id,Name
INTO #temp4
FROM TestTable
WHERE Id!=123
Query
SELECT t1.Id,t1.Name as Value1,t2.Name as Value2,t3.Name as Value3
FROM #temp1 t1
INNER JOIN #temp2 t2 on t1.Id=t2.Id
INNER JOIN #temp3 t3 on t1.Id=t3.Id
UNION ALL
SELECT t4.Id,t4.Name as Value1,null as Value2,null as Value3
FROM #temp4 t4
Result
If you want three different columns, you can use row_number() and conditional aggregation:
select a,
max(case when seqnum = 1 then b end) as b_1,
max(case when seqnum = 2 then b end) as b_2,
max(case when seqnum = 3 then b end) as b_3
from (select t.*,
row_number() over (partition by a order by b) as seqnum
from t
) t
group by a;

MySQL: Insert records virtually in SELECT statement

I get a set of result as follows
C1 C2 C3
10 2 T
10 3 E
10 6 S
I want my SELECT query in such a way that resultant records may look like
C1 C2 C3
10 2 T
10 3 E
10 4
10 5
10 6 S
where there is a blank line for the missing records. Couldn't figure out the same.
Original query: select C1, C2,C3 from Table
If your mysql version upper than 8.0, you can try to use cte RECURSIVE make a calendar table then do outer join
Schema (MySQL v8.0)
CREATE TABLE T(
C1 int,
C2 int,
C3 varchar(5)
);
INSERT INTO T VALUES (10,2,'T');
INSERT INTO T VALUES (10,3,'E');
INSERT INTO T VALUES (10,6,'S');
Query #1
WITH RECURSIVE CTE AS (
SELECT C1,MIN(C2) minC2,MAX(C2) maxC2
FROM T
GROUP BY C1
UNION ALL
SELECT C1,minC2 +1,maxC2
FROM CTE
WHERE minC2+1 <= maxC2
)
SELECT t1.C1,t1.minC2,t2.C3
FROM CTE t1 LEFT JOIN T t2 on t1.minC2 = t2.C2
ORDER BY C1,minC2;
| C1 | minC2 | C3 |
| --- | ----- | --- |
| 10 | 2 | T |
| 10 | 3 | E |
| 10 | 4 | |
| 10 | 5 | |
| 10 | 6 | S |
View on DB Fiddle
You can create a table of sequential numbers in your database, and then use an outer join to fill in the missing row values for C2.
It will be very useful for other queries as well, and takes very little space.
CREATE TABLE Numbers (Number INTEGER PRIMARY KEY);
INSERT INTO Numbers (Number) VALUES (1),(2),(3),(4),(5),(6) ...
And then:
SELECT T.C1, N.Number AS C2, T.C3
FROM Numbers AS N LEFT OUTER JOIN T ON T.C2 = N.Number
WHERE N.Number BETWEEN (SELECT MIN(C2) FROM T) AND (SELECT MAX(C2) FROM T)
ORDER BY C2;
HTH

Display row of another table as column of current table

Consider there are two tables:
Table1:
**Result Total**
Pass 102
Fail 3
Undetermined 1
Table 2:
**Pass% Fail% Undetermined%**
96.23 2.83 0.94
Result Needed:
**Result Total Percentage**
Pass 102 96.23
Fail 3 2.83
Undetermined 1 0.94
How to convert the table 2 rows as column in table 1 to obtain the result ?
first, You can try to do unpivot on Table2, then JOIN with Table1.
Your sql-server version is 2008, you can use unpivot by UNION ALL.
CREATE TABLE T1(
Result VARCHAR(50),
Total int
);
CREATE TABLE T2(
Pass FLOAT,
Fail FLOAT,
Undetermined FLOAT
);
insert into T2 VALUES (96.23,2.83,0.94)
INSERT INTO T1 VALUES ('Pass',102);
INSERT INTO T1 VALUES ('Fail',3);
INSERT INTO T1 VALUES ('Undetermined',1);
Query 1:
SELECT t1.*,s.val
FROM (
SELECT Pass val,'PASS' Name
FROM T2
UNION ALL
SELECT Fail val,'Fail' Name
FROM T2
UNION ALL
SELECT Undetermined val,'Undetermined' Name
FROM T2
) s inner join T1 t1 on t1.Result = s.Name
Results:
| Result | Total | val |
|--------------|-------|-------|
| Pass | 102 | 96.23 |
| Fail | 3 | 2.83 |
| Undetermined | 1 | 0.94 |
If you can use CROSS APPLY with VALUE you can try this.
Query:
SELECT t1.*,s.val
FROM (
SELECT v.* FROM T2
CROSS APPLY(VALUES
(Pass,'PASS'),
(Fail,'Fail'),
(Undetermined,'Undetermined')
) v(val,Name)
) s inner join T1 t1 on t1.Result = s.Name
Results:
| Result | Total | val |
|--------------|-------|-------|
| Pass | 102 | 96.23 |
| Fail | 3 | 2.83 |
| Undetermined | 1 | 0.94 |

Update value based on value from nearest smaller neigbour

I have a table with a column A that is INT(11) (it's a timestamp, but for now I just use small numbers)
id | A | diff |
---+----+------+
1 | 12 | |
2 | 7 | |
3 | 23 | |
4 | 9 | |
5 | 2 | |
6 | 30 | |
I like to update diff with the difference between A and it's nearest smaller neighbour. So if A=12 it's first smaller neightbour is A=7, if A=30 it is A=23. I should end up with a table like this (sorted on A):
id | A | diff |
---+----+------+
5 | 2 | - |
2 | 7 | 5 | (7-5)
4 | 9 | 2 | (9-7)
1 | 12 | 3 | (12-9)
3 | 23 | 11 | (23-12)
6 | 30 | 7 | (30-23)
I can calculate the difference at the moment of insertion, as I know A then (here: A=15):
INSERT INTO `table` (`A`,`diff`)
(SELECT 15 , 15-`A` FROM `table` WHERE `A` < 15 ORDER BY `A` DESC LIMIT 1)
This results in a new record:
id | A | diff |
---+----+------+
7 | 15 | 3 | (3 being the difference between A=12 and A=15
(NOTE: This fails miserably when A=1, being the new smallest value and having no smaller neighbour, so no value of diff)
But now the value of diff in record 3 is wrong, because it still is based on the difference between 23 - 12 as is now should be 23 - 15.
So I just want to insert the A value and then run an update on the table, refreshing diff where necessery. But that's where my knowledge of MYSQL ends...
I crafted this query, but it fails saying `You can't specify table 't1' for update in FROM clause
UPDATE `table` AS t1
SET
t1.`diff` = t1.`A` - (SELECT `A` FROM `table`
WHERE `A` < t1.`A`
ORDER BY `A` DESC LIMIT 1
)
Here's a query:
SELECT x.*
, x.a-MAX(y.a) diff
FROM my_table x
LEFT
JOIN my_table y
ON y.a < x.a
GROUP
BY x.id
ORDER
BY a;
I'm not sure why you would want to store derived data, but you can I guess...
UPDATE my_table m
JOIN
( SELECT x.*
, x.a-MAX(y.a) q
FROM my_table x
JOIN my_table y
ON y.a < x.a
GROUP
BY x.id
) n
ON n.id = m.id
SET m.diff = q;
You may try this after inserting new value :
UPDATE x
SET
x.diff = iq2.new_diff
FROM
#t x
INNER JOIN
(SELECt id,A,diff , new_diff
FROM
(select id,A,15 as new_number,
CASE WHEN (A-15) < 0 THEN NULL ELSE (A-15) END as new_diff,diff
from #t
) iq
WHERE
iq.new_diff <= iq.diff
AND iq.new_diff <> 0
)iq2
on x.A = iq2.A
inner query compares the previous difference and current one and then updates the relevant ones.

Copy columns and edit one column with different entries in a table

Copy 1st 2 rows in same table and insert it with edited column as shown below.
Table 1 (ID is auto increment)
ID | CL1 | CL2 | CL3
1 | A | text1 | NULL
2 | B | text2 | NULL
Table 2
ID | CL3
21 | 45
24 | 63
Converted Table 1
ID | CL1 | CL2 | CL3
1 | A | text1 | NULL
2 | B | text2 | NULL
3 | A | text1 | 45
4 | B | text2 | 63
I know how to copy and insert all the rows with one column duplicated, but changing some column with different value is the problem.
Below is the query to copy all fields with 1 column changed:
INSERT INTO table1 (col1, col2, col3)
SELECT col1, col2, 1
FROM table1 LIMIT 2;
Ex: So now we have table2 which has table1 CL3's values. Now can we get the data from another table and insert them while copying?
Assuming you want the first 2 records from 1 table updated with the values from the 1st 2 rows from another table, then I think you will need to add a sequence number to each one and join based on that.
Something like as follows, but it won't be quick!
INSERT INTO table1 (ID, CL1, CL2, CL3)
SELECT NULL, a.CL1, a.CL2, b.CL3
FROM
(
SELECT CL1, CL2, #cnt1:=#cnt1 + 1 AS cnt
FROM table1
CROSS JOIN (SELECT #cnt1:=0) sub0
ORDER BY ID
LIMIT 2
) a
INNER JOIN
(
SELECT CL3, #cnt2:=#cnt2 + 1 AS cnt
FROM table2
CROSS JOIN (SELECT #cnt2:=0) sub0
ORDER BY ID
LIMIT 2
) b
ON a.cnt = b.cnt