Getting the last answer id for the questions - sql-server

In M.C.Q test, when user come back to the same question again how can I track the last answer given for the question?,
following is the table structure
Method: 1
temptblUserAnswer
id uid qid ansid
1 1 1 5
2 1 2 6
Should I update the table value for that particular Question? OR Should I use following table structure?
Method: 2
temptblUserAnswer
id uid qid ansid timestamp
1 1 1 5 2011-09-28 11:54:32
2 1 2 12 2011-09-28 11:58:40
3 1 1 7 2011-09-28 12:02:50
Here with the help of timestamp I can find the the last answer for any particular question.
Now the question is, which method to follow, and which will be performance oriented?
Or any better suggestion for the same? Should I go with Method:1 and apply the index on uid, ansid column?

If your Id is autoincrementing, you can get last answer based on Id:
SELECT TOP 1 AnsID
WHERE Uid=#UserId AND Qid=#QuestionID
ORDER BY Id DESC
About performance:you should put non-clustered index on Uid AND Qid AND ID and Include AnsID
You can avoid of using timestamp column in this table.

1: For the first method:
To get the last answer id for a given question id, you can use this script:
--index creation
CREATE UNIQUE INDEX IUN_temptblUserAnswer_qid_id ON temptblUserAnswer(uid,qid,id DESC);
--query
DECLARE #QuestionID INT, #UserID INT;
SELECT #QuestionID=123, #UserID = 456;
SELECT TOP 1 ansid
FROM temptblUserAnswer
WHERE qid = #QuestionID
AND uid = #UserID
ORDER BY id DESC;
In this case, I have assumed that the last answer is given by the last temptblUserAnswer.id. Also I have assumed that temptblUserAnswer.id is IDENTITY(1,1).
Problem: if somebody runs an ad-hoc insert on temptblUserAnswer table it's possible to get wrong results.
2: For the second method:
You should use a date/time data type with higher precision (for SQL version<=SQL2005: DATETIME is the only option, for SQL version>SQL2005 you can use DATETIME2(7) data type and SYSDATETIME() function instead of GETDATE()/CURRENT_TIMESTAMP functions). Even so, you could get two or more answers with the same timestamp. In this case, you could use 'temptblUserAnswer.id' column as the second criteria.
--index creation
CREATE UNIQUE INDEX IUN_temptblUserAnswer_qid_id ON temptblUserAnswer(uid,qid,timestamp DESC, id DESC);
--query
DECLARE #QuestionID INT, #UserID INT;
SELECT #QuestionID=123, #UserID = 456;
SELECT TOP 1 ansid
FROM temptblUserAnswer
WHERE qid = #QuestionID
AND uid = #UserID
ORDER BY timestamp DESC, id DESC;

Related

T-SQL : Cleaning up data, merging rows into columns

