getting quize data, questions and answers in 1 query? - mysql

I need to get quize title, quize description, quize questions and answers for each questions. My table structure is:
quizes
quize_id | title | user_id | ...
questions
questions_id | quize_id | question | ...
question_answers
answer_id | question_id | user_id | answer | ...
I can use join
SELECT * FROM quizes JOIN questions q ON q.quize_id=quizes.quize_id JOIN question_answers a ON a.question_id=q.question_id
But the problem with this is that I will get in results many rows with redundant data. For example each row will carry field title,user_id, ... Another way is to make for each question extra query to get answers. Is there any better way? Should I use only 1 query or more?

Your tables hold 3 types of data. If you use the query you've got, you'll get all the data as a big table. You've said that this involves a lot of duplication.
If you use multiple queries, you will get multiple result sets, which effectively will leave you with multiple tables, and thus this is unlikely to help.
You could cut the query down to just the columns you want to get the data for:
SELECT qq.Question, qa.Answer
FROM quizes qz
join questions qq on qz.quize_id = qq.quize_id
join question_answers qa on qq.question_id = qa.question_id
WHERE qz.quize_id = #quize_id
ORDER BY 1, 2 --or other ordering
However where there are multiple answers for the same question, the question will be repeated on every row. There isnt much you can do about that, it is the price of combining multiple table's data into one table ("denormalising").
If you need to format your output table so that it looks like this (but with more columns):
Quize_id | Question | Answer
1 Q1 A1
A2
Q2 A3
2 Q3 A4
This is a whole different matter. You would need to use the query you've got to populate a temporary table, ordering the data by the sort order you want displayed. To this table you'd need to add a primary key (integer) column, then run a set of update statements to replace the repeated values with nulls, then output the table in the order of the primary key column. (There are other ways to do this, but this is the easiest to explain)
Does this help?

I found also another way which return all data I need, including user details for each question:
SELECT
question,
group_concat(qa.answer SEPARATOR ',') as answers,
group_concat(qa.user_id SEPARATOR ',') as userIds,
group_concat(up.nickname SEPARATOR ',') as nickname
FROM quize_questions qq
INNER JOIN question_answers qa ON qa.question_id=qq.question_id
INNER JOIN user_profile up ON up.user_id = qa.user_Id
GROUP BY qq.question_id
I am just not sure if this is the right way. I am worried about speed.

Related

SQL join mapping table and get back columns not rows [duplicate]

This question already has answers here:
How can I return pivot table output in MySQL?
(10 answers)
Closed 5 years ago.
since 2 days I'm trying to find a solution...
I have two tables:
-- components -- colums:
id | name | description
-- components_ingredients -- colums:
component_id | ingredient_id
=> one component can have multiple ingredients
so when I join the tables with my statement:
SELECT * FROM components c
INNER JOIN components_ingredients ci ON c.id = ci.component_id
I get back one row for every ingredient in table ci. But I want to get back only one row with the matched ingredients as additional columns like:
c.id | c.name | c.description | ci.ingredient1 | ci.ingredient2 | ci.ingredient3 ...
Is this possible and when how??
Thanks
You can try using MySQL's GROUP_CONCAT() function to create a CSV list of the ingredients for each given component.
SELECT c.id, c.name, c.description, ci.ingredients
FROM components c
INNER JOIN
(
SELECT component_id, GROUP_CONCAT(ingredient_id) AS ingredients
FROM components_ingredients
GROUP BY component_id
) ci
ON c.id = ci.component_id
Note that as #Gordon pointed out, you might be able to do without the subquery I used, but in general you might need it. The reason Gordon's query works, even according to the ANSI standard, is a given id in the components table should uniquely determine the name and description. Hence, it is OK to include those columns while using GROUP BY, because there is no ambiguity involved.
It is hard to put the ingredients in separate columns, because you don't now how many there are.
Much easier is to concatenate them together into a string in one column:
SELECT c.*, GROUP_CONCAT(ci.component_id) as component_ids
FROM components c INNER JOIN
components_ingredients ci
ON c.id = ci.component_id
GROUP BY c.id;
Note: It is generally bad practice to include columns in the SELECT that are not in the GROUP BY. However, it is okay in this case, because components.id uniquely identifies each row. This functionality is even specified as okay in the ANSI standard -- although few databases actually implement it.

SQL query to compare rows with each other based on relational data from other tables

