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)';
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.
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
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
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