How can I compare multiple rows with other rows from two different table and check if they are equal or not? - sql-server

I have 2 table named OptionText and SubmittedAns The first table contains the QuestionId OptionId and IsRight. Here IsRight is used for either the options against the question is either right or wrong.Here a QuestionId have multiple OptionId even can have one too. The second table represent what is submitted by the user. They can either select one option or multiple option based on their assumption. Now I need to make automatic script which will justify either the submitted answers against right or wrong.
Note: If a question have more than one correct option then the user have to select all right option, if one is missing then the result will be false. However if he selects all right answers then the answer will be true.
I have tried through this script. It's just able to count the selected numbers but can't justify the answer is either right or wrong. So I need help.
I can assume that I need a WHILE loop to check each element particularly. But how? So I need help. Here is my code.
CREATE TABLE OptionText(
[OptionTextId] [bigint] IDENTITY(1,1) NOT NULL,
[QuestionId] [bigint] NOT NULL,
[IsRightAnswer] [bit] NOT NULL)
Insert into OptionText (QuestionId, IsRightAnswer) VALUES (5, 1)
Insert into OptionText (QuestionId, IsRightAnswer) VALUES (5, 0)
Insert into OptionText (QuestionId, IsRightAnswer) VALUES (5, 0)
Insert into OptionText (QuestionId, IsRightAnswer) VALUES (17, 0)
Insert into OptionText (QuestionId, IsRightAnswer) VALUES (17, 1)
Insert into OptionText (QuestionId, IsRightAnswer) VALUES (17, 1)
CREATE TABLE SubmittedAns(
[Id] [bigint] IDENTITY(1,1) NOT NULL,
[QuestionId] [bigint] NOT NULL,
[OptionTextId] [bigint] NOT NULL)
Insert into SubmittedAns (QuestionId, OptionTextId) VALUES (5, 1)
Insert into SubmittedAns (QuestionId, OptionTextId) VALUES (5, 2)
Insert into SubmittedAns (QuestionId, OptionTextId) VALUES (2, 1)
Insert into SubmittedAns (QuestionId, OptionTextId) VALUES (2, 1)
select * from OptionText
select * from SubmittedAns
if (select count(OptionTextId) from SubmittedAns where QuestionId =5) = (select count(ot.OptionTextId) from OptionText as ot where ot.IsRightAnswer = 1)
select 1 as "isRight"
else
select 0 as "isRight"

Please refer to first & last line for critical material:
SELECT CASE COUNT(*) WHEN 0 THEN 'Pass' ELSE 'Fail' END AS Boolean
FROM (
SELECT *
FROM #OptionText
WHERE QuestionId = 5
AND IsRightAnswer = 1
) AS OT
FULL OUTER JOIN #SubmittedAns AS SA ON OT.QuestionId = SA.QuestionId AND OT.OptionTextId = SA.OptionTextId
WHERE SA.QuestionId = 5
AND OT.OptionTextId IS NULL -- This means some answers failed to be matched with your original questions/options, either because IsRightAnswer is zero, or because it doesn't exist in your questions/answers.

