How to know my progress with others progress using SQL - sql

I have below table
CREATE TABLE run_progress
(
id INT PRIMARY KEY,
user varchar(255),
progress numeric
)
INSERT INTO run_progress ( id, user, progress ) VALUES ( 1, 1, 100 )
INSERT INTO run_progress ( id, user, progress ) VALUES ( 2, 2, 90 )
INSERT INTO run_progress ( id, user, progress ) VALUES ( 3, 3, 60 )
INSERT INTO run_progress ( id, user, progress ) VALUES ( 4, 4, 10 )
I want to know user:4 progress compare to rest of the users in the table.
User:4 made a 10% progress, is it possible to know his progress compare to others progress in the table in a global view point?
this is to know how far he is behind or forward compare to the rest of the users.
thank you.

You can aggregate and compare summary statistics in one row:
select max(progress) filter (where id = 4) as user_4,
min(progress) filter (where id <> 4) as min_other_users,
max(progress) filter (where id <> 4) as max_other_users,
avg(progress) filter (where id <> 4) as avg_other_users
from run_progress p

Wouldn't a window sum fit your need?
select *
from (
select
p.*,
avg(progress) filter(where id <> 4) over() avg_progress_of_other_users
from run_progress p
) p
where id = 4
If you want this for all users at once (not only for one specific user), then a lateral join is a better fit:
select p.*, a.*
from run_progress p
left join lateral (
select avg(p1.progress) avg_progress_of_other_users
from run_progress p1
where p1.id <> p.id
) a on true

Related

Get the maximum values of column B per each distinct value of column A sql

