Mysql select query until reach the condition - mysql

Lets say I have list of users in my user table like below. I need to find the count of users from my table until the userid is equal to 100.
So here the answer is (3). But how can i find this is MySQL query. Any idea?
userid name
---------------
10 aaa
30 bbb
100 ccc
60 ddd

This is one of the work around to achieve your expectation.
SET #row_number:=0;
SELECT A.row_number FROM (
SELECT Userid, name, #row_number:=#row_number+1 AS row_number
FROM UserDetail
) AS A
WHERE A.Userid = 100;
Working DEMO
In case if the UserId is the not the unique id and it can repeat, you may add the ORDER BY with LIMIT 1
SET #row_number:=0;
SELECT A.row_number FROM (
SELECT Userid, name, #row_number:=#row_number+1 AS row_number
FROM UserDetail
) AS A
WHERE A.Userid = 100
ORDER BY A.row_number
LIMIT 1;
Working DEMO
Sample execution with given data:
--DROP TABLE UserDetail;
CREATE TABLE UserDetail (Userid INT, name VARCHAR (50));
INSERT INTO UserDetail (Userid, name) VALUES
(10 , 'Aaa'),
(30 , 'Bbb'),
(100, 'Ccc'),
(60 , 'ddd');
SET #row_number:=0;
SELECT A.row_number FROM (
SELECT Userid, name, #row_number:=#row_number+1 AS row_number
FROM UserDetail
) AS A
WHERE Userid = 100;

