Find unmatched records with 2 foreign keys - mysql

I have 2 tables:
table Companies
+----------+-------------+----+
| Id | MainId | Type | Name |
+----+--------+--------+------+
| 1 | | NO | |
| 2 | 1 | Filial | |
| 3 | | NO | |
| 4 | 3 | Filiar | |
+-------------+--------+------+
table Employees
+------------------+-------+-------+
| Id | Name | CompId| FilId |
+------------------+-------+-------+
| 333| Simon | 1 | |
| 444| John | 1 | 2 |
| 555| Andrew | | 4 |
| 777| Dennis | 11 | |
| 888| John | 3 | 10 |
+------------------+-------+-------+
How can I get all employees that work at companies listed in Companies table?
= At least one row (CompId or FilId) is linked to row in Companies table
How can I get all employees that DON'T work at companies listed in Companies table?
= BOTH CompId or FilId columns DON'T exist in Companies table
I have tried the solution but it doesn't perform well...
1st:
SELECT * FROM `Employees`
INNER JOIN `Companies` ON `Employees`.`FilId` = `Companies`.`Id`
OR `Employees`.`FilId` = `Companies`.`Id`
Added results for SHOW CREATE TABLE table_name
Removed unused columns
CREATE TABLE `Employees` (
`Id` varchar(10) NOT NULL,
`Name` varchar(255) NOT NULL
`CompId` varchar(10) NOT NULL,
`FilId` varchar(10) NOT NULL,
PRIMARY KEY (`Id`),
KEY `empl_to_comps` (`VuzId`),
KEY `empl_to_fils` (`FilId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
CREATE TABLE `Companies` (
`Id` varchar(10) NOT NULL DEFAULT '',
`MainId` varchar(10) DEFAULT '',
`Type` varchar(255) NOT NULL,
`Name` varchar(255) NOT NULL
PRIMARY KEY (`Id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

SELECT DISTINCT e.*
FROM Employees e
JOIN Companies c ON c.Id IN (e.CompId, FilId);
SELECT e.*
FROM Employees e
LEFT JOIN Companies c ON c.Id IN (e.CompId, FilId)
WHERE c.Id IS NULL;
Update
Depending on data and indexes the following two queries might be faster. And though verbose, they are more expressive than the JOIN queries.
SELECT e.*
FROM Employees e
WHERE EXISTS (
SELECT *
FROM Companies c
WHERE c.Id = e.CompId
)
OR EXISTS (
SELECT *
FROM Companies c
WHERE c.Id = e.FilId
);
SELECT e.*
FROM Employees e
WHERE NOT EXISTS (
SELECT *
FROM Companies c
WHERE c.Id = e.CompId
)
AND NOT EXISTS (
SELECT *
FROM Companies c
WHERE c.Id = e.FilId
);

Related

MySQL - Find listings posted by multiple people

I have a schema as follows;
CREATE TABLE `vehicle` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`vehiclelicence` varchar(50) DEFAULT NULL,
`userid` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) DEFAULT NULL,
`companyid` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE `company` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
);
insert into vehicle (vehiclelicence, userid) values ('PB1234', 1);
insert into vehicle (vehiclelicence, userid) values ('AB0987', 2);
insert into vehicle (vehiclelicence, userid) values ('UI7654', 3);
insert into vehicle (vehiclelicence, userid) values ('DV8888', 4);
insert into vehicle (vehiclelicence, userid) values ('PB1234', 2);
insert into vehicle (vehiclelicence, userid) values ('UI7654', 1);
insert into user (username, companyid) values ('Bob', 1);
insert into user (username, companyid) values ('Tim', 2);
insert into user (username, companyid) values ('Jim', 3);
insert into user (username, companyid) values ('Fred', 4);
insert into company (name) values ('Company 1');
insert into company (name) values ('Company 2');
insert into company (name) values ('Company 3');
insert into company (name) values ('Company 4');
Users add vehiclelicences all the time.
How can I return all vehiclelicences listed by more than one company and which company listed it?
This is the sql I have so far, but it lists all the records.
SELECT *
FROM vehicle v1
join user u on u.id = v1.userid
join company c on c.id = u.companyid
WHERE EXISTS
(
SELECT 1
FROM vehicle v2
WHERE v1.vehiclelicence = v1.vehiclelicence and v2.userid != v1.userid
LIMIT 1, 1
)
order by v1.vehiclelicence
;
I have created a Fiddle
This is the desired output
+----------------+--------+----------+-----------+-------------+
| vehiclelicence | userid | username | companyid | companyname |
+----------------+--------+----------+-----------+-------------+
| PB1234 | 1 | Bob | 1 | Company 1 |
| PB1234 | 2 | Tim | 2 | Company 2 |
| UI7654 | 3 | Jim | 3 | Company 3 |
| UI7654 | 1 | Bob | 1 | Company 1 |
+----------------+--------+----------+-----------+-------------+
Thank you.
WHERE v1.vehiclelicence = v1.vehiclelicence - a typo here..and remove the limit and it works.
SELECT *
FROM vehicle v1
join user u on u.id = v1.userid
join company c on c.id = u.companyid
WHERE EXISTS
(
SELECT 1
FROM vehicle v2
WHERE v1.vehiclelicence = v2.vehiclelicence and v2.userid != v1.userid
#LIMIT 1, 1
)
order by v1.vehiclelicence
;
OR start the offset from 0
SELECT *
FROM vehicle v1
join user u on u.id = v1.userid
join company c on c.id = u.companyid
WHERE EXISTS
(
SELECT 1
FROM vehicle v2
WHERE v1.vehiclelicence = v2.vehiclelicence and v2.userid != v1.userid
LIMIT 0, 1
)
order by v1.vehiclelicence
;
either way
+----+----------------+--------+----+----------+-----------+----+-----------+
| id | vehiclelicence | userid | id | username | companyid | id | name |
+----+----------------+--------+----+----------+-----------+----+-----------+
| 1 | PB1234 | 1 | 1 | Bob | 1 | 1 | Company 1 |
| 5 | PB1234 | 2 | 2 | Tim | 2 | 2 | Company 2 |
| 6 | UI7654 | 1 | 1 | Bob | 1 | 1 | Company 1 |
| 3 | UI7654 | 3 | 3 | Jim | 3 | 3 | Company 3 |
+----+----------------+--------+----+----------+-----------+----+-----------+
4 rows in set (0.001 sec)

