Deleting table entries based on criteria in another table - mysql

We want to be able to delete entries from a MySQL table, based on deletion criteria set in another table. Let me explain with an example.
I have two tables defined as follows:
CREATE TABLE base_tbl
(
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
f1 VARCHAR(8),
f2 VARCHAR(8),
PRIMARY KEY (id)
);
CREATE TABLE del_criteria_tbl
(
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
f3 VARCHAR(8),
f4 VARCHAR(8),
PRIMARY KEY (id)
);
base_tbl has the data and del_criteria_tbl has the criteria for deleting entries from base_tbl.
I populate the tables as follows:
INSERT INTO base_tbl(f1, f2) VALUES ('ABC', '123C57'),
('ABC', '532B49'), ('DEF', '397F45'),
('DEF', '684G65'), ('GHI', '793A86'),
('GHI', '541H32');
and
INSERT INTO del_criteria_tbl(f3, f4) VALUES ('ABC', '532B49'),
('ABC', '813E89'), ('DEF', '397F45'),
('GHI', '541H32');
Obviously:
mysql>SELECT * FROM base_tbl;
+----+------+--------+
| id | f1 | f2 |
+----+------+--------+
| 1 | ABC | 123C57 |
| 2 | ABC | 532B49 |
| 3 | DEF | 397F45 |
| 4 | DEF | 684G65 |
| 5 | GHI | 793A86 |
| 6 | GHI | 541H32 |
+----+------+--------+
mysql>SELECT * FROM del_criteria_tbl;
+----+------+--------+
| id | f3 | f4 |
+----+------+--------+
| 1 | ABC | 532B49 |
| 2 | ABC | 813E89 |
| 3 | DEF | 397F45 |
| 4 | GHI | 541H32 |
+----+------+--------+
I would like to define a succinct and efficient SQL operation that executes the following pseudo-SQL logic:
DELETE FROM base_tbl WHERE base_tbl.f1 = del_criteria_tbl.f3 AND base_tbl.f2 = del_criteria_tbl.f4
After the operation is executed, SELECT * FROM base_tbl should yield:
+----+------+--------+
| id | f1 | f2 |
+----+------+--------+
| 1 | ABC | 123C57 |
| 4 | DEF | 684G65 |
| 5 | GHI | 793A86 |
+----+------+--------+

A simple method is IN:
DELETE b FROM base_tbl b
WHERE (b.f1, b.f2) IN (SELECT dc.f3, dc.f4
FROM del_criteria_tbl dc
);
With indexes on (f1, f2), you might find a JOIN has better performance:
DELETE b
FROM base_tbl b JOIN
del_criteria_tbl dc
ON b.f1 = dc.f3 AND b.f2 = c.f4;

I would recommend exists:
delete b
from base_tbl b
where exists (
select 1
from del_criteria_tbl dc
where dc.f1 = b.f1 and dc.f2 = b.f2
)
This seems like the most natural way to phrase what you ask for. exists usually scales better than in over large datasets. For performance, you want an index on del_criteria_tbl(f1, f2).

Related

A join or function in Mysql to include all unique items of one column and all unique items of second column

I need to combine the unique values of one column and the unique values of another column as one new column. As shown in Table example 2
Union to append all DISTINCT return dates
drop table if exists t;
create table t
(cid int, bid int,cdate varchar(6),rdate varchar(6));
insert into t values
(103,23,'15-mar','26-jan'),
(103,23,'14-apr','26-jan'),
(103,23,'18-may','26-jan');
select cid,bid,cdate,rdate,cdate as newdate from t
union all
(select distinct cid,bid,null,null,rdate from t)
;
+------+------+--------+--------+---------+
| cid | bid | cdate | rdate | newdate |
+------+------+--------+--------+---------+
| 103 | 23 | 15-mar | 26-jan | 15-mar |
| 103 | 23 | 14-apr | 26-jan | 14-apr |
| 103 | 23 | 18-may | 26-jan | 18-may |
| 103 | 23 | NULL | NULL | 26-jan |
+------+------+--------+--------+---------+
4 rows in set (0.002 sec)
select column1,column2,count(column1) as c1 ,count(column2) as c2 from MyTable having c1 =1 OR c2 =1

