Update table to record position based on text column - mysql

Here is my table:
id | position | name
1 | 2 | a
1 | 3 | b
1 | 1 | c
-------
2 | 1 | a
2 | 3 | b
2 | 4 | c
2 | 2 | d
--------
3 | 2 | a
3 | 1 | b
How can I write an UPDATE statement (NOT SELECT) to get a table like this?
id | position | name
1 | 1 | a
1 | 2 | b
1 | 3 | c
-------
2 | 1 | a
2 | 2 | b
2 | 3 | c
2 | 4 | d
--------
3 | 1 | a
3 | 2 | b
The position will always start by 1.
Currently my code is
ORDER BY id, name
So it is arranged by id first and then name. I want to change the wrong position numbers.

If you want to update the table so that the position column corresponds to the position of the 'name' column (in this example, the alphabet), you can use a case statement:
UPDATE myTable SET position =
CASE
WHEN name = 'a' THEN 1
WHEN name = 'b' THEN 2
WHEN name = 'c' THEN 3
...
ELSE 26
END;
Here is an SQL Fiddle example.
EDIT
To order based on the strings you have, you can first write a query using a variable to get the position of each string like this:
SET #position := 0;
SELECT #position := #position + 1, name
FROM(
SELECT DISTINCT name
FROM myTable
ORDER BY name) t;
Once you have that temporary table, you can join it to your original table and update position of the original to match position of the temp table, like this:
SET #position := 0;
UPDATE myTable m
JOIN(
SELECT #position := #position + 1 AS position, name
FROM(
SELECT DISTINCT name
FROM myTable
ORDER BY name) t) tmp ON tmp.name = m.name
SET m.position = tmp.position;
Here is an SQL Fiddle example of that.

Related

get empty instead of repeated value in query

