MySQL - Tables With Shared Keys - mysql

I have two tables, that look something like this:
Table A
time_unix time_msecs measurementA
1000 329 3.14159
1000 791 9.32821
1001 227 138.3819
1001 599 -15.3289
Table B
time_unix time_msecs measurementB
1000 565 17.2938
1000 791 12348.132
1001 227 -128.3283
1001 293 225.12938
For both tables, I'm using a composite key (made up of time_unix, time_msecs). These measurement data (measurementA and measurementB) are in two different tables, because there are actually many, many columns (many thousands) - too many to keep in a single table.
I want to perform a query such that the result set is simply my keys and a select few columns combined from these two tables. Sometimes the times (keys) line up, sometimes they don't. If they don't, I just would like a null value returned for that column.
Desired Result
time_unix time_msecs measurementA measurementB
1000 329 3.14159 (null)
1000 565 (null) 17.2938
1000 791 9.32821 12348.132
1001 227 138.3819 -128.3283
1001 293 (null) 225.12938
1001 599 -15.3289 (null)
How to achieve this? I don't think JOINs are the way to go. I have otherwise been combining datasets inside Javascript, and it seems terribly cumbersome. There must be a way to do this on the database side.

You want a full outer join between the two tables:
SELECT a.time_unix,
a.time_msecs,
a.measurementA,
b.measurementB
FROM TableA a
LEFT JOIN TableB b
ON a.time_unix = b.time_unix AND
a.time_msecs = b.time_msecs
UNION ALL
SELECT b.time_unix,
b.time_msecs,
a.measurementA,
b.measurementB
FROM TableA a
RIGHT JOIN TableB b
ON a.time_unix = b.time_unix AND
a.time_msecs = b.time_msecs
WHERE a.time_unix IS NULL
ORDER BY 1, 2;

I used union since there are no full joins in MySQL.
select TableA.time_unix
,TableA.time_msecs
,TableA.measurementA
,TableB.measurementB
from TableA
left join TableB on TableA.time_msecs = TableB.time_msecs and
TableA.time_unix = TableB.time_unix
union
select TableB.time_unix
,TableB.time_msecs
,TableA.measurementA
,TableB.measurementB
FROM TableA
right join TableB on TableA.time_msecs = TableB.time_msecs and
TableA.time_unix = TableB.time_unix
order by time_unix
time_unix
time_msecs
measurementA
measurementB
1000
329
3.14159
null
1000
791
9.32821
12348.1
1000
565
null
17.2938
1001
227
138.382
-128.328
1001
599
-15.3289
null
1001
293
null
225.129
Fiddle

Related

MYSQL:Selecting SUM of a column but the column is based of another row ID

I want to have the sum of the beginning inventory of the entire year. The beginning inventory is based of the end_inventory of another month. The beginning_inventory_id contains the ID of another row which points to the end_inventory. How do I properly get the sum of the beginning_inventory of a certain year when it's based of another row's end_inventory. I have the following table
id
time_period
beginning_inventory_id
end_inventory
gross_sales
1
2020-09-01
null
1000
500
2
2020-10-01
1
2000
500
3
2020-11-01
2
3000
500
4
2020-12-01
3
4000
500
5
2021-01-01
4
5000
500
I have the following SQL query
SELECT SUM(a.gross_sales) as gross_sales, SUM(a.end_inventory) as end_inventory,
(SELECT SUM(b.end_inventory) FROM fs_summary as b WHERE a.beginning_inventory_id = b.id) as beginning_inventory
FROM fs_summary as a
WHERE YEAR(a.time_period) = 2020
Output I would like to generate is:
beginning_inventory = 6000
end_inventory = 10000
gross_sales = 2000
Instead, I am getting null on the beginning_inventory.
Any help would be great!
I am Assuming that you want to retrieve data from 1 table with self join.
SELECT SUM(a.gross_sales),SUM(a.end_inventory),SUM(b.end_inventory)
FROM fs_summary a, fs_aummary b
WHERE b.id=a.beginning_inventory_id AND YEAR(a.time_period) = 2020
using self join can help you in this situation
EDIT: You can also write this script as,
SELECT SUM(a.gross_sales),SUM(a.end_inventory),SUM(b.end_inventory)
FROM fs_summary a
INNER JOIN fs_aummary b
ON b.id=a.beginning_inventory_id
WHERE YEAR(a.time_period) = 2020
Using self-join SQL you can achieve your result instead of sub-queries.
You should specify the same table with two different names. Your query looks as below
select sum(virtual_tb.end_inventory) as 'beginning_inventory', sum(org_tb.end_inventory) as 'end_inventory', sum(org_tb.gross_sales) as 'gross_sales'
from fs_summary org_tb left join fs_aummary virtual_tb on (virtual_tb.beginning_inventory_id = org_tb.id)
where year(org_tb.time_period) = 2020;
(Approx Output)
beginning_inventory
end_inventory
gross_sales
6000
10000
2000