Vertically concat MySQL tables, inserting NULLs

I have a table mmm with main_ids, and 2 tables (ppp and ccc) with main_ids and sub-IDs, like this
Table Structure
Table `mmm` Table `ppp` Table `ccc`
+---------+ +---------+--------+ +---------+--------+
| main_id | | main_id | ppp_id | | main_id | ccc_id |
+---------+ +---------+--------+ +---------+--------+
| 1 | | 1 | 1 | | 3 | 1 |
| 2 | | 2 | 2 | | 4 | 2 |
| 3 | +---------+--------+ +---------+--------+
| 4 |
+---------+
The cccs and the ppps get their main_ids from the pool of main_ids in mmm.
Commands to construct
CREATE DATABASE test;
USE test;
CREATE TABLE mmm (main_id INT, PRIMARY KEY (main_id));
CREATE TABLE ppp (main_id INT, ppp_id INT, FOREIGN KEY (main_id) REFERENCES mmm (main_id));
CREATE TABLE ccc (main_id INT, ccc_id INT, FOREIGN KEY (main_id) REFERENCES mmm (main_id));
INSERT INTO mmm VALUES (1), (2), (3), (4);
INSERT INTO ppp VALUES (1, 1), (2, 2);
INSERT INTO ccc VALUES (3, 1), (4, 2);
I want to write a query that produces this result:
Desired Output
+---------+--------+--------+
| main_id | ppp_id | ccc_id |
+---------+--------+--------+
| 1 | 1 | NULL |
| 2 | 2 | NULL |
| 3 | NULL | 1 |
| 4 | NULL | 2 |
+---------+--------+--------+
So I want to stack ppp and ccc on top of each other, filling in NULL values where there isn't an entry in the other table. I have been experimenting with joins and haven't been able to produce this result. The closest I came was
SELECT
*
FROM
mmm,
ppp,
ccc
WHERE
mmm.main_id = ppp.main_id OR
mmm.main_id = ccc.main_id
GROUP BY
mmm.main_id
;
but that doesn't quite do it properly because there are still non-NULL values in the table with non-matching main_ids.
You want left joins:
select main_id, p.ppp_id, c.ccc_id
from mmm m
left join ppp p using (main_id)
left join ccc c using (main_id)
You can use CASE statements to replace values with NULL under certain conditions, and use DISTINCT instead of GROUP BY.
SELECT DISTINCT
mmm.main_id,
CASE
WHEN mmm.main_id = ppp.main_id THEN
ppp.ppp_id
ELSE
NULL
END AS ppp_id,
CASE
WHEN mmm.main_id = ccc.main_id THEN
ccc.ccc_id
ELSE
NULL
END AS ccc_id
FROM
mmm,
ppp,
ccc
WHERE
mmm.main_id = ppp.main_id OR
mmm.main_id = ccc.main_id
;

How to get column which is assigned under another column

