SQL join multiple criteria - mysql

I have a difficult task to build up an array retrieved from a table similar to the one below:
table_a
id | scenario_id | entity_id
1 1;2;3;4;5 1;3
2 4;5;8;10 2;3
3 1;5;8;11 1;2;4;
4 3;5;8;9 4;5;
Now, if one user selects from one entity_id, let's say 3, the SQL query should return something similiar to:
scenario_id
1;2;3;4;5;8;10
Or, if he selects 5, the returned array should look like:
scenario_id
3;5;8;9
Could that be done using only SQL statements?

For SQL Server you can use this to get desired output:
DECLARE #xml xml, #entity_id int = 3
--Here I generate data similar to yours
;WITH cte AS (
SELECT *
FROM (VALUES
(1, '1;2;3;4;5', '1;3'),
(2, '4;5;8;10', '2;3'),
(3, '1;5;8;11', '1;2;4;'),
(4, '3;5;8;9', '4;5;')
) as t(id, scenario_id, [entity_id])
)
--create xml
SELECT #xml = (
SELECT CAST('<i id="'+ CAST(id as nvarchar(10)) +'"><s>' + REPLACE(scenario_id,';','</s><s>') + '</s><e>' + REPLACE([entity_id],';','</e><e>') + '</e></i>' as xml)
FROM cte
FOR XML PATH('')
)
--Normalizing the table and getting result
SELECT STUFF((
SELECT ';' + CAST(scenario_id as nvarchar(10))
FROM (
SELECT DISTINCT t.v.value('.','int') as scenario_id
FROM #xml.nodes('/i/s') as t(v)
INNER JOIN #xml.nodes('/i/e') as s(r)
ON t.v.value('../#id','int') = s.r.value('../#id','int')
WHERE s.r.value('.','int') = #entity_id
) as p
FOR XML PATH('')),1,1,'') as scenario_id
Output for entity_id = 3:
scenario_id
1;2;3;4;5;8;10
For entity_id = 5
scenario_id
3;5;8;9

you can use something like this to find a id in the scenario_id, but its always a FULL TABLE scan.
SELECT *
FROM table_a
WHERE
FIND_IN_SET('3', REPLACE(scenario_id,';',',')) > 0;

Simple. NORMALISE your schema... At it's crudest, that might be as follows...
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(id INT NOT NULL
,scenario_id INT NOT NULL
,entity_id INT NOT NULL
,PRIMARY KEY (id,scenario_id,entity_id)
);
INSERT INTO my_table VALUES
(1, 1,1),
(1, 1,3),
(1, 2,1),
(1, 2,3),
(1, 3,1),
(1, 3,3),
(1, 4,1),
(1, 4,3),
(1, 5,1),
(1, 5,3),
(2, 4,2),
(2, 4,3),
(2, 5,2),
(2, 5,3),
(2, 8,2),
(2, 8,3),
(2,10,2),
(2,10,3),
(3, 1,1),
(3, 1,2),
(3, 1,4),
(3, 5,1),
(3, 5,2),
(3, 5,4),
(3, 8,1),
(3, 8,2),
(3, 8,4),
(3,11,1),
(3,11,2),
(3,11,4),
(4, 3,4),
(4, 3,5),
(4, 5,4),
(4, 5,5),
(4, 8,4),
(4, 8,5),
(4, 9,4),
(4, 9,5);
SELECT DISTINCT scenario_id FROM my_table WHERE entity_id = 3 ORDER BY scenario_id;
+-------------+
| scenario_id |
+-------------+
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
| 8 |
| 10 |
+-------------+

split the scenario_id by ';' and copy to temporary table to use that for your query use instr and substring functions
this link may help you but you need a loop function to call your procedure as the ';' is repeated

Related

How to join two mysql table and retrieve latest results from joined table?