First I apologize in advance if my question is too broad. I do not have a lot of experience with SQL and I am struggling with designing a query for a very specific task. In no way I am asking for someone to do all of the work. I just want some guidance on how to build the query and if it is a good solution for the task to be done entirely in SQL at all.
The query I am trying to build needs to list all rows from one table, matched with each other and compare them by checking in another table how many rows have matched. To illustrate I have the following tables:
members (member_id, name)
questions (question_id, title)
answers (answer_id, question_id, title)
members_answers (member_id, question_id, answer_id)
members_acceptable_answers (member_id, question_id, answer_id)
Each member has only one record in members_answers and multiple records in members_acceptable_answers.
What I am trying to show is a list with each members and how many of their acceptable answers have been matched with other members' answers producing a result like this:
member_id | member_id | total_intercepted |
-------------------------------------------
1 | 2 | 10 |
2 | 3 | 6 |
1 | 3 | 3 |
I can make the data on the application level but I want to know if there's a proper way for this to be done in the database. I have been experimenting with different queries but none of them are even close to producing the result and they aren't even worth mentioning here.
Again - I just want a guidance on how to build my query and opinion if such a task is even appropriate for SQL only.
Thank you in advance.
This might be a good start... you might need some indexes to pull this off, but here is some starting SQL:
SELECT ma.member_id AS 'Member 1',
ma2.member_id AS 'Member 2',
count(maa.answer_id) AS 'Total Matches'
FROM members_answers ma
JOIN questions q
ON q.question_id = ma.question_id
JOIN questions q2
ON q.question_id = q2.question_id
JOIN members_answers ma2
ON ma2.question_id = q2.question_id
JOIN members_acceptable_answers maa
ON ma2.answer_id = maa.answer_id
AND ma.member_id = maa.member_id
GROUP BY 1,2
ORDER BY 1,2
This way.. you are only counting members who answered questions that the potential matching members asked, then you are counting up the total answers that match, including the second member.
Hope this helps...
-Tony
I think the good place for you to start learning the sql will be http://www.w3schools.com/sql/default.asp , To bring data from multiple tables you need to join the tables , to determine which join needs to be done check out this article http://sqlbisam.blogspot.com/2013/12/InnerJoin-LeftOuterJoin-RighOuterJoin-and-FullOuterJoin.html and to compare data use except statement

MySQL - How do I optimize appending field from table b to a query of table a

I know this has to be a fairly common issue, and I am sure the answer is readily available but I am not sure how to phrase my search so I have been forced to troubleshoot this on my own for the most part.
Table A
id | content_id | score
1 | 2 | 16
2 | 2 | 4
3 | 3 | 8
4 | 3 | 12
Table B
id | content
1 | "Content Goes Here"
2 | "Content Goes Here"
3 | "Content Goes Here"
Objective: SUM all scores from table A, group by the unique content_id and show the content associated with the id, ordered by the sum score.
Current Working Query:
SELECT a.content_id, b.content, SUM(a.score) AS sum
FROM table_a a
LEFT JOIN table_b b ON a.content_id = b.id
GROUP BY a.content_id
ORDER BY sum ASC;
Problem: As far as I can tell, with the way I have structured my query, the content is grabbed from table_b by looping through each record on table_a, checking for a record in table_b with an identical id, and grabbing the content field. The problem here is that in table_a there is nearly 500k+ records, and in table_b there is 112 records. Which means that potentially 500,000 x 112 cross table lookups/matches are being performed just to attached 112 unique content fields to a total of 112 results in the ending result set.
HELP!: How do I more efficiently append the 112 content fields from table_b to the 112 results produced by the query? I am guessing it has something to do with the query execution order, like somehow only looking for and appending the content field to the matched result row AFTER the sums are produced and it is narrowed down to only 112 records? Have studied the MySQL API and benchmarked various subqueries, several joins, and even tried playing with UNION. It is probably something abundandtly obvious to you guys, but my brain just can't get around it.
FYI: Like mentioned earlier, the query does work. The results are produced in about 8 to 10 seconds, and of course each subsequent query after that is immediate because of query caching. But for me, with how simple this is, I know that 8 seconds can at LEAST be cut in half. I just feel it deep down in my guts. Right deep down in my gutssss.
I hope this is concise enough, if I need to clarify or explain something better please let me know! Thanks in advance.
The MySQL query optimiser only allows "nested loop joins" ** These are the internal operators for how an INNER join is evaluated. Other RDBMS allow other kinds of JOINs which are more efficient.
However, in your case you can try this. Hopefully the optimiser will do the aggregate before the JOIN
SELECT
a.content_id, b.content a.sum
FROM
(
SELECT content_id, SUM(score) AS sum
FROM table_a
GROUP BY content_id
) a
JOIN table_b b ON a.content_id = b.id
ORDER BY
sum ASC;
In addition, if you don't want the results ordered you can use ORDER BY NULL which usually removes a filesort from the EXPLAIN. And of course, I assume that there are indexes on the 2 content_id columns (one primary key, one foreign key index)
Finally, I would also assume that an INNER JOIN will be enough: every a.contentid exists in tableb. If not, you are missing a foreign key and index on a.contentid
** It's getting better but you need MariaDB or MySQL 5.6
This should be a little faster:
SELECT
tmp.content_id,
b.content,
tmp.asum
FROM (
SELECT
a.content_id,
SUM(a.score) AS asum
FROM
table_a a
GROUP BY
a.content_id
ORDER BY
NULL
) as tmp
LEFT JOIN table_b b
ON tmp.content_id = b.id
ORDER BY
tmp.asum ASC
You can use EXPLAIN to check the query execution plan for both queries when you want to benchmark them