Is it possible to write an SQL query that returns the values of something that doesn't exist in another table?

I'm trying to write an SQL query that returns the rows of a table in which certain data of a column does not appear in another table.
For example, let's say I have two tables:
Table 1:
Date Name Room
2020/01/23 John 201
2020/01/22 Rebecca 203
2020/01/22 Ronald 204
2020/01/22 Jimmy 205
Table 2 (does NOT have the same amount of columns):
Date Room
2020/01/22 203
2020/01/23 201
2020/01/22 202
2020/01/22 209
I want to find all the rows in Table 2 in which the room number does NOT show in table 1. Which means my SQL would return
2020/01/22 202
2020/01/22 209
Since room 202 and room 209 does not appear in Table 1.
How would I do this?
Use not exists:
select t2.*
from table2 t2
where not exists (select 1
from table1 t1
where t2.room = t1.room
);

MySQL Select statement with several joins returns duplicated records

I have 4 tables with the following structure:
Table Groups
Groupid
groupname
groupadmin
Table GroupMembership
devid
groupid
Table GroupLocator
devid
name
pass
color
sampling
connected
forget
trace
Table GroupTracker
devid
groupid
latitude
longitude
timestamp
There is only one groupid='1' with groupname="FBorges"
Table GroupLocator has 2 records where devid points to grouid='1' on GroupMembership
GroupTracker has two records where groupid='1'
When I run the following SELECT:
SELECT GroupLocator.name, GroupLocator.color, GroupLocator.sampling,
GroupLocator.forget, GroupLocator.connected, GroupLocator.trace,
Groups.groupname, GroupTracker.latitude, GroupTracker.longitude,
GroupTracker.timestamp
FROM GroupMembership
JOIN GroupLocator ON GroupLocator.devid=GroupMembership.devid
JOIN Groups ON Groups.groupid=GroupMembership.groupid
JOIN GroupTracker ON GroupTracker.groupid=GroupMembership.groupid
WHERE GroupMembership.groupid=1;
I get the result:
name color sampling forget connected trace groupname latitude longitude timestamp
PCBorges 2 1 45 0 1 FBorges -22.883639 -42.822542 2020-01-08 20:29:24
Test 3 2 45 1 0 FBorges -22.883639 -42.822542 2020-01-08 20:29:24
PCBorges 2 1 45 0 1 FBorges -22.873639 -42.322542 2020-01-11 16:56:30
Test 3 2 45 1 0 FBorges -22.873639 -42.322542 2020-01-11 16:56:30
What I hope to get is:
name color sampling forget connected trace groupname latitude longitude timestamp
PCBorges 2 1 45 0 1 FBorges -22.883639 -42.822542 2020-01-08 20:29:24
Test 3 2 45 1 0 FBorges -22.883639 -42.822542 2020-01-08 20:29:24
EDIT: Removed my previous speculation after structure and data was provided and wrote a new answer:
I believe that you want to JOIN GroupTracker on devid instead of on groupid. Groupid 1 matches both rows in the GroupTracker table, so it will provide two results for each 1 row in GroupMemebership. Devid only matches one row. A correct JOIN is more efficient than your current GROUP BY solution (in comments) and may also produce more consistent results as your database grows.
SELECT gl.name, gl.color, gl.sampling,
gl.forget, gl.connected, gl.trace,
g.groupname, gt.latitude, gt.longitude,
gt.timestamp
FROM GroupMembership AS gm
JOIN GroupLocator AS gl ON gm.devid = gl.devid
JOIN Groups AS g ON gm.groupid = g.groupid
JOIN GroupTracker AS gt ON gm.devid = gt.devid
WHERE gm.groupid=1
;
I aliased all your tables so the query is much shorter and hence faster to write. I also swapped positions of all your JOIN clauses. I prefer to have the left table on the left side and the right table on the right side. Makes it easier to read. These two changes are not important. It's only style. The query will work perfectly without them.