I have this table:
I am trying to pull all records from this table for the max value in the DIST_NO column for every distinct ID in the left most column, but I still want to pull every record for each ID in which there are different Product_ID's as well.
I tried partitioning and using row_number, but I am having trouble at the moment.
Here are my desired results:
This is what my code looks like currently:
select *
from
(SELECT *,
ROW_NUMBER() OVER (PARTITION BY ID ORDER BY DIST_NO DESC) RN
FROM Table) V
WHERE RN<=3
you want the max(DIST_NO) for each ID, product_ID?
If so, you can:
SELECT
ID, product_ID, max(DIST_NO)
from table
group by ID, product_ID
If you want the detail rows related to the max row, you just need to join it back to your table:
Select
t.ID, max_dist_no, TRANSaction_ID , LINE_NO , PRODUCT_ID
from
table t inner join
(SELECT
ID, max(DIST_NO) as max_dist_no
from table
group by ID) mx on
t.ID = mx.ID and
t.DIST_NO = max_DIST_NO
Try
SELECT MT.ID
, MT.DIST_NO
, MT.TRANS_ID
, MT.LINE_NO
, MT.PRODUCT_ID
FROM MYTABLE MT
INNER JOIN (
SELECT T.ID, MAX(T.DIST_NO) as DIST_NO FROM MYTABLE T
GROUP BY T.ID
) MAX_MT ON MT.Id = MAX_MT.ID AND MT.DIST_NO = MAX_MT.DIST_NO
The sub query returns each combination of ID and Max value of DIST_NO:
SELECT T.ID, MAX(T.DIST_NO) as DIST_NO FROM MYTABLE T
GROUP BY T.ID
Joining this back to your original table will basically filter your original data-set by only these combinations of values.
Tested on PostgreSQL:
WITH t1 AS (
SELECT id, product_id, MAX(dist_no) AS dist_no
FROM test
GROUP BY 1,2)
SELECT t1.id, t1.dist_no, t2.trans_id, t2.line_no, t1.product_id
FROM test t2, t1
WHERE t1.id=t2.id AND t1.product_id=t2.product_id AND t1.dist_no=t2.dist_no
Use rank() or dense_rank():
select t.*
from (SELECT t.*
RANK() OVER (PARTITION BY ID ORDER BY DIST_NO DESC) as seqnum
FROM Table t
) t
WHERE seqnum = 1;
This is almost a literal translation of your request:
I am trying to pull all records from this table for the max value in
the DIST_NO column for every distinct ID in the left most column.
you can try something like this one :). (But is your result correct? I think there is little mistake in TRANS_ID...)
DECLARE #ExampleTable TABLE
(ID INT,
DIST_NO INT,
TRANS_ID INT,
LINE_NO INT,
PRODUCT_ID INT)
INSERT INTO #ExampleTable
( ID, DIST_NO, TRANS_ID,LINE_NO, PRODUCT_ID )
VALUES ( 102657, 1, 1105365, 1, 109119 ),
( 102657, 1, 1105366, 2, 109114 ),
( 102657, 2, 1105365, 1, 109119 ),
( 102657, 2, 1105366, 2, 109114 ),
( 104371, 1, 1190538, 1, 110981 ),
( 104371, 2, 1190538, 1, 110981 )
;WITH CTE AS ( SELECT DISTINCT ID, LINE_NO
FROM #ExampleTable)
SELECT a.ID,
x.DIST_NO,
x.TRANS_ID,
x.LINE_NO,
x.PRODUCT_ID
FROM CTE a
CROSS APPLY (SELECT TOP 1 *
FROM #ExampleTable f
WHERE a.ID = f.ID AND
a.LINE_NO = f. LINE_NO
ORDER BY DIST_NO DESC) x

How do I count a Date Column based on another Date Column?

What I'm trying to achieve is to count the login attempts of a user based on the LoginAttempts value and on the LastLoginDate. For example, I need to query the LastLoginDate within 30 days with 2 Loginattempts.
result should be:
What I have is this..I created temp table to pull the information and and it doesn't seem to be counting correctly. Here's where I'm stuck..Any help would be appreciated!!
Your GROUP BY is incorrect. It includes LastLoginDateUTC. Consequently, you're counting logins per date, not logins per 30 days.
Drop LastLoginDateUTC from your GROUP BY, and change the SELECT clause to use max(LastLoginDateUTC) as LastLoginDateUTC. That should give you what you want.
Not sure if I understand your request completely, but here's something outside the box. Good luck!:
select FirstName, StudentID, UserName, LastLoginDate, LoginAttempts,
RowNumber = row_number() over(partition by StudentID order by StudentID, LoginAttempts)
into #temp1
from user
where cast(LastLoginDate as date) >= getdate()- 30
--should return all rows of data for past 30 days.
--should also rank each loginAttempt by student
select * from #temp1
where RowNumber >= 3
This not an answer... more a suggestion.
As I see it you have Students and you need to audit login attempts.
This for me is a one to many relation.
So I would keep the Student table, stripped off from any login related data.
This would simplify your Student table, keep less rows and save storage space for not using the same data (name, username, etc) over and over again.
That data would be in a StudentLoginAttempts, something like:
Create Table StudentLoginAttempts (
Id int not null identity(1,1),
StudentId int not null,
LoginDate datetime not null,
Successful bit not null,
Constraint PK_StudentLoginAttempts Primary Key Clustered (Id),
Constraint FK_StudentLoginAttempts_Student Foreign Key (StudentId) References Student(StudentId)
)
go
Create Index IX_StudentLoginAttempts_StudentId On StudentLoginAttempts(StudentId)
go
Create Index IX_StudentLoginAttempts_LoginDate On StudentLoginAttempts(LoginDate)
go
So things could be more clear and you can have more info.
Think the example bellow:
Create Table #Student (
StudentId int not null identity(1,1),
Username varchar(50) not null,
FirstName varchar(50) not null
)
Create Table #StudentLoginAttempts (
Id int not null identity(1,1),
StudentId int not null,
LoginDate datetime not null,
Successful bit not null
)
insert into #Student values
( 'Student001', 'JON' ),
( 'Student002', 'STEVE' )
insert into #StudentLoginAttempts values
( 1, '2016-01-01 09:12', 0 ),
( 1, '2016-02-01 09:12', 0 ),
( 1, '2016-03-01 09:12', 1 ),
( 2, '2016-03-02 10:12', 0 ),
( 2, '2016-04-02 10:12', 1 ),
( 2, '2016-05-02 10:12', 0 )
;with TotalAttemptsCte as (
select StudentId, TotalLoginAttempts = count(*) from #StudentLoginAttempts group by StudentId
),
FailedCte as (
select StudentId, FailedLogins = count(*) from #StudentLoginAttempts where ( Successful = 0 ) group by StudentId
),
SuccessfulCte as (
select StudentId, SuccessfulLogins = count(*) from #StudentLoginAttempts where ( Successful = 1 ) group by StudentId
),
LastSuccessFulDateCte as (
select StudentId, max(LoginDate) as LastSuccessfulLoginDate
from
#StudentLoginAttempts
where
( Successful = 1 )
group by StudentId
)
select
a.*, b.TotalLoginAttempts, c.FailedLogins, d.SuccessfulLogins, e.LastSuccessfulLoginDate
from
#Student a
left join TotalAttemptsCte b on ( a.StudentId = b.StudentId )
left join FailedCte c on ( a.StudentId = c.StudentId )
left join SuccessfulCte d on ( a.StudentId = d.StudentId )
left join LastSuccessFulDateCte e on ( a.StudentId = e.StudentId )
Drop Table #StudentLoginAttempts
Drop Table #Student
You could also create a view based on the query for more easy access.
I remind you that this is just a suggestion, how I would do it.