Group by query results differently depending on server

I have two tables I want to join on one attribute (Sensor_id). Then I want to GROUP BY on the same attribute but I need the result is ORDER BY Timestamp DESC attribute. So I used a subquery to first ORDER BY Timestamp DESC and then the outer query will GROUP BY Sensor_id
First table: Sensors_colocation
=========================================================================================
| Sensor_id | Sensor_longitude | Sensor_latitude | Paese | Pseudonimo | limit1 | limit2 |
=========================================================================================
Second table: log
===========================================
| Id | Mac_reali | Mac_random | Timestamp |
===========================================
Using
SELECT * FROM log AS L JOIN Sensors_colocation AS S ON L.Id = S.Sensor_id ORDER BY L.Id ASC, L.Timestamp DESC
I get what I want on every of the two servers I have.
The problem is when I perform the full query
SELECT * FROM (
SELECT * FROM log AS L JOIN Sensors_colocation AS S ON L.Id = S.Sensor_id
ORDER BY L.Id ASC, L.Timestamp DESC) AS temp
GROUP BY temp.Id
on one server I get the results sorted by Timestamp DESC and grouped by Id. On the other server (that has the same structure but different data) I get the results sorted by Timestamp ASC and grouped by Id. I don't understand why if I use a subquery the ORDER BY I have in my inner query is not considered.
Can you help me?
EDIT: My goal is to have all the attributes of the joined tables but only the last entry speaking of Timestamp of every Id.
EDIT2:
Not working:
10.1.41-MariaDB-0+deb9u1
CREATE TABLE `log` (
`Id` int(11) NOT NULL,
`Mac_reali` int(11) NOT NULL,
`Mac_random` int(11) NOT NULL,
`Timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
ALTER TABLE `log`
ADD PRIMARY KEY (`Id`,`Timestamp`);
CREATE TABLE `Sensors_colocation` (
`Sensor_id` int(11) NOT NULL,
`Sensor_longitude` decimal(7,6) NOT NULL,
`Sensor_latitude` decimal(8,6) NOT NULL,
`Paese` varchar(32) NOT NULL,
`Pseudonimo` varchar(32) NOT NULL,
`limit1` int(11) NOT NULL,
`limit2` int(11) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
ALTER TABLE `Sensors_colocation`
ADD PRIMARY KEY (`Sensor_id`);
Working:
5.6.33-log
CREATE TABLE IF NOT EXISTS `log` (
`Id` int(11) NOT NULL,
`Mac_reali` int(11) NOT NULL,
`Mac_random` int(11) NOT NULL,
`Timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`Id`,`Timestamp`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
CREATE TABLE IF NOT EXISTS `Sensors_colocation` (
`Sensor_id` int(11) NOT NULL,
`Sensor_longitude` decimal(7,6) NOT NULL,
`Sensor_latitude` decimal(8,6) NOT NULL,
`Paese` varchar(32) NOT NULL,
`Pseudonimo` varchar(32) NOT NULL,
`limit1` int(11) NOT NULL,
`limit2` int(11) NOT NULL,
PRIMARY KEY (`Sensor_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
EDIT3:
Consider the output of inner query (I do not write some attributes that we don't need)
Id | Mac_reali | Timestamp | Sensor_id | Pseudonimo
1 | 30 | "2019-09-29 17:27:33" | 1 | Manarola(Stazione)
1 | 23 | "2019-09-29 17:25:33" | 1 | Manarola(Stazione)
1 | 57 | "2019-09-29 17:23:33" | 1 | Manarola(Stazione)
2 | 12 | "2019-09-29 17:28:42" | 2 | Vernazza(Stazione)
2 | 33 | "2019-09-29 17:26:42" | 2 | Vernazza(Stazione)
2 | 12 | "2019-09-29 17:24:42" | 2 | Vernazza(Stazione)
3 | 23 | "2019-09-29 17:33:42" | 3 | Monterosso(Stazione)
3 | 17 | "2019-09-29 17:31:42" | 3 | Monterosso(Stazione)
3 | 16 | "2019-09-29 17:29:42" | 3 | Monterosso(Stazione)
From the "working" server, from the outer query I get
Id | Mac_reali | Timestamp | Sensor_id | Pseudonimo
1 | 30 | "2019-09-29 17:27:33" | 1 | Manarola(Stazione)
2 | 12 | "2019-09-29 17:28:42" | 2 | Vernazza(Stazione)
3 | 23 | "2019-09-29 17:33:42" | 3 | Monterosso(Stazione)
From the "not working" server I get the opposite speaking of Timestamp (as if ORDER BY is ignored)
Id | Mac_reali | Timestamp | Sensor_id | Pseudonimo
1 | 57 | "2019-09-29 17:23:33" | 1 | Manarola(Stazione)
2 | 12 | "2019-09-29 17:24:42" | 2 | Vernazza(Stazione)
3 | 16 | "2019-09-29 17:29:42" | 3 | Monterosso(Stazione)
My goal is to have all the attributes of the joined tables but only the last entry speaking of Timestamp of every Id.
Consider this approach that uses a correlated subquery to ensure that there is no other log record for the same id with a greater timestamp:
SELECT *
FROM log l
INNER JOIN sensors_colocation s ON l.id = s.sensor_id
WHERE NOT EXISTS (
SELECT 1
FROM log l1
WHERE l1.id = l.id AND l1.timestamp > l.timestamp
)
ORDER BY l.id ASC, l.timestamp DESC
If you are running MySQL 8.0, you can get the same result by using window function ROW_NUMBER() to rank records by descending timestamp within groups of records having the same id, and then filtering on the top record per group:
SELECT *
FROM (
SELECT
l.*,
s.*,
ROW_NUMBER() OVER(PARTITION BY l.id ORDER BY l.timestamp DESC) rn
FROM log l
INNER JOIN sensors_colocation s ON l.id = s.sensor_id
) x
WHERE rn = 1
Note: for performance, you need an index on log(id, timestamp).

Shared tenant objects

I have a multi tenant application with a single database. I've a "entity" table where all objects are stored. "sahred_entity" table is used to store objects that are shared by a Tenant X to Tenant Y. For example "Tenant 2" can share "Entity with ID 4" to "Tenant 1".
In the example below "Entity with ID 4" is shared to "Tenant 1" and "Tenant 3"
+--------+--------------------------------------------------
| Table | Create Table
+--------+--------------------------------------------------
| entity | CREATE TABLE `entity` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`tenant_id` int(10) unsigned NOT NULL,
`added_at` timestamp NOT NULL,
`color` varchar(20) NOT NULL,
`size` varchar(5) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=latin1 |
+--------+--------------------------------------------------
+---------------+---------------------------------------
| Table | Create Table
+---------------+---------------------------------------
| shared_entity | CREATE TABLE `shared_entity` (
`tenant_to` int(10) unsigned NOT NULL,
`tenant_from` int(10) unsigned NOT NULL,
`entity_id` int(10) unsigned NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1 |
+---------------+---------------------------------------
The sample data is
select * from entity;
+----+-----------+---------------------+--------+------+
| id | tenant_id | added_at | color | size |
+----+-----------+---------------------+--------+------+
| 1 | 1 | 2019-03-07 00:00:00 | red | m |
| 2 | 1 | 2019-03-07 00:00:00 | green | xl |
| 3 | 2 | 2019-03-07 00:00:00 | green | xl |
| 4 | 2 | 2019-03-07 00:00:00 | red | m |
| 5 | 3 | 2019-03-07 00:00:00 | yellow | l |
+----+-----------+---------------------+--------+------+
select * from shared_entity;
+-----------+-------------+-----------+
| tenant_to | tenant_from | entity_id |
+-----------+-------------+-----------+
| 1 | 2 | 4 |
| 3 | 2 | 4 |
+-----------+-------------+-----------+
Now I need to create a simple search query. For now I found two ways how to do it. The first is via self joining
SELECT e.* FROM `entity` as e
LEFT JOIN entity as e1 ON (e.id = e1.id AND e1.tenant_id = 1)
LEFT JOIN entity as e2 ON (e.id = e2.id AND e2.id IN (4))
WHERE (e1.id IS NOT NULL OR e2.id IS NOT NULL) AND e.`color` = 'red';
The second is via sub query and union
SELECT * FROM
(
SELECT * FROM entity as e1 WHERE e1.tenant_id = 1
UNION
SELECT * FROM entity as e2 WHERE e2.id IN(4)
) as entity
WHERE color = 'red';
Both of queries return expected result
+----+-----------+---------------------+-------+------+
| id | tenant_id | added_at | color | size |
+----+-----------+---------------------+-------+------+
| 1 | 1 | 2019-03-07 00:00:00 | red | m |
| 4 | 2 | 2019-03-07 00:00:00 | red | m |
+----+-----------+---------------------+-------+------+
But which approach is better for large tables? How to create right index? Or maybe there is a better solution?
You could also use the following query to get the same results
SELECT *
FROM entity
WHERE (tenant_id = 1 or id = 4) AND color = 'red'
It is not clear to me why you need all the joins
Every table should have a PRIMARY KEY. shared_entity needs PRIMARY KEY(tenant_from, tenant_to, entity_id); any order would probably suffice.
As for performance, hogan's suggestion, together with INDEX(color), is fine for a small table:
SELECT *
FROM entity
WHERE (tenant_id = 1 OR id = 4)
AND color = 'red'
But OR prevents most forms of optimization. If color is selective enough, then this is not a problem; it will simply scan through all the "red" items checking each for tenent_id and for id.
If there are thousands of red items, this will run faster:
( SELECT *
FROM entity
WHERE tenant_id = 1
AND color = 'red' )
UNION DISTINCT
( SELECT *
FROM entity
WHERE id = 4
AND color = 'red' )
together with
INDEX(color, tenant_id) -- in either order
-- PRIMARY KEY(id) -- already exists and is unique
UNION DISTINCT can be sped up to UNION ALL if you know that tenant-1 and id-4 don't refer to the same row.

How do I select a songnames.id from the songs table in this particular database layout?

How do I select a songnames.id from the songs table by specifying the songnames.name and multiple artists.name that are linked to the songnames.id in the songs table?
CREATE TABLE songnames (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
alias VARCHAR(255)
) ENGINE = 'InnoDB' DEFAULT CHARSET = 'UTF8';
CREATE TABLE artists (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) UNIQUE NOT NULL
) ENGINE = 'InnoDB' DEFAULT CHARSET= 'UTF8';
CREATE TABLE songs (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
artist_id INT UNSIGNED NOT NULL REFERENCES artists(id) ON UPDATE CASCADE ON DELETE CASCADE,
songname_id INT UNSIGNED NOT NULL REFERENCES songnames(id) ON UPDATE CASCADE ON DELETE CASCADE,
UNIQUE(artist_id, songname_id)
) ENGINE = 'InnoDB' DEFAULT CHARSET = 'UTF8';
songs table: artists table:
+----+-----------+-------------+ +----+---------------+
| id | artist_id | songname_id | | id | name |
+----+-----------+-------------+ +----+---------------+
| 1 | 1 | 1 | | 1 | Matt White |
+----+-----------+-------------+ +----+---------------+
| 2 | 2 | 2 | | 2 | Keyshia Cole |
+----+-----------+-------------+ +----+---------------+
| 3 | 3 | 3 | | 3 | Nitty Kutchie |
+----+-----------+-------------+ +----+---------------+
| 4 | 4 | 3 | | 4 | Lukie D |
+----+-----------+-------------+ +----+---------------+
| 5 | 5 | 4 | | 5 | Sia |
+----+-----------+-------------+ +----+---------------+
songnames table:
+----+--------------+--------------+
| id | name | alias |
+----+--------------+--------------+
| 1 | Love | NULL |
+----+--------------+--------------+
| 2 | Love | NULL |
+----+--------------+--------------+
| 3 | Love | Must Be Love |
+----+--------------+--------------+
| 4 | The Greatest | NULL |
+----+--------------+--------------+
This SQL Fiddle is what I have played around with so far, I just can't find a way to select a songname_id by given artists.name and songnames.name.
This seems to be working:
SELECT DISTINCT
songnames.id
FROM songs
JOIN songnames ON songs.songname_id = songnames.id
JOIN artists ON songs.artist_id = artists.id
WHERE songnames.name = 'Love' AND artists.name = 'Matt White'
But I need something similiar to (this one obviously fails to execute):
SELECT DISTINCT
songnames.id
FROM songs
JOIN songnames ON songs.songname_id = songnames.id
JOIN artists ON songs.artist_id = artists.id
WHERE songnames.name = 'Love' AND artists.name = 'Lukie D' AND artists.name = 'Nitty Kutchie'
How do I select the songnames.id (id 3) from the songs table by specifying a songnames.name ('Love') and (multiple) artists.name ('Lukie D', 'Nitty Kutchie')?
Get the songs matching the song name and an artist. Then group by the song name and see if you got a match for every artist.
The following query looks up all songs called 'Love', looks for 'Lukie D' and 'Nitty Kutchie', and then only keeps the one 'Love' song that is performed by both (i.e. count(*) = 2) artists.
select songname_id
from songs
where songname_id in (select id from songnames where name = 'Love')
and artist_id in (select id from artists where name in ('Lukie D', 'Nitty Kutchie'))
group by songname_id
having count(*) = 2;
SQL fiddle: http://sqlfiddle.com/#!9/fde558/10

how to get data based on a correlated table?

Listings table
+------------+---------+
| name | id |
+------------+---------+
| Example 1 | 1 |
| Example 2 | 2 |
| Example 3 | 3 |
| Example 4 | 4 |
| Example 5 | 5 |
| Example 6 | 6 |
+------------+---------+
Categories table
+------------+---------+
| name | id |
+------------+---------+
| Catname 1 | 1 |
| Catname 2 | 2 |
| Catname 3 | 3 |
+------------+---------+
ListingCats table
+--------+---------+
| cat_id | list_id |
+--------+---------+
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
| 2 | 1 |
| 3 | 1 |
| 3 | 3 |
| 2 | 2 |
| 1 | 5 |
| 2 | 6 |
+--------+---------+
I am trying to build 2 queries which should be simple.
The first thing needed is to get a count of how many listings in the listings table corelate to a given category ID in the listingcats table.
The second part is getting all of the data (*) in the rows from the listings table that corelate to the given category id in the listingcats table.
I have tried a number of joins and for some reason none want to work properly. Can anyone help based on the example tables given above please. The 'given' category ID in this case would be '1'.
For the first query, you can use a simple join, and return a count
SELECT COUNT(Name)
FROM Listings l
JOIN ListingCats lc ON l.id = lc.cat_id
WHERE lc.cat_id = 1
This will return all rows from the listings table such that the listings id has a corresponding cat_id in the listingcats table, but exclusive to those that have a cat_id of 1. Then, the count aggregate function returns the number of rows.
For the second one, you can just use the same subquery above, but without the aggregate function, and select all values.
SELECT * FROM Listings l
JOIN ListingCats lc ON l.id = lc.cat_id
WHERE lc.cat_id = 1
Try those, please let me know if they work or not and I will try to work through them more with you.
EDIT
After looking back at the question, if you are given a specific cat_id you don't even need to use a join, you can simply query the listings table for one that has that id. If the given id is one:
SELECT COUNT(Name)
FROM Listings l
WHERE l.id = 1
And then again, even more broad for the second one:
SELECT * FROM Listings l WHERE l.id = 1
CREATE TABLE `listings` (
`id` int(10) unsigned NOT NULL auto_increment,
`name` varchar(10) NOT NULL default '0',
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
CREATE TABLE `categories` (
`id` int(10) unsigned NOT NULL auto_increment,
`name` varchar(10) NOT NULL default '0',
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
CREATE TABLE `listings_cats` (
`cat_id` int(10) unsigned NOT NULL,
`list_id` int(10) unsigned NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
SELECT c.id, c.name, COUNT(lc.list_id) as the_count
FROM categories c
JOIN listings_cats lc ON (lc.cat_id = c.id)
GROUP BY c.id;
SELECT l.id, l.name, c.name AS category_name
FROM listings l JOIN listings_cats lc ON (lc.list_id = l.id)
JOIN categories c ON (lc.cat_id = c.id);