Commonality in many to many relationship SQL query - mysql

If I have agents that can have many properties, and vice versa, defined in a in a junction table, how would I query the top agents (an agent who has at least two properties in common with two other agents.)
agent
id
1
2
3
4
5
properties
id
1
2
3
4
5
6
agent_properties
agent_id
property_id
1
1
1
2
1
3
2
2
2
3
3
1
3
3
3
5
4
3
4
4
4
6
5
1
5
2
5
5
6
4
6
6
Example:
Agent 1 (propr1, propr2, propr3),
Agent 2 (propr2, propr3),
Agent 3 (propr1, propr3, propr5)
Agent 4 (propr3, propr4, propr6)
Agent 5 (propr1, propr2, propr5)
Agent 6 (propr4, propr6)
So the query should return Agent 1, Agent 3, Agent 5
If anyone can show how to do this using Laravel Eloquent that would be great.

A solution would be to work with bits.
First you should add a new column in table properties and this new column to be named bin_value. The values from this column must be unique per property and to be power of 2. In your case would be:
id
bin_value
1
1
2
2
3
4
4
8
5
16
6
32
Second, add a new column to agent table named properties and run following update query:
UPDATE agent AS a
INNER JOIN (
SELECT ap.agent_id, SUM(p.bin_value) AS bin_properties
FROM agent_properties AS ap
INNER JOIN properties AS p ON p.id = ap.property_id
GROUP BY ap.agent_id
) AS b ON a.id = b.agent_id
SET a.properties = b.bin_properties
;
Third, run the query with bit function which helps you to find agents which have at least 2 shared properties with at least 2 other agents:
SELECT a1.agent_id
, a1.name
, COUNT(*) AS total_other_agents
FROM agent AS a1
INNER JOIN agent AS a2 ON a1.id != a2.id
AND BIT_COUNT(a1.properties & a2.properties) >= 2 -- at least 2 shared properties
GROUP BY a1.id
HAVING total_other_agents >= 2 -- at least 2 other agents
ORDER BY total_other_agents DESC, a1.name
;
If you have a large number of properties try to set values in bin_value like this:
UPDATE properties
SET bin_value = BINARY(POWER(2, id-1))
;

Related

Find the lowest value of every row of selected columns

As the result of the query, I want to get all rows (Drivers), order by the drivers who got most series wins.
If a driver has won 4 tacks at least one or more times but failed to win the remaining track at least once, his series count is 0.
Driver Table
ID|Name| .........
1 A
2 B
3 C
4 D
Tracks Table
TID |FK|Track1_Wins|Track2_Wins| Track3_Wins|Track4_Wins|Track5_Wins|
1 1 5 6 3 2 4
2 2 2 4 0 5 3
3 3 6 3 9 4 7
4 4 5 8 2 4 1
My code sample
SELECT `Drivers`.`Name`, LEAST(`Track1_Wins`, `Track2_Wins`, `Track3_Wins`, `Track4_Wins`, `TRACK5_Wins`) AS Series
FROM `Drivers`, `Tracks`
ORDER BY Series DESC;
Accidently I got part expected output when I use WHERE with Driver ID
SELECT `Drivers`.`Name`, LEAST(`Track1_Wins`, `Track2_Wins`, `Track3_Wins`, `Track4_Wins`, `TRACK5_Wins`) AS Series FROM `Drivers`, `Tracks` WHERE `Drivers`.`ID` = 2 ORDER BY Series DESC;
It will give the expected result but with Same Driver Name as expected
B 3
B 2
B 1
B 0
My expected output is
Name | Series
C 3
A 2
D 1
B 0
Run this,
SELECT d.`Name`,
LEAST(`Track1_Wins`, `Track2_Wins`, `Track3_Wins`, `Track4_Wins`, `TRACK5_Wins`) AS Series
FROM `Drivers` d INNER JOIN `Tracks` t
ON t.`FK` = d.`ID`
ORDER BY Series DESC;
This returns the user name associated with the FK. Also, try to use kebab_case and lower case for all your column and table name. Makes it much easier to run the code

SQL - Max value from a group by when creating a new field

