SQL query to get records - sql

I don't know how to frame this question - so putting the sql statement directly here.
declare #tbl table(Id varchar(4),Userid varchar(10),Name varchar(max),Course varchar(max))
insert into #tbl values
('1','UserID1','UserA','Physics'),
('2','UserID2','UserB','Chemistry'),
('3,','UserID1','UserA','Chemistry')
Now,
To get a list of users who have taken Chemistry, I would write
select * from #tbl where Course='Chemistry'
Similarly for Physics, I would write
select * from #tbl where Course='Physics'
The problem is when I try to write the query "get a list of students who haven't taken Physics". Without thinking much, I wrote this
select * from #tbl where Course!='Physics'
and it results in, which is wrong (it is getting details about UserA - though he has registered in Physics)
Id Userid Name Course
2 UserID2 UserB Chemistry
3, UserID1 UserA Chemistry
To fix this, I rewrote the query like this - but somehow I think this is not the right way.
select * from #tbl where Course!='Physics'
and Userid not in (select Userid from #tbl where Course='Physics')
Please help!

Try the following:
SELECT *
FROM #tlb U
WHERE NOT EXISTS (SELECT *
FROM #tbl Inner
WHERE Inner.UserId = U.UserId
AND Course = 'Physics')
For a full discussion of NOT IN versus EXISTS, see this question. The consensus seems to be that NOT EXISTS is preferable.
(I note that your table definition does not mark the columns as NOT NULL; if it would be appropriate to add that in your scenario, it would be a good idea.)

If you want the list of students who haven't taken physics, then I would suggest aggregation with the having clause:
select userId
from #tbl
group by userId
having sum(case when course = 'Physics' then 1 else 0 end) = 0;
This has the obvious advantage of only returning the student ids, and not multiple rows for a student (when there are multiple other courses). It is also an example of a "set-within-sets" subquery, and is more easily generalized than the where version. On the downside, the use of not exists might be able to better take advantage of indexes.

Related

SQL - How to insert into table based on id's found in another table?

I would need something like this pseudo code:
I think you are probably looking for something like
INSERT INTO userauthorizations
SELECT DISTINCT userid, 11
FROM userauthorizations ua1
WHERE NOT EXISTS
(SELECT *
FROM userauthorizations ua2
WHERE ua2.userid = ua1.userid
AND ua2.authid = 11)
This will find all users who do not currently have a authid=11 and add a row for that userid with authid=11.
Note that this is different from your pseudocode (SELECT DISTINCT USERID FROM USERAUTHORIZATIONS WHERE AUTHID <> 11), which finds users who have at least one entry that isn't authid=11.

Higher Query result with the DISTINCT Keyword?

Say I have a table with 100,000 User IDs (UserID is an int).
When I run a query like
SELECT COUNT(Distinct User ID) from tableUserID
the result I get is HIGHER than the result from the following statement:
SELECT COUNT(User ID) from tableUserID
I thought Distinct implied unique, which would mean a lower result. What would cause this discrepancy and how would I identify those user IDs that don't show up in the 2nd query?
Thanks
**
UPDATE - 11:14 am est
**
Hi All
I sincerely apologize as I should've taken the trouble to reproduce this in my local environment. But I just wanted to see if there was a general consensus about this. Here are the full details:
The query is a result of an inner join between 2 tables.
One has this information:
TABLE ACTIVITY (NO PRIMARY KEY)
UserID int (not Nullable)
JoinDate datetime
Status tinyint
LeaveDate datetime
SentAutoMessage tinyint
SectionDetails varchar
And here is the second table:
TABLE USER_INFO (CLUSTERED PRIMARY KEY)
UserID int (not Nullable)
UserName varchar
UserActive int
CreatedOn datetime
DisabledOn datetime
The tables are joined on UserID and the UserID being selected in the original 2 queries is the one from the TABLE ACTIVITY.
Hope this clarifies the question.
This is not technically an answer, but since I took time to analyze this, I might as well post it (although I have the risk of being down voted).
There was no way I could reproduce the described behavior.
This is the scenario:
declare #table table ([user id] int)
insert into #table values
(1),(1),(1),(1),(1),(1),(1),(2),(2),(2),(2),(2),(2),(null),(null)
And here are some queries and their results:
SELECT COUNT(User ID) FROM #table --error: this does not run
SELECT COUNT(dsitinct User ID) FROM #table --error: this does not run
SELECT COUNT([User ID]) FROM #table --result: 13 (nulls not counted)
SELECT COUNT(distinct [User ID]) FROM #table --result: 2 (nulls not counted)
And something interesting:
SELECT user --result: 'dbo' in my sandbox DB
SELECT count(user) from #table --result: 15 (nulls are counted because user value
is not null)
SELECT count(distinct user) from #table --result: 1 (user is the same
value always)
I find it very odd that you are able to run the queries exactly how you described. You'd have to let us know the table structure and the data to get further help.
how would I identify those user IDs that don't show up in the 2nd query
Try this query
SELECT UserID from tableUserID Where UserID not in (SELECT Distinct User ID from tableUserID)
I think there will be no row.
Edit:
User is a reserved keyword. Do you mean UserID in your requests ?
Ray : Yes
I tried to reproduce the problem in my environment and my conclusion is that given the conditions you described, the result from the first query can not be higher than the second one. Even if there would be NULL's, that just won't happen.
Did you run the query #Jean-Charles sugested?
I'm very intrigued with this, please let us know what turns out to be the problem.