I have two tables. I need to join these two tables and retrieve latest status from execution table. How can I retrieve?
My schema and data:
CREATE TABLE test
(`id` serial primary key, `ref_id` int, `ref_name` varchar(7))
;
INSERT INTO test
(`id`, `ref_id`, `ref_name`)
VALUES
(1, 1, 'trial'),
(2, 3, 'test'),
(3, 7, 'testing')
;
CREATE TABLE execution
(`id` serial primary key, `ref_id` int, `status` varchar(11))
;
INSERT INTO execution
(`id`, `ref_id`, `status`)
VALUES
(1, 1, 'Completed'),
(2, 2, 'Completed'),
(3, 1, 'Completed'),
(4, 3, 'In progress'),
(5, 3, 'To do'),
(6, 2, 'In progress'),
(7, 1, 'Completed'),
(7, 1, 'To do')
;
Expected result is here below.
ref_id | ref_name | status |
3 | testing | In progress |
2 | test | To do |
1 | trial | To do |
I have tried with below query:
SELECT
ref_id,
ref_name,
status
FROM
test
JOIN execution ON test.ref_id = execution.ref_id
GROUP BY `ref_id`
ORDER BY `ref_id` DESC;
This query retrieves the status, but the retrieved status is not a latest one. How can retrieve the latest status by joining these two tables.
you can use below query
select T2.ref_id,T2.ref_name,OE.status from
(
select t1.ref_id,t1.ref_name,e.id from test t1 inner join
(select max(id) as id,ref_id from execution group by ref_id) as e
on
t1.ref_id=e.ref_id
) as T2
inner join execution OE on T2.id=OE.id
https://www.db-fiddle.com/f/rvnm8APX27dmW9a84JkCsS/1
It seems you have given in-correct data as an example as ref_id 7 not found in
execution table. However this might help you
SELECT b.ref_id,
b.ref_name,
a.status
FROM execution a
JOIN (SELECT MAX(id) id ,ref_id
FROM execution
GROUP BY ref_id) a1
USING(id,ref_id)
JOIN test b ON a.ref_id = b.ref_id ORDER BY ref_id DESC;

Group Concat TWO columns but in GROUPS -- THREE separators involved

We have a mysql table
id name groupid
1 user1 0
2 user2 0
3 user3 1
4 user4 1
We want the GROUP CONCAT such that we get the output as
1,user1;2,user2---3,user3;4,user4
This does what you describe:
create table NoOneEverNamesTheTableInSqlQuestions (
id int,
name text,
groupid int
);
insert into NoOneEverNamesTheTableInSqlQuestions values
(1, 'user1', 0),
(2, 'user2', 0),
(3, 'user3', 1),
(4, 'user4', 1);
select group_concat(g separator '---') as output
from (
select group_concat(concat_ws(',',id,name) separator ';') as g
from NoOneEverNamesTheTableInSqlQuestions
group by groupid
) as g;
Output, tested with MySQL 8.0.0-dmr:
+-----------------------------------+
| output |
+-----------------------------------+
| 1,user1;2,user2---3,user3;4,user4 |
+-----------------------------------+
But I don't know why you would want to do this. It seems like something that would be easier to do in application code.

How to aggregate results in many to many Query

I have 3 tables in my database:
Student:
id
name
Student_Course:
student_id
course_id
Course:
id
grade
And I want to list all the students and the results of whether they have pass all of the course they have chosen . Assuming that grade <= 'C' is pass.
I tried sql like:
SELECT s.*,
IF('C'>=ALL(SELECT c.grade FROM from Course c WHERE c.id=sc.course_id),1,0) as isPass
FROM Student s LEFT JOIN Student_Course sc on sc.student_id=s.id
This sql works, but if now I want a column 'isGood' which means all the grade='A', do I need to execute the subquery again? How can I get both 'isGood' and 'isPass' by executing subquery only once?
I believe the grade would be better served in the junction table. Using that, I have created a scenario that might help you solve your question:
Scenario
create table student (id int, fullname varchar(50));
insert into student values (1, 'john'), (2, 'mary'), (3, 'matt'), (4, 'donald');
create table course (id int, coursename varchar(50));
insert into course values (1, 'math'), (2, 'science'), (3, 'business');
create table student_course (student_id int, course_id int, grade char(1));
insert into student_course values
(1, 1, 'C'), (1, 2, 'C'), (1, 3, 'C'),
(2, 1, 'A'), (2, 2, 'A'), (2, 3, 'A'),
(3, 1, 'A'), (3, 2, 'C'), (3, 3, 'C'),
(4, 1, 'A'), (4, 2, 'C'), (4, 3, 'F');
Query
select s.*, case when all_a_grades.student_id is not null then 'GOOD' else 'MEH' end as grades
from student s
left join (
-- find students who got A in all classes
select student_id, count(distinct ca.id) as aclasses, count(distinct sc.course_id) as allclasses
from student_course sc
left join (select id, 'A' as agrade from course) ca
on ca.id = sc.course_id and ca.agrade = sc.grade
group by student_id
having aclasses = allclasses
) all_a_grades on all_a_grades.student_id = s.id
where not exists (
-- let's make sure we filter OUT students who have failed
-- at least one course
select 1
from (
-- find students who have failed at least one course
select distinct student_id
from student_course
where grade not in ('A', 'B', 'C')
) t where t.student_id = s.id
)
Result
| id | fullname | grades |
| 1 | john | MEH |
| 2 | mary | GOOD |
| 3 | matt | MEH |

