How can I perform this search with SQL? - sql

I have three tables with the following (relevant) columns:
questions: level, answer_id
choices: question_id, answer_id, user_id, date_answered
users: id, level
Users are presented with the questions and when they answer them by selecting one of the available answers, this creates a choices record and assigns the answer_id the appropriate value. Users can answer a question multiple times.
I need a query that will tell me, taking into account only the latest choice for each answered question, how many were answered correctly. I know enough SQL to be dangerous, but this one is above my head.
As an example:
questions
id level answer_id
------------------
1 1 101
2 1 102
3 1 103
4 2 110
choices
question_id answer_id user_id date_answered
--------------------------------------------
1 101 201 2019-01-24
1 104 201 2019-01-25
2 105 201 2019-01-25
2 102 201 2019-01-26
3 103 201 2019-01-26
So user 201 answered the first question correctly and then incorrectly. Then they answered the second question incorrectly and then correctly. Finally, they answered question 3 once and it was correct.
Note that this is all within a question level. If the user is at level 1, I'd only be considering choices to level 1 questions. If at level 2, only choice to level 2 question.
Although they've answered all the level 1 questions correctly, they've only answered level 1 most recently correctly twice. 2 would be the result I'm looking for, therefore.
Is there a SQL query that can do this or must I use a loop within the programming?

I don't currently have SQL Server setup so there might be some typos in my answer. This should get you questions answered correctly only if it is the most recent answer by that user.
SELECT *
FROM
questions a INNER JOIN
choices b on (a.question_id = b.question_id) INNER JOIN
( SELECT question_id, user_id, max(date_answered) as date_answered
FROM choices
GROUP BY question_id, user_id
) c
on (b.question_id = c.question_id and b.user_id = c.user_id and b.date_answered = c.date_answered)
Answer to updated question:
SELECT *
FROM
questions a INNER JOIN
choices b on (a.id = b.question_id) INNER JOIN
( SELECT question_id, user_id, max(date_answered) as date_answered
FROM choices
GROUP BY question_id, user_id
) c INNER JOIN
on (b.question_id = c.question_id and b.user_id = c.user_id and b.date_answered = c.date_answered) INNER JOIN
users d on (d.id = b.user_id and d.level = a.level)

Related

Select records that do not have a product associated on a second table

I have two tables:
Table PL:
plid
plname
1
Alice
2
John
3
Danielle
And table PLproducts
plid
productIdentifier
1
membership
1
life
1
dental
2
membership
3
membership
3
life
3
auto
I need to find those plid where productIdentifier does not contain "dental"
Expected results:
plid
plname
2
John
3
Danielle
If I Outer Join for PLproducts <> 'dental', I get all the records that do not contain 'dental' but that is not what Im looking for.
I've never found this scenario before. I understand it may be a simple question.
Thank you all.
You're looking for where something does not exist
select *
from pl
where not exists (
select * from plProducts p
where p.plid = pl.plid and p.productidentifier = 'dental'
);
There are multiple ways to approach this problem. You might be interested in looking at cross apply. It could be a useful approach in more complicated scenarios.
select pl.*
from pl cross apply (
select count(*) as hasdental from plproducts pp
where pp.plid = pl.plid and p2.productidentifier = 'dental'
) as oa
where hasdental = 0;
One of the methods is using string_agg and then use having to remove the phrases that includes "dental".
select * from PL p1
where p1.plid in
(select p2.plid from PLproducts p2
group by p2.plid
having STRING_AGG(productIdentifier,';') NOT LIKE N'%dental%')

SQL - ordering table by information from multiple tables