I got a solution on my way of understanding. So I have used a function to apply it for all question and created this code. It's working.
CREATE FUNCTION [dbo].[Set_IsCorrect_Answer_Deva]
(
#QuestionId BIGINT
)
RETURNS BIT
AS
BEGIN
DECLARE #IsRightAns BIT
DECLARE #count int
set #IsRightAns = 0
Declare #supplied_count int
select #supplied_count = Count(*) from SuppliedAnswersTemp where QuestionId=#QuestionId
IF(#supplied_count>0)
Begin
IF(#supplied_count=(select Count(*) from OptionText where QuestionId=#QuestionId and IsRightAnswer=1))
Begin
select #count=Count(*) from OptionText ot join SuppliedAnswersTemp sa on ot.QuestionId = sa.QuestionId
where ot.QuestionId= #QuestionId and ot.IsRightAnswer =1 and ot.Id=sa.OptionId
END
END
IF(#count>0)
Set #IsRightAns=1
RETURN #IsRightAns
END

Related

SQL Select from table where joined values from a second table are a subset of values from a third table

I have the following tables in MS SQL Server: Tasks, Users, Tags, TaskTags (maps a task to a tag), and UserTags (maps a user to a tag).
Given a User U, I want to find all tasks T where every tag of T is also a tag of U (e.g. a task should be returned if its tags are a subset of the user's tags).
Here is a table script with some sample data (it can be run at http://sqlfiddle.com/ with MS SQL Server 17):
CREATE TABLE [dbo].[Tasks](
[TaskId] [int] NOT NULL PRIMARY KEY,
[TaskName] [nvarchar](MAX) NOT NULL
)
CREATE TABLE [dbo].[Users](
[UserId] [int] NOT NULL PRIMARY KEY,
[UserName] [nvarchar](MAX) NOT NULL
)
CREATE TABLE [dbo].[Tags](
[TagId] [int] NOT NULL PRIMARY KEY,
[TagName] [nvarchar](MAX) NOT NULL
)
CREATE TABLE [dbo].[TaskTags](
[TaskId] [int] NOT NULL,
[TagId] [int] NOT NULL
)
CREATE TABLE [dbo].[UserTags](
[UserId] [int] NOT NULL,
[TagId] [int] NOT NULL
)
INSERT INTO Tasks VALUES (1,'Task for all SWEs');
INSERT INTO Tasks VALUES (2,'Task for USA SWEs');
INSERT INTO Tasks VALUES (3,'Task for all PMs');
INSERT INTO Tasks VALUES (4,'Task for Europe PMs');
INSERT INTO Users VALUES (1,'Europe SWE');
INSERT INTO Users VALUES (2,'USA SWE');
INSERT INTO Users VALUES (3,'Europe PM');
INSERT INTO Users VALUES (4,'USA PM');
INSERT INTO Tags VALUES (1,'swe');
INSERT INTO Tags VALUES (2,'pm');
INSERT INTO Tags VALUES (3,'usa');
INSERT INTO Tags VALUES (4,'europe');
INSERT INTO TaskTags VALUES (1,1);
INSERT INTO TaskTags VALUES (2,1);
INSERT INTO TaskTags VALUES (2,3);
INSERT INTO TaskTags VALUES (3,2);
INSERT INTO TaskTags VALUES (4,2);
INSERT INTO TaskTags VALUES (4,4);
INSERT INTO UserTags VALUES (1,1);
INSERT INTO UserTags VALUES (1,4);
INSERT INTO UserTags VALUES (2,1);
INSERT INTO UserTags VALUES (2,3);
INSERT INTO UserTags VALUES (3,2);
INSERT INTO UserTags VALUES (3,4);
INSERT INTO UserTags VALUES (4,2);
INSERT INTO UserTags VALUES (4,3);
I was able to figure out the inverse of this problem, when the Task T is given. E.g. given Task T, return all Users U where the tags of T are a subset of U. Here is that query:
WITH thisTaskTags AS (
SELECT DISTINCT TaskTags.TagId
FROM TaskTags
WHERE TaskTags.TaskId = #taskId
)
SELECT UserTags.UserId
FROM UserTags JOIN thisTaskTags
ON UserTags.TagId = thisTaskTags.TagId CROSS JOIN
(SELECT COUNT(*) AS keycnt FROM thisTaskTags) k
GROUP BY UserTags.UserId
HAVING COUNT(thisTaskTags.TagId) = MAX(k.keycnt)
When #taskId = 1, UserIds 1 and 2 are returned, and when #taskId = 2, only UserId 2 is returned (correct behavior).
However when I tried to convert this to returning all tasks a given user should have, I ran into trouble. I tried this query:
WITH thisUserTags AS (
SELECT DISTINCT UserTags.TagId
FROM UserTags
WHERE UserTags.UserId = #userId
)
SELECT TaskTags.TaskId
FROM TaskTags JOIN thisUserTags
ON thisUserTags.TagId = TaskTags.TagId CROSS JOIN
(SELECT COUNT(*) AS keycnt FROM thisUserTags) k
GROUP BY TaskTags.TaskId
HAVING COUNT(thisUserTags.TagId) = MAX(k.keycnt);
However this only returns tasks where all the task tags match all the user tasks, e.g. if U had tags: [a,b,c] it would only get tasks with tags: [a,b,c] instead of [a], [b], [b,c], etc.
With concrete examples, if you set #userId = 1, no task IDs are returned, when the correct output would be getting 1 row, Task ID = 1. And when #userId = 2, only taskID 2 is returned, when both taskIDs 1 and 2 should be returned (i.e. if a task only has the "swe" tag, all "swe" users should get it, but if a task has both "swe" and "usa", only users who have both of those tags should get it).
I also tried this query:
SELECT DISTINCT Tasks.TaskId FROM Tasks
INNER JOIN TaskTags ON TaskTags.TaskId = Tasks.TaskId
WHERE TaskTags.TagId IN (SELECT TagId from UserTags where UserId = #userId)
GROUP BY Tasks.TaskId
But the issue with this is it returns any task that has any tag in common, so U with tags: [a,b,c] would get T with tags: [b,d] even though U doesn't have tag d.
Again with concrete examples, if #userId = 1, taskIDs 1,2, and 4 are returned, when only taskIds 1 and 2 should be returned (task ID 4 should only be assigned to users with both tags "europe" and "pm", here it is erroneously being assigned to a user with tags "europe" and "swe" due to the common "europe" tag).
Could someone shed some light here?
This is a classic Relational Division With Remainder question.
You just need to frame it right:
You want all Tasks...
... whose TaskTags divide the set of all UserTags for a given User
There can be a remainder of UserTags but not a remainder of TaskTags so the former is the dividend, the latter is the divisor.
A typical solution (there are many) is to left join the dividend to the divisor, group it up, then ensure that the number of matched dividends is the same as the number of divisors. In other words, all divisors have a match.
Since you only seem to want the Tasks but not their TaskTags, you can do all this in an EXISTS subquery:
DECLARE #userId int = 1;
SELECT *
FROM Tasks t
WHERE EXISTS (SELECT 1
FROM TaskTags tt
LEFT JOIN UserTags ut ON ut.TagId = tt.TagId
AND ut.UserId = #userId
WHERE tt.TaskId = t.TaskId
HAVING COUNT(*) = COUNT(ut.UserId)
);
db<>fiddle
You're probably looking for something like the following...
declare #userId int = ...;
select Tasks.TaskId
from Tasks
where 0 = (
select count(1)
from (
select TagId from TaskTags where TaskTags.TaskId=Tasks.TaskId
except
select TagId from UserTags where UserTags.UserId=#userId
) TaskSpecificTags
);
It's not clear if you also want to return Tasks with 0 Tags so you may need to test that condition as well.

How to write query with cast and switch functions in SQL Server?

I have to convert an int column to a text column and replace the integer values within that column.
For example I have a column status that can contain values like 0, 1, 2, 3, 4, 5, 6, 7.
For '0' I have to replace the value with "New", for '1' with "Identified" and so on.
For this scenario how to write a SQL query?
Personally, I'd go with a mapping table, but another option is CHOOSE() or even the traditional CASE
Note the +1 in the CHOOSE option ... 0 is not a valid option and would return NULL
Example
Declare #YourTable Table ([Status] int)
Insert Into #YourTable Values
(0),(1),(2),(3)
Select *
,ViaCHOOSE = choose([Status]+1,'New','Identified','Some Other','...')
,ViaCASE = case [Status]
when 0 then 'New'
when 1 then 'Identified'
when 2 then 'Some Other'
else null -- or 'Undefined'
end
From #YourTable
Results
Status ViaCHOOSE ViaCASE
0 New New
1 Identified Identified
2 Some Other Some Other
3 ... ...
You could create a (temporary) table with that mapping.
create table XYMapping (number int, text varchar(max));
INSERT INTO XYMapping (number, text) VALUES (1, 'New'), (2, 'Identified'); -- ...
Insert all values and then join them.
Steps to follow to convert int column to text and replace the existing values.
Alter the table. Since INT converts to text implicitly NOT NULL is able to be maintained
Exec UPDATE statement using conversion table specified using VALUES table value constructor
Something like this
drop table if exists #samples;
go
create table #samples (
id int not null,
stat varchar(10) not null);
insert #samples(id, stat) values
(10, 'Start'),
(1, 'Start'),
(1, 'Failed'),
(2, 'Start'),
(3, 'Start'),
(3, 'Failed'),
(4, 'Start'),
(4, 'Failed'),
(4, 'Start'),
(4, 'Failed');
/* step 1: alter the table (and implicitly convert) */
alter table #samples
alter column
id varchar(20) not null;
/* step 2: update based on conversion table */
update s
set id=v.new_str
from #samples s
join
(values ('1', 'New'),
('2', 'Identified'),
('3', 'Identified'),
('4', 'Identified'),
('10', 'Other')) v(old_int, new_str) on s.id=v.old_int;
select * from #samples;
id stat
Other Start
New Start
New Failed
Identified Start
Identified Start
Identified Failed
Identified Start
Identified Failed
Identified Start
Identified Failed

update field from group by

In SQL Server 2016, let's have the following recordset
I need to update the "IsSame" field when the tpEndDay is the same for any arbDelivery field.
For example, arbDelivery 76873614 should have IsSame = 0, because tpEndDay is different. 76874450 shoud have IsSame = 1, etc.
How can I do this?
thanks for your time and help
I always recommend to specify FROM with the same table you UPDATE.
in this case you can do it like this:
UPDATE my_table
SET IsSame =
CASE
WHEN EXISTS
(
SELECT 1
FROM my_table AS same
WHERE same.tpEndDay = my_table.tpEndDay
AND same.arbDelivery <> my_table.arbDelivery
)
THEN 1
ELSE 0
END
FROM my_table
I added some sample data. You can change the On clause according to your join condition.
CREATE TABLE [dbo].[mydatatable](
[arbNoColis] [varchar](100) NULL,
[arbDelivery] [int] NULL,
[tbEndDate] [datetime] NULL,
[IsSame] [bit] NULL
) ON [PRIMARY]
GO
INSERT [dbo].[mydatatable] ([arbNoColis], [arbDelivery], [tbEndDate], [IsSame]) VALUES (N'wwwwwwwww', 24499002, CAST(N'2021-02-05T00:00:00.000' AS DateTime), 0)
GO
INSERT [dbo].[mydatatable] ([arbNoColis], [arbDelivery], [tbEndDate], [IsSame]) VALUES (N'wwwwwwwww', 244990502, CAST(N'2021-05-05T00:00:00.000' AS DateTime), 1)
GO
INSERT [dbo].[mydatatable] ([arbNoColis], [arbDelivery], [tbEndDate], [IsSame]) VALUES (N'tttttt', 244999002, CAST(N'2021-05-05T00:00:00.000' AS DateTime), 1)
GO
INSERT [dbo].[mydatatable] ([arbNoColis], [arbDelivery], [tbEndDate], [IsSame]) VALUES (N'tttttt', 222999002, CAST(N'2021-05-05T00:00:00.000' AS DateTime), 1)
GO
UPDATEA
SET A.IsSame = CASE WHEN B.[rowcount] = 1 THEN 0 ELSE 1 END
FROM mydatatable A
INNER JOIN (
SELECT
COUNT(1) OVER (PARTITION BY tbEndDate ORDER BY tbEndDate) AS [rowcount]
,*
FROM mydatatable ) AS B
ON A.arbNoColis = B.arbNoColis AND A.arbDelivery = B.arbDelivery
You can use a COUNT window aggregate in a derived table and update directly through the derived table
UPDATE t
SET IsSame = CASE WHEN t.cnt > 1 THEN 1 ELSE 0 END
FROM (
SELECT *
cnt = COUNT(*) OVER (PARTITION BY arbDelivery)
FROM YourTable
) t

Find Pairs in recursive table [duplicate]

This question already has an answer here:
SQL Server recursive self join
(1 answer)
Closed 5 years ago.
I have the followign table
CREATE TABLE [dbo].[MyTable2](
[ID] [int] IDENTITY(1,1) NOT NULL,
[ParentID] [int] NOT NULL,
)
I try to create a query which will return a list of pairs ID, ParentID. For example I have the followign data
ID ParentID
1 0
2 0
3 1
4 3
5 3
15 8
I want when I search by ID = 5 to have the following list:
ID ParentID
5 3
3 1
1 0
If I search by ID = 15 it should see that the sequence is boken and I will get the followign list.
ID ParentID
15 8
I used a temporary table in order to make it work and my code is the following:
if object_id('tempdb..#Pairs') is not null
DROP TABLE #Pairs
create table #Pairs
(
ID INT,
ParentID INT
)
Declare #ID integer = 5;
Declare #ParentID integer;
while (#ID > 0)
BEGIN
SET #ParentID = null; -- I set it to null so that I will be able to check in case the sequence is broken
select #ID=ID, #ParentID=ParentID
from MyTable
where ID = #ID;
if #ParentID IS NOT null
begin
Insert into #Pairs (ID, ParentID) Values (#ID, #ParentID)
SET #ID = #ParentID;
end
else
SET #ID = 0;
END
SELECT * from #Pairs
It works but I am sure that there is a better way to do it. I found some strange queries which was suspposed to do something similar but I was not able to convert it in order to cover my needs.
For example I found the following Question but I was not able to convert it to work with my table. All the queries that I found had similar answers.
You are looking for recursive queries. See following example:
SELECT * INTO tab FROM (VALUES
(1, 0),
(2, 0),
(3, 1),
(4, 3),
(5, 3),
(15, 8)) T(ID, ParentID);
DECLARE #whatAreYouLookingFor int = 5;
WITH Rec AS
(
SELECT * FROM tab WHERE ID=#whatAreYouLookingFor
UNION ALL
SELECT T.* FROM tab T JOIN Rec R ON R.ParentID=T.ID
)
SELECT * FROM Rec;
DROP TABLE tab
Output:
ID ParentID
-- --------
5 3
3 1
1 0

Smart Many to Many Query

I have a list of item descriptions in a c# application. What I want is when I select
1 or 2 or more item descriptions of that list (checkbox list) to predict via an sql query to a many to many table what my item is (minimizing each time the possible predictions);
For example
item 1: white,green,blue
item 2: white,red,cyan
item 3: red,blue,purple
user should select from a check list
white->query will return item 1,2
white&green->query will return only item 1
From your humble description of the problem, I suppose you want something like this:
CREATE TABLE items (
item_id INT NOT NULL PRIMARY KEY IDENTITY(1,1),
name VARCHAR(100) NOT NULL
)
CREATE TABLE colors (
color_id INT NOT NULL PRIMARY KEY IDENTITY(1,1),
name VARCHAR(100) NOT NULL
)
CREATE TABLE items_colors (
item_id INT NOT NULL FOREIGN KEY REFERENCES items(item_id),
color_id INT NOT NULL FOREIGN KEY REFERENCES colors(color_id),
PRIMARY KEY(item_id, color_id),
)
INSERT INTO items(name) VALUES ('item 1')
INSERT INTO items(name) VALUES ('item 2')
INSERT INTO items(name) VALUES ('item 3')
INSERT INTO colors(name) VALUES ('white')
INSERT INTO colors(name) VALUES ('green')
INSERT INTO colors(name) VALUES ('blue')
INSERT INTO colors(name) VALUES ('red')
INSERT INTO colors(name) VALUES ('cyan')
INSERT INTO colors(name) VALUES ('purple')
INSERT INTO items_colors(item_id, color_id) VALUES (1, 1)
INSERT INTO items_colors(item_id, color_id) VALUES (1, 2)
INSERT INTO items_colors(item_id, color_id) VALUES (1, 3)
INSERT INTO items_colors(item_id, color_id) VALUES (2, 1)
INSERT INTO items_colors(item_id, color_id) VALUES (2, 4)
INSERT INTO items_colors(item_id, color_id) VALUES (2, 5)
INSERT INTO items_colors(item_id, color_id) VALUES (3, 3)
INSERT INTO items_colors(item_id, color_id) VALUES (3, 4)
INSERT INTO items_colors(item_id, color_id) VALUES (3, 6)
SELECT i.*
FROM items i
WHERE 2 = (
SELECT COUNT(*)
FROM items_colors ic
JOIN colors c
ON ic.color_id = c.color_id
WHERE i.item_id = ic.item_id
AND c.name IN ('white', 'green')
)
Within "IN" clause you should provide list of values that user has selected in the UI (you have to build list of parameters dynamically). You also have to provide number of elements that user has selected ("2" in my example solution).
So the query in application will look like this:
SELECT i.*
FROM items i
WHERE #count = (
SELECT COUNT(*)
FROM items_colors ic
JOIN colors c
ON ic.color_id = c.color_id
WHERE i.item_id = ic.item_id
AND c.name IN (#color1, #color2, ..., #colorN)
)
(Where #count is the number of #colorX parameters.)

Resources