I have a situation where I have data in a primary table with additional data in a bunch of associated tables. Given a list of IDs from the primary table, I need to retrieve all data for a batch process. This batch process is high volume so I need to implement it very efficiently.
I have implemented this using subqueries with group_concat and some mapping code, but I'm not very confident in it. It seems efficient but hard to maintain (particularly because my real implementation is much uglier, with it's many more tables and columns). Is this implementation a good idea? Is there some other mysql querying/joining approach I could take to improve this?
The structure of the data is similar to this, but in actuality I have many more tables and columns:
Objects
ID | column1 | column2 | column3
----------
1 a b c
2 d e f
Attributes1
ID | ObjectsId| column1 | column2 | column3
--------------------
1 1 g k l
2 1 m n o
3 2 p q r
4 2 s t u
Attributes2
ID | ObjectsId| column1 | column2 | column3
--------------------
1 1 w v x
2 1 y z aa
3 2 bb cc dd
4 2 ee ff gg
This is approximately what the object produced by my application code should look like.
[
{
id = 1,
column1 = a,
Column2 = b,
Column3 = c,
Attributes1 = [
{
Id = 1,
ObjectsId = 1,
Column1 = g,
Column2 = k,
Column3 = l
},
{
Id = 2,
ObjectsId = 1,
Column1 = m,
Column2 = n,
Column3 = o
}]
Attributes2 = [
{
Id = 1,
ObjectsId = 1,
Column1 = w,
Column2 = v,
Column3 = x
},
{
Id = 2,
ObjectsId = 1,
Column1 = y,
Column2 = z,
Column3 = aa
}]
},
{
id = 2,
column1 = d,
Column2 = e,
Column3 = f,
Attributes1 = [
{
Id = 3,
ObjectsId = 2,
Column1 = p,
Column2 = q,
Column3 = r
},
{
Id = 4,
ObjectsId = 2,
Column1 = s,
Column2 = t,
Column3 = u
}]
Attributes2 = [
{
Id = 3,
ObjectsId = 2,
Column1 = bb,
Column2 = cc,
Column3 = dd
},
{
Id = 4,
ObjectsId = 2,
Column1 = ee,
Column2 = ff,
Column3 = gg
}]
}
]
This is my implementation:
private function get_all($object_ids)
{
$a1 = " SELECT group_concat(a1.ID, '|', a1.ObjectsId, '|', a1.column1, '|', a1.column2, '|', a1.column3)
FROM Attributes1 a1
WHERE a1.objectsId = o.id";
$a2 = " SELECT group_concat(a2.ID, '|', a2.ObjectsId, '|', a2.column1, '|', a2.column2, '|', a2.column3)
FROM Attributes2 a2
WHERE a2.objectsId = o.id";
$o = " SELECT ID, column1, column2, column3,
(%s) as attributes1_result,
(%s) as attributes2_result
FROM Objects o
WHERE o.id IN (?)";
$query = sprintf($o, [$a1, $a2]);
$results = $this->query($query, $object_ids);
foreach ($results as &$result) {
$a1Results = explode(',', $result['attributes1_result']);
foreach ($a1Results as $a1Result) {
$columns = explode('|', $a1Result);
$resultRow['id'] = $columns[0];
$resultRow['ObjectsId'] = $columns[1];
$resultRow['column1'] = $columns[2];
$resultRow['column2'] = $columns[3];
$resultRow['column3'] = $columns[4];
$result['Attributes1'][] = $resultRow;
}
unset($result['attributes1_result']);
$a2Results = explode(',', $result['attributes2_result']);
foreach ($a2Results as $a2Result) {
$columns = explode('|', $a2Result);
$resultRow['id'] = $columns[0];
$resultRow['ObjectsId'] = $columns[1];
$resultRow['column1'] = $columns[2];
$resultRow['column2'] = $columns[3];
$resultRow['column3'] = $columns[4];
$result['Attributes2'][] = $resultRow;
}
unset($result['attributes2_result']);
}
return $results;
}
Related
I have a table 'A' that look like that :
L
S
A
1
B
1
C
0
D
1
And I want to add a new column U saying 'OK' or 'NOK' depending on following condition :
If A = 0 & B = 1 => Line A = NOK Else OK
If A = 1 & C = 1 => Line B = NOK Else OK
If B = 1 & D = 1 => Line C = NOK Else OK
So it should look like :
L
S
U
A
1
OK
B
1
OK
C
0
NOK
D
1
OK
I have tried with a CASE, but it is not working, I cannot do the "then C = NOK" when I'm in "If B"
SELECT A.*,
(
CASE
WHEN (L = 'A' AND S = 0) AND (L = 'B' AND S = 1) THEN "NOK"
WHEN (L = 'A' AND S = 1) AND (L = 'C' AND S = 1) THEN "NOK"
WHEN (L = 'B' AND S = 1) AND (L = 'D' AND S = 1) THEN "NOK"
ELSE 'OK'
END
) AS `U`
FROM A
How can I achieve that in mysql directly ? Is it possible ?
Thanks
I want to fetch that SQL rows:
SELECT aa.*
FROM Answers AS aa
WHERE event_id = 1 AND
( (aa.form_item_id = 1 AND form_item_reply = "John") AND
(aa.form_item_id = 2 AND form_item_reply = "Doe")
)
ORDER BY aa.id DESC
But it's given wrong result, I want to fetch that (aa.form_item_id = 1 AND form_item_reply = "John") and (aa.form_item_id = 2 AND form_item_reply = "Doe").
It have to give me above 2 condition result.
You can use OR
SELECT aa.* FROM Answers AS aa WHERE event_id = 1 AND
(
(aa.form_item_id = 1 AND form_item_reply = "John")
OR (aa.form_item_id = 2 AND form_item_reply = "Doe")
) ORDER BY aa.id DESC
Perhaps you just want OR:
SELECT aa.*
FROM Answers AS aa
WHERE event_id = 1 AND
( (aa.form_item_id = 1 AND form_item_reply = 'John') OR
(aa.form_item_id = 2 AND form_item_reply = 'Doe')
)
ORDER BY aa.id DESC
I am looking for help with an advanced MySQL query. My current query, shown below, works fine. I would like to add an additional field, so I don't have to create a separate query. The new field, count(TableA.Field05), should result in the total number of records from TableA.
TableA has 10 records
TableB has 100 records
SELECT count(TableB.Answer) AS unAnswered
FROM TableA
LEFT JOIN TableB
ON ( TableA.Field01 = TableB.fkField01 AND TableA.Field02 = TableB.fkField02 AND TableB.Answer = '1')
WHERE TableB.fkField03 IS NULL AND TableA.Field04 = 10
GROUP BY TableA.Field01, TableA.Field02
Result:
unAnswered = 8 this is correct
The desired result is:
unAnswered = 8
count(TableA.Field05) = 10
--
This is an example of the data and results.
SELECT count(TableB.Answer) AS unAnswered
FROM TableA LEFT JOIN TableB ON ( TableA.Field01 = TableB.fkField01 AND TableA.Field02 = TableB.fkField02 AND TableB.Answer = '1')
WHERE TableB.fkField03 IS NULL AND TableA.Field04 = 10
GROUP BY TableA.Field01, TableA.Field02
TableA
Field01 = 1, Field02 = 1, Field03 = 10
Field01 = 1, Field02 = 2, Field03 = 21
Field01 = 1, Field02 = 3, Field03 = 22
Field01 = 1, Field02 = 4, Field03 = 34
TableB
Field01 = 1, Field02 = 1, Answer = 1
Field01 = 1, Field02 = 2, Answer = 1
Field01 = 1, Field02 = 3, Answer = 1
Field01 = 2, Field02 = 1, Answer = 1
Field01 = 2, Field02 = 2, Answer = 1
Field01 = 2, Field02 = 3, Answer = 1
Result
count(TableB.Answer) AS unAnswered = 1
Result trying to achive
count(TableB.Answer) AS unAnswered = 1
count(TableA.Field03) = 4
Any help will be greatly appreciated.
If you are just looking to add the count of tableA to your current query just add it to your select statement:
'SELECT count(TableB.Answer) AS unAnswered, count(TableA.Field02) FROM TableA ...'
I just figured out my own question.
SELECT **COALESCE(count(DISTINCT TableB.Answer)) AS unAnswered, count(DISTINCT TableA.Field05)**
FROM TableA
LEFT JOIN TableB
ON ( TableA.Field01 = TableB.fkField01 AND TableA.Field02 = TableB.fkField02 AND TableB.Answer = '1')
WHERE TableB.fkField03 IS NULL AND TableA.Field04 = 10
GROUP BY TableA.Field01, TableA.Field02
Adding COALESCE and DISTINCT solved my issue. Thank you everyone for your help.
Based on this table
key sampleID rs A1 A2
1 12345 rs123 C C
2 12345 rs345 C C
3 11110 rs123 C C
4 11110 rs345 C A
This statement
SELECT sampleID
FROM QS_base
WHERE (rs = 'rs123' AND A1 = 'C' AND A2 = 'C')
OR (rs = 'rs345' AND A1 = 'C' AND A2 = 'C')
Returns
12345
12345
11110
And this statement
SELECT sampleID
FROM QS_base
WHERE (rs = 'rs123' AND A1 = 'C' AND A2 = 'C')
AND (rs = 'rs345' AND A1 = 'C' AND A2 = 'C')
Returns no records. I expected it to return
12345
12345
Why is it retuning no results and is there a way to write it so that the above result can be obtained?
You can get what you want using group by and having:
SELECT sampleID
FROM QS_base
WHERE (rs = 'rs123' AND A1 = 'C' AND A2 = 'C') OR
(rs = 'rs345' AND A1 = 'C' AND A2 = 'C')
GROUP BY sampleID
HAVING COUNT(DISTINCT rs) = 2;
The AND will need both the condition to be satisfied at the same time and can not find a row with 2 different condition.
You can use exits to do it
select t1.sampleID
from QS_base t1
where
t1.rs = 'rs123' AND t1.A1 = 'C' AND t1.A2 = 'C'
and exists
(
select 1 from QS_base t2 where t1.sampleID = t2.sampleID
and t2.rs = 'rs345' AND t2.A1 = 'C' AND t2.A2 = 'C'
)
You're looking for samples where rs is both 'rs123' and 'rs345'. The query only looks at rows individually.
You have excluding conditions in the second example. The where clause:
WHERE (rs = 'rs123' AND A1 = 'C' AND A2 = 'C') AND (rs = 'rs345' AND A1 = 'C' AND A2 = 'C')
cloud be written as:
WHERE rs = 'rs123' AND rs = 'rs345' AND A1 = 'C' AND A2 = 'C'
rs cannot be equal to rs123 and rs345 at the sime time. :)
As for the second part of Your question, yes it certainly can return result that You need. Just write:
WHERE (rs = 'rs123' AND A1 = 'C' AND A2 = 'C')
I assume You have additional requirements on this query, if so, please state them in You post.
Say I have a query like this:
SELECT * FROM my_table WHERE name = "john doe" AND phone = "8183321234" AND email = "johndoe#yahoo.com" AND address = "330 some lane";
But say I only need 3 out of the 4 to match, I know I can write a very long query with several ORs but I was wondering if there was a feature for this?
Thanks.
SELECT
*
FROM
my_table
WHERE
CASE WHEN name = "john doe" THEN 1 ELSE 0 END +
CASE WHEN phone = "8183321234" THEN 1 ELSE 0 END +
CASE WHEN email = "johndoe#yahoo.com" THEN 1 ELSE 0 END +
CASE WHEN address = "330 some lane" THEN 1 ELSE 0 END
>= 3;
Side note: this will very likely not be using indexes efficiently. On the other hand, there will very likely be no indexes on these kinds of columns anyway.
Holy overcomplexity, Batman.
SELECT *
FROM my_table
WHERE (
(name = "john doe") +
(phone = "8183321234") +
(email = "johndoe#yahoo.com") +
(address = "330 some lane")
) >= 3;
Same thing using indexes:
SELECT *
FROM (
SELECT id
FROM (
SELECT id
FROM mytable _name
WHERE name = 'john doe'
UNION ALL
SELECT id
FROM mytable _name
WHERE phone = '8183321234'
UNION ALL
SELECT id
FROM mytable _name
WHERE email = "johndoe#yahoo.com"
UNION ALL
SELECT id
FROM mytable _name
WHERE address = '330 some lane'
) q
GROUP BY
id
HAVING
COUNT(*) >= 3
) di, mytable t
WHERE t.id = di.id
See the entry in my blog for performance details.
I like the IF construct:
SELECT * FROM my_table
WHERE
( IF(name = 'john doe', 1, 0) +
IF(phone = '8183311234', 1, 0) +
IF(email = 'johndoe#yahoo.com', 1, 0) +
IF(address = '330 some lane', 1, 0)
) >= 3
Modifying Tomalak's query slightly so that it will use indexes if they are present. Although unless there is an index on each field, a full table scan will happen anyway.
SELECT
*,
(
IF(name="john doe", 1, 0) +
IF(phone = "8183321234", 1, 0) +
IF(email = "johndoe#yahoo.com", 1, 0) +
IF(address = "330 some lane", 1, 0)
) as matchCount
FROM my_table
WHERE
name = "john doe" OR
phone = "8183321234" OR
email = "johndoe#yahoo.com" OR
address = "330 some lane"
HAVING matchCount >= 3