MySQL count occurrences of data displayed as list in cells

Good day all.
Lets say that I've a table organized like this:
|ColA |
---------------
|AAA |
|AAA#!#BBB |
|BBB#!#CCC#!#DDD|
|AAA#!#DDD |
|DDD |
What I would like to achieve is to count occurrences of every string, considering the simbols "#!#" as a separator.
The best should be having a MySQL Count() result style:
|count| |
|AAA |3 |
|BBB |2 |
|CCC |1 |
|DDD |3 |
Is it possible to combine a replace with a count in a SQL statement? is it possible to use the replace result as a table to apply the count on?
Something like this should do the trick:
SCHEMA
CREATE TABLE TableA
(`id` int, `ColA` varchar(255));
INSERT INTO TableA
(`id`, `ColA`)
VALUES
(1, 'AAA'),
(2, 'AAA#!#BBB'),
(3, 'BBB#!#CCC#!#DDD'),
(4, 'AAA#!#DDD'),
(5, 'DDD');
CREATE TABLE TableB
(`id` int, `Name` varchar(255));
INSERT INTO TableB
(`id`, `Name`)
VALUES
(1, 'AAA'),
(2, 'BBB'),
(3, 'CCC'),
(4, 'DDD');
QUERY
SELECT col, SUM(count) as count
FROM
(SELECT
t2.Name as col,
ROUND (
(
CHAR_LENGTH(t1.ColA)
- CHAR_LENGTH( REPLACE (t1.ColA, t2.Name, "") )
) / CHAR_LENGTH(t2.Name)
) as count
FROM TableA as t1
INNER JOIN TableB as t2) as SubB
GROUP BY col, count
HAVING count > 0;
SQL FIDDLE

MySQL multiple table query with average for each row

This is my setup:
Table "files": id (PK), filename, user_id, date, filesize
Table "scores": id(PK), file_id, user_id, score
Table "files" contains a list of files with details; table "scores" keeps track of 1-5 points scored per file. I need to get entries from the "files" table and in each row I need all the info for the file, as well as the average score. I can do another query for teh current file_id while I'm looping through the rows, but obviousely that's not very optimized. I tried something like below, but no success.
SELECT files.*, (SUM(scores.score)/(COUNT(scores.score))) AS total FROM files INNER JOIN scores ON files.id=scores.file_id;
Please point me in the right direction - thanks!
You may want to try the following:
SELECT f.id, f.filename, f.user_id, f.date, f.filesize,
(
SELECT AVG(s.score)
FROM scores s
WHERE s.file_id = f.id
) average_score
FROM files f;
Note that you can use the AVG() aggregate function. There is no need to divide the SUM() by the COUNT().
Test case:
CREATE TABLE files (id int, filename varchar(10));
CREATE TABLE scores (id int, file_id int, score int);
INSERT INTO files VALUES (1, 'f1.txt');
INSERT INTO files VALUES (2, 'f2.txt');
INSERT INTO files VALUES (3, 'f3.txt');
INSERT INTO files VALUES (4, 'f4.txt');
INSERT INTO scores VALUES (1, 1, 10);
INSERT INTO scores VALUES (2, 1, 15);
INSERT INTO scores VALUES (3, 1, 20);
INSERT INTO scores VALUES (4, 2, 5);
INSERT INTO scores VALUES (5, 2, 10);
INSERT INTO scores VALUES (6, 3, 20);
INSERT INTO scores VALUES (7, 3, 15);
INSERT INTO scores VALUES (8, 3, 15);
INSERT INTO scores VALUES (9, 4, 12);
Result:
SELECT f.id, f.filename,
(
SELECT AVG(s.score)
FROM scores s
WHERE s.file_id = f.id
) average_score
FROM files f;
+------+----------+---------------+
| id | filename | average_score |
+------+----------+---------------+
| 1 | f1.txt | 15.0000 |
| 2 | f2.txt | 7.5000 |
| 3 | f3.txt | 16.6667 |
| 4 | f4.txt | 12.0000 |
+------+----------+---------------+
4 rows in set (0.06 sec)
Note that #Ignacio's solution produces the same result, and is therefore another option.
Aggregate functions are not usually useful without aggregation.
SELECT f.*, AVG(s.score) AS total
FROM files AS f
INNER JOIN scores AS s
ON f.id=s.file_id
GROUP BY f.id