I have a database with a table called BOOKINGS containing the following values
main-id place-id start-date end-date
1 1 2018-8-1 2018-8-8
2 2 2018-6-6 2018-6-9
3 3 2018-5-5 2018-5-8
4 4 2018-4-4 2018-4-5
5 5 2018-3-3 2018-3-10
5 1 2018-1-1 2018-1-6
4 2 2018-2-1 2018-2-10
3 3 2018-3-1 2018-3-28
2 4 2018-4-1 2018-4-6
1 5 2018-5-1 2018-5-15
1 3 2018-6-1 2018-8-8
1 4 2018-7-1 2018-7-6
1 1 2018-8-1 2018-8-18
1 2 2018-9-1 2018-9-3
1 5 2018-10-1 2018-10-6
2 5 2018-11-1 2018-11-5
2 3 2018-12-1 2018-12-25
2 2 2018-2-2 2018-2-19
2 4 2018-4-4 2018-4-9
2 1 2018-5-5 2018-5-23
What I need to do is for each main-id I need to find the largest total number of days for every place-id. Basically, I need to determine where each main-id has spend the most time.
This information must then be put into a view, so unfortunately I can't use temporary tables.
The query that gets me the closest is
CREATE VIEW `MOSTTIME` (`main-id`,`place-id`,`total`) AS
SELECT `BOOKINGS`.`main-id`, `BOOKINGS`.`place-id`, SUM(DATEDIFF(`end-date`, `begin-date`)) AS `total`
FROM `BOOKINGS`
GROUP BY `BOOKINGS`.`main-id`,`RESERVATION`.`place-id`
Which yields:
main-id place-id total
1 1 24
1 2 18
1 5 5
2 1 2
2 2 20
2 4 9
3 1 68
3 2 24
3 3 30
4 1 5
4 2 10
4 4 1
5 1 19
5 2 4
5 5 7
What I need is then the max total for each distinct main-id:
main-id place-id total
1 1 24
2 2 20
3 1 68
4 2 10
5 1 19
I've dug through a large amount of similar posts that recommend things like self joins; however, due to the fact that I have to create the new field total using an aggregate function (SUM) and another function (DATEDIFF) rather than just querying an existing field, my attempts at implementing those solutions have been unsuccessful.
I am hoping that my query that got me close will only require a small modification to get the correct solution.
Having hyphen character - in column name (which is also minus operator) is a really bad idea. Do consider replacing it with underscore character _.
One possible way is to use Derived Tables. One Derived Table is used to determine the total on a group of main id and place id. Another Derived Table is used to get maximum value out of them based on main id. We can then join back to get only the row corresponding to the maximum value.
CREATE VIEW `MOSTTIME` (`main-id`,`place-id`,`total`) AS
SELECT b1.main_id, b1.place_id, b1.total
FROM
(
SELECT `main-id` AS main_id,
`place-id` AS place_id,
SUM(DATEDIFF(`end-date`, `begin-date`)) AS total
FROM BOOKINGS
GROUP BY main_id, place_id
) AS b1
JOIN
(
SELECT dt.main_id, MAX(dt.total) AS max_total
FROM
(
SELECT `main-id` AS main_id,
`place-id` AS place_id,
SUM(DATEDIFF(`end-date`, `begin-date`)) AS total
FROM BOOKINGS
GROUP BY main_id, place_id
) AS dt
GROUP BY dt.main_id
) AS b2
ON b1.main_id = b2.main_id AND
b1.total = b2.max_total
MySQL 8+ solution would be utilizing the Row_Number() functionality:
CREATE VIEW `MOSTTIME` (`main-id`,`place-id`,`total`) AS
SELECT b.main_id, b.place_id, b.total
FROM
(
SELECT dt.main_id,
dt.place_id,
dt.total
ROW_NUMBER() OVER (PARTITION BY dt.main_id
ORDER BY dt.total DESC) AS row_num
FROM
(
SELECT `main-id` AS main_id,
`place-id` AS place_id,
SUM(DATEDIFF(`end-date`, `begin-date`)) AS total
FROM BOOKINGS
GROUP BY main_id, place_id
) AS dt
GROUP BY dt.main_id
) AS b
WHERE b.row_num = 1

Mysql select statement contains where clause so unsuitable for insert into

