A query to find nearest distance trainers - mysql

Table Name: course_trainer_combination
Create Table Query:
create table `course_trainer_combination` (
`id` double ,
`course` varchar (150),
`trainer` varchar (150),
`distance` float
);
insert into `course_trainer_combination` (`id`, `course`, `trainer`, `distance`) values('1','Course A','Trainer A','110.00');
insert into `course_trainer_combination` (`id`, `course`, `trainer`, `distance`) values('2','Course A','Trainer B','105.00');
insert into `course_trainer_combination` (`id`, `course`, `trainer`, `distance`) values('3','Course A','Trainer C','115.00');
insert into `course_trainer_combination` (`id`, `course`, `trainer`, `distance`) values('4','Course B','Trainer A','112.00');
insert into `course_trainer_combination` (`id`, `course`, `trainer`, `distance`) values('5','Course B ','Trainer B','108.00');
insert into `course_trainer_combination` (`id`, `course`, `trainer`, `distance`) values('6','Course B','Trainer C','109.00');
insert into `course_trainer_combination` (`id`, `course`, `trainer`, `distance`) values('7','Course C','Trainer A','124.00');
insert into `course_trainer_combination` (`id`, `course`, `trainer`, `distance`) values('8','Course C','Trainer B','128.00');
insert into `course_trainer_combination` (`id`, `course`, `trainer`, `distance`) values('9','Course C','Trainer C','121.00');
My table is like
Expected result:
2 | Course A | Trainer B | 105
6 | Course B | Trainer C | 109
7 | Course C | Trainer A | 124

You can use correlated subquery
select ct.*
from course_trainer_combination ct
where ct.distance = (select min(ct1.distance)
from course_trainer_combination ct1
where ct1.course = ct.course
);
If you are using newer version then you can use analytical function :
select ct.*
from (select ct.*,
rank() over (partition by ct.course order by ct.distance) as seq
from course_trainer_combination ct
) ct
where ct.seq = 1;

Another approach can be like following.
select id,
m.course,
trainer,
m.distance
from course_trainer_combination m
inner join (select course,
Min(distance) distance
from course_trainer_combination
group by course)t
on t.course = m.course
and t.distance = m.distance
Note: This will show two records if two trainer have same distance for 1 course.

