Lets say i have a table like this
CREATE TABLE Parts (ID int, part_number varchar(100), isActive TINYINT);
and these sample records
|ID | part_number | isActive|
===============================
1 | 1N3.805.327 | 1
2 | 1N3.805.327.B | 1
3 | 1N3.804.108.B | 1
4 | 1N3.804.108.C | 1
5 | 1N3.804.107.B | 1
6 | 1N3.804.107.C | 1
7 | 1N3.804.106.A | 1
8 | 1N3.804.105.A | 1
Problem
I would like to combine a where in clause with the wildcard % operator
In my dbfiddle sample i tried the string function find_in_set and the comparison operator in(). Both do not work:
-- without wildcard the query works
SELECT * FROM Parts WHERE part_number in ('1N3.804.108.B', '1N3.804.106.A'); -- 2
-- with wildcard no records are returned
SELECT * FROM Parts WHERE part_number in ('1N3.804.108%', '1N3.804.106%'); -- 0
SELECT * FROM Parts WHERE FIND_IN_SET(part_number, '1N3.804.108%,1N3.804.106%'); -- 0
Questions
I assume i could use WHERE LEFT(part_number, 11) in ('1N3.804.108', '1N3.804.106') But i do not know if this has any disadvantages.
Is there a way to use a wildcard operator with in()?
Sample records
INSERT INTO
Parts(ID, part_number, isActive)
VALUES
(1, '1N3.805.327',1),
(2, '1N3.805.327.B',1),
(3, '1N3.804.108.B',1),
(4, '1N3.804.108.C',1),
(5, '1N3.804.107.B',1),
(6, '1N3.804.107.C',1),
(7, '1N3.804.106.A',1),
(8, '1N3.804.105.A',1);
Use REGEXP for that, when you want to use OR
CREATE TABLE Parts (ID int, part_number varchar(100), isActive TINYINT);
INSERT INTO
Parts(ID, part_number, isActive)
VALUES
(1, '1N3.805.327',1),
(2, '1N3.805.327.B',1),
(3, '1N3.804.108.B',1),
(4, '1N3.804.108.C',1),
(5, '1N3.804.107.B',1),
(6, '1N3.804.107.C',1),
(7, '1N3.804.106.A',1),
(8, '1N3.804.105.A',1);
✓
✓
SELECT * FROm Parts WHeRE part_number REGEXP '^(1N3.804.108|1N3.804.106)'
ID | part_number | isActive
-: | :------------ | -------:
3 | 1N3.804.108.B | 1
4 | 1N3.804.108.C | 1
7 | 1N3.804.106.A | 1
MySQL Can only UNION a certain number of tables. i think it is about 53.
With an index on partnumber, this will be the fastest.
SELECT * FROm Parts WHeRE part_number REGEXP '^1N3.804.108'
UNION all
SELECT * FROm Parts WHeRE part_number REGEXP '^1N3.804.106'
ID | part_number | isActive
-: | :------------ | -------:
3 | 1N3.804.108.B | 1
4 | 1N3.804.108.C | 1
7 | 1N3.804.106.A | 1
SELECT * FROm Parts WHeRE part_number LIKE '1N3.804.108%'
UNION all
SELECT * FROm Parts WHeRE part_number LIKE '1N3.804.106%'
ID | part_number | isActive
-: | :------------ | -------:
3 | 1N3.804.108.B | 1
4 | 1N3.804.108.C | 1
7 | 1N3.804.106.A | 1
db<>fiddle here
you can also try to do
select *,
REGEXP_LIKE(part_number,[pattern]) as pattern_test from Parts
where pattern_test is TRUE
here first you will create the pattern that you are interested in and apply it on the column you wish to apply it to. The pattern_test will return true if it matches with your pattern and then you can filter on that [where clause]
Related
We are working with some legacy code that is using a sub-optimally normalized DB. The problem that we are trying to solve is best described with an illustrative example.
hi_priority and lo_priority are two tables that have the same schema, as defined below:
CREATE TABLE hi_priority
(
id INT UNSIGNED AUTO_INCREMENT,
name VARCHAR(16),
col1 VARCHAR(16),
col2 INT,
PRIMARY KEY(id)
);
CREATE TABLE lo_priority
(
id INT UNSIGNED AUTO_INCREMENT,
name VARCHAR(16),
col1 VARCHAR(16),
col2 INT,
PRIMARY KEY(id)
);
In each table there is a 1:1 mapping between the name and col2.
The idea is to potentially SELECT name, col1, col2 from both tables but limit the total number of distinct names (let's say 7 for our illustrative example), subject to the following constraints:
Preference is to be given to entries from hi_priority table over entries from lo_priority tables.
For example, if there are 15 distinct names in hi_priority table,
we will select all rows with 7 names from hi_priority table,
ordered by col2.
If there are 5 distinct names in hi_priority
table, we select all rows with those 5 names and on top of that we
select all rows with 2 distinct names from lo_priority table,
ordered by col2
If there are some entries in both the
hi_priority table and the lo_priority table, we select entries
only from the hi_priority table
Let us assume that the tables are populated as follows:
INSERT INTO hi_priority(name, col1, col2) VALUES('john', 'kl7y5tis4yh4', 1), ('john', 'tiid6iywq02k', 1),
('john', 'detawgsxz615', 1), ('amy', 'i6u4hhc1trjk', 2), ('amy', 'wdpt0t5vtmbt', 2),
('amy', '87z5wgdfztwl', 2), ('amy', 'oj98jjdpb5yv', 2), ('steve', 'hllaazonflf0', 3),
('steve', '0h22y66kq3ow', 3), ('steve', 'o04ksti7di60', 3), ('steve', '3qlylbzqfr51', 3),
('steve', 'wmisshywtb12', 3), ('bob', 'ku8wpf7d6ta9', 3), ('bob', '6t7mn60g0g18', 3),
('bob', 'qv0s7ho3jku6', 3), ('bob', '5qgz7dznzwvn', 3), ('bob', 'mm76nhimm6fu', 3),
('bob', 'k7nlpksc55t2', 3), ('oliver', 'gvf9kjewpj7h', 4), ('oliver', '24w5s30w5te6', 4);
INSERT INTO lo_priority(name, col1, col2) VALUES('doug', 'j205tzrsfmax', 11), ('doug', '0w7rcazbh6es', 11),
('doug', '6xswf8frsjaw', 11), ('john', 'iw7d14vtysz2', 2), ('john', '6lg667dygaz1', 2),
('john', '83uk5dcobpu5', 2), ('john', 'tl8cpzatv0n9', 2), ('mike', '2dsarwozpmci', 3),
('mike', 'hinn6w03wdib', 3), ('mike', '4sxbgyacmjob', 3), ('mike', 'm4q13ln9gctj', 3),
('mike', 'pnip9c8cejo9', 3), ('steve', 'faff9p9v96x4', 4), ('steve', 'd5mxqpd3k8zi', 4),
('martha', 'bxggn5t6d2xn', 8), ('martha', 't05mi47i4n6l', 8), ('martha', 'p30wmw2o6nty', 8),
('martha', 'wip6efajt9yv', 8);
The expected output is:
+--------+------------+------+
| name | col1 | col2 |
+--------+------------+------+
| john | kl7y5tis4y | 1 |
| john | tiid6iywq0 | 1 |
| john | detawgsxz6 | 1 |
| amy | oj98jjdpb5 | 2 |
| amy | 87z5wgdfzt | 2 |
| amy | wdpt0t5vtm | 2 |
| amy | i6u4hhc1tr | 2 |
| bob | k7nlpksc55 | 3 |
| bob | mm76nhimm6 | 3 |
| bob | 5qgz7dznzw | 3 |
| bob | qv0s7ho3jk | 3 |
| bob | 6t7mn60g0g | 3 |
| bob | ku8wpf7d6t | 3 |
| steve | wmisshywtb | 3 |
| steve | 3qlylbzqfr | 3 |
| steve | o04ksti7di | 3 |
| steve | 0h22y66kq3 | 3 |
| steve | hllaazonfl | 3 |
| mike | 4sxbgyacmj | 3 |
| mike | pnip9c8cej | 3 |
| mike | m4q13ln9gc | 3 |
| mike | hinn6w03wd | 3 |
| mike | 2dsarwozpm | 3 |
| oliver | gvf9kjewpj | 4 |
| oliver | 24w5s30w5t | 4 |
| martha | bxggn5t6d2 | 8 |
| martha | t05mi47i4n | 8 |
| martha | p30wmw2o6n | 8 |
| martha | wip6efajt9 | 8 |
+--------+------------+------+
Amy, Bob and Oliver are only present in hi_priority table and will be selected.
Mike and Martha are only present in lo_priority table and will be selected.
John and Steve are present in both hi_priority table and lo_priority table but will be selected only from hi_priority table
Assuming you are using a version of MariaDB later than 10.2 (so it supports CTEs), you can use this query. It first gets a list of all the distinct names from hi_priority and lo_priority (in the names CTE), then finds the top 7 names by ordering those by priority and col2 (the top7 CTE) and finally joins that list back to hi_priority and lo_priority, giving preference to values from hi_priority over those from lo_priority:
WITH names AS (
SELECT DISTINCT name, col2, 1 AS priority
FROM hi_priority hi
UNION ALL
SELECT DISTINCT name, col2, 2 AS priority
FROM lo_priority lo
WHERE NOT EXISTS (
SELECT *
FROM hi_priority
WHERE name = lo.name
)
),
top7 AS (
SELECT name, priority
FROM names
ORDER BY priority, col2
LIMIT 7
)
SELECT DISTINCT
t.name,
COALESCE(hi.col1, lo.col1) AS col1,
COALESCE(hi.col2, lo.col2) AS col2
FROM top7 t
LEFT JOIN hi_priority hi ON hi.name = t.name
LEFT JOIN lo_priority lo ON lo.name = t.name
ORDER BY col2, priority, name, COALESCE(hi.id, lo.id)
Output (for your sample data):
name col1 col2
john kl7y5tis4yh4 1
john tiid6iywq02k 1
john detawgsxz615 1
amy i6u4hhc1trjk 2
amy wdpt0t5vtmbt 2
amy 87z5wgdfztwl 2
amy oj98jjdpb5yv 2
bob ku8wpf7d6ta9 3
bob 6t7mn60g0g18 3
bob qv0s7ho3jku6 3
bob 5qgz7dznzwvn 3
bob mm76nhimm6fu 3
bob k7nlpksc55t2 3
steve hllaazonflf0 3
steve 0h22y66kq3ow 3
steve o04ksti7di60 3
steve 3qlylbzqfr51 3
steve wmisshywtb12 3
mike 2dsarwozpmci 3
mike hinn6w03wdib 3
mike 4sxbgyacmjob 3
mike m4q13ln9gctj 3
mike pnip9c8cejo9 3
oliver gvf9kjewpj7h 4
oliver 24w5s30w5te6 4
martha bxggn5t6d2xn 8
martha t05mi47i4n6l 8
martha p30wmw2o6nty 8
martha wip6efajt9yv 8
Demo on dbfiddle
If you're running an earlier version, you can write the same query using subqueries:
SELECT DISTINCT
t.name,
COALESCE(hi.col1, lo.col1) AS col1,
COALESCE(hi.col2, lo.col2) AS col2
FROM (
SELECT name, priority
FROM (
SELECT DISTINCT name, col2, 1 AS priority
FROM hi_priority hi
UNION ALL
SELECT DISTINCT name, col2, 2 AS priority
FROM lo_priority lo
WHERE NOT EXISTS (
SELECT *
FROM hi_priority
WHERE name = lo.name
)
) n
ORDER BY priority, col2
LIMIT 7
) t
LEFT JOIN hi_priority hi ON hi.name = t.name
LEFT JOIN lo_priority lo ON lo.name = t.name
ORDER BY col2, priority, name, COALESCE(hi.id, lo.id)
Output is the same. Demo on dbfiddle
Here is my example (mysql Ver 14.14 Distrib 5.7.21, for Win64 (x86_64))
DROP TABLE IF EXISTS t_tt;
CREATE TEMPORARY TABLE t_tt SELECT 1 AS tid,'team 1' AS teamName,111 AS teamData;
INSERT INTO t_tt VALUES(2,'team 2',222);
INSERT INTO t_tt VALUES(3,'team 3',333);
SELECT
tid,
isnull(tid),
IF(isnull(tid),'total',teamName) AS displayName,
SUM(teamData)
FROM t_tt GROUP BY tid WITH ROLLUP;
And I think the result must be :
+-----+-------------+-------------+---------------+
| tid | isnull(tid) | displayName | SUM(teamData) |
+-----+-------------+-------------+---------------+
| 1 | 0 | team 1 | 111 |
| 2 | 0 | team 2 | 222 |
| 3 | 0 | team 3 | 333 |
| 3 | 1 | total | 666 |
+-----+-------------+-------------+---------------+
Actually,the real answer is :
+-----+-------------+-------------+---------------+
| tid | isnull(tid) | displayName | SUM(teamData) |
+-----+-------------+-------------+---------------+
| 1 | 0 | team 2 | 111 |
| 2 | 0 | team 3 | 222 |
| 3 | 0 | team 3 | 333 |
| NULL| 0 | team 3 | 666 |
+-----+-------------+-------------+---------------+
I dont know why the column of displayName is begins with "team 2" ,but not "team 1".
And the last row isnull(tid) should equals 1 , but not 0.
The problem is that you are using a MySQL extension. You have teamName in the SELECT, but it is not in the GROUP BY. So, MySQL is free to take this value from any row. To be honest, I thought it would need to be from a matching row. I don't fully understand the behavior.
But, this is easy to fix. One simple method is simply to add an aggregation function:
SELECT tid, tid is null,
COALESCE(max(teamName), 'total') AS displayName,
SUM(teamData)
FROM t_tt
GROUP BY tid WITH ROLLUP;
Note that for the additional row from the ROLLUP, the value is arbitrary.
I also found that the results were fixed if the table were pre-declared with a primary key:
CREATE TABLE t_tt (
tid int primary key,
teamName varchar(255),
teamData int
);
INSERT INTO t_tt VALUES(1, 'team 1', 111);
INSERT INTO t_tt VALUES(2, 'team 2', 222);
INSERT INTO t_tt VALUES(3, 'team 3', 333);
It's better to process all the extra information out of the group query, since some columns not grouped could have an arbitrary value:
SELECT tid,
isnull(tid),
IF(isnull(tid),'total',teamName) AS displayName,
team_sum
FROM
(
SELECT
tid,
teamName,
SUM(teamData) as team_sum
FROM t_tt
GROUP BY tid WITH ROLLUP
) t
See this Sql Fiddle: http://sqlfiddle.com/#!9/8871cc/9
I have a table with below-mentioned columns:
ID (int) AUTO_INCREMENT PRIMARY KEY
DOCTOR_ID (int)
PATIENT_IN_TIME (datetime)
AVG_CHECKUP_TIME
I want to subtract row 1 PATIENT_IN_TIME with row 2 PATIENT_IN_TIME and save the result in minutes to AVG_CHECKUP_TIME.
Suppose there are 5 entries in the table.
|1|2|2018-03-22 02:49:51|NULL|
|2|2|2018-03-22 02:56:37|NULL|
So I want to find the difference of both the rows and save the minutes in the last column. So, the output would look like,
|1|2|2018-03-22 02:49:51|7|
|2|2|2018-03-22 02:56:37|NULL|
Please let me know if you need more information.
create table tbl
(
id int auto_increment primary key,
doctor_id int,
patient_in_time datetime,
avg_checkup_time datetime
);
insert into tbl values
(1, 2, '2018-03-22 02:49:51', null),
(2, 2, '2018-03-22 02:56:37', null),
(3, 2, '2018-03-22 03:00:15', null),
(4, 2, '2018-03-22 03:03:37', null);
select t1.id, t1.doctor_id, t1.patient_in_time,
timestampdiff(minute, t1.patient_in_time,
(select patient_in_time
from tbl where id = t1.id +1)) diff
from tbl t1
id | doctor_id | patient_in_time | diff
-: | --------: | :------------------ | ---:
1 | 2 | 2018-03-22 02:49:51 | 6
2 | 2 | 2018-03-22 02:56:37 | 3
3 | 2 | 2018-03-22 03:00:15 | 3
4 | 2 | 2018-03-22 03:03:37 | null
dbfiddle here
As per comments if order is set by patient_in_time then you can use an scalar subquery that returns next row in this way:
select t1.id,
t1.doctor_id,
t1.patient_in_time,
timestampdiff(minute,
t1.patient_in_time,
(select patient_in_time
from tbl
where patient_in_time > t1.patient_in_time
order by patient_in_time asc
limit 1)) diff
from tbl t1
order by patient_in_time
id | doctor_id | patient_in_time | diff
-: | --------: | :------------------ | ---:
1 | 2 | 2018-03-22 02:49:51 | 6
2 | 2 | 2018-03-22 02:56:37 | 3
3 | 2 | 2018-03-22 03:00:15 | 3
4 | 2 | 2018-03-22 03:03:37 | null
dbfiddle here
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 |
Performing a WITH ROLLUP when grouping by multiple fields, MySQL returns a rollup row for each group, as well as the overall summary:
CREATE TABLE test (name VARCHAR(50), number TINYINT);
INSERT INTO test VALUES
('foo', 1), ('foo', 1), ('foo', 2), ('foo', 3), ('foo', 3),
('bar', 1), ('bar', 2), ('bar', 2), ('bar', 2), ('bar', 3),
('baz', 1), ('baz', 2), ('bar', 2);
SELECT name, number, COUNT(1) FROM test GROUP BY name, number WITH ROLLUP;
+------+--------+----------+
| name | number | count(1) |
+------+--------+----------+
| bar | 1 | 1 |
| bar | 2 | 3 |
| bar | 3 | 1 |
| bar | NULL | 5 |
| baz | 1 | 1 |
| baz | 2 | 2 |
| baz | NULL | 3 |
| foo | 1 | 2 |
| foo | 2 | 1 |
| foo | 3 | 2 |
| foo | NULL | 5 |
| NULL | NULL | 13 |
+------+--------+----------+
I'm not interested in the rollups for foo/bar/baz, only the overall summary. What's the most efficient way to achieve this?
I know I can't use HAVING due to the rollup rows being added afterwards. Is the best solution to use a nested query for this, selecting where name and number are either both NOT NULL or both NULL?
HAVING can do the trick with no subquery:
SELECT `name`, number, COUNT(1) FROM test GROUP BY `name`, number WITH ROLLUP
HAVING number IS NOT NULL OR `name` IS NULL;
This filters out the post-rollup rows except for the grand total:
name number COUNT(1)
------ ------ --------
bar 1 1
bar 2 4
bar 3 1
baz 1 1
baz 2 1
foo 1 2
foo 2 1
foo 3 2
(NULL) (NULL) 13
Try to use a subquery, e.g. -
SELECT * FROM (
SELECT name, number, COUNT(1) FROM test GROUP BY name, number WITH ROLLUP) t
WHERE name IS NULL OR number IS NULL
You also may want to change NULL values with appropriate texts.
SELECT COALESCE(name, 'TOTAL') as Name, number, COUNT(1) FROM test GROUP BY name, number WITH ROLLUP;
Below Name column it would Display as Total. If you have issue with number as null same can be done for that too.