I'm very inexperienced. I've prepared a select statement which gives the information I need to populate a matches table. However it is not suitable because it contains a where clause. Is there a different way to use it, or how can I change it so that it is suitable for INSERT INTO.
The tables are as follows:-
match_order
match_order_id||match_descrip||first_player||second_player
1 1v2 1 2
2 1v3 1 3
3 2v3 2 3
4 1v4 1 4
5 2v4 2 4
6 3v4 3 4
entries
entry_id||round_id||league_id||box_id||box_position
1 1 1 1 1
2 1 1 1 2
3 1 1 1 3
4 1 2 1 4
5 1 2 1 2
6 1 2 1 1
7 1 2 1 1
matches
match_id||round_id||league_id||box_id||match_order_id||player1||player2
I need to insert new rows every month for a new round of matches. League size, box size & positions change each month.
This is the statement which gives the correct rows.
SELECT e.round_id, e.league_id, e.box_id, mo.match_order_id, e.entry_id as player1, e1.entry_id as player2
FROM match_order mo
LEFT JOIN entries e ON mo.first_player = e.box_position
LEFT JOIN entries e1 ON mo.second_player = e1.box_position
WHERE e.round_id = e1.round_id AND e.league_id = e1.league_id AND e.box_id = e1.box_id
ORDER BY round_id, league_id, box_id, match_order_id
Any help & advise would be greatly appreciated.
Thank you
Assuming match_id is an auto-increment column, you have the data for the other columns. You can just add the INSERT statement before your SELECT.
INSERT INTO matches(round_id, leage_id, box_id, match_order_id, player1, player2)
SELECT e.round_id, e.league_id, e.box_id, mo.match_order_id, e.entry_id as player1, e1.entry_id as player2
FROM match_order mo
LEFT JOIN entries e ON mo.first_player = e.box_position
LEFT JOIN entries e1 ON mo.second_player = e1.box_position
WHERE e.round_id = e1.round_id AND e.league_id = e1.league_id AND e.box_id = e1.box_id

MySQL VIEW with morph type in where statement

I have the following tables:
Apps
id name
1 a
2 b
3 c
Parts
id name app_id
1 x 1
2 x 2
3 y 2
4 z 1
5 z 2
6 z 3
Settings
id name morph_id morph_type
1 setting 1 1 App
2 setting 1 2 App
3 setting 2 1 Part
4 setting 2 2 Part
5 setting 3 3 Part
6 setting 4 3 App
7 setting 5 5 Part
This means that App 1 (A), has Part 1 and 4 (X/Z), and Setting 1 and 2 (1 belongs to the app, 2 belongs to part 1).
I would like to generate a view, that displays this situation:
SettingsView
app_id setting_type setting_name // app_name part_id part_name
1 App setting 1 // a null null
1 Part setting 2 // a 1 x
2 App setting 1 // b null null
2 Part setting 2 // b 2 x
2 Part setting 5 // b 5 z
3 App setting 4 // c null null
3 Part setting 3 // c 3 z
I however have no idea on how to accomplish this and whether it is possible? The two columns after // would be nice if possible, as it would be useful to see to which app/part (name) a setting belongs.
Kind regards,
Tjab
Another edit to explain a bit more:
SELECT a.id, s.name
FROM settings s JOIN app a ON a.id = s.morph_id
WHERE s.morph_type = 'App' #IMPORTANT
Shows all app settings. This I need to combine with the query:
SELECT p.app_id, s.name
FROM settings s JOIN part p ON p.id = s.morph_id
WHERE s.morph_type = 'Part' #IMPORTANT
You might try this:
SELECT
a.`id` as `app_id`,
b.`morph_type` as `setting_type`,
b.`name` as `setting_name`,
a.`name` as `app_name`,
c.`id` as `part_id`,
c.`name` as `part_name`
FROM `Apps` a
LEFT JOIN `Settings` b
ON a.`morph_id` = a.`id`
LEFT JOIN `Parts` c
ON c.`app_id` = a.`id`
ORDER BY a.`name`,b.`name`
You may not need the LEFT JOIN on the Settings table. If you're getting nulls in the setting_name column, change it to a JOIN instead.

Mysql tree table join and count Rows

I have these three tables below. I want to join them using a MySQL query. Below the tables I've provided also how my output should look like.
GameTable:
GameID GameName
1 NvsA
2 NvsB
3 DvsA
4 NvsE
PlayerOnGame:
GameID PlayerName PlayerNumber PlayerID
1 Clement 7 10
1 Niyoyita 8 11
2 Clement 8 10
2 David 6 5
PlayerdataTable:
GameID Action PlayerNumber
1 kick 7
1 pass 8
1 Run 7
1 Kick 7
2 Kick 8
2 Pass 6
2 Run 8
2 Run 8
Output for only PlayerName = Clement:
GameName Action ActionCount
NvsA Kick 2
NvsA Run 1
NvsB Kick 1
NvsB Run 2
Please can someone help me with the query to get this output?
You can use this query to count
SELECT
gt.GameName,
pdt.Action,
count(pdt.Action) AS ActionCount
FROM GameTable AS gt
INNER JOIN PlayerdataTable AS pdt ON pdt.GameID = gt.GameID
INNER JOIN PlayerOnGame AS pg ON pg.GameID = pdt.GameID AND pg.PlayerNumber = pdt.PlayerNumber
WHERE pg.PlayerName = 'Clement'
GROUP BY gt.GameID , pdt.Action