One way to solve this is by:
keeping track of the trainers assigned to each course and
selecting a row with the below conditions:
the trainer was not already assigned to a course
no other row of the same course, with a trainer not yet selected, exists
Use a mysql user variable to keep track of the trainers already selected and use FIND_IN_SET() to check weather the trainer has already assigned to a course:
SET #selectedTrainers :='';
SELECT *, #selectedTrainers := CONCAT(#selectedTrainers, ',', a.trainer) AS selectedTrainers
FROM `course_trainer_combination` a
where NOT EXISTS (
SELECT 1
FROM `course_trainer_combination` b
WHERE b.course = a.course
AND a.distance > b.distance
AND FIND_IN_SET(b.trainer, #selectedTrainers) = 0
)
AND FIND_IN_SET(a.trainer, #selectedTrainers) = 0

Related

Update duplicate records with incremental column

-- create
CREATE TABLE employee
(
emp_id INTEGER,
job_code TEXT,
cnt_check int
);
-- insert
INSERT INTO employee(emp_id,job_code) VALUES (0001, 'JC001');
INSERT INTO employee(emp_id,job_code) VALUES (0001, 'JC001');
INSERT INTO employee(emp_id,job_code) VALUES (0002, 'JC002');
INSERT INTO employee(emp_id,job_code) VALUES (0002, 'JC002');
INSERT INTO employee(emp_id,job_code) VALUES (0003, 'JC003');
INSERT INTO employee(emp_id,job_code) VALUES (0004, 'JC004');
INSERT INTO employee(emp_id,job_code) VALUES (0004, 'JC004');
INSERT INTO employee(emp_id,job_code) VALUES (0004, 'JC004');
Expected Output:
emp_id job_code cnt_check
--------------------------------
0001 JC001 1
0001 JC001 2
0002 JC002 1
0002 JC002 2
0003 JC003 1
0004 JC004 1
0004 JC004 2
0004 JC004 3
My try:
update employee t1
join
(
select emp_id ,job_code ,row_number() over(partition by emp_id ,job_code ) rnk
from employee
) t2
on t1.emp_id = t2.emp_id and t1.job_code = t2.job_code
set t1.cnt_check = t2.rnk;
But all records getting updated with value 1.
Demo link
Using the exact columns in your sample data, we can try:
SELECT emp_id, job_code,
ROW_NUMBER() OVER (PARTITION BY emp_id ORDER BY emp_id) AS cnt_check
FROM employee
ORDER BY 1, 3;
Note that I suggest not doing this update, as every time your data changes you might have to run the update again.
You may use generation which uses BEFORE INSERT trigger and additional table:
CREATE TABLE autoinc_helper (
emp_id INT,
cnt_check INT AUTO_INCREMENT,
PRIMARY KEY (emp_id, cnt_check)
) ENGINE = MyISAM;
CREATE TRIGGER generate_autoinc
BEFORE INSERT ON employee
FOR EACH ROW
BEGIN
DECLARE autoinc INT;
INSERT INTO autoinc_helper (emp_id) VALUES (NEW.emp_id);
SET autoinc = LAST_INSERT_ID();
SET NEW.cnt_check = autoinc;
DELETE FROM autoinc_helper WHERE emp_id = NEW.emp_id AND cnt_check < autoinc;
END
DEMO fiddle

Mysql8 join and count unique real appearances

I have the following talbes:
CREATE TABLE topics (
id INT,
text VARCHAR(100),
parent VARCHAR(1)
);
CREATE TABLE sentiment (
id INT,
grade INT,
parent VARCHAR(1)
);
And the following data:
INSERT INTO topics (id, text, parent) VALUES (1, 'Cryptocurrency', 'A');
INSERT INTO topics (id, text, parent) VALUES (2, 'Cryptocurrency', 'B');
INSERT INTO topics (id, text, parent) VALUES (2, 'ETH', 'B');
INSERT INTO sentiment (id, grade, parent) VALUES (2, 0 , 'A');
INSERT INTO sentiment (id, grade, parent) VALUES (2, 1 , 'A');
INSERT INTO sentiment (id, grade, parent) VALUES (2, 1 , 'A');
INSERT INTO sentiment (id, grade, parent) VALUES (2, 1 , 'A');
INSERT INTO sentiment (id, grade, parent) VALUES (2, 0 , 'B');
INSERT INTO sentiment (id, grade, parent) VALUES (2, 1 , 'B');
I want to select count of each topics.text and shared parent sum of sentiment.grade.
So I came up with the following query:
SELECT
count(topics.text),
topics.text,
sum(sentiment.grade)
FROM topics
inner join sentiment on (sentiment.parent = topics.parent)
group by text
The result:
| count(topics.text) | sum(sentiment.grade) | text |
| ------------------ | -------------------- | -------------- |
| 6 | 4 | Cryptocurrency |
| 2 | 1 | ETH |
---
I only have a problem with the first column, the real count of Cryptocurrency is 2 and the real count of ETH is 1.
Can you fix this query?
(I'm using mysql8, would be glad to have 5.7 compliant if possible)
View on DB Fiddle
SELECT
count(distinct t.id),
t.text,
sum(s.grade)
FROM topics t
JOIN sentiment s on s.parent = t.parent
GROUP BY t.text
As you have two rows with text=cryptocurrency in topics, one with parent=A and the other with parent=B, when you join you should expect to see 6 rows for crpytocurrency(the first row of topics matches the first four of sentiment, and the second row of topics matches the last two of sentiment). You can see that if you change your original query to this one:
SELECT
*
FROM topics
inner join sentiment on (sentiment.parent = topics.parent)
I guess you want to see the number of topics with the same text and the total grades their parents have (for cryptocurrency, the sum of A and B). This could help you:
SELECT
topics_count.n_text,
topics.text,
SUM(sentiment.grade)
FROM topics
INNER JOIN (SELECT text, count(*) 'n_text' FROM topics GROUP BY text) topics_count ON topics.text = topics_count.text
INNER JOIN sentiment ON (sentiment.parent = topics.parent)
GROUP BY text

How do you insert a new record or update an existing if id already exists with inner join?

The goal is to insert a new record or update an existing if the id column exists. The ON DUPLICATE KEY UPDATE function seems to be a good fit but the problem I have is I want to make sure that the ID matches an ID in another table. This is for permissions. An example is:
INSERT INTO `testtable` (`id`, `user_id`, `order`, `active`)
VALUES
(1, 24, 3, 0),
(2, 24, 1, 1),
(NULL, 24, 2, 0) AS newupdate
ON DUPLICATE KEY UPDATE
order = newupdate.order,
active = newupdate.active
This works but I want to add a check where the testtable.user_id from above matches users.id from another table. I've been using this SELECT statement:
SELECT testtable.id, testtable.order, testtable.active
FROM testtable
INNER JOIN users on testtable.user_id = users.id
WHERE users.username = bob
So I can't figure out how to utilize the ON DUPLICATE KEY UPDATE with an INNER JOIN or perhaps I'm going about this all the wrong way. I search Stackoverflow and saw some suggestions to use a SELECT statement with the ON DUPLICATE KEY UPDATE but I don't understand how that passes the values. Anyway, is this possible? Thanks in advance.
UPDATE: I was able to get it working but it seems like there should be an easier/better way? Anyone have any other suggestions? This example below I'm inserting a row with id 24 which exists so the update runs and then I'm inserting a row with id NULL so it gets inserted as is.
INSERT INTO `testtable` (`id`, `user_id`, `order`, `active`)
SELECT `id`, `user_id`, `order`, `active`
FROM (SELECT
24 AS id,
(SELECT user_id FROM testtable INNER JOIN users on testtable.user_id = users.id WHERE users.username = 'bob' LIMIT 1) AS user_id,
6 AS order,
1 AS active
UNION ALL
SELECT
NULL AS id,
(SELECT user_id FROM testtable INNER JOIN users on testtable.user_id = users.id WHERE users.username = 'bob' LIMIT 1) AS user_id,
7 AS order,
1 AS active
)
AS newtable
ON DUPLICATE KEY UPDATE
order = newtable.order,
active = newtable.active
Here we have a foreign key to users and a trigger to block changing user_id.
create table users(
id int primary key,
user varchar(25));
insert into users values
(12,'Andrew'),
(24,'Bernard');
✓
✓
create table testtable (
id int ,
user_id int unique,
order_ int,
active int,
foreign key fk_user_id (user_id)
references users(id));
✓
INSERT INTO `testtable` (`id`, `user_id`, `order_`, `active`)
VALUES
(1, 24, 3, 0),
(2, 24, 1, 1),
(NULL, 24, 2, 0) AS newupdate
ON DUPLICATE KEY UPDATE
order_ = newupdate.order_,
active = newupdate.active
✓
/*delimiter $$ */
create trigger user_id_update
before update on testtable
for each row
begin
if (old.user_id <>new.user_id) then
signal SQLSTATE VALUE '45000'
SET MESSAGE_TEXT = 'user_id change forbidden';
end if ;
end ;
/* delimiter ; */
✓
✓
select * from testtable;
id | user_id | order_ | active
-: | ------: | -----: | -----:
1 | 24 | 2 | 0
update testtable
set user_id = 12 where user_id = 24;
user_id change forbidden
select * from testtable;
id | user_id | order_ | active
-: | ------: | -----: | -----:
1 | 24 | 2 | 0
db<>fiddle here

Change the column value if it fits another table

Hello to every poeple of stackoverflow community
i want to ask something to you
The type i don't know how can i compare it and to what
CREATE TABLE spawnhist (
`npctempladeld` INTEGER,
`count` INTEGER
);
INSERT INTO spawnhist
(`npctempladeld`, `count`)
VALUES
('100', '1'),
('200', '1'),
('300', '1');
CREATE TABLE npc (
`npcid` INTEGER,
`type` VARCHAR(9)
);
INSERT INTO npc
(`npcid`, `type`)
VALUES
('100', 'L2Monster'),
('200', 'L2NPC'),
('300', 'L2PET');
Update spawnhist SET `count` = `count` +1
WHERE npctempladeld IN (SELECT `npcid` FROM npc WHERE type = 'L2Monster')
SELECT * FROM spawnhist
npctempladeld | count
------------: | ----:
100 | 2
200 | 1
300 | 1
db<>fiddle here
Something like, ( what I understood at first glance)
My code is based on oracle but I believe it works the same in MySQL. Please try.
Update spawnlist sl
Set count = 2
Where exists (select 1
from npc nc
where nc.npc_id = sl.npc_templadeId
and nc.type = 'L2Monster')

How to JOIN multiple tables and return results with NULL values too

I'm trying to join 4 tables and one of the tables doesn't have all the matching ID's BUT I still need to show the results of the join and even for the rows that didn't have a corresponding ID.
Here's an example of what I'm talking about:
Example tables:
DECLARE #Table1 TABLE (id INT PRIMARY KEY CLUSTERED, ts DateTime, tbl2_id INT, price DECIMAL(4,2), tbl3_id INT, tbl4_id INT)
INSERT INTO #Table1 VALUES(1, '2013-07-25 09:30:00', 10, 10.25, 1);
INSERT INTO #Table1 VALUES(2, '2013-07-25 10:25:00', 20, 25.25, 1);
INSERT INTO #Table1 VALUES(3, '2013-07-25 11:45:00', 30, 30.15, 2);
INSERT INTO #Table1 VALUES(4, '2013-07-25 13:31:00', 40, 80.40, 2);
DECLARE #Table2 TABLE (id INT PRIMARY KEY CLUSTERED, symbol VARCHAR(25), tbl1_id int)
INSERT INTO #Table2 VALUES(10, 'XYZ', 1);
INSERT INTO #Table2 VALUES(20, 'ABC', 2);
INSERT INTO #Table2 VALUES(30, 'RST', 3);
INSERT INTO #Table2 VALUES(40, 'EFG', 4);
DECLARE #Table3 TABLE (id INT PRIMARY KEY CLUSTERED, exch VARCHAR(25))
INSERT INTO #Table3 VALUES(1, 'A');
INSERT INTO #Table3 VALUES(2, 'B');
INSERT INTO #Table3 VALUES(3, 'C');
INSERT INTO #Table3 VALUES(4, 'D');
DECLARE #Table4 TABLE (id INT PRIMARY KEY CLUSTERED, int tbl1_id, cnt INT)
INSERT INTO #Table4 VALUES(1, 2, 19);
INSERT INTO #Table4 VALUES(2, 4, 2013);
Example Query:
SELECT tbl1.id, tbl1.ts, tbl2.symbol, IFNULL(tbl3.cnt,0) AS cnt
FROM TABLE1 tbl1
JOIN TABLE2 tbl2
ON tbl1.tbl2_id = tbl2.id
JOIN TABLE3 tbl3
ON tbl3.id = tbl1.tbl3_id
LEFT OUTER JOIN TABLE4 tbl4
ON tbl1.tbl4_id = tbl4.id
WHERE tbl1.ts BETWEEN '2013-07-25 09:30:00 AND '2013-07-25 16:00:00'
AND tbl1.price >= 15.00
LIMIT 1000;
So basically what I'm trying to do is if tbl4 doesn't have a tbl1_id I'd still want to see the result from table1 BUT show a 0 value for Cnt...when i run this query I'm getting a bunch of duplicate entries and the data isn't looking right.
You are only returning the one with matches from the left hand side of the join. Change the join with table 4 to right join and it will return all the records from the right regardless. I know that was an example query that doesn't work with fake data. But I have to work with what you give me. This probably wont work through copy and paste, but the theory is correct, you just gotta modify it. If you give more detailed information I will tailor it to that.
SELECT tbl1.id, tbl1.ts, tbl2.symbol, IFNULL(tbl3.cnt,0) AS cnt
FROM TABLE1 tbl1
JOIN TABLE2 tbl2
ON tbl1.tbl2_id = tbl2.id
JOIN TABLE3 tbl3
ON tbl3.id = tbl1.tbl3_id
RIGHT JOIN TABLE4 tbl4
ON tbl1.tbl4_id = tbl4.id
WHERE tbl1.ts BETWEEN '2013-07-25 09:30:00 AND '2013-07-25 16:00:00'
AND tbl1.price >= 15.00
LIMIT 1000;
First of all In your sample code you gave a mix of SQL Server and MySql syntax:
DECLARE TABLE, CLUSTERED are SQL Server features
IFNULL(), LIMIT are MySql's
Secondly cnt column is in Table4 not in Table3 so IFNULL(tbl3.cnt,0) is not going to fly.
Thirdly your last edit when you changed tbl1.id = tbl4.tbl1_id to tbl1.tbl4_id = tbl4.id doesn't add up with the rest of your sample data since you don't have values in your insert statements either for tbl3_id or for newly introduced tbl4_id. And tbl1_id column now seems to be unnecessary.
Therefore IMHO you need to revisit and correct your sample data. And posting desired output might also help you to get your answer.
Now here is a query that works and based on your data schema before your last edit. It will successfully work both in MySql and SQL Server except for the LIMIT part
SELECT t1.id, t1.ts, t2.symbol, COALESCE(t4.cnt, 0) cnt
FROM Table1 t1 JOIN Table2 t2
ON t1.id = t2.tbl1_id JOIN Table3 t3
ON t1.tbl3_id = t3.id LEFT JOIN Table4 t4
ON t1.id = t4.tbl1_id
WHERE t1.ts BETWEEN '2013-07-25 09:30:00' AND '2013-07-25 16:00:00'
AND t1.price >= 15.00
LIMIT 1000 -- LIMIT will work only in MySql
Output:
| ID | TS | SYMBOL | CNT |
----------------------------------------------------
| 2 | July, 25 2013 10:25:00+0000 | ABC | 19 |
| 3 | July, 25 2013 11:45:00+0000 | RST | 0 |
| 4 | July, 25 2013 13:31:00+0000 | EFG | 2013 |
Here is SQLFiddle (MySql) demo
Here is SQLFiddle (SQL Server) demo