MySQL Select with Subquery - mysql

I have 2 table in the database: mainTable and classificationTable:
mainTable:
id | classification_id | name
---------------------------------------
1 | 1 | Name1
2 | 2,3,4 | Name2
3 | 1,4 | Name3
4 | 4 | Name4
classificationTable:
classification_id | class_name
---------------------------------------
1 | Class Name1
2 | Class Name2
3 | Class Name3
4 | Class Name4
I want to get a select for example for row with ID 3 from mainTable like:
id = 3
class_name = Class Name1, Class Name4
Name = Name3
I try this select, but this return only first elemnt from array (fore exempla for row with ID 3, this return only Class Name1)
SELECT i.*,
(SELECT GROUP_CONCAT(cl.class_name) FROM classificationTable as cl WHERE cl.classification_id IN(i.classification_id)) as class_name
FROM mainTable as i;
Help PLZ.

Yes, you have a poorly designed database. But you can do what you want.
This requires two steps. The first is to join the mainTable to the classificationTable by looking for each classification_id in the list in the mainTable. You can do this using the like operator.
The second step is to bring all the class names back into one column. This is handled using group_concat.
The following query accomplishes this (although it is not tested, so it might have a typo):
select mt.id, mt.name, group_concat(ct.class_name separator ', ')
from mainTable mt join
classificationTable ct
on concat(',', classification_id, ',') like concat('%,', ct.classification_id, ',%')
group by mt.id, mt.name

The problem is your mainTable, which not only violates the 1st normal form but goes even beyond because it concatenates values in the same field (textbook example of a 1NF violation often includes multiple fields, which is bad enough).
The classification_id column should not be a concatenation of values but you should have a new table where you have a separate row for each classification_id and it should be tied into mainTable by id.
E.g.
id | classification_id
---------------------------------------
1 | 1
2 | 2
2 | 3
2 | 4
3 | 1
3 | 4
4 | 4
Once you've done that, your query will be much easier.

Related

Mysql IN function

class_table
+----+-------+--------------+
| id |teac_id| student_id |
+----+-------+--------------+
| 1 | 1 | 1,2,3,4 |
+----+-------+--------------+
student_mark
+----+----------+--------+
| id |student_id| marks |
+----+----------+--------+
| 1 | 1 | 12 |
+----+----------+--------+
| 2 | 2 | 80 |
+----+----------+--------+
| 3 | 3 | 20 |
+----+----------+--------+
I have these two tables and i want to calculate the total marks of student and my sql is:
SELECT SUM(`marks`)
FROM `student_mark`
WHERE `student_id` IN
(SELECT `student_id` FROM `class_table` WHERE `teac_id` = '1')
But this will return null, please help!!
DB fiddle
Firstly, you should never store comma separated data in your column. You should really normalize your data. So basically, you could have a many-to-many table mapping teacher_to_student, which will have teac_id and student_id columns.
In this particular case, you can utilize Find_in_set() function.
From your current query, it seems that you are trying to getting total marks for a teacher (summing up marks of all his/her students).
Try:
SELECT SUM(sm.`marks`)
FROM `student_mark` AS sm
JOIN `class_table` AS ct
ON FIND_IN_SET(sm.`student_id`, ct.`student_id`) > 0
WHERE ct.`teac_id` = '1'
In case, you want to get total marks per student, you would need to add a Group By. The query would look like:
SELECT sm.`student_id`,
SUM(sm.`marks`)
FROM `student_mark` AS sm
JOIN `class_table` AS ct
ON FIND_IN_SET(sm.`student_id`, ct.`student_id`) > 0
WHERE ct.`teac_id` = '1'
GROUP BY sm.`student_id`
Just in case you want to know why, The reason it returned null is because the subquery returned as '1,2,3,4' as a whole. What you need is to make it returned 1,2,3,4 separately.
What your query returned
SELECT SUM(`marks`)
FROM `student_mark`
WHERE `student_id` IN ('1,2,3,4')
What you expect is
SELECT SUM(`marks`)
FROM `student_mark`
WHERE `student_id` IN (1,2,3,4)
The best way is it normalize as #madhur said. In your case you need to make the teacher and student as one to many link
+----+-------+--------------+
| id |teac_id| student_id |
+----+-------+--------------+
| 1 | 1 | 1 |
+----+-------+--------------+
| 2 | 1 | 2 |
+----+-------+--------------+
| 3 | 1 | 3 |
+----+-------+--------------+
| 4 | 1 | 4 |
+----+-------+--------------+
If you want to filter your table based on a comma separated list with ID, my approach is to
append extra commas at the beginning and at the end of a list as well as at the beginning and at the end of an ID, eg.
1 becomes ,1, and list would become ,1,2,3,4,. The reason for that is to avoid ambigious matches like 1 matches 21 or 12 in a list.
Also, EXISTS is well-suited in that situation, which together with INSTR function should work:
SELECT SUM(`marks`)
FROM `student_mark` sm
WHERE EXISTS(SELECT 1 FROM `class_table`
WHERE `teac_id` = '1' AND
INSTR(CONCAT(',', student_id, ','), CONCAT(',', sm.student_id, ',')) > 0)
Demo
BUT you shouldn't store related IDs in one cell as comma separated list - it should be foreign key column to form proper relation. Joins would become trivial then.