Title of the question may not have been very clear - I am not really sure how to name this question, but I hope that my explanation will make my problem clearer.
I have 3 tables:
[1] score
id
rating_type
1
UPVOTE
2
UPVOTE
3
DOWNVOTE
4
UPVOTE
5
DOWNVOTE
6
DOWNVOTE
[2] post_score
post_id
score_id
1
1
1
2
1
3
2
4
2
5
2
6
and [3] post
id
title
1
title1
2
title2
My goal is to order [3] post table by score.
Assume UPVOTE represents value of 1 and DOWNVOTE value of -1; In this example, post where id = 1 has 3 scores related to it, and the values of them are UPVOTE, UPVOTE, DOWNVOTE, making the "numeric score" of this post: 2;
likewise, post where id = 2, also has 3 scores, and those values are: UPVOTE, DOWNVOTE, DOWNVOTE, making the "numeric score": -1;
How would I order post table by this score? In this example, if I ordered by score asc, I would expect the following result:
id
title
2
title2
1
title1
My attempts didn't go far, I am stuck here with this query currently, which doesn't really do anything useful yet:
WITH fullScoreInformation AS (
SELECT * FROM score s
JOIN post_score ps ON s.id = ps.score_id),
upvotes AS (SELECT * FROM fullScoreInformation WHERE rating_type = 'UPVOTE'),
downvotes AS (SELECT * FROM fullScoreInformation WHERE rating_type = 'DOWNVOTE')
SELECT p.id, rating_type, title FROM post p JOIN fullScoreInformation fsi on p.id = fsi.post_id
I am using PostgreSQL. Queries will be used in my Spring Boot application (I normally use native queries).
Perhaps this data structure is bad and I should have constructed my entities differently ?
My goal is to order post table by score. Assume UPVOTE represents value of 1 and DOWNVOTE value of -1
One option uses a subquery to count the upvotes and downvotes of each post:
select p.*, s.*
from post p
cross join lateral (
select
count(*) filter(where s.rating_type = 'UPVOTE' ) as cnt_up,
count(*) filter(where s.rating_type = 'DOWNVOTE') as cnt_down
from post_score ps
inner join score s on s.id = ps.score_id
where ps.post_id = p.id
) s
order by s.cnt_up - s.cnt_down desc
Perhaps this data structure is bad and I should have constructed my entities differently ?
As it stands, I don't see the need for two distinct tables post_score and score. For the data you have showed, this is a 1-1 relationship, so just one table should be sufficient, storing the post id and the rating type.
You better use a LEFT join, otherwise you wouldn't get posts that have no votes yet. Then aggregate to get the fitered sum of the scores. Then add these sums, apply coalesce() to get 0 for posts without votes and order by the result.
SELECT p.id,
p.title
FROM post p
LEFT JOIN post_score ps
ON ps.post_id = p.id
LEFT JOIN score s
ON s.id = ps.score_id
GROUP BY p.id,
p.title
ORDER BY coalesce(sum(1) FILTER (WHERE rating_type = 'UPVOTE')
+
sum(-1) FILTER (WHERE rating_type = 'DOWNVOTE'),
0);
I second GMB's comment about the superfluous table.

Multiple rows get only specific values

Feel like this should be a rather simple problem yet, I'm struggling to find the solution.
We have three tables to create a Question Answer system. One is the question, other is answer and then the third is finally where we store the user's selection.
Question table
QuestionID Question
1 What is your favorite color?
2 Where were you born?
Answer table
AnswerID QuestionID Answer
1 1 Blue
2 1 Green
3 1 Yellow
4 2 USA
5 2 Africa
Answer stored table
AnswerStoreID QuestionID AnswerID UserID
1 1 1 1
2 1 2 1
3 2 4 2
4 2 5 2
5 1 1 3
I want to find the UserID that answered QuestionID 1 as AnswerID 1 AND QuestionID 2 as AnswerID 4.
Thought it would be simple like this
SELECT UserID
FROM Question Q
INNER JOIN Answer A ON A.QuestionID = A.QuestionID
INNER JOIN AnswerStore AS ON AS.AnswerID = A.AnswerID
WHERE (AS.AnswerID = 1 AND AS.QuestionID = 1)
AND (AS.AnswerID = 2 AND AS.QuestionID = 4)
That renders nothing though. When replacing the AND between the two where statements with an OR gets results that don't have both those answers though which is not desired either. I want only those users who answered both of these questions.
I then did a query with some various joins to do a query per question but feel that is too complicated and heavy for this problem and I'm overthinking it. Is there an easier solution to this problem?
---- Edit ----
Actually, you don't even need the JOINs in your original query:
SELECT t.UserID
FROM AnswerStore AS t
WHERE (t.AnswerID = 1 AND t.QuestionID = 1)
OR (t.AnswerID = 2 AND t.QuestionID = 4)
GROUP BY t.UserID
HAVING COUNT(*) = 2
---- Original Full Answer ----
This is actually a fairly common question, that appears a couple times a week. Unfortunately, it is really hard to formulate a repeatable/searchable question to reference for it.
SELECT UserID
FROM Question Q
INNER JOIN Answer A ON A.QuestionID = A.QuestionID
INNER JOIN AnswerStore AS ON AS.AnswerID = A.AnswerID
WHERE (AS.AnswerID = 1 AND AS.QuestionID = 1)
OR (AS.AnswerID = 2 AND AS.QuestionID = 4)
GROUP BY UserID
HAVING COUNT(*) = 2
The general form is:
SELECT A.a_id
FROM A
INNER JOIN B ON A.a_id = B.a_id
WHERE B.something IN ([list])
GROUP BY a_id
HAVING COUNT(*) = [length of list]
-- or in cases where B matches may be non-unique
-- HAVING COUNT(DISTINCT B.something) = [length of list]
You are really looking at two sets of data, UserIDs that answered QuestionID 1 as AnswerID 1, and UserIDs that answered QuestionID 2 as AnswerID 4. So you can join the sets together to find UserIDs that are in both sets of data:
SELECT UserID
FROM AnswerStore as1 INNER JOIN AnswerStore as2 ON as1.UserID = as2.UserID
AND as1.QuestionID = 1 AND as1.AnswerID = 1
AND as2.QuestionID = 2 AND as2.AnswerID = 4

How to select all values between 2 tables in SQL?

