MySQL Query With Combinatorial Where Clause - mysql

Let's say I have a table with Columns A, B, C, D, E and F.
How would I query for entries where (A, B, C, D, E, F) = (1, 2, 3, 4, 5, 6) but only a subset of columns need to match? For example at least 3 out of the 6 columns have to match.
The only solution I can think of is to go through all combinations where (A, B, C) = (1, 2 ,3) or (A, B, D) = (1, 2, 4) or...
But in this example that would already be 20 where clauses, if my math is correct. Is there a better solution, that also works with more columns? Or is my only option to programmatically create a huge, non human-readable query string with hundreds of where clauses?

In MySql boolean expressions are evaluated as 1 for true or 0 for false, so you can add them in the WHERE clause:
WHERE (A = 1) + (B = 2) + (C = 3) + (D = 4) + (E = 5) + (F = 6) >= 3
Just in case any of the 6 columns is nullable, use the NULL-safe equal to operator <=> instead of =:

You can use a score system and then get the rows sorted by score. For example:
select *
from (
select t.*,
case when a = 1 then 1 else 0 end +
case when b = 2 then 1 else 0 end +
case when c = 3 then 1 else 0 end +
case when d = 4 then 1 else 0 end +
case when e = 5 then 1 else 0 end +
case when f = 6 then 1 else 0 end as score
from t
) x
where score >= 3
order by score desc
Of course, this query won't be efficient in terms of execution time, but should work well for small subsets of data.

Related

Extract numeric part of string and get max value in column

I have a table foo that stores codes in format lnnnnn where l is at least one letter and n is numeric value. Both letters or numbers can be of various length, so trying to solve this like mentioned here won't work.
Example:
group | code
=============
1 | a0010
1 | a0012
1 | a0013
2 | bn0014
2 | bn0015
2 | bn0016
3 | u0017
3 | u0018
My task is to get current highest numeric value of this column in desired group, to generate new number (like sequence).
Note that I cannot redesign table and explode string and text parts.
So far I tried:
select
max(code rlike '[0-9]$')
from
foo
where
group = 2
but, sadly, regexp or rlike (synonyms) returns only 0 or 1 (matched or not matched).
One method is a brute force method:
select grp,
max(case when substr(code, 1, 1) between '0' and '9' then code + 0
when substr(code, 2, 1) between '0' and '9' then substr(code, 2) + 0
when substr(code, 3, 1) between '0' and '9' then substr(code, 3) + 0
when substr(code, 4, 1) between '0' and '9' then substr(code, 4) + 0
when substr(code, 5, 1) between '0' and '9' then substr(code, 5) + 0
when substr(code, 6, 1) between '0' and '9' then substr(code, 6) + 0
when substr(code, 7, 1) between '0' and '9' then substr(code, 7) + 0
when substr(code, 8, 1) between '0' and '9' then substr(code, 8) + 0
end)
from foo
group by grp;
If your numeric codes is always four digits then you can do it like:
select groupid, max(right(code,4)) as maxcode
from foo
group by groupid
See it here on fiddle: http://sqlfiddle.com/#!2/775b3/2
If all numeric parts start with a 0:
select gp, max(cast(substr(code, instr(code, '0')) as unsigned))
from t
group by gp
See sqlfiddle
If not, for arbitrary numeric parts (that start with any digit):
select gp, max(cast(substr(code, instr(code, n)) as unsigned))
from t
join (select 0 n 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) x
group by gp
See sqlfiddle

MYSQL: Get average of fields in case where value is not a certain value

Say I have a table with 5 columns,
A, B, C, D, E
that are integers. I want to get the average of all of the fields in that case, that are not 3.
So, on some sample data:
A, B, C, D, E DESIRED RESULT
-------------
1, 1, 4, 4, 3 -> 2.5 (NOT 2.6)
1, 2, 3, 3, 3 -> 1.5 (NOT 2.4)
EDIT: I found a solution.
(
(
IF(A!=3,A,0)
+IF(B!=3,B,0)
+IF(C!=3,C,0)
+IF(D!=3,D,0)
+IF(E!=3,E,0)
)
/
(
IF(A!=3,1,0)
+IF(B!=3,1,0)
+IF(C!=3,1,0)
+IF(D!=3,1,0)
+IF(E!=3,1,0)
)
) as VALUE
Try that:
select avg(CASE WHEN A = 3 then 0 else A end +
CASE WHEN B = 3 then 0 else B end +
CASE WHEN C = 3 then 0 else C end +
CASE WHEN D = 3 then 0 else D end +
CASE WHEN E = 3 then 0 else E end)/
(CASE WHEN A = 3 then 0 else 1 end +
CASE WHEN B = 3 then 0 else 1 end +
CASE WHEN C = 3 then 0 else 1 end +
CASE WHEN D = 3 then 0 else 1 end +
CASE WHEN E = 3 then 0 else 1 end) as av from table1
group by A,B,C,D,E
DEMO HERE