How to craft an SQL to create a report that selects all quotes with a certian line item, and to include another item if it is on that quote?

I have two tables: quote and item.
quote table
id | quote_number
---+-------
1 | 100
2 | 200
item table
id | model | quote_id | other_data
---+-------+----------+-----------
1 | ABC | 1 | xxx
2 | DISC | 1 | xxx
3 | ABC | 2 | xxx
4 | DISC | 2 | xxx
3 | XXX | 3 | xxx
4 | DISC | 3 | xxx
3 | ABC | 4 | xxx
I need to create a report to where I select all quotes that must contain the model ABC AND if they do, I also wish to list model DISC belonging to that quote. I want to list all such item lines. How?
In plain English that would be "give me all quotes with model ABC on them that may also have the discount (DISC) associated with it".
Example Report
quote_id | model | other_data
---------+-------+---------+-
1 | ABC | xxx
1 | DISC | xxx
2 | ABC | xxx
2 | DISC | xxx
So far I can only figure out how to pull lines with ABC in them, but I don't know how to pull in the lines with DISC, which have a condition of "must have ABC connected to the same quote".
You could use a subquery, You can use CTE's as well if you are using MySQL 8.0
SELECT it.quote_id, it.model, it.other_data
FROM item it INNER JOIN (
SELECT quote_id
FROM item i WHERE model = 'ABC') as sub_it
ON it.quote_id=sub_it.quote_id
WHERE it.model='ABC' OR it.model='DISC'
ORDER BY it.quote_id DESC;
DB Fiddle
UNION ALL seems like a good approach:
select ia.*
from items ia
where ia.model = 'ABC'
union all
select id.*
from items id
where id.model = 'DISC' and
exists (select 1 from items ia where ia.quote_id = id.quote_id and ia.model = 'ABC')
order by quote_id, model;
This logic can take advantage of indexes on items(model) and items(quote_id, model). The order by would be needed for any solution.
I vote for the sub query approach as the easiest and cleanest way to go. I dislike using OR conditions so I've used an IN List instead.
SELECT
quote_id,
model,
other_data
FROM item
Where quote_id in ( SELECT quote_id FROM item i WHERE model = 'ABC' )
AND model IN ('ABC', 'DISC')
ORDER BY 1,2;
You should consider the JOIN Statements:
SELECT quote_id, model, other_data FROM item i INNER JOIN quote q ON i.id = q.quote_id WHERE model = 'ABC' or model = 'DISC' ORDER BY quote_id DESC;

SQL: Count distinct row values in table