MySQL: Ordering By Using Two Counts From Another Table?

I have one sql table that looks like this called "posts":
id | user
--------------------------------
0 | tim
1 | tim
2 | bob
And another called "votes" that stores either upvotes or downvotes on the posts in the "posts" table:
id | postID | type
--------------------------------
0 | 0 | 0
1 | 2 | 1
2 | 0 | 1
3 | 0 | 1
4 | 3 | 0
In this table, the 'type' is either a 0 for downvote or 1 for upvote.
How would I go about ordering posts by "tim" by the number of (upvotes - downvotes) the post has?
SELECT
p.id,
p.user,
SUM(v.type * 2 - 1) AS votecount
FROM posts p
LEFT JOIN votes v ON p.id = v.postID
WHERE p.user = 'tim'
GROUP BY p.id, p.user
ORDER BY votes DESC
UPDATE – p and v explained.
In this query, p and v are aliases of, respectively, posts and votes. An alias is essentially an alternative name and it is defined only within the scope of the statement that declares it (in this case, the SELECT statement). Not only a table can have an alias, but a column too. In this query, votecount is an alias of the column represented by the SUM(v.type * 2 - 1) expression. But presently we are talking only about tables.
Before I go on with explanation about table aliases, I'll briefly explain why you may need to prefix column names with table names, like posts.id as opposed to just id. Basically, when a query references more than one table, like in this case, you may find it quite useful always to prefix column names with the respective table names. That way, when you are revisiting an old script, you can always tell which column belongs to which table without having to look up the structures of the tables referenced. Also it is mandatory to include the table reference when omitting it creates ambiguity as to which table the column belongs to. (In this case, referencing the id column without referencing the posts table does create ambiguous situation, because each table has got their own id.)
Now, a large and complex query may be difficult to read when you write out complete table names before column names. This is where (short) aliases come in handy: they make a query easier to read and understand, although I've already learnt that not all people share that opinion, and so you should judge for yourself: this question contains two versions of the same query, one with long-named table references and the other with short-aliased ones, as well as an opinion (in a comment to one of the answers) why aliases are not suitable.
Anyway, using short table aliases in this particular query may not be as beneficial as in some more complex statements. It's just that I'm used to aliasing tables whenever the query references more than one.
This MySQL documentation article contains the official syntax for aliasing tables in MySQL (which is actually the same as in standard SQL).
Not tested, but should work:
select post.id, sum(if(type = 0, -1, 1)) as score
from posts join votes on post.id = votes.postID
where user = 'tim'
group by post.id
order by score
Do you plan to concur SO? ;-)
Edit: I cut out the subquery since in mysql its unnecessary. The original query was portable, but unnecessary for mysql.
select
p.id, SUM(case
when v.type = 0 then -1
when v.type = 1 then 1
else 0 end) as VoteCount
from
posts p
left join votes v
on p.id = v.postid
where
p.[user] = 'tim'
group by
p.id
order by
VoteCount desc

Advanced searching in a joined multirelation MySQL DB