Count specific rows to match the query

I have a table named results retrieved and displayed as follows
the columns sub1 to sub2 represents the subject names and the rows have values scored by the students
While the information is retrieved from the db, I also need to count in how many subjects did a student score less than 40 for example, Tom scored less than 40 in 2 subjects and the result would look like as follows
Please help how to write a query to display the last column
Normalizing your table would have made this much easier, but even without changing its structure, you can get this result with some case statements:
SELECT id, students,
sub1, sub2, sub3, sub4, sub5, sub6,
CASE WHEN sub1 < 40 THEN 1 ELSE 0 END +
CASE WHEN sub2 < 40 THEN 1 ELSE 0 END +
CASE WHEN sub3 < 40 THEN 1 ELSE 0 END +
CASE WHEN sub4 < 40 THEN 1 ELSE 0 END +
CASE WHEN sub5 < 40 THEN 1 ELSE 0 END +
CASE WHEN sub6 < 40 THEN 1 ELSE 0 END AS "Failed IN"
FROM my_table
MySQL has the convenient ability to treat boolean comparisons as integers. Here is a pretty simple way to express your logic:
select id, students, sub1, sub2, sub3, sub4, sub5, sub6,
((sub1 < 40) + (sub2 < 40) + (sub3 < 40) + (sub4 < 40) + (sub5 < 40) + (sub6 < 40)
) as FailedIn
from table t;

MySQL matrix multiplication