I have the following two tables in Oracle database (read only access so I can only use select).
Question table:
N Question ID
1 1
2 2
3 3
Response table:
Question ID Response day
1 01-04-15
3 02-04-15
4 03-04-15
I want the output result to be:
Question ID Response day
1 01-04-15
2 null
3 02-04-15
4 03-04-15
I m strying with the following query and I'm ony getting the question ID when there is a response. Anyone know where I'm going wrong?
select questionID, responseday
from questions
join response
where question.question.ID = response.question.ID;
With this query I'm getting these results:
Question ID Response day
1 01-04-15
3 02-04-15
4 03-04-15
One method is a full outer join:
select coalesce(q.questionid, r.questionid) as questionid,
r.responseday
from questions q full outer join
responses r
on q.questionid = r.questionid;
Based on your schema, a simple LEFT JOIN will be sufficient for your model.
SELECT q.Question_ID,
r.Response_Day
FROM Question AS "q"
LEFT JOIN Response AS "r"
ON r.Question_ID = q.Question_ID;
JOIN or INNER JOIN will only return the result sets from both tables where ALL attributes match. Because your Question record with ID of 2 does not exist in the Response table, SQL will exclude this data.
To account for NULLs, you have to LEFT JOIN, see the sqlfiddle here:
SQL Fiddle
Also, have a look at this graphical model explaining all the JOINs:
Visual Representation of SQL Joins
Use Left Join
select
q.questionid as questionid,
r.responseday
from
questions q
left join
responses r
on
q.questionid = r.questionid;
FULL outer JOIN is sufficient or the output required.
SELECT NVL(a.id,b.id),
b.nm
FROM
(SELECT 1 id,1 nm FROM dual
UNION ALL
SELECT 2 id,2 nm FROM dual
UNION ALL
SELECT 3 id,3 nm FROM dual
)a
FULL OUTER JOIN
(SELECT 1 id,'01-04-15' nm FROM dual
UNION
SELECT 3 id,'02-04-15' nm FROM dual
UNION ALL
SELECT 4 id,'03-04-15' nm FROM dual
)b
ON a.id = b.id;
---------------------------------OUTPUT -----------------------------------
ID NM
1 01-04-15
3 02-04-15
4 03-04-15
2 NULL
----------------------------------OUTPUT-----------------------------------
select questionID
, responseday
from questions
join response
on question.question.ID = response.question.ID;

SQL Query to display heirarchical data

I have two tables - 'Users' and 'Supervision'
For this example, my users table is very simple:-
Users
=====
ID (PK)
UserName
Some users manage other users, so I've built a second table 'Supervision' to manage this:-
Supervision
===========
UserID
SuperID - this is the ID of the staff member that the user supervises.
This table is used to join the Users table to itself to identify a particular users supervisor. It might be that a user has more than one supervisor, so this table works perfectly to this end.
Here's my sample data in 'Users':-
userID userName
1 Bob
2 Margaret
3 Amy
4 Emma
5 Carol
6 Albert
7 Robert
8 Richard
9 Harry
10 Arthur
And my data in 'Supervision':-
userID superID
1 2
1 3
2 4
2 5
3 4
3 5
6 1
6 7
7 8
7 9
9 10
If I want to see who directly reports to Bob, writing an SQL query is straightforward, and tells me that Margaret and Amy are his direct reports.
What I want to do however is to write a query that shows everybody who comes under Bob, so it would need to look at Bobs direct reports, and then their direct reports, and so on - it would give Margaret, Amy, Emma and Carol as the result in this case.
I'm assuming this requires some kind of recursion but I'm completely stuck..
You should use recursive CTE:
WITH RCTE AS
(
SELECT * FROM dbo.Supervision WHERE UserID = 1
UNION ALL
SELECT s.* FROM dbo.Supervision s
INNER JOIN RCTE r ON s.userID = r.superID
)
SELECT DISTINCT u.userID, u.userName
FROM RCTE r
LEFT JOIN dbo.Users u ON r.superID = u.userID
SQLFiddle DEMO
Sounds to me like you need a Recursive CTE. This article serves as a primer, and includes a fairly similar example to the one you have:
http://blog.sqlauthority.com/2012/04/24/sql-server-introduction-to-hierarchical-query-using-a-recursive-cte-a-primer/
Hope it helps.
WITH MyCTE
AS (
-- ID's and Names
SELECT SuperID, ID
FROM Users
join dbo.Supervision
on ID = dbo.Supervision.UserID
WHERE UserID = 1
UNION ALL
--Who Manages who...
SELECT s.SuperID, ID
FROM Supervision s
INNER JOIN MyCTE ON s.UserID = MyCTE.SuperID
WHERE s.UserID IS NOT NULL
)
SELECT distinct MyCTE.ID, NAMES.UserName, '<------Reports to ' as Hierarchy, res_name.UserName
FROM MyCTE
join dbo.Users NAMES on
MyCTE.ID = NAMES.ID
join dbo.Users res_name
on res_name.ID = MyCTE.SuperID
order by MyCTE.ID, NAMES.UserName, res_name.UserName