I have 2 tables.
This is my GroupTable.
CREATE TABLE GroupTable(
ID INT,
GROUPNAME VARCHAR(50),
UnderGroupId INT
);
INSERT INTO GroupTable VALUES (1,'A',0);
INSERT INTO GroupTable VALUES (2,'B',1);
INSERT INTO GroupTable VALUES (3,'C',2);
INSERT INTO GroupTable VALUES (4,'D',3);
Below is the datatable where i'm passing groupId in data table for reference
CREATE TABLE Reference(
ID INT,
GROUPID VARCHAR(50),
GroupValue VARCHAR(50)
);
INSERT INTO Reference VALUES (1,3,'X');
INSERT INTO Reference VALUES (2,4,'Y');
INSERT INTO Reference VALUES (3,1,'Z');
and i want to show the result like this
| ID | GROUPID | GroupValue | GROUPNAME1 | GROUPNAME2 | GROUPNAME3 | GROUPNAME4 |
|----|---------|------------|------------|------------|------------|------------|
| 1 | 3 | X | A | B | C | |
| 2 | 4 | Y | A | B | C | D |
| 3 | 1 | Z | A | | | |
From your comment, you can try to OUTER JOIN by GROUPID > UnderGroupId condition because that condition is those two table relationship condition.
then
UnderGroupId = 0 mean the group1
UnderGroupId = 1 mean the group2
UnderGroupId = 2 mean the group3
UnderGroupId = 3 mean the group4
You can do condition aggregate function on UnderGroupId, to get the pivot result.
TestDLL
CREATE TABLE GroupTable(
ID INT,
GROUPNAME VARCHAR(50),
UnderGroupId INT
);
INSERT INTO GroupTable VALUES (1,'A',0);
INSERT INTO GroupTable VALUES (2,'B',1);
INSERT INTO GroupTable VALUES (3,'C',2);
INSERT INTO GroupTable VALUES (4,'D',3);
CREATE TABLE Reference(
ID INT,
GROUPID VARCHAR(50),
GroupValue VARCHAR(50)
);
INSERT INTO Reference VALUES (1,3,'X');
INSERT INTO Reference VALUES (2,4,'Y');
INSERT INTO Reference VALUES (3,1,'Z');
Query 1:
SELECT t1.ID,
t1.GROUPID,
t1.GroupValue,
coalesce(MAX(CASE WHEN UnderGroupId = 0 THEN tt.GROUPNAME end),'') GROUPNAME1,
coalesce(MAX(CASE WHEN UnderGroupId = 1 THEN tt.GROUPNAME end),'') GROUPNAME2,
coalesce(MAX(CASE WHEN UnderGroupId = 2 THEN tt.GROUPNAME end),'') GROUPNAME3,
coalesce(MAX(CASE WHEN UnderGroupId = 3 THEN tt.GROUPNAME end),'') GROUPNAME4
FROM Reference t1
LEFT JOIN GroupTable tt ON t1.GROUPID > tt.UnderGroupId
GROUP BY t1.ID,
t1.GROUPID,
t1.GroupValue
Results:
| ID | GROUPID | GroupValue | GROUPNAME1 | GROUPNAME2 | GROUPNAME3 | GROUPNAME4 |
|----|---------|------------|------------|------------|------------|------------|
| 1 | 3 | X | A | B | C | |
| 2 | 4 | Y | A | B | C | D |
| 3 | 1 | Z | A | | | |

MySQL UPDATE with multiple tables and derived calculation

