How do I backfill missing mysql data from one table? - mysql

I have a MySQL table called employee that looks like this:
ID | User | Phone_No | Phone_No_Count
1 | Fred | 9999 | 1
2 | John | 8888 | 2
3 | Pablo | 123 | 1
4 | John | | 0
5 | John | 8888 | 2
6 | Pablo | | 0
7 | John | 456 | 1
Phone_No_Count is a count of the Phone_No column, if there is no Phone_No then Phone_No_Count is set to zero.
I want to backfill the missing Phone_No entries using Phone_No entries which have the highest Phone_No_Count.
e.g. User John has 2 Phone_No's (8888 and 456) so I just want to use 8888 as it has the highest Phone_No_Count (2)
The backfilled data in employee would then look like this:
ID | User | Phone_No | Phone_No_Count
1 | Fred | 9999 | 1
2 | John | 8888 | 2
3 | Pablo | 123 | 1
4 | John | 8888 | 0
5 | John | 8888 | 2
6 | Pablo | 123 | 0
7 | John | 456 | 1
I can then update the Phone_No_Count separately, which I know how to do anyway.
All the examples I've seen online are for backfilling multiple tables or if it's just one table they don't have the required logic for this.
Can somebody please help as this has been frying my brain all day!!

One way to go about this kind of update you can use user defined variables in your query and store the phone for the user which has the maximum of phone count (i.e a correlated subquery) then join this data with your table and do update
update Table1 t1a
inner join(
select t1.id,
t1.`User`,
#p:= case
when t1.Phone_No is null then #c
else t1.Phone_No END Phone_No,
#c:=(select Phone_No from Table1 where t1.`User`=`User` order by `Phone_No_Count` DESC limit 1 ) max_phone
from Table1 t1,(select #p:=0,#c:=0) t
order by t1.`User`,t1.`Phone_No_Count` DESC
) t2 on(t1a.id=t2.id)
set t1a.Phone_No = t2.Phone_No
Fiddle Demo

The trick is to get the phone number for the highest count. Unfortunately, MySQL doesn't let you have subqueries on the same query being updated, but you can do this with a trick. This allows you to use update/join syntax:
update employee e join
(select e.user,
substring_index(group_concat(phone_no order by phone_no_count desc
), ',', 1) as new_phone_no
from employee e
group by e.user
) toupdate
on e.user = toupdate.user
set e.phone_no = toupdate.new_phone_no
where e.phone_no is null;

Related

Mysql query to get max age by section and if two or more has same age return student with smallest id

I have a table of students with temporary test values like this:
Table students
+----+-------------+-------+-----------+
| id | section_id | age | name |
+----+-------------+-------+-----------+
| 1 | 1 | 18 | Justin |
+----+-------------+-------+-----------+
| 2 | 2 | 14 | Jillian |
+----+-------------+-------+-----------+
| 3 | 2 | 16 | Cherry |
+----+-------------+-------+-----------+
| 4 | 3 | 19 | Ronald |
+----+-------------+-------+-----------+
| 5 | 3 | 21 | Marie |
+----+-------------+-------+-----------+
| 6 | 3 | 21 | Arthur |
+----+-------------+-------+-----------+
I want to query the table such that I want to get all the maximum ages of each section. However, if two students have the same age, the table produced will return the student with smallest id.
Return:
+----+------------+-----+--------+
| id | section_id | age | name |
+----+------------+-----+--------+
| 1 | 1 | 18 | Justin |
+----+------------+-----+--------+
| 3 | 2 | 16 | Cherry |
+----+------------+-----+--------+
| 5 | 3 | 21 | Marie |
+----+------------+-----+--------+
I tried this query:
SELECT ANY_VALUE(id), ANY_VALUE(section_id), MAX(age), ANY_VALUE(name) FROM
(SELECT id, section_id, age, name FROM students ORDER BY id) as X
GROUP BY section_id
Unfortunately, there are instances that id does not match the age and name.
I have on my end:
sql_mode = only_full_group_by
and I don't have a privilege to edit that, hence the any_value function but I have no idea how to use it.
This will do what you want.
It starts by finding the maximum age per section (including duplicates).
Then it joins those results with the minimum id per section (to eliminate duplicates).
And finally, select all fields for the matching id and section combinations.
SELECT s3.*
FROM students s3
INNER JOIN (
SELECT MIN(s2.id) AS id, s2.section_id
FROM students s2
INNER JOIN (
SELECT s1.section_id, MAX(s1.age) AS age
FROM students s1
GROUP BY s1.section_id
) s1 USING (section_id, age)
GROUP BY s2.section_id
) s2 USING (id, section_id);
Working SQL fiddle: https://www.db-fiddle.com/f/aezgAYM6A5KnXykceB7At1/0
I would simply use a correlated subquery:
select s.*
from students s
where s.id = (select s2.id
from students s2
where s2.section_id = s.section_id
order by s2.age desc, s2.id asc
limit 1
);
This is pretty much the simplest way to express the logic. And with an index on students(section, age, id), it should be the most performant as well.

Sum of Counted records that calculated using "group by" with condition and "group by"

I'm sorry for fuzzy title of this question.
I have 2 Tables in my database and want to count records of first_table using "group by" on a foreign key id that exists in a column of second_table (which stores ids like array "1,2,3,4,5").
id | name | fk_id
1 | john | 1
2 | mike | 1
3 | jane | 2
4 | tailor | 1
5 | jane | 3
6 | tailor | 5
7 | jane | 4
8 | tailor | 5
9 | jane | 5
10 | tailor | 5
id | name | fk_ids | s_fk_id
1 | xxx | 1,5,6 | 1
2 | yyy | 2,3 | 1
3 | zzz | 9 | 1
4 | www | 7,8 | 1
Now i wrote the following query but it not working properly and displays wrong numbers.
I WANT TO:
1-Count records in first_table group by "fk_id"
2-Sum the counted records which exists in "fk_ids"
3-Display the sum result (sum of related counts) grouped by id.
symbol ' ' means ``.
select sum(if(FIND_IN_SET('fk_id', 'fk_ids')>0,'count',0) 'sum', 'count', 'from'.'fk_id', 'second_table'.* FROM 'second_table'
LEFT JOIN
(
SELECT 'fk_id', count(*) 'count'
FROM 'first_table'
group BY 'fk_id'
) AS 'from'
ON FIND_IN_SET('fk_id', 'fk_ids')>0
WHERE 'second_table'.'s_fk_id'=1
GROUP BY 'id'
ORDER by 'count' DESC
This table has many data and we have no plan to change the structure.
Edit:
Desired output:
id | name | sum
1 | xxx | 7 (3+4+0)
2 | yyy | 2 (1+1)
3 | zzz | 0 (0)
4 | www | 0 (0+0)
After two holidays i came back to work and found out that the "FIND_IN_SET" function is not working properly with space contained string.
And the problem is that i was ignored the spaces too, (same as this question)
Finnaly this query worked:
select sum(`count`) `sum`, `count`, `from`.`fk_id`, `second_table`.* FROM `second_table`
LEFT JOIN
(
SELECT `fk_id`, count(*) `count`
FROM `first_table`
group BY `fk_id`
) AS `from`
ON FIND_IN_SET(`fk_id`, replace(`fk_ids`,' ',''))>0
WHERE `second_table`.`s_fk_id`=1
GROUP BY `id`
ORDER by `count` DESC
And the magic is replace(fk_ids,' ','')

MySQL Select use IN and GROUP BY

I have two tables messages and users I want to find out which users received the messages however the query is only returning one message.
My Schemas are as follow
Messages
msg_id | msg_content | recipients |
-----------------------------------
1 | Hello world | 1,2,3,4,5
2 | Test | 1,3,5
3 | Welcome | 1,2,4
Users
uid | fname | lname |
---------------------------
1 | John |Doe |
2 | Jane |Doe |
3 | Mark |Someone |
4 | Mary |lady |
5 | Anthony |Doe |
So I would love to see my results simply as
msg_id | msg_content | recipients |
-----------------------------------
1 | Hello world | John,Jane,Mark,Mary,Anthony
2 | Test | John,Mark,Anthony
3 | Welcome | John,Jane,Mary
So I am doing my query as so
SELECT msg_id,msg_content,fname AS recepients FROM messages a
LEFT JOIN users ON uid IN(a.recipients)
When I run that query I only get one recipient. Please advice. Thanks.
I think you have to use a alternative way for create tables
Messages
msg_id | msg_content |
----------------------
1 | Hello world |
2 | Test |
3 | Welcome |
Users
uid | fname | lname |
---------------------------
1 | John |Doe |
2 | Jane |Doe |
3 | Mark |Someone |
4 | Mary |lady |
5 | Anthony |Doe |
users_has_messages
uhm_id | uid | msg_id |
---------------------------
1 | 1 | 1 |
2 | 2 | 1 |
3 | 3 | 1 |
4 | 2 | 2 |
5 | 1 | 3 |
Then you can use your code
Okay, so this schema isn't the best (using comma separated lists of IDs is not a great idea, and the performance of any joins will get pretty bad pretty quick). Best bet is to have a third table mapping uid's to msg_id's as mentioned by #Thilina.
That said, this query will do probably what you're after:
SELECT msg_id,msg_content,GROUP_CONCAT(fname) AS recepients FROM messages a
LEFT JOIN users ON FIND_IN_SET(uid, a.recipients)
GROUP BY msg_id
I tried this in Oracle 12c and it is working fine.
So basically what I did is
- Separate the userid from recipient field and used this a columns.
- Join with USERS table to get user fnames
- Used LISTAGG function to aggregate it back.
For MySql we need to find the corresponding functions to Separate the IDs between commas, Convert it to rows and Aggregate. But the inherent logic would be same.
with users (user_id,fname) as (
select 1 ,'John' from dual union
select 2 ,'Jane' from dual union
select 3 ,'Mark' from dual union
select 4 ,'Mary' from dual union
select 5 ,'Anthony' from dual
),
messages(msg_id, msg_content,recipients) as(
select 1,'Hello world','1,2,3,4,5' from dual union
select 2 , 'Test' ,'1,3,5' from dual union
select 3,' Welcome','1,2,4' from dual
),
flat as(
select msg_id,msg_content,
REGEXP_SUBSTR (recipients, '[^,]+', 1, COLUMN_VALUE) as user_id
from messages,
TABLE(
CAST(
MULTISET(
SELECT LEVEL
FROM DUAL
CONNECT BY LEVEL <= REGEXP_COUNT(recipients ,',' ) + 1
) AS SYS.ODCINUMBERLIST
)
)
),
unames as
( select f.msg_id,f.msg_content,u.fname from flat f inner join users u
on f.user_id = u.user_id
order by f.msg_id
)
SELECT msg_id,msg_content,LISTAGG(fname, ',') WITHIN GROUP (ORDER BY fname) as recipients
from unames
group by msg_id,msg_content

Selecting multiple unrelated data from two tables and insert into one table mysql

This is my scenario
I have a permissions table with the following fields.
id | module | permission
1 | client | add
2 | client | edit
3 | client | delete
4 | someth | edit
5 | someth | delete
employee table
id | status | somestatus
1 | act | 1
2 | den | 1
3 | act | 0
4 | den | 1
5 | act | 0
6 | act | 1
Now what i would need to do is select the employee who have status="act" and somestatus=1 and give them all permissions where module="client"
so the table employee_permissions should have these rows
id | empid | permid | permvalue
1 | 1 | 1 | 1
2 | 1 | 2 | 1
3 | 1 | 3 | 1
1 | 6 | 1 | 1
2 | 6 | 2 | 1
3 | 6 | 3 | 1
This is the query I tried and I'm stuck here
INSERT INTO at2_permission_employee (employee_id,permission_id)
SELECT at2_employee.employee_id as employee_id
, (SELECT at2_permission.permission_id as permission_id
FROM at2_permission
where at2_permission.permission_module='client'
)
from at2_employee
where at2_employee.employee_status='Active'
and at2_employee.employees_served_admin = 1;
I get the error sub query returns multiple rows which makes sense to me. But I'm not sure how to modify the query to account for iterating over the rows returned by sub query
If I'm not wrong, like this:
INSERT INTO at2_permission_employee (employee_id, permission_id, permvalue)
SELECT
at2_employee.employee_id,
at2_permission.permission_id,
1
FROM at2_permission cross join at2_employee
WHERE
at2_employee.employee_status='Active'
and at2_employee.employees_served_admin = 1
and at2_permission.permission_module='client';
It's a bit unclear where the value for permvalue should come from so I hard coded it and used the permission.id for both id and permid, but this query should give you an idea on how to accomplish what you want:
insert employee_permissions (id, empid, permid, permvalue)
select p.id, e.id, p.id, 1
from employee e, permissions p
where p.module = 'client' and e.status = 'act' and e.somestatus = 1;

LIMIT results to n unique column values?

I have some MySQL results like this:
---------------------------
| name | something_random |
---------------------------
| john | ekjalsdjalfjkldd |
| alex | akjsldfjaekallee |
| alex | jkjlkjslakjfjflj |
| alex | kajslejajejjaddd |
| bob | ekakdie33kkd93ld |
| bob | 33kd993kakakl3ll |
| paul | 3k309dki595k3lkd |
| paul | 3k399kkfkg93lk3l |
etc...
This goes on for 1000's of rows of results. I need to limit the number of results to the first 50 unique names. I think there is a simple solution to this but I'm not sure.
I've tried using derived tables and variables but can't quite get there. If I could figure out how to increment a variable once every time a name is different I think I could say WHERE variable <= 50.
UPDATED
I've tried the Inner Join approach(es) suggested below. The problem is this:
The subselect SELECT DISTINCT name FROM testTable LIMIT 50 grabs the first 50 distinct names. Perhaps I wasn't clear enough in my original post, but this limits my query too much. In my query, not every name in the table is returned in the result. Let me modify my original example:
----------------------------------
| id | name | something_random |
----------------------------------
| 1 | john | ekjalsdjalfjkldd |
| 4 | alex | akjsldfjaekallee |
| 4 | alex | jkjlkjslakjfjflj |
| 4 | alex | kajslejajejjaddd |
| 6 | bob | ekakdie33kkd93ld |
| 6 | bob | 33kd993kakakl3ll |
| 12 | paul | 3k309dki595k3lkd |
| 12 | paul | 3k399kkfkg93lk3l |
etc...
So I added in some id numbers here. These ID numbers pertain to the people's names in the tables. So you can see in the results, not every single person/name in the table is necessarily in the result (due to some WHERE condition). So the 50th distinct name in the list will always have an ID number higher than 49. The 50th person could be id 79, 234, 4954 etc...
So back to the problem. The subselect SELECT DISTINCT name FROM testTable LIMIT 50 selects the first 50 names in the table. That means that my search results will be limited to names that have ID <=50, which is too constricting. If there are certain names that don't show up in the query (due to some WHERE condition), then they are still counted as one of the 50 distinct names. So you end up with too few results.
UPDATE 2
To #trapper: This is a basic simplification of what my query looks like:
SELECT
t1.id,
t1.name,
t2.details
FROM t1
LEFT JOIN t2 ON t1.id = t2.some_id
INNER JOIN
(SELECT DISTINCT name FROM t1 ORDER BY id LIMIT 0,50) s ON s.name = t1.name
WHERE
SOME CONDITIONS
ORDER BY
t1.id,
t1.name
And my results look like this:
----------------------------------
| id | name | details |
----------------------------------
| 1 | john | ekjalsdjalfjkldd |
| 3 | alex | akjsldfjaekallee |
| 3 | alex | jkjlkjslakjfjflj |
| 4 | alex | kajslejajejjaddd |
| 6 | bob | ekakdie33kkd93ld |
| 6 | bob | 33kd993kakakl3ll |
| 12 | paul | 3k309dki595k3lkd |
| 12 | paul | 3k399kkfkg93lk3l |
...
| 37 | bill | kajslejajejjaddd |
| 37 | bill | ekakdie33kkd93ld |
| 41 | matt | 33kd993kakakl3ll |
| 50 | jake | 3k309dki595k3lkd |
| 50 | jake | 3k399kkfkg93lk3l |
----------------------------------
The results stop at id=50. There are NOT 50 distinct names in the list. There are only roughly 23 distinct names.
My MySql syntax may be rusty, but the idea is to use a query to select the top 50 distinct names, then do a self-join on name and select the name and other information from the join.
select a.name, b.something_random
from Table b
inner join (select distinct name from Table order by RAND() limit 0,50) a
on a.name = b.name
SELECT DISTINCT name FROM table LIMIT 0,50
Edited: Ahh yes I misread question first time, this should do the trick though :)
SELECT a.name, b.something_random
FROM `table` b
INNER JOIN (SELECT DISTINCT name FROM `table` ORDER BY RAND() LIMIT 0,50) a
ON a.name = b.name ORDER BY a.name
How this work is the (SELECT DISTINCT name FROMtableORDER BY RAND() LIMIT 0,50) part is what pulls out the names to include in the join. So here I am taking 50 unique names at random, but you can change this to any other selection criteria if you want.
Then you join those results back into your table. This links each of those 50 selected names back to all of the rows with a matching name for your final results. Finally ORDER BY a.name just to be sure all the rows for each name end up grouped together.
This should do it:
SELECT tA.*
FROM
testTable tA
INNER JOIN
(SELECT distinct name FROM testTable LIMIT 50) tB ON tA.name = tB.name
;