MySQL left join returning wrong number of results

hoping someone can help me with this issue. I have a table with the following data:
site_id type key data
2 organic-keywords-last-month (not provided) 9064
2 organic-keywords-last-month bmi 345
2 organic-keywords-last-month bmi kalkulator 445
2 organic-keywords-last-month grove pannekaker 678
2 organic-keywords-last-month grove vafler 976
2 organic-keywords-last-month lapper 475
2 organic-keywords-last-month melk.no 624
2 organic-keywords-last-month ostesuffle 361
2 organic-keywords-last-month scones 697
2 organic-keywords-last-month sunne pannekaker 658
2 organic-keywords-last-month sunne vafler 484
2 organic-keywords-this-month (not provided) 10034
2 organic-keywords-this-month bmi kalkulator 659
2 organic-keywords-this-month grove pannekaker 721
2 organic-keywords-this-month grove vafler 857
2 organic-keywords-this-month lapper 515
2 organic-keywords-this-month melk.no 587
2 organic-keywords-this-month ostesuffle 433
2 organic-keywords-this-month scones 626
2 organic-keywords-this-month smultringer 401
2 organic-keywords-this-month sunne pannekaker 566
2 organic-keywords-this-month sunne vafler 407
What I want to do is query for all the organic-keywords-this-month, and left join them to the organic-keywords-last-month, so that I get a list of the 11 keywords from this month, their performance, and their performance last month.
I am using the following query:
SELECT
ktm.key AS keyword,
ktm.data AS `this month`,
klm.data AS `last month`
FROM
data AS ktm
LEFT JOIN data AS klm ON
(ktm.key = klm.key AND ktm.type = 'organic-keywords-this-month'
AND klm.type = 'organic-keywords-last-month')
WHERE
ktm.site_id = 2
AND klm.site_id = 2
ORDER BY
`this month` ASC
Which I think should return 11 rows, as there are 11 rows of type 'organic-keywords-this-month', yet when I run it it only return 10 rows, and looks more like it is inner joining the tables rather than left join.
Anyone have any ideas?
I think the query is very close... but put your join condition on the types being the same too, and only put the QUALIFIER of the type in the WHERE clause associated with THIS MONTHs data
SELECT
ktm.key AS keyword,
ktm.data AS this_month,
klm.data AS last_month
FROM
data AS ktm
LEFT JOIN data AS klm
ON klm.site_id = ktm.site_id
AND klm.key = ktm.key
AND klm.type = 'organic-keywords-last-month'
WHERE
ktm.site_id = 2
AND ktm.type = 'organic-keywords-this-month'
ORDER BY
this_month ASC
By applying the "AND ktm.type = 'organic...', that will properly qualify WHAT YOU WANT NOW.
However, the LEFT JOIN will also explicitly match based on "AND ktm.type = klm.type" to the clause. Notice I adjusted to similarly apply your "same site ID" as last month too.
I would ensure the data table has an index on (site_id, type, key)
Your WHERE requires a particular value in klm, therefore the query is not returning your 11th line that doesn't even have any klm values.
Take the klm half of the WHERE out and put it in the LEFT JOIN.
Like Paul Gregory says. I think that should be accepted as an answer. I would just add that the site values should be compared inside the join 'on' clause if and only if you want to restrict comparisons of the key values to those from the same site, which it seems you want. In either case, if you want to restrict results to ktm.site = 2 it's ok to have that in either the join on clause, along with a ktm.site = klm.site, or the where clause, just don't put klm.site in the where clause.
If you seperate your table into two tables, one for this-month, one for last-month, you'll notice a slight discrepancy in the key column. The last-month group has a key called 'bmi' which is missing from this-month, and this-month has a key called 'smultringer', which is missing from last-month. Because of the lack of a match, which you specified in klm.key = ktm.key, those rows will be left out of the join, giving you only 10 rows instead of 11.
Edit: I'm not saying to actually seperate the table, I meant to do that mentally to compare the groups.