I'm in need of assistance with a MySQL UPDATE involving two tables and a calculated value. The biggest issue seems to be getting the derived value in place.
I need to calculate the DENS column based on the number of IDs in Table 2 (Total ID by FIPS / Table.POP)
Table 1
---------------------
| FIPS | POP | DENS |
---------------------
| 0001 | 100 | |
| 0002 | 25 | |
| 0003 | 500 | |
---------------------
Table 2
-------------
| ID | FIPS |
-------------
| 01 | 0001 |
| 02 | 0001 |
| 03 | 0002 |
| 04 | 0003 |
| 05 | 0003 |
| 06 | 0003 |
-------------
I can't figure out the syntax for the UPDATE statement to properly associate the count and subsequent calculation with the FIPS value.
I thought the following might work but it has not:
UPDATE Table1
SET Table1.DENS = (
SELECT (COUNT(DISTINCT Table2.id) / Table1.POP )
FROM Table2
WHERE Table2.FIPS = Table1.FIPS
)
Any help is appreciated!
EDIT: Desired result would look like:
----------------------
| FIPS | POP | DENS |
----------------------
| 0001 | 100 | 0.020 |
| 0002 | 25 | 0.040 |
| 0003 | 500 | 0.006 |
----------------------
The DENS is calulated from Table 2 and Table 1 (using POP: total IDs/POP = DENS) in that there are 2 IDs for FIPS 0001 (2/100 = 0.0200), 1 ID For FIPS 0002 (1/25 = 0.0400), and 3 IDs for FIPS 0003 (3/500 = 0.0060)
Try
UPDATE table1 t1
JOIN (
SELECT FIPS, count( distinct ID ) As DENS
FROM table2
GROUP BY FIPS
) t2
ON t1.FIPS = t2.FIPS
SET t1.DENS = t2.DENS
;
DROP TABLE IF EXISTS table1;
CREATE TABLE table1
(fips INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,pop INT NOT NULL
,dens DECIMAL(5,3) NULL
);
INSERT INTO table1 VALUES
(1,100,NULL),
(2,25,NULL),
(3,500,NULL);
DROP TABLE IF EXISTS table2;
CREATE TABLE table2
(id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,fips INT NOT NULL
);
INSERT INTO table2 VALUES
(1,1),
(2,1),
(3,2),
(4,3),
(5,3),
(6,3);
UPDATE table1 x
JOIN
( SELECT t1.fips,COUNT(t2.fips)/SUM(DISTINCT t1.pop) n FROM table1 t1 JOIN table2 t2 ON t2.fips = t1.fips GROUP BY t1.fips ) y
ON y.fips = x.fips
SET x.dens = n;
SELECT * FROM table1;
+------+-----+-------+
| fips | pop | dens |
+------+-----+-------+
| 1 | 100 | 0.020 |
| 2 | 25 | 0.040 |
| 3 | 500 | 0.006 |
+------+-----+-------+
...or something like that
Correct answer ended up being combination of both with the addition of the reset of innodb_lock_wait_timeout (set to 600)
SET SESSION innodb_lock_wait_timeout = 600
Final update code was:
UPDATE Table1 x
JOIN (SELECT t1.FIPS, COUNT(DISTINCT t2.ID)/t1.POP n
FROM Table1 t1
JOIN Table2 t2 ON t2.FIPS = t1.FIPS
GROUP BY t1.FIPS) y
ON y.fips = x.fips
SET x.DENS = n
Thanks to both Kordirko and Strawberry for the assist on this!

Mysql select rows using cross reference but omit on a condition

Given these tables:
+---------------+---------+
| Field | Type |
+---------------+---------+
| group_id | int(10) |
| subscriber_id | int(10) |
+---------------+---------+
+---------------+--------------+
| Field | Type |
+---------------+--------------+
| subscriber_id | int(10) |
| firstname | varchar(50) |
| lastname | varchar(50) |
| company | varchar(120) |
| position | varchar(50) |
| email | text |
| lettertype | varchar(5) |
| status | varchar(20) |
+---------------+--------------+
I used the following query to get a subset of subscribers:
SELECT *
FROM newsletter_subscribe AS a, newsletter_subscriber AS b
WHERE (a.group_id = 1 or a.group_id = 4)AND (a.subscriber_id = b.subscriber_id)
What I'd like to do is exclude from the subset if a row exists in newsletter_subscribe where group_id = 3 then the newsletter_subscribe from that row is excluded from the result.
My thought was to make a temporary table to replace a, but I'm not certain how to go about it.
SELECT *
FROM newsletter_subscribe AS a, newsletter_subscriber AS b
WHERE (a.group_id = 1 or a.group_id = 4) AND (a.subscriber_id = b.subscriber_id) AND (b.subscriber_id NOT IN (SELECT subscriber_id FROM newsletter_subscribe WHERE group_id = 3))
As it stands now, you'll never get group_id=3, because you only allow groups 1 and 4 in the first term of your where clause. If you want ALL groups EXCEPT 3, then use
WHERE (a.group_id <> 3) AND (a.subscriber_id = b.subscriber_id)
or perhaps
WHERE 3 NOT IN (a.group_id, b.group_id) AND (a.subscriber_id = b.subscriber_id)
to exclude it from both tables.