Combine query results from one table with the defaults from another

This is a dumbed down version of the real table data, so may look bit silly.
Table 1 (users):
id INT
username TEXT
favourite_food TEXT
food_pref_id INT
Table 2 (food_preferences):
id INT
food_type TEXT
The logic is as follows:
Let's say I have this in my food preference table:
1, 'VEGETARIAN'
and this in the users table:
1, 'John', NULL, 1
2, 'Pete', 'Curry', 1
In which case John defaults to be a vegetarian, but Pete should show up as a person who enjoys curry.
Question, is there any way to combine the query into one select statement, so that it would get the default from the preferences table if the favourite_food column is NULL?
I can obviously do this in application logic, but would be nice just to offload this to SQL, if possible.
DB is SQLite3...
You could use COALESCE(X,Y,...) to select the first item that isn't NULL.
If you combine this with an inner join, you should be able to do what you want.
It should go something like this:
SELECT u.id AS id,
u.username AS username,
COALESCE(u.favorite_food, p.food_type) AS favorite_food,
u.food_pref_id AS food_pref_id
FROM users AS u INNER JOIN food_preferences AS p
ON u.food_pref_id = p.id
I don't have a SQLite database handy to test on, however, so the syntax might not be 100% correct, but it's the gist of it.

Help me with this SQL: 'DO THIS for ALL ROWS in TABLE'

[using SQL Server 2005]
I have a table full of users, I want to assign every single user in the table (16,000+) to a course by creating a new entry in the assignment table as well as a new entry in the course tracking table so their data can be tracked. The problem is I do no know how to do a loop in SQL because I don't think you can but there has got to be a way to do this...
FOR EACH user in TABLE
write a row to each of the two tables with userID from user TABLE...
how would I do this? please help!
You'd do this with 2 insert statements. You'd want to wrap this with a transaction to ensure consistency, and may want to double-check our isolation level to make sure that you get a consistent read from the users table between the 2 queries (take a look at SNAPSHOT or SERIALIZABLE to avoid phantom reads).
BEGIN TRAN
INSERT Courses
(UserID, CourseNumber, ...)
SELECT UserID, 'YourCourseNumberHere', ...
FROM Users
INSERT Assignments
(UserID, AssignmentNumber, ...)
SELECT UserID, 'YourAssignmentNumberHere', ...
FROM Users
COMMIT TRAN
Something like:
insert into CourseAssignment (CourseId, StudentId)
select 1 -- whatever the course number is
, StudendId
from Student
something like this, no need for looping, if you have dups use distinct
also change 1 with the course value
insert into AssingmentTable
select userid,1
from UserTable
insert into OtherTable
select userid,1
from UserTable
maybe I misuderstand your question, but I think you need INSERT..SELECT statement
INSERT INTO TABLE2
SELECT filed1, field2 field3 from TABLE1
SQL works on sets. It doesn't require loops ..
what you are looking for might be the "insert into" command.
INSERT INTO <new_table> (<list of fields, comma separated>)
SELECT <list of fields,comma separated>
FROM <usertable>
WHERE <selection condition if needed>
--grab 1 record for each student, and push it into the courses table
--i am using a sub-select to look up a course id based on a name
--that may not work for your situation, but then again, it may...
INSERT INTO COURSES(
COURSE_ID
,STUDENT_ID
)
SELECT
(SELECT COURSE_ID FROM COURSES WHERE COURSE_NAME = 'MATH')
,STUDENT_ID
FROM
STUDENTS;
--grab your recently entered course data and create an entry in
--your log table too
INSERT INTO COURSE_DATA(
COURSE_ID
,STUDENT_ID
)
SELECT
COURSE_ID
,STUDENT_ID
FROM
COURSES;
I would do this using the set based approaches that lots of others have already posted...
...however, just for completeness it is worth noting that you could do a loop if you really wanted to. Look up cursors and while loops in books online to see some examples.
Just please don't fall in to the trap of using cursors as lots of newbies do. They have their uses but if they're used incorrectly they can be terrible - there's almost always a better way of doing things.

Fetch top X users, plus a specific user (if they're not in the top X)

I have a list of ranked users, and would like to select the top 50. I also want to make sure one particular user is in this result set, even if they aren't in the top 50. Is there a sensible way to do this in a single mysql query? Or should I just check the results for the particular user and fetch him separately, if necessary?
Thanks!
If I understand correctly, you could do:
select * from users order by max(rank) desc limit 0, 49
union
select * from users where user = x
This way you get 49 top users plus your particular user.
Regardless if a single, fancy SQL query could be made, the most maintainable code would probably be two queries:
select user from users where id = "fred";
select user from users where id != "fred" order by rank limit 49;
Of course "fred" (or whomever) would usually be replaced by a placeholder but the specifics depend on the environment.
declare #topUsers table(
userId int primary key,
username varchar(25)
)
insert into #topUsers
select top 50
userId,
userName
from Users
order by rank desc
insert into #topUsers
select
userID,
userName
from Users
where userID = 1234 --userID of special user
select * from #topUsers
The simplest solution depends on your requirements, and what your database supports.
If you don't mind the possibility of having duplicate results, then a simple union (as Mariano Conti demonstrated) is fine.
Otherwise, you could do something like
select distinct <columnlist>
from (select * from users order by max(rank) desc limit 0, 49
union
select * from users where user = x)
if you database supports it.