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
;
Related
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
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).
Let's assume I have the table:
id | val_1 | val_2
1 | 1 | 0
2 | 1 | 1
3 | 1 | 2
4 | 2 | 0
val_2 should be zero at first if there was no rows with val_1 before. Otherwise it should be previous val_2 + 1 for this val_1.
I can't figure it out by myself the best way to do it. The one thing I've invented is trigger after insert, but I think here maybe some other way to do it cleaner and faster?
My code is something like:
DELIMITER $$
CREATE TRIGGER after_table_insert
AFTER INSERT
ON table FOR EACH ROW
BEGIN
UPDATE table SET val_2 = t.val_2 + 1
FROM (
SELECT val_2 FROM table WHERE val_1 = new.val_1 ORDER BY id DESC LIMIT 1
) t
WHERE id = new.id;
END$$
DELIMITER ;
I will appreciate for any help!
Have a great day/night.
You have couple of issues with such setup:
What's going on if you UPDATE or DELETE rows? It can mess up everything with val2. Be careful with that.
Val2 can always be calculated and there is no need to store it.
Having said that, below I will show you a setup with which I will store only id and val1. Then val2 will be calculated within the SELECT statement (so it will always be correct).
CREATE TABLE vals(
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
val INT NOT NULL
);
INSERT INTO vals(val) VALUES(1),(1),(1),(2);
Now what I am going to do is to use the ROW_NUMBER() function (which prints the row number) and run it over a PARTITION BY val:
SELECT id, val,
ROW_NUMBER() OVER (
PARTITION BY val
) AS val2
FROM vals;
We are almost there. Sadly it will offset them by 1 compared to what you need:
+----+-----+------+
| id | val | val2 |
+----+-----+------+
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 1 | 3 |
| 4 | 2 | 1 |
+----+-----+------+
The fix is simple. Just add "-1" to it and you are ready.
SELECT id, val,
-1+ROW_NUMBER() OVER (
PARTITION BY val
) AS val2
FROM vals;
This will produce:
+----+-----+------+
| id | val | val2 |
+----+-----+------+
| 1 | 1 | 0 |
| 2 | 1 | 1 |
| 3 | 1 | 2 |
| 4 | 2 | 0 |
+----+-----+------+
With this solution there is no need to store val2 at all (you can create it as a VIEW if you wish) and it is not vulnerable to the issue when you delete a row (it will continue to work properly).
This is one possibility
Schema (MySQL v5.7)
CREATE TABLE table1 (
`id` INTEGER,
`val_1` INTEGER,
`val_2` INTEGER
);
DELIMITER //
CREATE TRIGGER after_table_insert
BEFORE INSERT
ON table1 FOR EACH ROW
BEGIN
SET #maxval2 = 0;
SELECT max(val_2) + 1 into #maxval2 FROM table1 WHERE val_1 = new.val_1;
IF #maxval2 IS NULL THEN
SET #maxval2 = 0;
END IF;
SET NEW.val_2 = #maxval2;
END//
DELIMITER ;
INSERT INTO table1
(`id`, `val_1`, `val_2`)
VALUES
('1', '1', '0'),
('2', '1', '0'),
('3', '1', '0'),
('4', '2', '0'),
('4', '2', '0');
Query #1
SELECT * FROM table1;
| id | val_1 | val_2 |
| --- | ----- | ----- |
| 1 | 1 | 0 |
| 2 | 1 | 1 |
| 3 | 1 | 2 |
| 4 | 2 | 0 |
| 4 | 2 | 1 |
View on DB Fiddle
SELECT
e.*,
(
SELECT GROUP_CONCAT(topic_name)
FROM topic
WHERE id IN (e.topic_ids)) AS topics
FROM exam e
result :
topics = xyz topic
this query returns a single name of topic as result but when i use this :
SELECT
e.*,
(
SELECT GROUP_CONCAT(topic_name)
FROM topic
WHERE id IN (1,4)) AS topics
FROM exam e
result :
topics = xyz topic,abc topic
That works fine,and exam table had the same value in DB (comma separated topic ids = 1,4) as varchar type field.
is there any issue with datatype of field?
First, let me lecture you about how bad CSV in field is.
| id | topic_ids |
|----|-----------|
| 1 | a,b,c |
| 2 | a,b |
This, is how Satan look like in relational DB. Probably the worst, just after the
"lets put columns as line and use a recursive join to get everything back."
How it should be ?
exam
| id |
|----|
| 1 |
| 2 |
exam_topic
| exam_id | topic_id |
|---------|----------|
| 1 | a |
| 1 | b |
| 1 | c |
| 2 | a |
| 2 | b |
topic
| id |
|----|
| a |
| b |
| c |
Now, as awful as it may be, this is the "dynamic" alternative, using FIND_IN_SET() :
SELECT
e.*,
(
SELECT GROUP_CONCAT(topic_name)
FROM topic
WHERE FIND_IN_SET(id, e.topic_ids) > 0
) AS topics
FROM exam e
SQL Fiddle
MySQL 5.6 Schema Setup:
CREATE TABLE exam
(`id` int, `topic_ids` varchar(5))
;
INSERT INTO exam
(`id`, `topic_ids`)
VALUES
(1, 'a,b,c'),
(2, 'a,b'),
(3, 'b,c,d'),
(4, 'd')
;
CREATE TABLE topic
(`id` varchar(1), `topic_name` varchar(4))
;
INSERT INTO topic
(`id`, `topic_name`)
VALUES
('a', 'topA'),
('b', 'topB'),
('c', 'topC'),
('d', 'topD')
;
Query 1:
SELECT
e.*,
(
SELECT GROUP_CONCAT(topic_name)
FROM topic
WHERE FIND_IN_SET(id, e.topic_ids) > 0
) AS topics
FROM exam e
Results:
| id | topic_ids | topics |
|----|-----------|----------------|
| 1 | a,b,c | topA,topB,topC |
| 2 | a,b | topA,topB |
| 3 | b,c,d | topB,topC,topD |
| 4 | d | topD |
I have the following schema (mysql)
create table test(
userid int(11) not null,
item varchar(15),
bookid int(11));
insert into test values ('1','journal',NULL);
insert into test values ('1','journal',NULL);
insert into test values ('1','book',NULL);
insert into test values ('2','book',NULL);
insert into test values ('2','journal',NULL);
insert into test values ('1','book',NULL);
insert into test values ('2','journal',NULL);
insert into test values ('3','book',NULL);
insert into test values ('1','book',NULL);
insert into test values ('1','journal',NULL);
insert into test values ('3','journal',NULL);
insert into test values ('1','journal',NULL);
insert into test values ('2','journal',NULL);
insert into test values ('2','book',NULL);
insert into test values ('2','journal',NULL);
insert into test values ('1','journal',NULL);
insert into test values ('3','book',NULL);
insert into test values ('3','book',NULL);
insert into test values ('3','book',NULL);
insert into test values ('3','book',NULL);
whenever there is a book, I'm trying assign an auto increment beginning with 1 in the bookid column. For each user, the numbering begins again from 1. I know a way this can be done by creating a separate table. Is there a way I can avoid that and accomplish that using some sort of update query in this very table and update the column bookid? I am trying to get output similar to the following:
userid,item,bookid
'1','journal',NULL
'1','journal',NULL
'1','book',1
'2','book',1
'2','journal',NULL
'1','book',2
'2','journal',NULL
'3','book',1
'1','book',3
'1','journal',NULL
'3','journal',NULL
'1','journal',NULL
'2','journal',NULL
'2','book',2
'2','journal',NULL
'1','journal',NULL
'3','book',2
'3','book',3
'3','book',4
'3','book',5
I appreciate if someone could guide me on how to accomplish this?
Here's one idea...
drop table if exists test;
create table test
(id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,userid int not null
,item varchar(15) NOT NULL
);
insert into test (userid,item) values
(1,'journal')
,(1,'journal')
,(1,'book')
,(2,'book')
,(2,'journal')
,(1,'book')
,(2,'journal')
,(3,'book')
,(1,'book')
,(1,'journal')
,(3,'journal')
,(1,'journal')
,(2,'journal')
,(2,'book')
,(2,'journal')
,(1,'journal')
,(3,'book')
,(3,'book')
,(3,'book')
,(3,'book');
SELECT x.*
, COUNT(*) rank
FROM test x
JOIN test y
ON y.userid = x.userid
AND y.item = x.item
AND y.id <= x.id
GROUP
BY id
ORDER
BY userid
, item
, rank;
+----+--------+---------+------+
| id | userid | item | rank |
+----+--------+---------+------+
| 3 | 1 | book | 1 |
| 6 | 1 | book | 2 |
| 9 | 1 | book | 3 |
| 1 | 1 | journal | 1 |
| 2 | 1 | journal | 2 |
| 10 | 1 | journal | 3 |
| 12 | 1 | journal | 4 |
| 16 | 1 | journal | 5 |
| 4 | 2 | book | 1 |
| 14 | 2 | book | 2 |
| 5 | 2 | journal | 1 |
| 7 | 2 | journal | 2 |
| 13 | 2 | journal | 3 |
| 15 | 2 | journal | 4 |
| 8 | 3 | book | 1 |
| 17 | 3 | book | 2 |
| 18 | 3 | book | 3 |
| 19 | 3 | book | 4 |
| 20 | 3 | book | 5 |
| 11 | 3 | journal | 1 |
+----+--------+---------+------+
Note that MyISAM actually lets you use a composite PK in which part of that composite is an auto-incrementing id, but InnoDB prohinits this.
On larger datasets a query along these lines will likely be far more efficient...
SELECT id
, userid
, item
, IF(#userid=userid,IF(#item=item,#i:=#i+1,#i:=1),#i:=1) rank
, #userid := userid
, #item := item
FROM test
, (SELECT #userid = NULL,#item:='',#i:=1) vars
ORDER
BY userid,item,id;