I'm trying to clean up some Active Directory data in SQL Server. I have managed to read the raw LFD file into a table. Now I need to clean up some attributes where values are spread out over multiple rows. I can identify records that need to be appended to the prior record by the fact the have a leading space.
Example:
ID Record IsPartOfPrior
3114 memberOf: 0
3115 CN=Sharepoint-Members-home 1
3116 memberOf: 0
3117 This is 1
3118 part of the 1
3119 next line. 1
Ultimately, I would like to have the following table generated:
ID Record
3114 memberOf:CN=Sharepoint-Members-home
3116 memberOf:This is part of the next line
I could write it through a cursor, setting variables, working with temp tables and populating a table. But there has to be a set based (maybe recursive?) approach to this?
I could use the STUFF method to combine various rows together, but how am I about to group the various sets together? I'm thinking that I first have to define groupID's per record, and then stuff them together per groupID?
Thanks for any help.
Batch with comments below. Should work starting with SQL Server 2008.
--Here I emulate your table
DECLARE #yourtable TABLE (ID int, Record nvarchar(max), IsPartOfPrior bit)
INSERT INTO #yourtable VALUES
(3114,'memberOf:',0),(3115,'CN=Sharepoint-Members-home',1),(3116,'memberOf:',0),(3117,'This is',1),(3118,'part of the',1),(3119,'next line.',1)
--Getting max ID
DECLARE #max_id int
SELECT #max_id = MAX(ID)+1
FROM #yourtable
--We get next prior for each prior record
--And use STUFF and FOR XML PATH to build new Record
SELECT y.ID,
y.Record + b.Record as Record
FROM #yourtable y
OUTER APPLY (
SELECT TOP 1 ID as NextPrior
FROM #yourtable
WHERE IsPartOfPrior = 0 and y.ID < ID
ORDER BY ID ASC
) as t
OUTER APPLY (
SELECT STUFF((
SELECT ' '+Record
FROM #yourtable
WHERE ID > y.ID and ID < ISNULL(t.NextPrior,#max_id)
ORDER BY id ASC
FOR XML PATH('')
),1,1,'') as Record
) as b
WHERE y.IsPartOfPrior = 0
The output:
ID Record
----------- -----------------------------------------
3114 memberOf:CN=Sharepoint-Members-home
3116 memberOf:This is part of the next line.
This will work if ID are numeric and ascending.
Yet another option if 2012+
Example
Declare #YourTable Table ([ID] int,[Record] varchar(50),[IsPartOfPrior] int)
Insert Into #YourTable Values
(3114,'memberOf:',0)
,(3115,'CN=Sharepoint-Members-home',1)
,(3116,'memberOf:',0)
,(3117,'This is',1)
,(3118,'part of the',1)
,(3119,'next line.',1)
;with cte as (
Select *,Grp = sum(IIF([IsPartOfPrior]=0,1,0)) over (Order By ID)
From #YourTable
)
Select ID
,Record = Stuff((Select ' ' +Record From cte Where Grp=A.Grp Order by ID For XML Path ('')),1,1,'')
From (Select Grp,ID=min(ID)from cte Group By Grp ) A
Returns
ID Record
3114 memberOf: CN=Sharepoint-Members-home
3116 memberOf: This is part of the next line.
If it Helps with the Visualization, the cte Produces:
ID Record IsPartOfPrior Grp << Notice Grp Values
3114 memberOf: 0 1
3115 CN=Sharepoint-Members-home 1 1
3116 memberOf: 0 2
3117 This is 1 2
3118 part of the 1 2
3119 next line. 1 2

select Top 3 unexpexted result?

This is table structure !
name varchar(10),
score float
With values:
('Alex',7),('john',5.6),('Tom',8.9),('Anonio',6),('sharti',7),('mamuzi',9)
I need o/p
name score
mamuzi 9
Tom 8.9
Alex 7
sharti 7
When i try using TOP as : select top 3 * from table order by score desc I can't get the expected results
Try using with Ties in sql server since Alex and sharti have same score
select top 3 with ties * from #t order by score desc
See here
Unless you include an ORDER BY statement, the ordering will be random when you use TOP.
Do this:
SELECT TOP 3 WITH TIES * FROM YourTable ORDER BY score DESC
Sample SQL Fiddle.
From the documentation:
When TOP is used in conjunction with the ORDER BY clause, the result
set is limited to the first N number of ordered rows; otherwise, it
returns the first N number of rows in an undefined order.
Edit: just noticed the change in your sample data. To make it work now you need to add the WITH TIES clause to TOP, as pointed out in the other answers. I've updated my answer accordingly.
You forgot ordering by score field. Modify your query as:
SELECT TOP 3 * FROM table ORDER BY score DESC
Answer for the last edit to your question: Using TOP and ORDER BY DESC worked for me but i tested with the new values you added to your question. My test was as follows:
DECLARE #table TABLE
(
name varchar(10),
score float
)
INSERT INTO #table VALUES ('Alex', 7)
INSERT INTO #table VALUES ('john',5.6)
INSERT INTO #table VALUES ('Tom',8.9)
INSERT INTO #table VALUES ('Anonio',6)
INSERT INTO #table VALUES ('sharti',7)
INSERT INTO #table VALUES ('mamuzi',9)
SELECT TOP 3 * FROM #table ORDER BY score DESC
It works since the result is as expected.
To include sharti and his score in the results since he has the same score as Alex, you will need to use WITH TIES as Vijaykumar Hadalgi suggested (More for WITH TIES).
Modify your query as: SELECT TOP(3) WITH TIES * FROM #table ORDER BY score DESC
name score
--------------
mamuzi 9
Tom 8,9
Alex 7
sharti 7

Get the Currently logged user and their last login time in SQL Server

Here is my user log table
ID USERID TIME TYPE
1 6 12:48:45 OUT
2 11 12:08:46 IN
3 6 12:18:45 IN
4 6 12:08:45 IN
5 9 12:06:44 IN
6 11 11:08:46 IN
I need get currently loggedin user and last logged in time in SQL Server . Output like this
ID USERID TIME TYPE
2 11 12:08:46 IN
5 9 12:06:44 IN
Using your test data..
Declare #logintable table (id int, userid int, time datetime, type varchar(3))
Insert into #logintable values (1,6,'12:48:45','OUT')
Insert into #logintable values (2,11,'12:08:46','IN')
Insert into #logintable values (3,6,'12:18:45','IN')
Insert into #logintable values (4,6,'12:08:45','IN')
Insert into #logintable values (5,9,'12:06:44','IN')
Insert into #logintable values (6,11,'11:08:46','IN')
..this query should get you the result set you are after
SELECT
a.*
FROM
#logintable a
INNER JOIN
(SELECT userid, MAX(time) as time FROM #logintable b GROUP BY userid) b
ON
a.userid = b.userid AND a.time = b.time
WHERE
a.type = 'IN'
ORDER BY
a.id
The subquery finds the most recent entry for each user and compares it with the record we're currently looking at (that must be an 'IN' record). By doing this we automatically find logged in users.
Note that I have joined on MAX(TIME) here where it would be nicer to join on MAX(ID) - I would guess that in production your ID column would be sequential (ideally an identity column) and so the bigger the value of id the bigger the value of time.
The above query gives these results
ID UserID Time Type
2 11 1900-01-01 12:08:46.000 IN
5 9 1900-01-01 12:06:44.000 IN
The solution consists in using 2 CTEs, the first one contains all logged in users and the second contains all logged out users. Then the final select take those logged-in users that are not yet logged-out.
WITH loggedIn
AS
(
SELECT userId, MAX([time]) LastLogIn
FROM logtable
WHERE [type]='IN'
GROUP BY userId
),
loggedOut
AS
(
SELECT userId, MAX([time]) LastLoginOut
FROM logtable
WHERE [type]='OUT'
GROUP BY userId
)
SELECT loggedIn.userId, loggedIn.LastLogin
FROM loggedIn
WHERE
userId NOT IN (SELECT userId FROM loggedOut)
--AND LastLogIn > #currentTime

How do I get the "Next available number" from an SQL Server? (Not an Identity column)

Technologies: SQL Server 2008
So I've tried a few options that I've found on SO, but nothing really provided me with a definitive answer.
I have a table with two columns, (Transaction ID, GroupID) where neither has unique values. For example:
TransID | GroupID
-----------------
23 | 4001
99 | 4001
63 | 4001
123 | 4001
77 | 2113
2645 | 2113
123 | 2113
99 | 2113
Originally, the groupID was just chosen at random by the user, but now we're automating it. Thing is, we're keeping the existing DB without any changes to the existing data(too much work, for too little gain)
Is there a way to query "GroupID" on table "GroupTransactions" for the next available value of GroupID > 2000?
I think from the question you're after the next available, although that may not be the same as max+1 right? - In that case:
Start with a list of integers, and look for those that aren't there in the groupid column, for example:
;WITH CTE_Numbers AS (
SELECT n = 2001
UNION ALL
SELECT n + 1 FROM CTE_Numbers WHERE n < 4000
)
SELECT top 1 n
FROM CTE_Numbers num
WHERE NOT EXISTS (SELECT 1 FROM MyTable tab WHERE num.n = tab.groupid)
ORDER BY n
Note: you need to tweak the 2001/4000 values int the CTE to allow for the range you want. I assumed the name of your table to by MyTable
select max(groupid) + 1 from GroupTransactions
The following will find the next gap above 2000:
SELECT MIN(t.GroupID)+1 AS NextID
FROM GroupTransactions t (updlock)
WHERE NOT EXISTS
(SELECT NULL FROM GroupTransactions n WHERE n.GroupID=t.GroupID+1 AND n.GroupID>2000)
AND t.GroupID>2000
There are always many ways to do everything. I resolved this problem by doing like this:
declare #i int = null
declare #t table (i int)
insert into #t values (1)
insert into #t values (2)
--insert into #t values (3)
--insert into #t values (4)
insert into #t values (5)
--insert into #t values (6)
--get the first missing number
select #i = min(RowNumber)
from (
select ROW_NUMBER() OVER(ORDER BY i) AS RowNumber, i
from (
--select distinct in case a number is in there multiple times
select distinct i
from #t
--start after 0 in case there are negative or 0 number
where i > 0
) as a
) as b
where RowNumber <> i
--if there are no missing numbers or no records, get the max record
if #i is null
begin
select #i = isnull(max(i),0) + 1 from #t
end
select #i
In my situation I have a system to generate message numbers or a file/case/reservation number sequentially from 1 every year. But in some situations a number does not get use (user was testing/practicing or whatever reason) and the number was deleted.
You can use a where clause to filter by year if all entries are in the same table, and make it dynamic (my example is hardcoded). if you archive your yearly data then not needed. The sub-query part for mID and mID2 must be identical.
The "union 0 as seq " for mID is there in case your table is empty; this is the base seed number. It can be anything ex: 3000000 or {prefix}0000. The field is an integer. If you omit " Union 0 as seq " it will not work on an empty table or when you have a table missing ID 1 it will given you the next ID ( if the first number is 4 the value returned will be 5).
This query is very quick - hint: the field must be indexed; it was tested on a table of 100,000+ rows. I found that using a domain aggregate get slower as the table increases in size.
If you remove the "top 1" you will get a list of 'next numbers' but not all the missing numbers in a sequence; ie if you have 1 2 4 7 the result will be 3 5 8.
set #newID = select top 1 mID.seq + 1 as seq from
(select a.[msg_number] as seq from [tblMSG] a --where a.[msg_date] between '2023-01-01' and '2023-12-31'
union select 0 as seq ) as mID
left outer join
(Select b.[msg_number] as seq from [tblMSG] b --where b.[msg_date] between '2023-01-01' and '2023-12-31'
) as mID2 on mID.seq + 1 = mID2.seq where mID2.seq is null order by mID.seq
-- Next: a statement to insert a row with #newID immediately in tblMSG (in a transaction block).
-- Then the row can be updated by your app.

Assign Unique ID within groups of records

I have a situation where I need to add an arbitrary unique id to each of a group of records. It's easier to visualize this below.
Edited 11:26 est:
Currently the lineNum field has garbage. This is running on sql server 2000. The sample that follows is what the results should look like but the actual values aren't important, the numbers could anything as long as the two combined fields can be used for a unique key.
OrderID lineNum
AAA 1
AAA 2
AAA 3
BBB 1
CCC 1
CCC 2
The value of line num is not important, but the field is only 4 characters. This needs to by done in a sql server stored procedure. I have no problem doing it programatically.
Assuming your using SQL Server 2005 or better you can use Row_Number()
select orderId,
row_number() over(PARTITION BY orderId ORDER BY orderId) as lineNum
from Order
While adding a record to the table, you could create the "linenum" field dynamically:
In Transact-SQL, something like this:
Declare #lineNum AS INT
-- Get next linenum
SELECT #lineNum = MAX(COALESCE(linenum, 0)) FROM Orders WHERE OrderID = #OrderID
SET #lineNum = #lineNum + 1
INSERT INTO ORDERS (OrderID, linenum, .....)
VALUES (#OrderID, #lineNum, ....)
You could create a cursor that reads all values sorted, then at each change in value resets the 1 then steps through incrementing each time.
E.g.:
AAA reset 1
AAA set 1 + 1 = 2
AAA set 2 + 1 = 3
BBB reset 1
CCC reset 1
CCC set 1 + 1 = 1
Hmmmmm, could you create a view that returns the line number information in order and group it based on your order ID? Making sure the line number is always returned in the same order.
Either that or you could use a trigger and on the insert calculate the max id for the order?
Or perhaps you could use a select from max statement on the insert?
Perhaps none of these are satisfactory?
If you're not using SQL 2005 this is a slightly more set based way to do this (I don't like temp tables much but I like cursors less):
declare #out table (id tinyint identity(1,1), orderid char(4))
insert #out select orderid from THESOURCETABLE
select
o.orderid, o.id - omin.minid + 1 as linenum
from #out o
inner join
(select orderid, min(id) minid from #out group by orderid) as omin on
o.orderid = omin.orderid

Resources