I have multiple values in comma separated
(1,3,5) want to compare with (2,3,4,5,7,5) and this set refer to column value. So it should return 3 and 5
and this values are dynamic
I have used
SELECT * FROM table WHERE FIND_IN_SET('3', ('2,3,4,5,7,5')) AND FIND_IN_SET('5', ('2,3,4,5,7,5')) and so on
but it very tedius let me know any better solution for this.
Short answer
You should avoid this. While it actually can be done, your current architecture is violating at least first NF. And that's bad case. Storing delimiter-separated list is applicable only if you need to work with entire string, but not separate value itself. Therefore, most proper solution would be: create additional table and put your values there.
Long answer
This can be treated as some sort of puzzle - but I strongly do not recommend to use it on real application. So, let's suppose we have table t:
+------+------------------+
| id | col |
+------+------------------+
| 1 | 1,35,61,12,8 |
| 4 | 82,12,99,100,1,3 |
| 6 | 35,99,1 |
+------+------------------+
And we want to 'intersect' our strings with string '1,3,35'. I assume that your string is derived from application - therefore, you're able to do some preparations with it.
Final SQL will look like:
SELECT
resulted.id,
GROUP_CONCAT(resulted.sub) AS result
FROM
(SELECT
r.id,
TRIM(BOTH ',' FROM SUBSTR(
r.col,
#cur,
LOCATE(',', r.col, #cur+1)-#cur
)) AS sub,
#cur:=IF(
CHAR_LENGTH(r.col)=LOCATE(',', r.col, #cur+1),
1,
LOCATE(',', r.col, #cur+1)
) AS cur
FROM
(SELECT
id,
CONCAT(TRIM(BOTH ',' FROM t.col), ',') AS col,
CHAR_LENGTH(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(col
, '9', '')
, '8', '')
, '7', '')
, '6', '')
, '5', '')
, '4', '')
, '3', '')
, '2', '')
, '1', '')
, '0', '')
) + 1 AS repeats
FROM t) AS r
LEFT JOIN
(SELECT
(two_1.id + two_2.id + two_4.id +
two_8.id + two_16.id) AS id
FROM
(SELECT 0 AS id UNION ALL SELECT 1 AS id) AS two_1
CROSS JOIN (SELECT 0 id UNION ALL SELECT 2 id) AS two_2
CROSS JOIN (SELECT 0 id UNION ALL SELECT 4 id) AS two_4
CROSS JOIN (SELECT 0 id UNION ALL SELECT 8 id) AS two_8
CROSS JOIN (SELECT 0 id UNION ALL SELECT 16 id) AS two_16
) AS init
ON init.id<r.repeats
CROSS JOIN
(SELECT #cur:=1) AS vars
) AS resulted
INNER JOIN
(SELECT '1' AS sub UNION ALL
SELECT '3' UNION ALL
SELECT '35'
) AS input
ON resulted.sub=input.sub
GROUP BY
resulted.id
(the demo is available here).
How it works
There are some tricks, that were used for this SQL. First, iteration variable. MySQL supports user-defined variables and they can be used for some sort of iterations in queries. And we're using it to pass valid offset and length into our string - to get piece of it via SUBSTR().
Next trick: we need to produce certain amount of rows - otherwise iteration won't work. That can be done the following way: count delimiters in each row and repeat it with that count+1. MySQL has no sequences, but there is third trick: to create desired count via huge CROSS JOIN (with summation of powers of 2 to get consecutive numbers). And that's for what internal LEFT JOIN is. In fact, I've faced this issue in one of my questions.
And, finally, we're doing INNER JOIN on entire result to get our intersected values. Note: this is the part, for which you'll need to make some preparations on your string. But it's easy to split string in application, getting needed UNION ALL part of query above.
What is out of the issue
Invalid strings. No checks will be done for things like '1,,,,4,5'. Really - it's not an intention of this method
Invalid non-numeric values. Since we're replacing 0..9 (that huge REPLACE part) - we can't do that dynamically - MySQL can't "replace any char, except.." This is a bottleneck, yes - but, again - not intention of the method
While I wouldn't recommend doing this in live code, it can be done without the need for variables:-
SELECT id, some_col, GROUP_CONCAT(DISTINCT SUBSTRING_INDEX(SUBSTRING_INDEX('1,3,5', ',', AnInt), ',', -1) ORDER BY 1) AS anItem
FROM some_table
CROSS JOIN
(
SELECT 1 + Units.i + Tens.i * 10 as AnInt
FROM
(SELECT 0 AS i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) Units,
(SELECT 0 AS i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) Tens
) Sub1
WHERE FIND_IN_SET(SUBSTRING_INDEX(SUBSTRING_INDEX('1,3,5', ',', AnInt), ',', -1), some_col)
GROUP BY id, some_col
What this is doing is selecting 0 to 9 unioned, and joining this against itself. This gets 100 combinations, and by a bit of multiplication it gets the numbers 0 to 100. It then cross joins this against the table ou want to check, and uses this number as a parameter to SUBSTRING_INDEX to split it up on the commas. As such it can cope with ~100 numbers in the comma separated string you want to check. Down side is that it will duplicate some of these numbers, hence duplicates need to be removed.
The resulting numbers can then be used with FIND_IN_SET() to check the rows that contain these numbers in their comma separated field.
I have then used GROUP_CONCAT with DISTINCT to display the matching numbers for that row.
SQL Fiddle for it here:-
http://www.sqlfiddle.com/#!2/edf97/3
This is my table, I should fetch the MAX (id) of each status_id.
id status_id
10 1
11 1
12 2
13 2
14 2
15 4
16 4
So, I use this sql query, it works true and fetchs me all max ID.
select status_id, max(id) as max FROM `table`
where status_id in (1,2,3,4) group by status_id
This sql command fetchs me 3 MAX id using while.
11, 14, 16....
You see, there is not any suitable id to 3rd status_id. And if there is not any suitable id to 3rd status_id just mark it as zero. So I want that sql will bring these results:
11, 14, 0, 16
You can create a subquery which basically has all the ID's you need and have a left join against it.
SELECT a.status_ID,
IFNULL(MAX(b.id), 0) maxVal
FROM
(
SELECT 1 status_ID UNION ALL
SELECT 2 UNION ALL
SELECT 3 UNION ALL
SELECT 4
) a
LEFT JOIN `table` b ON a.status_id = b.status_id
GROUP BY a.status_id
SQLFiddle Demo
You can join with a temporary dummy table containing all ids and a stats_id value of 0:
SELECT dummy.status_id, COALESCE(MAX(id), 0) AS max
FROM (
SELECT 1 status_id
UNION SELECT 2 status_id
UNION SELECT 3 status_id
UNION SELECT 4 status_id
) dummy
LEFT JOIN `table`
ON dummy.status_id = table.status_id
GROUP BY dummy.status_id
But this does not scale and is a maintenance nightmare (you have to change the dummy-select in case a new status_id turns up). If you have a table containing ALL status_ids, replace the dummy-select with that table.
1) This is a much simplified example of what I am trying to do for ease of explaining the problem. 2) I will be doing thousands of these queries.
I have a mysql table like so:
NUMBER TEXTCOLOUR
one green
two red
three orange
four pink
I want to display the following (each word having a different textcolour), in this order:
three two one three
I have tried:
$query = "SELECT * FROM `table` WHERE `number` IN ('three', 'two', 'one','three')
ORDER BY FIND_IN_SET(number, 'three,two,one,three')";
$result = mysql_query($query);
while($row = mysql_fetch_array($result, MYSQL_ASSOC))
{
echo "
<div class='" .$textcolour. "'>$number</div>
";
}
but this will not display the record 'three' more than once. Also I don't want to have to write the same array twice (once for IN and again for the ORDER BY) as I have to write thousands of these queries.
So the question: How to show mysql records more than once and in a specific order with the least amount of code?
Thanks for any suggestions!
Try
SELECT b.textcolour
FROM
(SELECT 'three' number UNION ALL
SELECT 'two' UNION ALL
SELECT 'one' UNION ALL
SELECT 'three') a LEFT JOIN table1 b ON a.number = b.number
Output:
| TEXTCOLOUR |
--------------
| orange |
| red |
| green |
| orange |
SQLFiddle
You can consider inserting those sequences in some (temp) table with an auto_increment column and a column that designates particular sequence. Then you can just do
SELECT b.textcolour
FROM sequences a JOIN colours b
ON a.number = b.number
WHERE a.seq_no = ?
ORDER BY a.id
SQLFIddle
Thanks to #peterm for the answer (with extra php added for others' use)
$result = mysql_query("SELECT b.textcolour FROM (
SELECT 'three' number UNION ALL
SELECT 'two' UNION ALL
SELECT 'one' UNION ALL
SELECT 'three'
) a LEFT JOIN table1 b ON a.number = b.number");
while ($row = mysql_fetch_object($result))
I have table like this
name | personal_number
-----------------------------------------
Jon | 222
Alex | 555
Jon | 222
Jimmy | 999
I need get every name, which personal_number repeates in table more than 1, that is result must be:
Jon
Jon
So, Variant 1):
SELECT name FROM mytable WHERE personal_number IN (
SELECT personal_number FROM mytable GROUP BY personal_number
HAVING COUNT(*) > 1
)
Variant 2):
SELECT personal_number FROM mytable GROUP BY personal_number
HAVING COUNT(*) > 1
)
Then, using php, retrieved personal_numbers join as string (soemthing like this '222', '222' ) and run other query
SELECT name FROM mytable WHERE personal_number IN( here joined string )
Variant 2 works approximately 10 times faster, than variant 1, this is surprise for me, I was thinking that one query will be faster, but...
(In table is 500 000 rows, column personal_number not indexed)
So, what you mean about cases like this? why variant 2 is many faster than variant 1 ?
It seems that subqueries are very slow as mentioned in this article http://www.mysqlperformanceblog.com/2010/10/25/mysql-limitations-part-3-subqueries.
You should try to avoid having subqueries and use joining instead.
First query has heavy subquery. You must avoid this.
The best solution for your problem is only one query:
SELECT name FROM mytable GROUP BY personal_number HAVING COUNT(*) > 1;
This query will return you each repeated name only once. If you want to display the name of the duplicate as many times as they met you must use next query:
SELECT name, COUNT(*) AS count FROM mytable GROUP BY personal_number HAVING COUNT(*) > 1;
And then in PHP do something like this:
foreach ($rows as $row) {
for ($i = 0; $i++; $i < $row['count']) {
echo $row['name'] . "\n";
}
}
Since indexing is not done so the 1 is slow,as it has to match personal_numbers from selected personal_numbers. If indexing is done it consumes less time than earlier.
Variant 2 is a direct query hence its faster.
This should be quicker:
SELECT name FROM mytable join (
SELECT personal_number FROM mytable GROUP BY personal_number
HAVING COUNT(*) > 1
)a using (personel_number)
Edit: If this is faster than variant 1, then it means at variant 1 mysql reproduces the inner table for each record again and again.
I have a table with two columns:
column A column B
1 2
1 2
2 1
I want to return total of ones = 3 total of twos = 3
The best I can come up with is two queries like so:
SELECT sum(CASE WHEN columnA =1 THEN 1 ELSE 0 END )
+ sum(CASE WHEN columnB =1 THEN 1 ELSE 0 END )
SELECT sum(CASE WHEN columnA =2 THEN 1 ELSE 0 END )
+ sum(CASE WHEN columnB =2 THEN 1 ELSE 0 END )
Can this be done in one query?
Thanks
You didn't specify if you want to do this as 2 rows or as 2 values in a row.
Two rows are somewhat obvious (just union together all the values from each columns, and count(1) group by value against the result of the union; so I'll assume you want to do one row.
If you only have 1s or 2s, it's simple:
SELECT SUM(A+B-2) 'twos', SUM(4-A-B) 'ones' FROM myTable
SELECT SUM(IF(columnA=1, 1, 0) + IF(columnB=1, 1, 0)) as ones,
SUM(IF(columnA=2, 1, 0) + IF(columnB=2, 1, 0)) as twos
FROM myTable;
C.
To get everything in one query, I would try something like this.
SELECT Result.Val, COUNT(Result.Val) AS Count
FROM (
SELECT ColumnA AS Val
FROM TableName
UNION
SELECT ColumnB AS Val
FROM TableName
) AS Result
GROUP BY Result.Val
In general, you would count things like so:
SELECT columnA, COUNT(*) FROM myTable
GROUP BY columnA
to get the count of all different values in columnA.
SELECT COUNT(*) FROM table WHERE columnA=1 or columnB=1