This is difficult to do in MySql as the 'first' instance of a number can be difficult to pin down.
One way to resolve this would be to create row numbers, then choose the minimum row number with Userid = 100
select min(row_number) from
(
SELECT #rownum:=#rownum + 1 as row_number,
p.*
FROM p,
(SELECT #rownum := 0) r
) g
where userid = 100
Here is a functional example

Related

SELECT without duplicates unless interupted by other values

Given the following data:
name | temp
-----------
hoi | 15
hoi | 15
hoi | 16
hoi | 15
hej | 13
hoi | 13
I would like to select the data in the given two columns without duplicates, However I do want to keep duplicates that are duplicates if they where interrupted by another value:
name | temp
-----------
hoi | 15 // selected
hoi | 15 // ignored duplicate
hoi | 15 // ignored duplicate
hoi | 16 // selected
hoi | 15 // selected because while not being unique it follows a different value
hoi | 15 // ignored duplicate
hej | 13 // selected
hoi | 13 // selected
hoi | 13 // ignored duplicate
hoi | 14 // selected
hoi | 13 // selected because while not being unique it follows a different value
This question was hard to formulate for me given English is not my native tongue, Feel free to edit the question or ask for clarifications.
Edit:
There is an id field and a datetime field.
Edit 2:
I use mySQL 5.7
Since you are using MySQL 5.7, which doesn't support analytical functions, you will need to use variables to store the values of temp and name, from the previous row:
SELECT t.ID,
t.Name,
t.Temp
FROM ( SELECT t.*,
IF(#temp = t.temp AND #name = t.Name, 1, 0) AS IsDuplicate,
#temp:= t.temp,
#name:= t.Name
FROM YourTable AS t
CROSS JOIN (SELECT #temp := 0, #name := '') AS v
ORDER BY t.ID
) AS t
WHERE t.IsDuplicate = 0
ORDER BY ID;
Example on DB<>Fiddle
The key parts are (not in the order in which they appear, but in the order in which it is logical to think about it).
(1) Initialise the variables, and order by ID (or whatever field(s) you like) to ensure variables are assigned in the correct order
CROSS JOIN (SELECT #temp := 0, #name := '') AS v
ORDER BY t.ID
(2) Check if the values stored in the variables matches the current row, and flag with a 1 or a 0
IIF(#temp = t.temp AND #name = t.Name, 1, 0) AS IsDuplicate
(3) Assign the values of temp and name in the current row to the variables, so they can be checked against the next row:
#temp:= t.temp,
#name:= t.Name
(4) Remove duplicates from the final data set:
WHERE t.IsDuplicate = 0;
To go one further, you could change the IsDuplicate flag to be a group marker, and use GROUP BY, so you can find out how many records there were in total, while still not displaying duplicates:
SELECT MIN(ID) AS FirstID,
t.Name,
t.Temp,
COUNT(*) AS Records,
MAX(ID) AS LastID
FROM ( SELECT t.*,
#group:= IF(#temp = t.temp AND #name = t.Name, #group, #group + 1) AS GroupID,
#temp:= t.temp,
#name:= t.Name
FROM YourTable AS t
CROSS JOIN (SELECT #temp := 0, #name := '', #group:= 0) AS v
ORDER BY t.ID
) AS t
GROUP BY t.GroupID, t.Name, t.Temp
ORDER BY t.GroupID;
Example on DB<>Fiddle
This may be surplus to requirements, but it can be useful as you are able to extract a lot more information than when just identifying duplicate rows.
Finally if/when you upgrade to version 8.0 or newer, you will be able to use ROW_NUMBER(), or if you move to any other DBMS that supports ROW_NUMBER() (which is most nowadays), then you can use the following:
SELECT MIN(ID) AS FirstID,
t.Name,
t.Temp,
COUNT(*) AS Records,
MAX(ID) AS LastID
FROM ( SELECT t.*,
ROW_NUMBER() OVER(ORDER BY ID) -
ROW_NUMBER() OVER(PARTITION BY Temp, Name ORDER BY ID) AS GroupID
FROM YourTable AS t
ORDER BY t.ID
) AS t
GROUP BY t.GroupID, t.Name, t.Temp
ORDER BY t.GroupID;
Example on DB<>Fiddle
Generic Solution
You can use the following query to do this on any DBMS:
select nd.*
from dedup nd
inner join (
-- find the previous id for each id
select id, (select max(id) from dedup where id < o.id) prev_id
from dedup o
) id_to_prev on id_to_prev.id = nd.id
-- join with the prev row to check for dups
left join dedup d on d.id = id_to_prev.prev_id
and d.name = nd.name
and d.temp = nd.temp
where d.id is null -- if no prev row found with same name+temp, include this row
order by nd.id
SQL Fiddle: http://sqlfiddle.com/#!9/0584ca3/9
If You are using Oracle:
select name, temp from (
select id,
name,
temp,
lag(temp,1,-99999) over (order by id) as temp_prev
from table
order by id) t
where t.temp != t.temp_prev
might work for You (depending on Your Oracle version!), it uses the LAG analytics function to look into previous rows values, creates a temp table then filters it.
create table #temp (name varchar(3),temp int)
insert into #temp values ('hoi',15)
insert into #temp values ('hoi',15)
insert into #temp values ('hoi',15)
insert into #temp values ('hoi',16)
insert into #temp values ('hoi',15)
insert into #temp values ('hoi',15)
insert into #temp values ('hej',13)
insert into #temp values ('hoi',13)
insert into #temp values ('hoi',13)
insert into #temp values ('hoi',14)
insert into #temp values ('hoi',13)
;with FinalResult as (
select ROW_NUMBER()Over(partition by name,temp order by name) RowNumber,*
from #temp
)
select * from FinalResult where RowNumber =1
drop table #temp
You want to look at the previous row in order to decide whether to show a row or not. This would be easy with LAG, available as of MySQL 8. With MySQL 5.7 you need a correlated subquery with LIMIT instead to get the previous row.
select *
from mytable
where not (name, temp) <=>
(
select prev.name, prev.temp
from mytable prev
where prev.id < mytable.id
order by prev.id desc
limit 1
);
Demo: https://dbfiddle.uk/?rdbms=mysql_5.7&fiddle=4c775dbee12298cd93c5087d7085982f

Group by select based on OR condition

After using UNION with two select queries, I'm getting following results
UserId Name Status
------ ------ --------
1 User1 Active
2 User2 Active
1 User1 InActive
3 User3 InActive
But the expected results is
UserId Name Status
---------------------
1 User1 Active
2 User2 Active
3 User3 InActive
Here what I need is, I want to group by column Id and get status as Active if any one result is active. How to form a SQL query for this?
Can anyone suggest query for any one of the following DB?
MSSQL
Oracle
MySQL
PostgreSQL
Edit:
This is the query I've tried in PostgreSQL
(SELECT DISTINCT User.Id,User.DisplayName,AppAccessToUsers.IsActive='1' AND User.IsActive='1' AS IsStatusActive
FROM Applications Left JOIN AppAccessToUsers ON (Applications.Id=AppAccessToUsers.ApplicationId)
Left JOIN User ON (AppAccessToUsers.UserId=User.Id) WHERE Applications.ClientId='e7e66c1b-b3b8-4ffb-844b-fc4840803265')
UNION
(SELECT DISTINCT User.Id,User.DisplayName,AppAccessToGroups.IsActive='1' AND Group.IsActive='1' AND UserGroup.IsActive='1' AND User.IsActive='1' AS IsStatusActive
FROM Applications Left JOIN AppAccessToGroups ON (Applications.Id=AppAccessToGroups.ApplicationId)
Left JOIN Group ON (AppAccessToGroups.GroupId=Group.Id) Left JOIN UserGroup ON (Group.Id=UserGroup.GroupId)
Left JOIN User ON (UserGroup.UserId=User.Id) WHERE Applications.ClientId='e7e66c1b-b3b8-4ffb-844b-fc4840803265')
Use this query,
SELECT UserId
,Name
,CASE WHEN min(status) = 'Active' THEN 'Active' ELSE 'InActive' END
FROM users GROUP BY UserId,Name
I would do the following, assuming a) your tables are called t1 and t2 (amend as appropriate for your actual table names) and b) the names for each userid in both tables are the same - ie. for userid = 1, both tables have the same name:
SELECT userid,
NAME,
MIN(status)
FROM (SELECT userid, NAME, status FROM t1
UNION ALL
SELECT userid, NAME, status FROM t2)
GROUP BY userid, NAME;
This works in Oracle, and I'm pretty sure it'll work in the other database platforms you mentioned.
N.B. I used MIN(status) since you appear to want a status of Active to override a status of Inactive, and A comes before I in the alphabet.
In Sql-server, you could use group by or Row_number like this
DECLARE #SampleData AS TABLE
(
UserId int,
Name varchar(20),
Status varchar(10)
)
INSERT INTO #SampleData
(
UserId,Name,Status
)
VALUES
(1,'User1', 'Active'),
(2,'User2', 'Active'),
(1,'User1', 'InActive'),
(3,'User3', 'InActive')
-- use row_number
;WITH temp AS
(
SELECT *, row_number() OVER(PARTITION BY sd.UserId ORDER BY sd.Status ) AS Rn
FROM #SampleData sd
)
SELECT t.UserId, t.Name, t.Status
FROM temp t WHERE t.Rn = 1
--or use group by
SELECT sd.UserId, sd.Name, min(sd.Status) AS status
FROM #SampleData sd
GROUP BY sd.UserId, sd.Name
Results:
UserId Name Status
1 User1 Active
2 User2 Active
3 User3 InActive
In case of MS Sql Server you can try row_number
;with cte as (
select top 1 with ties * from
( select * from #youruser
union all
select * from #youruser) a
order by row_number() over (partition by userid order by [status] desc)
) select * from cte where status = 'Active'
select your_table.* from your_table
inner join (
select UserId, min(Status) as st from your_table
group by UserId
) t
on your_table.UserId = t.UserId and your_table.Status = t.st
Note: if same UserId can have same Status more than 1 times, then this returns duplicated results.
;With cte (UserId, Name,Status)
AS
(
SELECT 1,'User1','Active' Union all
SELECT 2,'User2','Active' Union all
SELECT 1,'User1','InActive' Union all
SELECT 3,'User3','InActive'
)
SELECT UserId
,NAME
,[Status]
FROM (
SELECT *
,ROW_NUMBER() OVER (
PARTITION BY UserId
,NAME ORDER BY STATUS
) AS Seq
FROM cte
) dt
WHERE dt.Seq = 1
OutPut
UserId Name Status
-----------------------
1 User1 Active
2 User2 Active
3 User3 InActive
for postgres you can use CASE and bool_or, eg:
t=# with a(i,n,b) as (
values (1,'a','active'), (1,'a','inactive'), (2,'b','inactive'), (2,'b','inactive')
)
select i,n,case when bool_or(b = 'active') then 'active' else 'inactive' end
from a
group by i,n
;
i | n | case
---+---+----------
1 | a | active
2 | b | inactive
(2 rows)
Another approach:
Note : Group by is to remove duplicate
select
A.USERID, A.NAME,A.STATUS
from TAB_1 A
LEFT JOIN
(SELECT * FROM TAB_1 WHERE STATUS='Active') B
ON A.USERID=B.USERID
WHERE
( B.STATUS IS NULL OR A.STATUS=B.STATUS)
GROUP BY A.USERID, A.NAME,A.STATUS
ORDER BY A.USERID
;

Mysql calculate rank based on level DESC and experience DESC

Basically I have a table named hiscores where I want to search a nickname of one user and get his current rank, since the rank rown doesn't exist because the ranks are organized by lvl DESC and then by Experience , so I want a sql query where I search the name of that
"player1" and it returs me rank 2. or input healdeal and get rank 1
Table = hiscores
id - nickname- lvl - experience
1 - healdeal - 99 - 1000
2 - philip - 98 - 595
3 - Player1 - 98 - 620
4 - Mindblow - 52 - 35
I have tried the following
SELECT (COUNT(*) + 1) AS rank FROM hiscores WHERE lvl >(SELECT lvl FROM hiscores WHERE nickname="player1")
Assuming this is MySQL, this will work:
select #rownum:=#rownum+1 Rank,
h.*
from hiscores h,
(SELECT #rownum:=0) r
order by level desc, experience desc
SQLFiddle
If this is MS SQL Server 2005 onwards, you can directly use window functions, like so:
select *, rank() over (order by level desc, experience desc) Rank
from hiscores
In either case, if you want to filter by the nickname, you can put the above expression into a subquery and filter that by the nickname i.e.
select * from
(<ranking expression from above>) rankedresults
where nickname = <input>
I see. You are trying to calculate the rank. I think this might do it:
select count(*) as rank
from hiscores hs cross join
(select hs.*
from hiscores
where nickname = 'player1'
) hs1
where hs.lvl > hs1.lvl or
hs.lvl = hs1.lvl and hs.experience >= hs1.experience;
Actually, if you have ties on both experience and lvl, then this might be a better rank:
select 1 + count(*) as rank
from hiscores hs cross join
(select hs.*
from hiscores
where nickname = 'player1'
) hs1
where hs.lvl > hs1.lvl or
hs.lvl = hs1.lvl and hs.experience > hs1.experience;
If you are using MSSQL 2005+. You can do this:
Test data:
DECLARE #tbl TABLE(id INT,nickname VARCHAR(100),lvl INT, experience INT)
INSERT INTO #tbl
VALUES
(1 ,'healdeal',99,1000),
(2 ,'philip',98,595),
(3 ,'Player1',98,620),
(4 ,'Mindblow',52,35)
Query
;WITH CTE
AS
(
SELECT
RANK() OVER(ORDER BY lvl DESC,experience DESC) AS rank,
tbl.*
FROM
#tbl AS tbl
)
SELECT
*
FROM
CTE
WHERE
CTE.nickname='Player1'
Output
2 3 Player1 98 620

How to distribute accounts on users equally based on a time zone in mysql?

I have 1000 account. each account has account_name, account_id, time_zone_id
I want to generate activities for every account to 3 users.
So I will need to generate 333 activities for used #10 and 333 for user #11 and 334 for user #12. But I need to make sure that the time zone is distributed equally. so if I have 200 account in a time zone 18 and 400 account in time zone 10 and 200 in time zone 7 and 200 in time zone 39 then I want to make sure I distribute those new activities for the users equally
I have tried something like this as a select to get the count and see if I am going the correct direction
SELECT count(ac.account_id) AS total, ac.time_zone_id,
(SELECT user_id FROM users where team_id = 4 ORDER BY RAND() LIMIT 1 ) AS user_id
FROM accounts AS ac
GROUP BY ac.time_zone_id
this created the activities but it is not equal distribution.
The following would return a user_no ( 10-12 ) for each account.
It sorts by time_zone_id and then uses the mod function to pick each of the three users in turn (user 10 for the first result, 11 for second, 12 for third, 10 for fourth and so on).
set #r = 0 ;
select
#r:=#r+1 row_no,
account_id,
account_name,
mod(#r,3)+10 user_no
from
account
order by
time_zone_id
Revision
you can get users in a similar way
set #ur = 0;
select
#ur:=#ur+1 user_row_no,
user_id
from users
where team_id = 4
Revised again
It would be something like this
Make some sample data
create table users( user_id int, team_id int) ;
insert into users select 2,4
union select 3,4
union select 1,2
union select 7,4
union select 6,4;
create table account ( account_id int,
account_name varchar(20),
time_zone_id varchar(3),
assigned_to int
);
insert into account(account_id ,account_name,time_zone_id)
select 1,'Janice','pst'
union select 2,'Jonny','gmt'
union select 3,'Jane','gmt'
union select 4,'Janet','pst'
union select 5,'James','gmt';
Make a table to pop the users in that we are interested in
(could/should be a temp_table)
create table temp_user( id int AUTO_INCREMENT primary key,
user_id int
);
insert into temp_user( user_id )
select user_id
from users
where team_id = 4;
The update
set #r=0;
update account join
(
select
#r:=#r+1 row_no,
account_id,
account_name,
assigned_to
from
account
order by
time_zone_id
) x
on x.account_id = account.account_id
join
temp_user
on
temp_user.id=(1+ mod(row_no,(select count(*) from temp_user)))
set account.assigned_to = temp_user.user_id
http://sqlfiddle.com/#!2/164733/10

How to find the mode of a set of data before joining with another table?

These are the two tables I am looking at:
k3_alert_types
Type Description
0 No Show
1 Stop Arrival
2 ...
3 ...
4 ...
5 ...
k3_alert
Type
1
22
33
2
4
5
65
33
1
The tables are just examples, as the actual data sets are much larger. What I would like to do is find the mode of types in the k3_alert table, which I have done with the following:
SELECT TYPE , number_of_alerts
FROM
(
SELECT id, TYPE, COUNT(TYPE) AS number_of_alerts FROM k3_alert
GROUP BY TYPE
)t1
WHERE number_of_alerts IN
(
SELECT MAX( count_type ) FROM
(
SELECT id, TYPE , COUNT(TYPE ) AS count_type FROM k3_alert
GROUP BY TYPE
)t
)
I know how to join both tables:
SELECT k3_alert_types.description, k3_alert_types.type as type
FROM k3_alert_types
INNER JOIN k3_alert ON k3_alert_types.type = k3_alert.type
ORDER BY type
But I don't know how to do both at once.
I want to see this as the outcome of the whole process (just an example):
Description Type number_of_alerts
No Show 1 350
Any suggestions?
edit: Server type: MariaDB,
PHP extension: mysql
This should work:
SELECT at.description, at.type, COUNT(*) as number_of_alerts
FROM k3_alert_types at
INNER JOIN k3_alert a ON at.type = a.type
GROUP BY at.description, at.type
ORDER BY number_of_alerts DESC
LIMIT 1
So what I did was I used a CTE to store the value of mode and then selected top 1. If you wanted more flexibility or have a huge dataset you can use a temp table instead of a CTE.
Below is the code:
DECLARE #AlertType TABLE
(Type1 INT,
Descr varchar(20))
INSERT INTO #AlertType
(
Type1,
Descr
)
VALUES
( 1, 'Stop Arrival'),( 0,'No Show')
DECLARE #Alert TABLE
(Type1 INT)
INSERT INTO #Alert
(
Type1
)
VALUES (1),(0),(1),(23),(1),(5),(1)
;WITH CTE AS
(SELECT Type1, COUNT(*) AS number_of_alerts
FROM #Alert
GROUP BY Type1
)
SELECT TOP 1 AT.Descr, t1.Type1, t1.number_of_alerts
FROM CTE AS t1
JOIN #AlertType AS AT
ON AT.Type1 = t1.Type1
ORDER BY t1.number_of_alerts DESC