SELECT values from table with grouped id but differences in non-grouped columns

I'm having trouble achieving something that seems like it should be simple. The example below shows people enrolled in classes, and a person can be enrolled in multiple classes, but only one currently:
DECLARE #Test TABLE
(
PersonId int NOT NULL,
LocationId int NOT NULL,
ClassId int NOT NULL,
IsEnrolled bit NOT NULL,
IsExited bit NOT NULL
)
Add Data:
INSERT INTO #Test
SELECT 1,5,6,1,0
UNION SELECT 1,6,7,0,1
UNION SELECT 2,5,8,1,0
UNION SELECT 2,5,9,0,1
UNION SELECT 3,5,9,0,1
UNION SELECT 3,6,9,1,0
I want to get all the records (current or not) for the people where all of their enrollment is at the same location (having the same LocationId), but only the values that are current (IsEnrolled = 1) where the locations are different.
For the same PersonId I'd like all the records if the LocationId unique, and only the current (IsEnrolled = 1) if the LocationId is not unique for the PersonId.
Data I want to get back from a query:
SELECT
1 AS PersonId,
6 AS ClassId,
1 As IsEnrolled,
0 AS IsExited
UNION SELECT 2, 8, 1, 0
UNION SELECT 2, 9, 0, 1
UNION SELECT 3, 9, 1, 0
I think it's a lot simpler than previously tried methods:
SELECT Test.*
FROM #Test Test
JOIN
(
SELECT PersonID, LocationID
FROM #Test T
WHERE ISENROLLED = 1
GROUP BY PeronID, LocationID
) T
ON Test.PersonID = T.PersonID
AND Test.LocationID = T.LocationID
Since you can only have one "isenrolled" record per person, the inner query is guaranteed to return one person/location combination for each person. Thus, joining to it on person and location ensures that you get every record for that person which was at the location of their currently enrolled class.
If I understand correctly you want all records that are either current or belong to a person who has always been in the same location. Hence:
select personid, classid, isenrolled, isexited
from mytable
where isenrolled = 1
or personid in
(
select personid
from mytable
group by personid
having min(locationid) = max(locationid)
)
order by personid, classid;
The same with window functions, so the table has to be read just once:
select personid, classid, isenrolled, isexited
from
(
select
personid, classid, isenrolled, isexited,
min(locationid) over (partition by personid) as minloc,
max(locationid) over (partition by personid) as maxloc
from mytable
)
where isenrolled = 1 or minloc = maxloc
order by personid, classid;
Here's one approach you could use:
SELECT *
FROM
(
SELECT
*,
(SELECT COUNT(DISTINCT t2.LocationID) FROM tab t2 WHERE t2.PersonId = t1.PersonId) AS LocationCount
FROM tab t1
) t
WHERE
t.LocationCount = 1 --get all records for those at the same location
OR (t.LocationCount > 1 AND t.IsEnrolled = 1) --get only current records for those # multiple location
Essentially, you count the locations a person is enrolled at. If they are just at one, take all the records, and if multiple take only the current enrollment.
You can get the personids using an aggregation query:
select personid
from #test t
where isenrolled = 1
group by personid
having min(location) = max(location);
At that point, it is easy enough to use in to get everything else:
select t.*
from #test t
where personid in (select personid
from #test t
where isenrolled = 1
group by personid
having min(location) = max(location)
);
one join
select t2.PersonId, t2.ClassId, t2.IsEnrolled, t2.IsExited
from #Test t1
left join #Test t2
on t2.PersonId = t1.PersonId
and ((t2.IsEnrolled = 0 and t2.LocationId <> t1.LocationId)
or (t2.IsEnrolled = 1)
)
where t1.IsEnrolled = 1

Inserting a value into a table, determining the minimum available

