Can't get expected result using left join and on condition - mysql

Running the following scripts, you will get 2 tables with records.
-- ----------------------------
-- Table structure for data
-- ----------------------------
DROP TABLE IF EXISTS `data`;
CREATE TABLE `data` (
`id` int(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of data
-- ----------------------------
INSERT INTO `data` VALUES ('1');
INSERT INTO `data` VALUES ('2');
-- ----------------------------
-- Table structure for status
-- ----------------------------
DROP TABLE IF EXISTS `status`;
CREATE TABLE `status` (
`id` int(255) DEFAULT NULL,
`status` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of status
-- ----------------------------
INSERT INTO `status` VALUES ('1', '0');
INSERT INTO `status` VALUES ('1', '1');
Table data
+--------+--------+
| id | more...|
|--------|--------+
| 1 | |
| 2 | |
+--------+--------+
Table status
+--------+--------+
| id | status |
|--------|--------+
| 1 | 0 |
| 1 | 1 |
+--------+--------+
I need select data of table data excluded items which status is 0 after refer to status.
Wrong try 1
I write the following SQL, and get the following result. It's not the expected result because the item id=2 is also needed.
select * from data d LEFT JOIN status s on d.id=s.id where s.status=1
+--------+--------+--------+
| id | id1 | status |
|--------|--------|--------+
| 1 | 1 | 1 |
+--------+--------+--------+
Wrong try 2
Then I change where to on, and get the following result which looks good.
select * from data d LEFT JOIN status s on d.id=s.id and s.status=1
+--------+--------+--------+
| id | id1 | status |
|--------|--------|--------+
| 1 | 1 | 1 |
+--------+--------+--------+
| 2 | (Null) | (Null) |
+--------+--------+--------+
Try 3
Table status has two items now. If I delete the item (id=1,status=1) of table status , what will be the result ?
select * from data d LEFT JOIN status s on d.id=s.id and s.status=1
+--------+--------+--------+
| id | id1 | status |
|--------|--------|--------+
| 1 | (Null) | (Null) |
+--------+--------+--------+
| 2 | (Null) | (Null) |
+--------+--------+--------+
In this case, I expect the item id=1 should not be here . The expected result is
+--------+--------+--------+
| id | id1 | status |
+--------+--------+--------+
| 2 | (Null) | (Null) |
+--------+--------+--------+
Why does the item id=1 appear in the result ? Can't it resolve my issue using on status=1 ?

A LEFT JOIN returns all the rows of the left table.
Use NOT EXISTS:
select d.* from data d
where not exists (
select 1 from status s
where s.id = d.id and s.status = 0
)
Or with NOT IN:
select * from data
where id not in (select id from status where status = 0)
See the demo.

I've resolved my issue with the following SQL. I have learned my case is not a typical LEFT JOIN scenario, so just putting a condition status=1 to where or on can't resolve it directly.
SELECT
*
FROM
data d
LEFT JOIN status s ON d.id = s.id
WHERE
s.status = 1
OR ISNULL(s.status)

Perhaps post your expected answer, but to return values in data where status is not zero:
Select id
from data d
Left join status s on d.id = s.id
Where ((s.status <> 0) or (s.status isnull))

Related

Conditionally delete last row in mysql

How do I conditionally delete the last row in a mysql table?
me/my_machine#17:26:57>cat create_tables.sql
CREATE DATABASE IF NOT EXISTS test_db;
USE test_db;
CREATE TABLE IF NOT EXISTS err_hist_table (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
cl INT NOT NULL,
usr VARCHAR(16),
fault_code VARCHAR(10));
INSERT INTO err_hist_table (cl,usr,fault_code) VALUES (1,'pA','A'), (2,'pA','NULL'),(3,'pC','B'),(4,'pB','NULL');
The above SQL commands create a table that is like this:
MySQL [test_db]> SELECT * FROM err_hist_table;
+----+----+------+------------+
| id | cl | usr | fault_code |
+----+----+------+------------+
| 1 | 1 | pA | A |
| 2 | 2 | pA | NULL |
| 3 | 3 | pC | B |
| 4 | 4 | pB | NULL |
+----+----+------+------------+
4 rows in set (0.00 sec)
Now I want to DELETE the last row (biggest value of id) only when the value of fault_code is NULL. If it is not NULL, I want to skip the delete.
To my sql-newbie eyes, it seems like there should be a simple something like:
SELECT * IF err_history_table.id=max(err_history_table) AND fault_code = 'NULL';
I could not find my answer on the mysql docs page. Is there a simple solution for something like this?
You can do it with a subquery that returns the row of the maximum id where you check the value of fault_code:
DELETE FROM err_hist_table
WHERE id = (
SELECT id FROM (
SELECT * FROM err_hist_table
ORDER BY id DESC LIMIT 1
) t
WHERE fault_code IS NULL
);
See the demo.
Results:
| id | cl | usr | fault_code |
| --- | --- | --- | ---------- |
| 1 | 1 | pA | A |
| 2 | 2 | pA | |
| 3 | 3 | pC | B |
I'm not totally sure if this is what you want, but it should work. If you have a recent version of MySQL you should be able to use sub-queries, like this:
DELETE FROM err_hist_table WHERE id = (SELECT MAX(id) FROM err_hist_table) AND fault_code IS NULL
Untested on your specific table of course. I would even suggest performing a SELECT first to verify it does what you expect.
It's worth mentioning that you can acheive the desired result using a single JOIN, without needing a sub-sub-query and in a way which seems to me both concise and readable:
DELETE e1 FROM err_hist_table e1
JOIN (
SELECT MAX(id) AS id
FROM err_hist_table
) e2 USING (id)
WHERE e1.fault_code = 'NULL'
More on the topic of using joins in a DELETE statement here: https://dev.mysql.com/doc/refman/8.0/en/delete.html#idm45306662494864

How can I update a column between two unique values in another column in MySQL?

How can I update a column between two unique values in another column in MySQL or MariaDB?
Consider a table called Example that has three columns:
Id: An auto-increment integer ID
RandomId: A series of random and unique GUIDs
IsUpdated: A column currently only containing NULL values, that needs to be updated
* ----------------------------------------------------- *
| Id | RandomId | IsUpdated |
| ----------------------------------------------------- |
| 1 | c446980b-cf2f-4f2d-a27b-28d6bde6415d | NULL |
| 2 | d6a1a52c-d073-4019-836a-67cf6551d958 | NULL |
| 3 | 7a339a6a-8e57-4373-84fd-1b40ee51c884 | NULL |
| 4 | 56b908a7-fb07-4f4c-a25d-699cf40cf690 | NULL |
| 5 | fac75ce6-a605-453a-958c-74f197e20a11 | NULL |
* ----------------------------------------------------- *
I would like to update IsUpdated between two specific GUIDs, like so:
UPDATE Example
SET IsUpdated = 1
WHERE RandomId >= 'd6a1a52c-d073-4019-836a-67cf6551d958' -- Starting Here
AND RandomId <= '56b908a7-fb07-4f4c-a25d-699cf40cf690' -- Ending Here
The resulting table should look like the following:
* ----------------------------------------------------- *
| Id | RandomId | IsUpdated |
| ----------------------------------------------------- |
| 1 | c446980b-cf2f-4f2d-a27b-28d6bde6415d | NULL |
| 2 | d6a1a52c-d073-4019-836a-67cf6551d958 | 1 |
| 3 | 7a339a6a-8e57-4373-84fd-1b40ee51c884 | 1 |
| 4 | 56b908a7-fb07-4f4c-a25d-699cf40cf690 | 1 |
| 5 | fac75ce6-a605-453a-958c-74f197e20a11 | NULL |
* ----------------------------------------------------- *
But since the Ids are not sequential, this method does not appear to work.
What would be the most efficient way to update a column (IsUpdated) between two unique values in another column (RandomId)?
You need to filter by id instead. I would recommend the update ... join syntax:
update example e
inner join (
select min(Id) minId, max(Id) maxId
from example
where RandomId in (
'd6a1a52c-d073-4019-836a-67cf6551d958',
'56b908a7-fb07-4f4c-a25d-699cf40cf690'
)
) i on e.id between i.minId and i.maxId
set e.IsUpdated = 1
Note that this does not stricly guarantee that guid are matched on the first and last rows (it would also work the other way around). You can be more specific with two joins:
update example e
inner join (
select Id
from example
where RandomId = 'd6a1a52c-d073-4019-836a-67cf6551d958'
) eMin on e.id >= eMin.id
inner join (
select Id
from example
where RandomId = '56b908a7-fb07-4f4c-a25d-699cf40cf690'
) eMax on e.id <= eMax.id
set e.IsUpdated = 1

Percentage calculus based on multiple tables in sql

I have two tables in sql and I need to create another table performing the calculations based on two other tables.
The first one has the sum of revenue for each Ad Unit, table name is ad_unit_table
SELECT
d.`Date`,
'App' as `Partner`,
d.`Ad Unit`,
sum(d.`Revenue`) as `Revenue`
from
`d_master` as d
group by
`Ad Unit`, `Date`
The other table has the sum of revenue for ALL Ad Units, table name is sum_revenue
SELECT
`Date`,
`Partner`
`Ad Unit`,
sum(`Revenue`) as `Sum Revenue`
from
`ad_unit_table`
group by
`Date`
Now I have to find the percentage of the revenue for each Ad Unit. So the formula is (Ad Unit Rev / Sum Rev) * 100. My code currently looks like this:
SELECT
ad.`Date`,
ad.`Partner`,
ad.`Ad Unit`,
(ad.`Revenue` / s.`Sum Revenue`) * 100 as `Percentage`
FROM
`ad_unit_table` as ad
LEFT JOIN `sum_revenue` as s ON ad.`Partner`
GROUP BY
`Date`,
`Ad Unit`
It gives me all NULLS. I would appreciate any help. Thank you!
Are you sure you want to do LEFT JOIN sum_revenue as s ON ad.Partner in your last query?. I tested this construct and this creates a so called cartesian product.
All rows of the left table are combined with all the rows from the other table.
See: https://en.wikipedia.org/wiki/Cartesian_product
For example:
create table testing.test_a (id INT);
create table testing.test_b (id INT);
INSERT INTO test_a VALUES(1),(2),(3),(4);
INSERT INTO test_b VALUES(1),(2),(3),(5);
# Resulting in a cartesian product (4x4 entries)
SELECT * FROM test_a AS a LEFT JOIN test_b AS b ON a.id;
+------+------+
| id | id |
+------+------+
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
| 4 | 1 |
| 1 | 2 |
| 2 | 2 |
| 3 | 2 |
| 4 | 2 |
| 1 | 3 |
| 2 | 3 |
| 3 | 3 |
| 4 | 3 |
| 1 | 5 |
| 2 | 5 |
| 3 | 5 |
| 4 | 5 |
+------+------+
# Correctly LEFT joining test_a and test_b would be:
SELECT a.id, b.id FROM test_a AS a LEFT JOIN test_b AS b ON a.id = b.id
# Or use the USING clause to join on column from both tables with same name.
SELECT test_a.id, test_b.id FROM test_a LEFT JOIN test_b USING(id);
+------+------+
| id | id |
+------+------+
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
| 4 | NULL |
+------+------+
Are you sure the table sum_revenue has entries for all units in ad_unit_table. If this is not the case, some values from sum_revenue result in NULL values because of the not matching entries.
If you only want the values that match use an INNER JOIN instead of an LEFT JOIN.
Also make sure that when could the calculations. none of the values are NULL. Doing calculating with NULL values results in NULL values.
Example:
SELECT 100 / NULL; -- Result NULL
SELECT (10 * NULL) * 100; -- Result NULL
Without more information like table definitions and/or sample data this is all that I can do.

Get result from joined tables

I have 2 tables:
Table 1:
| jobid | jobname |
| 1 | job a |
| 2 | job b |
Table 2:
| id | jobid | statusid | statusdate | desc |
| 1 | 1 | 100 | 2019.04.25 10:00:00 | first |
| 2 | 2 | 100 | 2019.04.25 11:00:00 | first |
| 3 | 2 | 100 | 2019.04.25 12:00:00 | second |
Jobs in table2 can have more then one same "statusid", but different "statusdate" and "desc"
I need to get jobs list with the last "statusid" = 100 like this :
| 1 | job a | 1 | 1 | 100 | 2019.04.25 10:00:00 | first |
| 2 | job b | 3 | 2 | 100 | 2019.04.25 12:00:00 | second |
SELECT * FROM table1
INNER JOIN table2 ON table1.id = table2.jobid
GROUP BY table1.id
This query return wrong result like:
| 1 | job a | 1 | 1 | | 100 | 2019.04.25 10:00:00 | first |
| 2 | job b | 3 | 2 | 2 | 100 | 2019.04.25 11:00:00 | first |
You should be able to accomplish that by doing something like this:
Table
drop table if exists table1;
create table table1 (jobid int, jobname char(10));
insert into table1 values (1, 'job a'), (2, 'job b');
drop table if exists table2;
create table table2 (
id int,
jobid int,
statusid int,
statusdate timestamp,
`desc` char(10)
);
insert into table2 values
(1,1,100,'2019.04.25 10:00:00','first')
,(2,2,100,'2019.04.25 11:00:00','first')
,(3,2,100,'2019.04.25 12:00:00','second');
Query
select
t1.*,
t2.*
from table1 t1
inner join (
select jobid, max(statusdate) as maxstatusdate
from table2
group by jobid
) tn on t1.jobid = tn.jobid
inner join table2 t2 on tn.jobid = t2.jobid and tn.maxstatusdate = t2.statusdate;
Results
jobid jobname id jobid statusid statusdate desc
1 job a 1 1 100 25.04.2019 10:00:00 first
2 job b 3 2 100 25.04.2019 12:00:00 second
Explanation
For each job ID, find the maximum status date
Join that to table1 to get information from table1. Common field is jobid
Join their result to table2 that has all the remaining information you want. Common fields are jobid and statusdate. Since we aliased max status date to a different name, make sure we are using the correct name in the join
Example: https://rextester.com/HRSWZ89705
DROP TABLE IF EXISTS table1;
CREATE TABLE table1
(jobid INT NOT NULL PRIMARY KEY
,jobname VARCHAR(12) UNIQUE
);
INSERT INTO table1 VALUES
(1,'job a'),
(2,'job b'),
(3,'job c');
DROP TABLE IF EXISTS table2;
CREATE TABLE table2
(id SERIAL PRIMARY KEY
,jobid INT NOT NULL
,statusid INT NOT NULL
,statusdate DATETIME NOT NULL
,description VARCHAR(12) NOT NULL
);
INSERT INTO table2 VALUES
(1,1,100,'2019-04-25 10:00:00','first'),
(2,2,100,'2019-04-25 11:00:00','first'),
(3,2,100,'2019-04-25 12:00:00','second');
SELECT a.*
, b.id x_id
, b.statusid
, b.statusdate
, b.description
FROM table1 a
LEFT
JOIN
( SELECT x.*
FROM table2 x
JOIN
( SELECT MAX(id) id
FROM table2
WHERE statusid = 100
GROUP
BY jobid
) y
ON y.id = x.id
) b
ON b.jobid = a.jobid
;
+-------+---------+------+----------+---------------------+-------------+
| jobid | jobname | x_id | statusid | statusdate | description |
+-------+---------+------+----------+---------------------+-------------+
| 1 | job a | 1 | 100 | 2019-04-25 10:00:00 | first |
| 2 | job b | 3 | 100 | 2019-04-25 12:00:00 | second |
| 3 | job c | NULL | NULL | NULL | NULL |
+-------+---------+------+----------+---------------------+-------------+
SELECT
t1.*,t2.* FROM
(SELECT
MAX(id) as id
FROM
table2
WHERE statusid = 100
GROUP BY jobid) AS f
JOIN table2 t2
ON t2.id = f.id
JOIN table1 t1
ON t2.jobid = t1.jobid
The sub query select finds the last id for a row with statusid 100, then it joins the actual table based on this id.
You can reorder this as you wish using the correct joins.

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);