MySql - Increment according to another column - mysql

I have a table (innoDB) that has 3 columns: ID, ID_FATHER, ROWPOS. ID is auto_increment and ROWPOS has values from other table. I need ID_FATHER to be incremented by 1 if ROWPOS is not a sequence, if it is a sequence ID_FATHER should not increment.
Like this:
ID | ID_FATHER | ROWPOS
1 | 1 | 250
2 | 2 | 253
3 | 2 | 254
4 | 3 | 260
5 | 4 | 263
6 | 5 | 268
7 | 6 | 270
8 | 6 | 271
9 | 6 | 272
10 | 7 | 276
Is there a way to do that?
With this query:
INSERT INTO mytable (i, rowpos)
SELECT #i := IF(t.rowpos = #prev_rowpos + 1, #i, #i + 1) AS i
, #prev_rowpos := t.rowpos AS rowpos
FROM temp
JOIN (SELECT #prev_rowpos := NULL, #i := 0) v
ORDER BY t.rowpos
I am able to import into the tables I want. But the problem is in the TABLE.Service, as you can see with this solution the ID_FATHER is wrong because it only increments by 1
but in this case it actually should be 2 because invoice 1 doesn't have service.
How can I solve this problem without changing all my schema.
TABLE.temp
ROW|TYPE |INVOICE_temp
1 |xxx |10
2 |xxP |led tv
3 |xxP |mp3 Player
4 |xxx |11
5 |xxP |tv cable
6 |xxS |install
xxx = Invoice number
xxP = Product
xxs = service
TABLE.Invoice_Number TABLE.Product
ID|ID_FATHER|ROWPOS|NUM ID|ID_FATHER|ROWPOS|PROD
1 | 1 | 1 | 10 1 | 1 | 2 | led tv
2 | 2 | 4 | 11 2 | 1 | 3 | mp3 player
3 | 2 | 5 | tv cable
TABLE.Service
ID|ID_FATHER|ROWPOS|SERV
1 | 1 | 6 | install
I made some changes in the query to work as I needed.

You could do something like this:
INSERT INTO mytable (i, rowpos)
SELECT #i := IF(t.rowpos = #prev_rowpos + 1, #i, #i + 1) AS i
, #prev_rowpos := t.rowpos AS rowpos
FROM another_table t
JOIN (SELECT #prev_rowpos := NULL, #i := 0) v
ORDER BY t.rowpos
(Test just the SELECT query, get that working returning the resultset you want, before you preface it with the INSERT.)
For completeness, I will add that this technique is dependent on UNDOCUMENTED and non-guaranteed behavior in MysQL, using "user variables". I've successfully used this approach many times, but for "one off" type admin functions, not ever embedded as SQL in an application.
Note that the ORDER of the expressions in the SELECT list is important, they are evaluated in the order they appear in the SELECT list. (MySQL doesn't guarantee this behavior, but we do observe it. It's important that the check of the user variables containing values from the previous row to precede the assignment of the current row values to the user variables. That's why i is returned first, followed by rowpos. If you reversed the order of those in the SELECT list, the query would operate differently, and we wouldn't get the same results.
The purpose of the inline view (aliased as v) is to initialize the user variables. Since MySQL materializes that view query into a "derived table" before the outer query runs, those variables get initialized before they are referenced in the outer query. We don't really care what the inline view query actually returns, except that we need it to return exactly one row (because we reference it in a JOIN operation to the table we really want to query).

E.g.:
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,rowpos INT NOT NULL
);
INSERT INTO my_table (rowpos) VALUES
(250),
(253),
(254),
(260),
(263),
(268),
(270),
(271),
(272),
(276);
SELECT x.*
, #i:=#i+ISNULL(y.id) i
FROM my_table x
LEFT
JOIN my_table y
ON y.id < x.id
AND y.rowpos = x.rowpos - 1
, (SELECT #i:=0) vals
ORDER
BY x.id;
+----+--------+------+
| id | rowpos | i |
+----+--------+------+
| 1 | 250 | 1 |
| 2 | 253 | 2 |
| 3 | 254 | 2 |
| 4 | 260 | 3 |
| 5 | 263 | 4 |
| 6 | 268 | 5 |
| 7 | 270 | 6 |
| 8 | 271 | 6 |
| 9 | 272 | 6 |
| 10 | 276 | 7 |
+----+--------+------+

Related

How to select only a specific set that includes some or all of another set in mySQL

I'm trying to extract specific rows from a mySQL table that contains lists of numbers.
I have a single table with 2 columns- id and data. Each row has a sorted, comma separated record of numbers ranging from 1 to 1000. I want to only select records with a partial or full set of specific numbers in it. I've tried using LIKE and IN and also looked at FIND_IN_SET.
t1.id t1.data
1 2,9,569
2 2,9,991,979
3 9,569,763
4 52,57,569,763,892,897
5 763
6 2,9,10,15,151,569,771,801,888,973
If I'm looking for rows with one or more of the values (2,9,569,763), I don't want to have to write:
SELECT t1.id from t1
WHERE t1.data NOT IN (1,3,4,5,6,7,8,10,11,...........,1000);
to return 3 rows, t1.id = 1,3 and 5.
Is there a simpler way? Something like (in mySQL):
SELECT t1.id from t1
WHERE t1.data "only includes one or more of" (2,9,569,763);
Paul Spiegel's answer gives the correct result, but it can't be optimized with indexes, because of the use of FIND_IN_SET(). It will always do a table-scan, which will get more and more expensive the more rows you have.
You should take this as a clue that storing lists of numbers as a comma-separated list in a string column is a bad idea when you actually want to do some searches for discrete members of that list.
What you should do instead is store the list as a child table, with one member per row.
CREATE TABLE mydata (
t1id INT NOT NULL,
member INT NOT NULL,
PRIMARY KEY (t1id, member),
FOREIGN KEY (t1id) REFERENCES t1(id)
);
INSERT INTO mydata VALUES
(1,2),(1,9),(1,569),
(2,2),(2,9),(2,991),(2,979),
(3,9),(3,569),(3,763),
(4,52,(4,57,(4,569,(4,763,(4,892),(4,897),
(5,763),
(6,2),(6,9),(6,10),(6,15),(6,151),(6,569),(6,771),(6,801),(6,888),(6,973);
Now you would join your original table t1 to mydata but exclude the matches to values in your desired list.
mysql> select * from t1 left join mydata on t1.id=mydata.t1id
and mydata.member not in (2,9,569,763);
+----+------+--------+
| id | t1id | member |
+----+------+--------+
| 1 | NULL | NULL |
| 2 | 2 | 979 |
| 2 | 2 | 991 |
| 3 | NULL | NULL |
| 4 | 4 | 52 |
| 4 | 4 | 57 |
| 4 | 4 | 892 |
| 4 | 4 | 897 |
| 5 | NULL | NULL |
| 6 | 6 | 10 |
| 6 | 6 | 15 |
| 6 | 6 | 151 |
| 6 | 6 | 771 |
| 6 | 6 | 801 |
| 6 | 6 | 888 |
| 6 | 6 | 973 |
+----+------+--------+
You see there are NULLs of id 1, 3, 5 because there are no values that are NOT in your specified list. Those are the id's that you want to return.
mysql> select t1.id from t1 left join mydata on t1.id=mydata.t1id
and mydata.member not in (2,9,569,763)
where mydata.member is null;
+----+
| id |
+----+
| 1 |
| 3 |
| 5 |
+----+
Not simple but..
Count single hits and compare it with the number of all values in the data column. They must be equal.
select id, data
from t1
where (find_in_set(2, data) > 0)
+ (find_in_set(9, data) > 0)
+ (find_in_set(569, data) > 0)
+ (find_in_set(763, data) > 0)
= char_length(data) - char_length(replace(data, ',', '')) + 1
Demo: https://www.db-fiddle.com/f/oLcrz4vmXRWXqYQhCnZA5z/0
Try to use the REGEXP function:
SELECT t1.id from t1
WHERE t1.data REGEXP '(2,9,569,763)';

mysql - can i differentiate values of same column?

Is there a way to make two or more same values in the same column to be different by database engine?
Imagine that I have column like this:
id | votes
----------
1 | 20
2 | 20
3 | 19
4 | 16
5 | 15
I have to make all the votes different in that way that i increment first of two equal values, and substract other one.
After one iteration my DB should look like this:
----------
1 | 21
2 | 19
3 | 19
4 | 16
5 | 15
Because we still have two same values (id 2 and 3) we keep going with second iteration:
----------
1 | 21
2 | 20
3 | 18
4 | 16
5 | 15
Can i update mysql database somehow to make that for me? Or should i select values, compare them in php and update?
Thank you!
This does not exactly match your expected result, but may lead you toward an acceptable compromise.
MySQL does not yet have window functions such as rank(), dense_rank() or row_number() but you can mimic row_number using #variables such as seen below.
See this demo
MySQL 5.6 Schema Setup:
CREATE TABLE Table1
(`id` int, `votes` int)
;
INSERT INTO Table1
(`id`, `votes`)
VALUES
(1, 20),
(2, 20),
(3, 19),
(4, 16),
(5, 15)
;
Query 1:
SELECT
t.id
, t.votes
, m.v - #row_num AS adjusted_votes
, #row_num := #row_num+1 AS RowNumber
FROM Table1 t
CROSS JOIN (SELECT MAX(votes) v from Table1) m
CROSS JOIN (SELECT #row_num :=0) vars
ORDER BY
t.votes DESC
, t.id
Results:
| id | votes | adjusted_votes | RowNumber |
|----|-------|----------------|-----------|
| 1 | 20 | 20 | 1 |
| 2 | 20 | 19 | 2 |
| 3 | 19 | 18 | 3 |
| 4 | 16 | 17 | 4 |
| 5 | 15 | 16 | 5 |
Note that the reduction of 20 to 19 for id 2 appears to be completely arbitrary and that id is used purely as a tie-breaker when values are equal.

Latest datetime from unique mysql index

I have a table. It has a pk of id and an index of [service, check, datetime].
id service check datetime score
---|-------|-------|----------|-----
1 | 1 | 4 |4/03/2009 | 399
2 | 2 | 4 |4/03/2009 | 522
3 | 1 | 5 |4/03/2009 | 244
4 | 2 | 5 |4/03/2009 | 555
5 | 1 | 4 |4/04/2009 | 111
6 | 2 | 4 |4/04/2009 | 322
7 | 1 | 5 |4/05/2009 | 455
8 | 2 | 5 |4/05/2009 | 675
Given a service 2 I need to select the rows for each unique check where it has the max date. So my result would look like this table.
id service check datetime score
---|-------|-------|----------|-----
6 | 2 | 4 |4/04/2009 | 322
8 | 2 | 5 |4/05/2009 | 675
Is there a short query for this? The best I have is this, but it returns too many checks. I just need the unique checks at it's latest datetime.
SELECT * FROM table where service=?;
First you need find out the biggest date for each check
SELECT `check`, MAX(`datetime`)
FROM YourTable
WHERE `service` = 2
GROUP BY `check`
Then join back to get the rest of the data.
SELECT Y.*
FROM YourTable Y
JOIN ( SELECT `check`, MAX(`datetime`) as m_date
FROM YourTable
WHERE `service` = 2
GROUP BY check) as `filter`
ON Y.`service` = `filter`.service
AND Y.`datetime` = `fiter`.m_date
WHERE Y.`service` = 2

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 counting number of occurrences

So lets say we have the following table of data:
A | B
_________
1 | 2
3 | 4
5 | 6
6 | 5
And what if I wanted to count the times the same numbers collide or are in the same line? So in the above example 1-2 and 3-4 would return a count of one because they are on the same line only once however 5-6 and 6-5 would return a value of 2.
A more real life illustration: think that the numbers are sport team id's and the A and B columns determine the host-team and the guest-team. Ao teams 5 and 6 have played a total of 2 games against each other, first team 5 as a host and then team 6 as a host.
So how could I count these in mysql?
DROP TABLE IF EXISTS fixtures;
CREATE TABLE fixtures
(fixture_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,home INT NOT NULL
,away INT NOT NULL
);
INSERT INTO fixtures (home,away) VALUES (1,2),(3,4),(5,6),(6,5);
SELECT * FROM fixtures;
+------------+------+------+
| fixture_id | home | away |
+------------+------+------+
| 1 | 1 | 2 |
| 2 | 3 | 4 |
| 3 | 5 | 6 |
| 4 | 6 | 5 |
+------------+------+------+
SELECT LEAST(home,away) a,GREATEST(home,away) b, COUNT(*) ttl FROM fixtures GROUP BY a,b;
+---+---+-----+
| a | b | ttl |
+---+---+-----+
| 1 | 2 | 1 |
| 3 | 4 | 1 |
| 5 | 6 | 2 |
+---+---+-----+
SELECT
CASE WHEN A < B THEN A ELSE B END AS aa,
CASE WHEN B > A THEN B ELSE A END AS bb,
COUNT(*)
FROM
Table1 t1
GROUP BY aa, bb
See it live in an sqlfiddle.
As a-b is the same as b-a, you want to normalize that result:
SELECT LEAST(a,b) AS x, GREATEST(a,b) AS y ...
Now you can count the occurences:
SELECT LEAST(a,b) AS x, GREATEST(a,b) AS y, count(*) as c FROM tablename GROUP BY x,y
Greets