I have a similar table to this in SQL:
id |tag | entryID
---+----+--------
1 |foo | 0
2 |foo | 0
3 |bar | 3
5 |bar | 3
6 |foo | 3
7 |foo | 3
I want to run a query to count distinct rows in the table (with the id column dropped). The result should look like this (or a transpose of this table):
(tag=foo, entryID=0) | (tag=foo, entryID=3) | (tag=bar, entryID=3)
---------------------+----------------------+---------------------
2 | 2 | 2
What should this query look like?
Note: The values in each of the columns are not known beforehand.
You can do this using conditional aggregation:
select sum(tag = 'foo' and entryId = 0) as "tag=foo, entryID=0",
sum(tag = 'foo' and entryId = 3) as "tag=foo, entryID=3",
sum(tag = 'bar' and entryId = 3) as "tag=bar, entryID=0"
from t;
However, the normal method is to put the counts in rows, not columns:
select tag, entryId, count(*)
from t
group by tag, entryId;
The rows are much more versatile, because you don't have to list out every combination you might want to match.

Select one fixed line and several arbitrary

I have a table with many lines. I need select several (no more than three) lines with certain values and one more. Moreover, need ORDER BY id DESC and required line position before other. Example:
id | name | group
-----------------
1 | One | null
2 | Two | null
3 | Three| 2
4 | Four | 3
5 | Five | 1
6 | Six | 2
I need lines with group == 2 (no more than three) and line with id == 2. Result:
id | name | group
-----------------
2 | Two | null
3 | Three| 2
6 | Six | 2
Line with id == 2 must be selected, other lines - no more than three. If I use WHERE id = 2 OR group = 2 LIMIT 4 than if exist more than 4 lines with group == 2, then required line with id == 2 not selected.
How solve the problem in one SQL-request?
You can try using UNION. Also note that group is a reserved word for MySQL.
SELECT aa.*
FROM (
SELECT id, firstname, lastname
FROM user
WHERE id IN (1, 2, 3, 4)
LIMIT 2
UNION ALL
SELECT id, firstname, lastname
FROM user
WHERE id = 5
) AS aa
select * from table_name where id=2
union all
select * from table_name where group=2

MySQL Efficient Multi-Table Multi-Index Select

I want to search by name1 or name2 from two tables in a database.
IMPORTANT: Only some names have a secondary name2 associated with them.
The tables n1, and n2 are as follows:
table n1
|---------------*----------------*-----------------|
| id | name1 | n2_id |
|---------------*----------------*-----------------|
| 1 | Joseph | 1 |
| 2 | David | NULL |
| 3 | James | 2 |
|---------------*----------------*-----------------|
table n2
|---------------*----------------|
| id | name2 |
|---------------*----------------|
| 1 | Joe |
| 2 | Jim |
|---------------*----------------|
(I know that I could have just created a name 2 field in the n1 table, however this is just a simplification of a more complex system that requires this structure.)
I currently select from table n1 as follows:
SELECT id, name1, MATCH ( name1 ) AGAINST ( "+joe*") AS score
FROM n1
WHERE MATCH ( name1 ) AGAINST ( "+joe*" in boolean mode)
order by score desc;
This works very efficiently with a very large table.
However, I'd like to select from n1 and n2, MATCHING name1 or name2 (when it exists) against a search string as if both tables were combined.
The issue is that I have had problems with efficiency of a select that orders by a combined score when there are different indexes across multiple tables.
This question isn't really just about how to construct the SELECT. It's about how to construct the SELECT in such a way that the indexes are used efficiently. (the tables are very large)
Try this
SELECT
n1.id, name1, n2_id, n2.id, name2
FROM n1 LEFT OUTER JOIN n2 ON
n2.id = n2_id
WHERE name1 LIKE '%j%' OR name2 LIKE '%j%'
ORDER BY name1;
I used LEFT OUTER JOIN, due to
"Only some names have a secondary name2 associated with them."
I combined two tables based on n2_id against id field in n2.
I used mysql LIKE for String Comparison.
Here is the SQLFIDDLE