I have a Postgres table which holds information for users, the fields in this table are:
id (int)
name (string)
I would like to be able to add a user, and providing him an ID that will be deferred from the current values that are in the table:
The ID that will be given to the added user should be the minimal that is available in the table i.e if I have a table with the following IDs: 1, 3, 4, 5, 8,
I would like the ID of the user to be 2, next time I'll add a user its ID will be 6, and the next one will be 7, and so on.
What would be the right query?
Following select query can be used to find out missed ids from table
SELECT t
FROM generate_series((
SELECT min(id) + 1
FROM tb
), (
SELECT max(id) + 1
FROM tb
)) t
WHERE NOT t IN (
SELECT id
FROM tb
)
ORDER BY t ASC
and the insert can be done by following ways,
INSERT INTO tb
VALUES (
(
SELECT t
FROM generate_series((
SELECT min(id) + 1
FROM tb
), (
SELECT max(id) + 1
FROM tb
)) t
WHERE NOT t IN (
SELECT id
FROM tb
)
ORDER BY t ASC limit 1
)
,'B'
)
OR
Create a function like this
CREATE OR replace FUNCTION missed_id ()
RETURNS INTEGER AS $$
SELECT t
FROM generate_series((
SELECT min(id) + 1
FROM tb
), (
SELECT max(id) + 1
FROM tb
)) t
WHERE NOT t IN (
SELECT id
FROM tb
)
ORDER BY t ASC limit 1;;$$
LANGUAGE sql
and insert should be
insert into tb values (missed_id(),'B')
insert into auth_user(id, user_name)
values((select a from generate_series((select min(id) from auth_user)
,(select max(id)+1 from auth_user)
) as a
left join auth_user on (a = id )
where id is null order by a limit 1)
, 'new user')
this is a bad idea, and its not transaction safe if you have simultaneous inserts

How do I get records before and after given one?

I have the following table structure:
Id, Message
1, John Doe
2, Jane Smith
3, Error
4, Jane Smith
Is there a way to get the error record and the surrounding records? i.e. find all Errors and the record before and after them.
;WITH numberedlogtable AS
(
SELECT Id,Message,
ROW_NUMBER() OVER (ORDER BY ID) AS RN
FROM logtable
)
SELECT Id,Message
FROM numberedlogtable
WHERE RN IN (SELECT RN+i
FROM numberedlogtable
CROSS JOIN (SELECT -1 AS i UNION ALL SELECT 0 UNION ALL SELECT 1) n
WHERE Message='Error')
WITH err AS
(
SELECT TOP 1 *
FROM log
WHERE message = 'Error'
ORDER BY
id
),
p AS
(
SELECT TOP 1 l.*
FROM log
WHERE id <
(
SELECT id
FROM err
)
ORDER BY
id DESC
)
SELECT TOP 3 *
FROM log
WHERE id >
(
SELECT id
FROM p
)
ORDER BY
id
Adapt this routine to pick out your target.
DECLARE #TargetId int
SET #TargetId = 3
select *
from LogTable
where Id in (-- "before"
select max(Id)
from LogTable
where Id < #TargetId
-- target
union all select #TargetId
-- "after"
union all select min(Id)
from LogTable
where Id > #TargetId)
select id,messag from
(Select (Row_Number() over (order by ID)) as RNO, * from #Temp) as A,
(select SubRNO-1 as A,
SubRNO as B,
SubRNO+1 as C
from (Select (Row_Number() over (order by ID)) as SubRNO, * from #Temp) as C
where messag = 'Error') as B
where A.RNO = B.A or A.RNO = B.B or A.RNO = B.C
;WITH Logs AS
(
SELECT ROW_NUMBER() OVER (ORDER BY id), id, message as rownum FROM LogTable lt
)
SELECT curr.id, prev.id, next.id
FROM Logs curr
LEFT OUTER JOIN Logs prev ON curr.rownum+1=prev.rownum
RIGHT OUTER JOIN Logs next ON curr.rownum-1=next.rownum
WHERE curr.message = 'Error'
select id, message from tbl where id in (
select id from tbl where message = "error"
union
select id-1 from tbl where message = "error"
union
select id+1 from tbl where message = "error"
)
Get fixed number of rows before & after target
Using UNION for a simple, high performance query (I found selected answer WITH query above to be extremely slow)
Here is a high performance alternative to the WITH top selected answer, when you know an ID or specific identifier for a given record, and you want to select a fixed number of records BEFORE and AFTER that record. Requires a number field for ID, or something like date that can be sorted ascending / descending.
Example: You want to select the 10 records before and after a specific error was recorded, you know the error ID, and can sort by date or ID.
The following query gets (inclusive) the 1 result above, the identified record itself, and the 1 record below. After the UNION, the results are sorted again in descending order.
SELECT q.*
FROM(
SELECT TOP 2
id, content
FROM
the_table
WHERE
id >= [ID]
ORDER BY id ASC
UNION
SELECT TOP 1
id, content
FROM
the_table
WHERE
id < [ID]
ORDER BY id DESC
) q
ORDER BY q.id DESC