I have a table like this
|num|id|name|prj|
| 1 | 1|abc | 1 |
| 2 | 1|efg | 1 |
| 3 | 1|cde | 1 |
| 4 | 2|zzz | 1 |
I want to run a query like this:
SELECT * FROM table WHERE prj=1 ORDER BY name
but printing out repeated values only once. I want to keep all the rows and I would like to do this at database level and not on the presentation layer (I know how to do it in php).
Desired result is
|num|id|name|prj|
| 1 | 1|abc | 1 |
| 3 | |cde | 1 |
| 2 | |efg | 1 |
| 4 | 2|zzz | 1 |
any hint on where to start from to build that query?
Use a session variable to test if the previous ID is the same as the current ID:
SELECT num, IF(#lastid = id, '', #lastid := id) AS id, name, prj
FROM table
CROSS JOIN (SELECT #lastid := null) x
ORDER BY table.id, name
DEMO
Note that you need to qualify table.id, because ORDER BY defaults to using the alias from the SELECT list if it's the same as a table column, and that would order the empty fields first.

How to write a MySQL SELECT Query to achieve this result?

I have an abstract problem which can be simplified as the following problem: Assume that we have two tables persons and names that look as follows:
SELECT * FROM persons;
+----+-------+--------+
| id | name | fan_of |
+----+-------+--------+
| 1 | alice | 2 |
| 2 | bob | 4 |
| 3 | carol | 1 |
| 4 | dave | 3 |
| 5 | bob | 2 |
+----+-------+--------+
and
SELECT * FROM names;
+----+-------+--------+
| id | name | active |
+----+-------+--------+
| 1 | alice | 1 |
| 2 | bob | 1 |
| 3 | carol | 0 |
| 4 | dave | 1 |
+----+-------+--------+
Every person (a row in the persons) table is a fan of itself or another person (represented by that other persons id in the fan_of column). The names table contains names that can be active or inactive.
For a given offset k, I want to SELECT the persons (rows of persons) that have the k+1-th active name as their name or that have one of these people as their fans. For example, if the offset is 1, the second active name is bob and hence I want to select all people with the name bob plus the people that have one of these bobs as their fans, which is in this example the row of persons with id=4. This means that I want to have the result:
+----+------+--------+
| id | name | fan_of |
+----+------+--------+
| 2 | bob | 4 |
| 4 | dave | 3 |
| 5 | bob | 2 |
+----+------+--------+
What I have so far is the following query:
1 SELECT * FROM persons WHERE
2 EXISTS (
3 SELECT * FROM (
4 SELECT * FROM names WHERE active=true LIMIT 1 OFFSET 1
5 ) AS selectedname WHERE (selectedname.name=persons.name)
6 )
7 OR
8 EXISTS (
9 SELECT * FROM(
10 SELECT * FROM persons WHERE EXISTS (
11 SELECT * FROM (
12 SELECT * FROM names WHERE active=true LIMIT 1 OFFSET 1
13 ) AS selectedname WHERE (selectedname.name=persons.name)
14 )
15 ) AS personswiththatname WHERE persons.id=personswiththatname.fan_of
16 );
It gives me the desired result from above but please note that it is inefficient because the lines 3-5 and 11-13 are the same.
I have the following two questions:
What can be done to avoid this inefficiency?
I actually need to distinguish between those rows that came from the
name condition (here the rows with name=bob) and those that came
from the fan_of condition (here the row with name=dave). This
could be done in the application code but then I would need another
database query before to find out the k+1-th active name and this might
be slow (please correct me if this is the better solution). I would
rather prefer an additional column z that helps me to distinguish
like
+----+------+--------+---+
| id | name | fan_of | z |
+----+------+--------+---+
| 2 | bob | 4 | 1 |
| 4 | dave | 3 | 0 |
| 5 | bob | 2 | 1 |
+----+------+--------+---+
How can such an output be achieved?
It looks like I can get the minimum you want to achieve using parameters (should this be an option).
It's not pretty, but I can't see a simple way of achieving what you're asking for, so this is what I have so far....(set #offset to suit 'k')
SET #offset = 1;
SET #name = (SELECT name FROM (select name, #rank := #rank +1 as Rank from names n, (SELECT #rank := 0) r where active !=0) as activeRanked where activeRanked.rank = (1 + #offset));
select
a.*
From persons a
where (a.name = #name) OR (a.id IN (SELECT fan_of from persons where name = #name));
If you still don't have an answer by the time I've had food, I'll look at part 2.
(hopefully I've read your brief correctly)
P.S. I've kept the #name SQL in a single line as it seems to read better in this context.
Edit: Here's a pretty messy but functional indicator of source, using your example. Z = 1 is where the row is from the name, '0' is from fan_of
SET #offset = 1;
SET #name = (SELECT name FROM (select name, #rank := #rank +1 as Rank from names n, (SELECT #rank := 0) r where active !=0) as activeRanked where activeRanked.rank = (1 + #offset));
select
a.*,'1' as z
From persons a
where (a.name = #name)
union
select
a.*,'0' as z
From persons a
where (a.id IN (SELECT fan_of from persons where name = #name));
Distinct ID Query:
SET #offset = 1;
SET #name = (SELECT name FROM (select name, #rank := #rank +1 as Rank from names n, (SELECT #rank := 0) r where active !=0) as activeRanked where activeRanked.rank = (1 + #offset));
SELECT id, name, fan_of, z FROM
(select
distinct a.id,
a.name,
a.fan_of,
1 as z
From persons a
where (a.name = #name)
union
select
distinct a.id,
a.name,
a.fan_of,
0 as z
From persons a
where (a.id IN (SELECT fan_of from persons where name = #name))
ORDER BY z desc) qry
GROUP BY id;
This produces:
+----+------+--------+---+
| id | name | fan_of | z |
+----+------+--------+---+
| 2 | bob | 4 | 1 |
| 5 | bob | 2 | 1 |
| 4 | dave | 3 | 0 |
+----+------+--------+---+

cumulative product over a big MySQL table

I have a big MySQL table on which I'd like to calculate a cumulative product. This product has to be calculated for each group, a group is defined by the value of the first column.
For example :
name | number | cumul | order
-----------------------------
a | 1 | 1 | 1
a | 2 | 2 | 2
a | 1 | 2 | 3
a | 4 | 8 | 4
b | 1 | 1 | 1
b | 1 | 1 | 2
b | 2 | 2 | 3
b | 1 | 2 | 4
I've seen this solution but don't think it would be efficient to join or subselect in my case.
I've seen this solution which is what I want except it does not partition by name.
This is similar to a cumulative sum:
select t.*,
(#p := if(#n = name, #p * number,
if(#n := name, number, number)
)
) as cumul
from t cross join
(select #n := '', #p := 1) params
order by name, `order`;

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

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.