I am trying to write matrix multiplication for MySQL and am kinda stuck:
basically, my matrices are stored in format
[row#, column#, matrixID, value], so for example matrix [3 x 2] would be something like:
[row#, column#, matrixID, value]
1 1 mat01 1
1 2 mat01 2
1 3 mat01 3
2 1 mat01 4
2 2 mat01 5
2 3 mat01 6
being equivalent to: [[1 2 3],[4 5 6]]
following does calculation of single element of matrix1 * matrix2 quite well:
SELECT SUM(row1.`val` * col2.`val`)
FROM matValues row1
INNER JOIN `matValues` col2
WHERE row1.`row` = 1 AND row1.`mID`='matrix1' AND
col2.`mID`='matrix2' AND col2.`col` = 1 AND row1.col = col2.row
wrapping this into function and then using another function to iterate over row and column numbers might work, but I have problems with generating this set of numbers and iterating over them using SQL.
Any advice / suggestions are welcome
Try:
select m1.`row#`, m2.`column#`, sum(m1.value*m2.value)
from matValues m1
join matValues m2 on m2.`row#` = m1.`column#`
where m1.matrixID = 'mat01' and m2.matrixID = 'mat02'
group by m1.`row#`, m2.`column#`
Example here.
(Replace 'mat01' and 'mat02' with suitable matrixID values.)
You can do the entire calculation in SQL. You only give an example with a single matrix, which because it is not square, cannot be multiplied by itself.
Here is the idea:
SELECT mout.row, mout.col, SUM(m1.value*m2.value)
FROM (select distinct row from matValues cross join
select distinct COL from matValues
) mout left outer join
matValues m1
on m1.row = mout.row left outer join
matValues m2
on m2.col = mout.col and
m2.row = m1.col
I know this is SQL-Server syntax, but it should give you a start on the corresponding MySql syntax. The sparse matrix nature seems to handle well.
with I as (
select * from ( values
(1,1, 1),
(2,2, 1),
(3,3, 1)
) data(row,col,value)
)
,z_90 as (
select * from ( values
(1,2, 1),
(2,1,-1),
(3,3, 1)
) data(row,col,value)
)
,xy as (
select * from ( values
(1,2, 1),
(2,1, 1),
(3,3, 1)
) data(row,col,value)
)
,x_90 as (
select * from ( values
(1,1, 1),
(2,3, 1),
(3,2,-1)
) data(row,col,value)
)
select
'I * z_90' as instance,
a.row,
b.col,
sum( case when a.value is null then 0 else a.value end
* case when b.value is null then 0 else b.value end ) as value
from I as a
join z_90 as b on a.col = b.row
group by a.row, b.col
union all
select
'z_90 * xy' as instance,
a.row,
b.col,
sum( case when a.value is null then 0 else a.value end
* case when b.value is null then 0 else b.value end ) as value
from z_90 as a
join xy as b on a.col = b.row
group by a.row, b.col
union all
select
'z_90 * x_90' as instance,
a.row,
b.col,
sum( case when a.value is null then 0 else a.value end
* case when b.value is null then 0 else b.value end ) as value
from z_90 as a
join x_90 as b on a.col = b.row
group by a.row, b.col
order by instance, a.row, b.col
yields:
instance row col value
----------- ----------- ----------- -----------
I * z_90 1 2 1
I * z_90 2 1 -1
I * z_90 3 3 1
z_90 * x_90 1 3 1
z_90 * x_90 2 1 -1
z_90 * x_90 3 2 -1
z_90 * xy 1 1 1
z_90 * xy 2 2 -1
z_90 * xy 3 3 1
However, I suggest you also check out performing this on your graphics card. NVIDIA has a good example of implementing matrix multiplication in theri C Programming Guide.

MySQL conditionally counting results

I have a query which returns the counts of several different types of records but I now need to further qualify the result set. I am curious if there is an elegant way to combine these statements into a single statement. Basically if column 2 is true increment ND_true and if column 2 is false increment ND_false instead.
sum(if(c.1 = 'ND' and c.2 is true, if(c.2 = 'P', 1, 0), 0)) as 'ND_true'
sum(if(c.1 = 'ND' and c.2 is false, if(c.2 = 'P', 1, 0), 0)) as 'ND_false'
Erm...
select count(*) from `tablename` where [something something something]
Seems like a much better alternative to what you're doing. Either that or you're not explaining very clearly what you are doing and what led you to the solution you have.
One alternative:
Select ...
, C.ND_True As ND_True
, C.ND_False As ND_False
From ...
Cross Join (
Select Sum( Case When C1.P = 1 Then 1 Else 0 End ) As ND_True
, Sum( Case When C1.P = 0 Then 1 Else 0 End ) As ND_False
From SomeTable As C1
Where C1.1 = 'ND'
And C1.P = 'P'
Union All
Select Z.Val, Z.Val
From ( Select 0 As Val ) As Z
Where Not Exists (
Select 1
From SomeTable As C2
Where C2.1 = 'ND'
And C2.P = 'P'
)
) As C
Your query sample although brief is unclear... you are first testing of c1 = 'ND' (string comparison) ANDed with c.2 (implying c.2 is logical), then another if( c.2 = 'P'... ) I'm sure you are abbreviating column names, but this isn't making sense. Is c.2 a logical field or a string field... one or the other.
sum(if(c.1 = 'ND' and c.2 is true, if(c.2 = 'P', 1, 0), 0)) as 'ND_true'
sum(if(c.1 = 'ND' and c.2 is false, if(c.2 = 'P', 1, 0), 0)) as 'ND_false'
Here is a simplified version of what I think you are looking for.. In this case, you are concerned with c.1 being "ND", so put that as your WHERE clause to limit what is retrieved from the table. Then you don't have to re-duplicate it as part of the IF() clause test. Then, just put in the "other" criteria where I have the c.2 expression... Since the clause is identical for what is being tested, the 2nd and 3rd columns trigger which column they will be counted in...
select
sum( if( c.2, 1, 0 )) as ND_True,
sum( if( c.2, 0, 1 )) as ND_False
from
yourTable c
where
c.1 = 'ND'
Ex: Data
col1 col2
AX true
BC true
ND true <-- this row
XY false
ND true <-- this row
ND false <-- this row
AX false
ND true <-- this row
would result in only the 4 marked rows being queried with a final count of
ND_True = 3
ND_False = 1