This might be a bit advanced to explain, as it's a pretty complicated thing I'm trying to do (at least to me).
I'm currently building a movie-database for personal use in PHP and MySQL, and the MySQL part is killing me. The current setup is like this:
I have a main movie database containing names, description and values with a single option (like year, age-limit and media-type (DVD, Blu-Ray, etc).
I have additional tables for language, subtitles, audio-formats etc. which all have two columns. One for the ID of the movie, and one that matches an index (eg. language id). These are supposed to be joined together with the main table, and concatted into a single field.
Example of my language table:
movid | langid
--------------
1 | 2
1 | 4
2 | 4
3 | 5
Optimally, I would like something like this:
| ID | name | description | year | subtitles | languages | audio |
--------------------------------------------------------------------
| 1 | One | Bla bla | 2010 | 2,3,5,6,7 | 3,6,22,6 | 10,5 |
| 2 | Another | foo bar | 2008 | 6,33,5,27 | 10,4,2,3 | 8,15 |
With the subtitles and languages being able to be exploded to a PHP array. That part I've actually got working just fine using GROUP_CONCAT, right up 'till the part where I need to search for specific subid's or langid's. This is the query I've been using so far. I hope you'll get the idea even though I havent written out all my table info:
SET SESSION group_concat_max_len = 512;
SELECT
movie.id,
movie.name,
movie.origname,
movie.`year`,
movie.`type`,
movie.duration,
movie.age,
GROUP_CONCAT(DISTINCT movie_language.langid ORDER BY langid) AS lang,
GROUP_CONCAT(DISTINCT movie_subtitles.subid ORDER BY subid) AS subtitles
FROM `movie`
LEFT JOIN `movie_audio` ON `movie`.`id`=`movie_audio`.`movid`
LEFT JOIN `movie_company` ON `movie`.`id`=`movie_company`.`movid`
LEFT JOIN `movie_genre` ON `movie`.`id`=`movie_genre`.`movid`
LEFT JOIN `movie_language` ON `movie`.`id`=`movie_language`.`movid`
LEFT JOIN `movie_subtitles` ON `movie`.`id`=`movie_subtitles`.`movid`
GROUP BY movie.id
I use the group_concat_max_len to prevent me getting a BLOB, and so far I have only tried group_concatting two of my joined tables (will add the rest later).
This returns exactly what I want, but I can only have one WHERE clause per joined table or it'll return 0 rows. Again, if I only search for one, it will only return the searched number/id in the GROUP_CONCAT'ted field.
Then I sorta fixed it using the IN() function. At least I thought I did. But the problem is that only works with what I'd call OR-searches. Adding:
WHERE movie_subtitles.subid IN ()
With numbers not in the subtitles table will still return the row, just only with the matching numbers. This is fine for half the searches, but I need a way to search with the AND-like method as well.
I have no idea if I need to restructure completely, or need a totally different query, but I hope for some assistance or hints.
I should perhaps say that I've had a look at the HAVING option as well, but as far as I've understood, it will not be effective on my query.
By the way, if this is impossible to do, I've considered scrapping the joined tables and replacing them with a field easily searchable in the main movie table (like using this "syntax": '#2##4#' and then using LIKE '%#2#%' AND '%#4#%' to match results, or as a last resort using PHP to sort it out (I'd rather die than doing that), though I'd rather like it if above solution could be fixed and used).
Thanks a lot in advance for helping away my headaches!
Sub-query your select then you will have an easier time with your clauses.
Like:
select *
from (`your big query above`) as t
where subtitles regexp `your ids you want`
and lang regexp `your ids you want`
Well, its not perfect since your ids will have been turned into strings (In postgres you have arrays so you could do a proper search from the top level.) I don't think I would really want search for ids with regular expressions though.
It would be better then, to not concatenate your ids until the final level. So you want 3 levels of queries:
select
stuff, ...
group_concats
from
(
select *
from (`your big query above but without the group_concat`) as inner
conditions ...
) as outer
edit
try this:
SELECT
id,
name,
origname,
`year`,
`type`,
duration,
age,
-- at this point we have the right rows we are just
-- grouping lang and subtitles
GROUP_CONCAT(DISTINCT langid ORDER BY langid) AS lang,
GROUP_CONCAT(DISTINCT subid ORDER BY subid) AS subtitles
from
(
(
SELECT
movie.id,
movie.name,
movie.origname,
movie.`year`,
movie.`type`,
movie.duration,
movie.age,
langid,
subid
FROM `movie`
LEFT JOIN `movie_audio` ON `movie`.`id`=`movie_audio`.`movid`
LEFT JOIN `movie_company` ON `movie`.`id`=`movie_company`.`movid`
LEFT JOIN `movie_genre` ON `movie`.`id`=`movie_genre`.`movid`
LEFT JOIN `movie_language` ON `movie`.`id`=`movie_language`.`movid`
LEFT JOIN `movie_subtitles` ON `movie`.`id`=`movie_subtitles`.`movid`
-- each row will have a different langid and different subid
GROUP BY
movie.id, langid, subid
) as inner
-- you should be able to do any complex condition as this point
where
(langid = 1 or langid = 2)
and (subid = 2 or subid = 3)
) as outer