MySQL Left outer join with where clause - mysql

I am trying to use the following MySQL query:
SELECT *
FROM top_lines t
LEFT OUTER JOIN last_24_topline AS l ON l.`member_no` = t.`member_no`
AND l.`mfg` = t.`line_no`
WHERE l.account_no = 32049 OR l.account_no IS NULL
However this is returning no rows, as there are no account_no rows in last_24_topline that match. From all that I understand and have read this query should still return all the rows from top_lines, even though no rows match in last_24_topline since I am checking for a value or null, but it is not. Are there any options or settings in MySQL (5.7.2) that would cause this behavior?
Just for information, this query works as expected:
SELECT *
FROM top_lines t
LEFT OUTER JOIN last_24_topline l ON l.`member_no` = t.`member_no`
AND l.`mfg` = t.`line_no`
AND l.`account_no` = 32049
I'm unable to use this construct however since I am using entity framework and you can only pass columns in and not values to the joins
CREATE TABLE `last_24_topline` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`member_no` varchar(30) NOT NULL,
`branch_no` int(11) DEFAULT NULL,
`employee_no` varchar(25) DEFAULT NULL,
`account_no` varchar(25) DEFAULT NULL,
`salesperson_name` varchar(255) DEFAULT NULL,
`customer_name` varchar(255) DEFAULT NULL,
`mfg` varchar(5) DEFAULT NULL,
`mfg_description` varchar(255) DEFAULT NULL,
`last_three` decimal(10,2) DEFAULT '0.00',
`last_twelve` decimal(10,2) DEFAULT '0.00',
`ly_last_three` decimal(10,2) DEFAULT '0.00',
`ly_last_twelve` decimal(10,2) DEFAULT '0.00',
PRIMARY KEY (`id`),
KEY `ix_branch_no` (`branch_no`),
KEY `ix_employee_no` (`employee_no`),
KEY `ix_member_line_account` (`member_no`,`mfg`,`account_no`),
KEY `ix_member_line` (`member_no`,`mfg`),
KEY `ix_account_no` (`account_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
CREATE TABLE `top_lines` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`member_no` varchar(30) NOT NULL,
`line_no` varchar(5) NOT NULL,
`line_description` varchar(255) NOT NULL,
PRIMARY KEY (`id`),
KEY `ix_line_no` (`member_no`,`line_no`)
) ENGINE=InnoDB AUTO_INCREMENT=41 DEFAULT CHARSET=latin1
insert into `top_lines`(`id`,`member_no`,`line_no`,`line_description`) values (1,'520','772','FED ROTOR/DRUM');
insert into `top_lines`(`id`,`member_no`,`line_no`,`line_description`) values (2,'520','952','FED SST CERAMIC');
insert into `top_lines`(`id`,`member_no`,`line_no`,`line_description`) values (3,'520','954','FED SST FRICTION');
insert into `top_lines`(`id`,`member_no`,`line_no`,`line_description`) values (4,'520','162','EVS FRICTION');
INSERT INTO `last_24_topline` (`id`, `member_no`, `branch_no`, `employee_no`, `account_no`, `salesperson_name`, `customer_name`, `mfg`, `mfg_description`, `last_three`, `last_twelve`, `ly_last_three`, `ly_last_twelve`) VALUES('1','520','0','10856','463854','FORD, JAMES,','JIFFY LUBE','459','FEDERATED AIR FILTER','0.00','15.21','0.00','0.00');
INSERT INTO `last_24_topline` (`id`, `member_no`, `branch_no`, `employee_no`, `account_no`, `salesperson_name`, `customer_name`, `mfg`, `mfg_description`, `last_three`, `last_twelve`, `ly_last_three`, `ly_last_twelve`) VALUES('2','520','0','10856','463854','FORD, JAMES,','JIFFY LUBE','460','FILTERS','0.00','0.00','0.00','16.48');
INSERT INTO `last_24_topline` (`id`, `member_no`, `branch_no`, `employee_no`, `account_no`, `salesperson_name`, `customer_name`, `mfg`, `mfg_description`, `last_three`, `last_twelve`, `ly_last_three`, `ly_last_twelve`) VALUES('3','520','0','10856','463854','FORD, JAMES,','JIFFY LUBE','863','SMP T SERIES','0.00','0.00','0.00','50.67');
I would expect, even with no data in last_24_topline that matches, for the first query to produce a result set containing all the rows in top_lines with null values for the columns from last_24_topline.
Expected results:
So, creating the same schema into another database and inserting only the example data I provided above, I get the results I expect. I am testing further with copying the full rows to the second database to see if it still gives the expected results.
update
Copying all data into the new tables causes the problem to resurface. I'm trying to pare down to the minimum necessary to replicate the issue.

Try detecting for empty string too, maybe the fields are not NULL, but empty strings.
SELECT *
FROM
top_lines t
LEFT JOIN
last_24_topline AS l ON l.member_no = t.member_no AND l.mfg = t.line_no
WHERE
(l.account_no = '' OR l.account_no = '32049' OR l.account_no IS NULL)
If you want more help, i will need a sample data for "table last_24_topline" and the expected output after the join.
As a second try, you can use this one:
SELECT *
FROM
top_lines t
LEFT JOIN
last_24_topline AS l ON l.member_no = t.member_no AND l.mfg = t.line_no
WHERE
l.id IS NULL
OR
(l.id IS NOT NULL AND l.account_no = '32049')

Use a column involved in the join instead of l.account_no this way rows from top_lines will be returned if there is no matching row in the left table.
SELECT *
FROM top_lines t
LEFT OUTER JOIN last_24_topline AS l ON l.`member_no` = t.`member_no`
AND l.`mfg` = t.`line_no`
WHERE l.account_no = 32049 OR l.`member_no` IS NULL
Alternatively place the account number filter directly in to the join
SELECT *
FROM top_lines t
LEFT OUTER JOIN last_24_topline AS l ON l.`member_no` = t.`member_no`
AND l.`mfg` = t.`line_no`
AND l.account_no = 32049

On the query without the account number in the join, the join does match row in the last_24_toplines table, but it does not match the account number in the where clause, so it is filtered out and not seen as a row from top_lines without a matching row from last_24_topline.
So, for example this row from the top_line table
Will match this row from the last_24_toplines
but both will then be filtered out, because the account_no didn't match what was in the where clause: WHERE l.account_no = 32049 OR l.account_no IS NULL
The query with the check for account_no within the join will still match the row from top_line, but will not have a matching row from last_24_topline, so you will get a row with top_lines data with null last_24_topline.

Related

MySQL Deleting based upon a query result

I have the following mysql query which finds the most recently modified and unique spawnpoint_id from a pokemon table in my database:
SELECT
t1.spawnpoint_id, t1.last_modified
FROM
pokemon t1
INNER JOIN
(SELECT
MAX(last_modified) last_modified, spawnpoint_id
FROM
pokemon
GROUP BY spawnpoint_id) t2 ON
t1.spawnpoint_id = t2.spawnpoint_id
AND t1.last_modified = t2.last_modified;
I get the results I want with the above.... but now, I want to delete all records that don't match these results.
I have tried to enclose the query in a DELETE .. NOT IN something like this:
DELETE FROM pokemon WHERE (spawnpoint_id, last_modified) NOT IN (
SELECT
t1.spawnpoint_id, t1.last_modified
FROM
pokemon t1
INNER JOIN
(SELECT
MAX(last_modified) last_modified, spawnpoint_id
FROM
pokemon
GROUP BY spawnpoint_id) t2 ON
t1.spawnpoint_id = t2.spawnpoint_id
AND t1.last_modified = t2.last_modified) x;
but I'm getting MySQL syntax error. I've been searching for a couple of hours, and finally hoped someone here might be able to help me discover what I'm doing wrong. Many thanks.
EDIT: SHOW CREATE TABLE pokemon;
CREATE TABLE pokemon (
encounter_id varchar(50) NOT NULL,
spawnpoint_id varchar(255) NOT NULL,
pokemon_id int(11) NOT NULL,
latitude double NOT NULL,
longitude double NOT NULL,
disappear_time datetime NOT NULL,
individual_attack int(11) DEFAULT NULL,
individual_defense int(11) DEFAULT NULL,
individual_stamina int(11) DEFAULT NULL,
move_1 int(11) DEFAULT NULL,
move_2 int(11) DEFAULT NULL,
last_modified datetime DEFAULT NULL,
time_detail int(11) NOT NULL,
PRIMARY KEY (encounter_id),
KEY pokemon_spawnpoint_id (spawnpoint_id),
KEY pokemon_pokemon_id (pokemon_id),
KEY pokemon_disappear_time (disappear_time),
KEY pokemon_last_modified (last_modified),
KEY pokemon_time_detail (time_detail),
KEY pokemon_latitude_longitude (latitude,longitude)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
I think the problem is that you use table pokemon, from which you want to delete rows, in the from-part of a subquery (which is not permitted).
One could get around this by first doing an update-statement that marks the rows to be deleted, and then do a separate delete statement. Note that the "must not use in the from-part"-restriction also applies to update-statements. Yet this can be solved by using a join rather than a subselect as follows:
create table a (
x int,
y int
);
insert into a (x,y) values (1,2),(3,4);
update a a1, (select max(a2.x) as x from a a2) a3 set a1.y = 0 where a1.x = a3.x;
delete from a where y=0

LEFT JOIN two tables so that left table data is filtered but right table data is displayed even if left table data does not exist

To allow our customers to store some of their own data along with our data structure I have created two extra tables:
CREATE TABLE external_columns
(
`id` INT(11) PRIMARY KEY NOT NULL,
`column` VARCHAR(30) NOT NULL,
`sid` INT(11) NOT NULL,
`bid` INT(11) NOT NULL,
`label` VARCHAR(30) NOT NULL,
`table` VARCHAR(30) NOT NULL,
`default` TINYTEXT NOT NULL
);
CREATE TABLE external_data
(
`id` INT(11) PRIMARY KEY NOT NULL,
`extcol_id` INT(11) NOT NULL,
`sid` INT(11) NOT NULL,
`bid` INT(11) NOT NULL,
`data` MEDIUMTEXT NOT NULL,
`row_id` INT(11) NOT NULL,
CONSTRAINT `external_data_external_columns_id_fk`
FOREIGN KEY (extcol_id) REFERENCES external_columns (id)
);
CREATE UNIQUE INDEX combinedUniqueIndex
ON external_data (extcol_id, sid, bid, row_id);
sid and bid are system values that identify the customer the data belongs to. row_id refers to the primary key of table referenced in table.
To get data for a certain row I have created this prepared statement:
SELECT `data`.*, `columns`.`column`, `columns`.`default`
FROM `external_columns` as `columns`
LEFT JOIN `external_data` as `data`
ON `columns`.`id` = `data`.`extcol_id`
WHERE (
`columns`.`sid` = :sid
AND `columns`.`bid` = :bid
AND `data`.`row_id` = :row_id
AND `columns`.`table` = :tableName
)
This works fine as long as for each external_column there is an entry in external_data for the given :row_id. But I want to make sure that there is always a row for each column, even if there is no data for the given :row_id. Is there a way to do this with one query?
Very close, by placing AND data.row_id = :row_id in your WHERE, you have effectively written an INNER JOIN as nulled data.row_ids won't match.
You should move this condition to the LEFT JOIN conditions:
SELECT `data`.*, `columns`.`column`, `columns`.`default`
FROM `external_columns` as `columns`
LEFT JOIN `external_data` as `data`
ON `data`.`extcol_id`= `columns`.id
AND `data`.`row_id` = :row_id
WHERE `columns`.`sid` = :sid
AND `columns`.`bid` = :bid
AND `columns`.`table` = :tableName
Personal Preferences:
Don't need the WHERE parentheses and I always tend to put the table conditions for a JOIN in the JOIN conditions where applicable and JOIN table on the LHS to make indexing options more obvious..
No difference for INNER JOINs but essential for certain LEFT JOINs.

How to joint Two customs Queries with two Joins in only One Query in MySQL

The Queries are working perfectly each one separatedly:
SELECT asf.surface_name, am.*
FROM atp_matchs_to_surfaces m2s
LEFT JOIN atp_surfaces asf ON m2s.surfaces_id = asf.surfaces_id
LEFT JOIN atp_matchs am ON am.matchs_id = m2s.matchs_id;
SELECT att.tournament_type_name, am.*
FROM atp_matchs_to_tournament_type m2s
LEFT JOIN atp_tournament_type att ON m2s.tournament_type_id = att.tournament_type_id
LEFT JOIN atp_matchs am ON am.matchs_id = m2s.matchs_id;
The tables 'atp_matchs_to_surfaces' and 'atp_matchs_to_tournament_type' are defined in that way:
CREATE TABLE IF NOT EXISTS `atp_matchs_to_tournament_type` (
`tournament_type_id` int(4) NOT NULL,
`matchs_id` int(6) NOT NULL,
PRIMARY KEY (`tournament_type_id`,`matchs_id`)
CREATE TABLE IF NOT EXISTS `atp_matchs_to_surfaces` (
`surfaces_id` int(4) NOT NULL,
`matchs_id` int(6) NOT NULL,
PRIMARY KEY (`surfaces_id`,`matchs_id`)
And the other Tables with all the data:
CREATE TABLE IF NOT EXISTS `atp_matchs` (
`matchs_id` int(7) NOT NULL AUTO_INCREMENT,
`tournament_name` varchar(36) NOT NULL,
`tournament_year` year NOT NULL,-- DEFAULT '0000',
`tournament_country` varchar(26) NOT NULL,
`match_datetime` datetime NOT NULL,-- DEFAULT '0000-00-00 00:00:00',
`match_link` varchar(85) NOT NULL,
`prize_money` int(12) NOT NULL,
`round` varchar(8) NOT NULL,-- DEFAULT '1R',
`sets` varchar(34) NOT NULL,-- DEFAULT '0-0',
`result` varchar(4) NOT NULL,-- DEFAULT '0-0',
`p1_odd` decimal(4,2) NOT NULL,-- DEFAULT '0.00',
`p2_odd` decimal(4,2) NOT NULL,-- DEFAULT '0.00',
PRIMARY KEY (`matchs_id`)
CREATE TABLE IF NOT EXISTS `atp_surfaces` (
`surfaces_id` int(4) NOT NULL AUTO_INCREMENT,
`surface_name` varchar(24) NOT NULL,
PRIMARY KEY (`surfaces_id`)
CREATE TABLE IF NOT EXISTS `atp_tournament_type` (
`tournament_type_id` int(4) NOT NULL AUTO_INCREMENT,
`tournament_type_name` varchar(22) NOT NULL,
PRIMARY KEY (`tournament_type_id`)
I want in the same Query all the records of match and surface name+tournament type. It's clear? I hope...
I tried to implement this with SubQueries: http://www.w3resource.com/mysql/subqueries/ and How can an SQL query return data from multiple tables but i can't do it to work.
OK, this is your current schema. As you can see, one match can be played on multiple surfaces and one match can be played within multiple tournament types.
If this schema is OK, you can get your result with this query:
SELECT am.*, asu.surface_name, att.tournament_type_name
FROM atp_matchs AS am
LEFT JOIN atp_matchs_to_surfaces AS m2s ON m2s.matchs_id = am.matchs_id
LEFT JOIN atp_surfaces AS asu ON asu.surfaces_id = m2s.surfaces_id
LEFT JOIN atp_matchs_to_tournament_type AS m2t ON m2t.matchs_id = am.matchs_id
LEFT JOIN atp_tournament_type AS att ON att.tournament_type_id = m2t.tournament_type_id
However, if one match can be played on one surface only and within one tournament type only, I would change your schema to:
Tables atp_matchs_to_surfaces and atp_matchs_to_tournament_type are removed and fields surfaces_id and tournament_type_id moved to atp_matchs table. Your query is now:
SELECT am.*, asu.surface_name, att.tournament_type_name
FROM atp_matchs AS am
LEFT JOIN atp_surfaces AS asu ON asu.surfaces_id = am.surfaces_id
LEFT JOIN atp_tournament_type AS att ON att.tournament_type_id = am.tournament_type_id
The LEFT JOIN keyword returns all rows from the left table (table1), with the matching rows in the right table (table2).
SELECT asf.surface_name, am.*
FROM atp_matchs_to_surfaces m2s
LEFT JOIN (SELECT att.tournament_type, am.*
FROM atp_matchs_to_tournament_type m2s) as......
SELECT asf.surface_name, am.*
FROM atp_matchs_to_surfaces m2s
LEFT JOIN atp_surfaces asf ON m2s.surfaces_id = asf.surfaces_id
LEFT JOIN atp_matchs am ON am.matchs_id = m2s.matchs_id
LEFT JOIN(
SELECT att.tournament_type, am.*
FROM atp_matchs_to_tournament_type m2s
LEFT JOIN atp_tournament_type att AS Q1 ON m2s.surfaces_id = att.surfaces_id
LEFT JOIN atp_matchs am AS Q2 ON am.matchs_id = m2s.matchs_id);
I added some "AS" because I had the error: Every derived table must have its own alias. I'm a little lost here!

MySQL - LEFT JOIN value of columnX where MAX(columnY)

So this is my query:
SELECT SQL_CALC_FOUND_ROWS wagons.id,
wagons.mid,
wagons.year,
wagons.make,
wagons.model,
wagons.nickname,
wagons.description,
members.first,
members.last,
wagon_photos.filename,
Count(DISTINCT( likes.id )) AS likes,
Count(DISTINCT( comments.id )) AS comments
FROM wagons
INNER JOIN members
ON members.mid = wagons.mid
LEFT JOIN wagon_photos
ON wagon_photos.wid = wagons.id
LEFT JOIN likes
ON likes.wid = wagons.id
LEFT JOIN comments
ON comments.wid = wagons.id
GROUP BY wagons.id
ORDER BY wagons.id DESC
LIMIT 10
I am trying to fetch the wagon_photos.filename where wagon_photos.default is the maximum value. (It's a boolean, where only one row (for each wagon_photos.wid) will be 1.) On the off-chance the member does not have a default photo selected, I'd like it to return the lowest wagon_photos.id if possible.
I have tried numerous queries with ORDER BY and GROUP BY, but I think it may be a little more complicated than that. If I use a WHERE clause, it left out any wagons that did not have any photos linked to it, which I do not want to do.
CREATE TABLE `wagons` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`mid` int(8) DEFAULT NULL,
`year` varchar(4) DEFAULT NULL,
`make` varchar(50) DEFAULT NULL,
`model` varchar(100) DEFAULT NULL,
`nickname` varchar(200) DEFAULT NULL,
`description` mediumtext,
`featured` int(1) DEFAULT '0',
`visibility` int(1) DEFAULT '1',
`commision` int(1) DEFAULT '1',
`feat1title` varchar(50) DEFAULT NULL,
`feat2title` varchar(50) DEFAULT NULL,
`feat3title` varchar(50) DEFAULT NULL,
`feat4title` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=617 DEFAULT CHARSET=utf8;
CREATE TABLE `wagon_photos` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`wid` int(11) DEFAULT NULL,
`filename` varchar(255) DEFAULT NULL,
`caption` varchar(255) DEFAULT '',
`default` int(1) DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=41 DEFAULT CHARSET=utf8;
Let me try and understand what you're asking. I think what you're asking is "how do I construct a query where I want to select all of the items that have the max value of a particular column". If that's the case, you want to break this down into simpler units in order to solve it. First, you'll need the max value itself:
select max(wp.default) as max_val from wagon_photos wp
This gets you the max value.
Next you can create a query to select rows where a condition is true, but leave the condition out for a moment:
select (your rows) from wagons w where (a condition will go here)
And your condition is that you want wagon_photos.default to equal the max value. So you'll need to join in that table:
select (your rows) from wagons w join wagon_photos wp
on wp.wid = w.id where wp.default = (something)
Your "something" happens to be that max_value that we created a query for in the beginning. So putting this together, you'll use a subquery which will look like this:
select (your rows) from wagons w join wagon_photos wp
on wp.wid = w.id where wp.default = (
select max(wpb.default) from wagon_photos wpb
)
I haven't set up the tables myself to run this, so I might have goofed on parentheses, and it doesn't include all your joins. But I think this should point you in the right direction.
Well I feel a little bit silly now.
I finally thought of adding AND wagon_photos.default = 1 after the LEFT JOIN for wagon_photos and it works. I just need to make sure they always have a default setup, so I ran a script that on every update to their photos, it checks for a default, and if one isn't set, set the newest upload as default.
... LEFT JOIN wagon_photos ON wagon_photos.wid = wagons.id AND wagon_photos.default = 1 ...

Update statement causes fields to be updated with NULL or maximum value

If you had to pick one of the two following queries, which would you choose and why:
UPDATE `table1` AS e
SET e.points = e.points+(
SELECT points FROM `table2` AS ep WHERE e.cardnbr=ep.cardnbr);
or:
UPDATE `table1` AS e
INNER JOIN
(
SELECT points, cardnbr
FROM `table2`
) AS ep ON (e.cardnbr=ep.cardnbr)
SET e.points = e.points+ep.points;
Tables' definitions:
CREATE TABLE `table1` (
`cardnbr` int(10) DEFAULT NULL,
`name` varchar(50) DEFAULT NULL,
`points` decimal(7,3) DEFAULT '0.000',
`email` varchar(50) NOT NULL DEFAULT 'user#company.com',
`id` int(11) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=25205 DEFAULT CHARSET=latin1$$
CREATE TABLE `table2` (
`cardnbr` int(10) DEFAULT NULL,
`id` int(11) NOT NULL AUTO_INCREMENT,
`points` decimal(7,3) DEFAULT '0.000',
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci$$
UPDATE: BOTH are causing problems the first is causing non matched rows to update into NULL.
The second is causing them to update into the max value 999.9999 (decimal 7,3).
PS the cardnbr field is NOT a key
I prefer the second one..reason for that is
When using JOIN the databse can create an execution plan that is better for your query and save time whereas subqueries (like your first one ) will run all the queries and load all the datas which may take time.
i think subqueries is easy to read but performance wise JOIN is faster...
First, the two statements are not equivalent, as you found out yourself. The first one will update all rows of table1, putting NULL values for those rows that have no related rows in table2.
So the second query looks better because it doesn't update all rows of table1. It could be written in a more simpel way, like this though:
UPDATE table1 AS e
INNER JOIN table2 AS ep
ON e.cardnbr = ep.cardnbr
SET e.points = e.points + ep.points ;
So, the 2nd query would be the best to use, if cardnbr was the primary key of table2. Is it?
If it isn't, then which values from table2 should be used for the update of table1 (added to points)? All of them? You could use this:
UPDATE table1 AS e
INNER JOIN
( SELECT SUM(points) AS points, cardnbr
FROM table2
GROUP BY cardnbr
) AS ep ON e.cardnbr = ep.cardnbr
SET
e.points = e.points + ep.points ;
Just one of them? That would require some other derived table, depending on what you want.