MySQL SUM outputs wrong value in a query consisting multiple joins

I'm getting information on these tables using the following query, however defenderhit and defenderdamage SUM values are getting multiplied with the row count of first join's row number.
Table 'battles':
battle_id city_id attacker defender battle_time
1 07 6 0 1342918014
Table 'battlehits':
battle_id family_id user_id hits damage
1 0 0 1000 50000
1 6 15 108 3816
1 6 2 81 2046
1 6 1 852 1344
MySQL Query:
SELECT b.battle_id, b.city_id, b.attacker, b.defender, b.battle_time,
SUM(COALESCE(bh1.damage,0)) AS attackerdamage, SUM(COALESCE(bh2.damage,0)) AS defenderdamage,
SUM(COALESCE(bh1.hits,0)) AS attackerhit, SUM(COALESCE(bh2.hits,0)) AS defenderhit
FROM battles AS b
LEFT JOIN battlehits AS bh1 ON b.attacker = bh1.family_id
LEFT JOIN battlehits AS bh2 ON b.defender = bh2.family_id
WHERE b.battle_id=1
GROUP BY b.battle_id LIMIT 1
Result of this query is as following:
battle_id city_id attacker defender battle_time attackerdamage defenderdamage attackerhit defenderhit
1 07 6 0 1342918014 7206 150000 1041 3000
As you can see in the table data, defenderhit and defenderdamage SUM values are supposed to be 1000 and 50000, but they're multiplied by 3.
What am I doing in here? What's the problem?
Thanks in advance.
You are getting three rows, before the group by/sum. You have one row for each of the three attacker rows from battlehits. Each of these is paired with the same defender row from battlehits, causing the defender data to be tripled. To see this, remove the group by and limit clauses and take out the sum()s. You are effectively creating the cross product of all defenders X all attackers, and then summing.
This shows the three rows with duplicated defender data. This is a consequence of doing a join on a one to many to many relationship, instead of a one to one to one.
SELECT b.battle_id, b.city_id, b.attacker, b.defender, b.battle_time,
COALESCE(bh1.damage,0) AS attackerdamage, COALESCE(bh2.damage,0) AS defenderdamage,
COALESCE(bh1.hits,0) AS attackerhit, COALESCE(bh2.hits,0) AS defenderhit
FROM battles AS b
LEFT JOIN battlehits AS bh1 ON b.attacker = bh1.family_id
LEFT JOIN battlehits AS bh2 ON b.defender = bh2.family_id
WHERE b.battle_id=1;
Output:
battle_id city_id attacker defender battle_time attackerdamage defenderdamage attackerhit defenderhit
1 7 6 0 1342918014 3816 50000 108 1000
1 7 6 0 1342918014 2046 50000 81 1000
1 7 6 0 1342918014 1344 50000 852 1000
You need to split this into separate queries. One for the attacker sums and another for the defender sums.
You can emulate a SUM of distinct rows by using
(SUM(t.field_to_sum) / COUNT(t.primary_key) * COUNT(DISTINCT t.primary_key))