ORDER by multiple queries in MySql - mysql

I am trying to build a chat list page where latest sent/received contact is shown at the top from one table. For this, I have a table smshistory where i store sent/received sms with numbers where one is company phone and other is client phone number
CREATE TABLE IF NOT EXISTS `smshistory` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`fromnumber` varchar(20) NOT NULL,
`tonumber` varchar(20) NOT NULL,
`sms` varchar(20) NOT NULL,
`added` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=11 ;
--
-- Dumping data for table `smshistory`
--
INSERT INTO `smshistory` (`id`, `fromnumber`, `tonumber`, `sms`, `added`) VALUES
(1, 'companynum', 'client1num', 'Hello', '2021-07-16 12:28:23'),
(2, 'companynum', 'client2num', 'Hello', '2021-07-16 12:28:23'),
(3, 'companynum', 'client3num', 'Hello', '2021-07-16 12:28:23'),
(4, 'client1num', 'companynum', 'Hi', '2021-07-16 12:28:23'),
(5, 'companynum', 'client1num', 'Hello', '2021-07-16 12:28:23'),
(6, 'client1num', 'companynum', 'Hi', '2021-07-16 12:28:23'),
(7, 'client2num', 'companynum', 'Hi', '2021-07-16 12:28:23'),
(8, 'companynum', 'client2num', 'Hello', '2021-07-16 12:28:23'),
(9, 'client3num', 'companynum', 'Hi', '2021-07-16 12:28:23');
As first message will always be from company number, so I am showing DISTINCT list with:
SELECT DISTINCT (`tonumber`) FROM `smshistory` WHERE `fromnumber` = $companynum
Which gives me list like:
client1num
client2num
client3num
Requirement:
What I require is to show the DISTINCT with order of added DESC column in a way that if a client's number is in fromnumber or tonumber, it should show at top. So, according to my table, results should be:
client3num
client2num
client1num
Fiddle is at http://sqlfiddle.com/#!9/4256d1d/1
Any idea on how to achieve that?

In answer to your question you can use the following query:
SELECT distinct client_num from (
SELECT CASE WHEN fromnumber = 'companynum' THEN tonumber
ELSE fromnumber END client_num
FROM smshistory ORDER BY id DESC ) as a

For each of the rows that contain $companynum either in fromnumber or in tonumber you must extract the client's number with a CASE expression and use GROUP BY to remove duplicates.
Finally, sort the results by the max value of added:
SELECT CASE WHEN fromnumber = $companynum THEN tonumber ELSE fromnumber END client_num
FROM smshistory
WHERE $companynum IN (fromnumber, tonumber)
GROUP BY client_num
ORDER BY MAX(added) DESC

In answer to the question 'How do I sort a de-normalised collection of strings (that a follow a very constrained pattern) according to the numerals contained within those strings?', here's one method...
DROP TABLE IF EXISTS x;
CREATE TABLE x
(id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,bad_string VARCHAR(20) NOT NULL
);
INSERT INTO x VALUES (11,'client2num'),(17,'client3num'),(21,'client1num');
SELECT REPLACE(REPLACE(bad_string,'client',''),'num','') p FROM x;
+---+
| p |
+---+
| 2 |
| 3 |
| 1 |
+---+
...which can be rewritten as follows:
SELECT * FROM x ORDER BY REPLACE(REPLACE(bad_string,'client',''),'num','') DESC;
+----+------------+
| id | bad_string |
+----+------------+
| 17 | client3num |
| 11 | client2num |
| 21 | client1num |
+----+------------+

Related

SQL search multitable and contains

Suppose that i have the following data:
CREATE TABLE IF NOT EXISTS `Class` (
`id` INT NOT NULL AUTO_INCREMENT,
`class name` CHAR(55) NOT NULL,
PRIMARY KEY (`id`)
);
REPLACE INTO `Class` (`id`,`class name`) VALUES
(1,'Mammalia'),(2, 'Amphibia'),(3, 'Aves'),(4, 'Reptile');
CREATE TABLE IF NOT EXISTS `Animals` (
`id` INT NOT NULL AUTO_INCREMENT,
`class_id` INT DEFAULT NULL,
`name` CHAR(55) NOT NULL,
PRIMARY KEY (`id`),
FOREIGN KEY (`class_id`) REFERENCES `Class`(`id`)
);
INSERT INTO `Animals` (`class_id`,`name`) VALUES
(1,'Przewalskis horse'),
(1,'Bos taurus'),
(1,'Sus scrofa'),
(1,'Panthera leo'),
(1,'Felis catus'),
(1,'Canis lupus'),
(1,'Pan troglodytes'),
(1,'Nasalis larvatus'),
(1,'Ailuropoda melanoleuca'),
(1,'Elephas maximus sumatranus'),
(1,'Panthera pardus orientalis'),
(2, 'Bufu Bufu'),
(2, 'Notophthalmus viridescens'),
(2, 'Dermophis mexicanus'),
(3, 'Campephilus melanoleucos melanoleucos'),
(3, 'Tyto alba'),
(3, 'Cathartes aura'),
(3, 'Serinus canaria'),
(3, 'Amazona aestiva'),
(3, 'Amazona Oratrix'),
(3, 'Anodorhynchus hyacinthinus'),
(3, 'Ara ararauna'),
(3, 'Ara chloropterus'),
(3, 'Strigops habroptilus'),
(4, 'Ameivula venetacaudus'),
(4, 'Chelonia mydas'),
(4, 'Caretta caretta');
I want to do a query with only one input html field that return the following results:
if i type: "Amphibia" returns:
-------------------
| Class | Animals |
-------------------
| Amphibia | Bufu Bufu |
| Amphibia | Notophthalmus viridescens |
| Amphibia | Dermophis mexicanus |
----------------------------------
if i type: "Amphibia Amazona" returns:
-------------------
| Class | Animals |
-------------------
| Amphibia | Bufu Bufu |
| Amphibia | Notophthalmus viridescens |
| Amphibia | Dermophis mexicanus |
| Aves | Amazona aestiva |
| Aves | Amazona Oratrix |
--------------------------
if i type: "caudu" returns
-------------------
| Class | Animals |
-------------------
| Reptile | Ameivula venetacaudus |
--------------------------
Sample codes in Fiddle are welcome!
Thankful right now
Considering that input_var is the variable that you will receive from your HTML, you can do the following query:
SELECT `class name`, name
FROM Animals
LEFT JOIN Class
ON (Class.id= Animals.class_id)
WHERE `class name` like concat('%',SUBSTRING_INDEX(SUBSTRING_INDEX(input_var, ' ', 1), ' ', -1),'%')
OR name like concat('%',SUBSTRING_INDEX(SUBSTRING_INDEX(input_var, ' ', 1), ' ', -1),'%')
OR `class name` like concat('%',SUBSTRING_INDEX(SUBSTRING_INDEX(input_var, ' ', 2), ' ', -1),'%')
OR name like concat('%',SUBSTRING_INDEX(SUBSTRING_INDEX(input_var, ' ', 2), ' ', -1),'%')
;
I am also considering that you can receive an input with the inversed animal/class order, like "Amazona Amphibia" instead of "Amphibia Amazona".
If may also consider if case sensitive is important to you. If not, you may use upper in both your input string and the table column.
Keep this as your base.
select id, `class name`, `name`
from animals
left join class
on (class.id= animals.class_id)
And for searching
select `class name`, `name` from
(select ...)
where concat("%", "$search1", "%") like `class name` or
concat("%", "$search2", "%") like `name`;
To split the first word (mysql)
substring_index(substring_index(var, ' ', 1), ' ', -1)
and for second word (mysql)
substring_index(substring_index(var, ' ', 2), ' ', -1)
In PHP you can use substr function.

How to add a character to specific index in string

I have a db with email-addresses which are 15 characters in total.
Some are 14 characters long and they have the same in common, they miss a 0 in their name at the 3th index.
I had a what similar problem with the id numbers but that was easy to fix because i had to push 0 to the first index.
My expected results should be changing 'aa-001#test.me' to 'aa-0001#test.me'.
To add a 0 as 4th character in emails having 14 characters, you may simply use SUBSTR :
SELECT CONCAT(SUBSTR(email, 1, 3), '0', SUBSTR(email, 4))
FROM mytable
WHERE LENGTH(email) = 14
Another solution is to use REGEXP_REPLACE, with a regular expression that matches only on emails with 14 characters ; this avoids the need for a WHERE clause, as emails that do not match the pattern will be returned untouched :
SELECT REGEXP_REPLACE(email, '^(.{3})(.{11})$', CONCAT('$1', '0', '$2'))
FROM t
db<>fiddle here :
WITH t AS (SELECT 'aa-001#test.me' email UNION SELECT 'bb-0002#test.me')
SELECT CONCAT(SUBSTR(email, 1, 3), '0', SUBSTR(email, 4))
FROM t
WHERE LENGTH(email) = 14
| CONCAT(SUBSTR(email, 1, 3), '0', SUBSTR(email, 4)) |
| :----------------------------------------------------- |
| aa-0001#test.me |
WITH t AS (SELECT 'aa-001#test.me' email UNION SELECT 'bb-0002#test.me')
SELECT REGEXP_REPLACE(email, '^(.{3})(.{11})$', CONCAT('$1', '0', '$2'))
FROM t
| REGEXP_REPLACE(email, '^(.{3})(.{11})$', CONCAT('$1', '0', '$2')) |
| :---------------------------------------------------------------- |
| aa-0001#test.me |
| bb-0002#test.me

Creating a Daily Report from non-Daily time-serie without the use of a calendar table

I have a "time_serie" table with date gaps (not existing dates) that looks like:
+-----+-------+-------------+------+
| id | TS_ID | Date_publi | Val |
+-----+-------+-------------+------+
| 4 | 3 | 1996-11-01 | 50.5 |
| 5 | 3 | 1996-12-02 | 53 |
| 6 | 3 | 1997-01-02 | 55.2 |
... ... .......... ...
I would like to create an output which replaces missing values with either zeros or #N/A or previous value so it looks like:
1996-10-30 : #N/A
1996-10-31 : #N/A
1996-11-01 : 50.5
1996-11-02 : #N/A
1996-11-03 : #N/A
.... ...
To do so, I thought about creating a "calendar" table with every single date in it and then call the right-join query:
SELECT calendar.dates AS DATE, IFNULL(time_serie.val, "#N/A") AS val
FROM time_serie RIGHT JOIN calendar ON (DATE(time_serie.date_publi) = calendar.dates)
WHERE (calendar.datefield BETWEEN start_date AND end_date)
But, I would rather not have to create and manage a calendar table.
Does someone has an idea how to do such a report without using a calendar table?
You can use the following to get it done without using Calendar table. It isn't in MySQL but would do the trick:
DECLARE #temp TABLE (Date DATETIME, Users NVARCHAR(40), Score INT) ---- Temporary table strucrure
INSERT INTO #temp (Date, Users, Score)
VALUES ---- Default values
('20120101', 'User1', 17),('20120201', 'User1', 19),
('20120401', 'User1', 15),('20120501', 'User1', 16),
('20120701', 'User1', 14),('20120801', 'User1', 15),
('20120901', 'User1', 15),('20121001', 'User1', 13),
('20121201', 'User1', 11),('20130101', 'User1', 10),
('20130201', 'User1', 15),('20130301', 'User1', 13),
('20130501', 'User1', 18),('20130601', 'User1', 14),
('20130801', 'User1', 15),('20130901', 'User1', 14),
('20161001', 'User1', 10),('20120601', 'User1', 10)
;WITH cte AS ---- Created a common table expression. You can say another query executed here
(
SELECT Users, StartDate = MIN(Date), EndDate = MAX(Date) ---- Retrieved the max and min date from the table
FROM #temp
GROUP BY Users
UNION ALL ---- Used 'UNION ALL' to combine all the dates missing and existing one
SELECT Users, DATEADD(MONTH, 1, StartDate), EndDate ---- Checks the months that are missing
FROM cte
WHERE StartDate <= EndDate
)
SELECT e.StartDate, t.Users, Score = ISNULL(t.Score, 0) ---- Finally checks the user scores for the existing and non-existing months using 'CTE'
FROM cte e
LEFT JOIN #temp t ON e.StartDate = t.Date AND e.Users = t.Users
ORDER BY e.StartDate, t.Users
The above returns 0 or null if there is no entry for a specific month.
Please check this out for more: Find Missing Dates - MySQL
More specifically, this would do just fine: Find Missing Dates - MySQL 2

mysql 5.6: group_concat help sought

I have the tables:
CREATE TABLE IF NOT EXISTS `buildingAccess` (
`id` int(10) unsigned NOT NULL,
`building` varchar(16) NOT NULL,
`person` varchar(16) NOT NULL,
`enteryDate` datetime NOT NULL,
`exitDate` datetime DEFAULT NULL
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8;
INSERT INTO `buildingAccess` (`id`, `building`, `person`, `enteryDate`, `exitDate`) VALUES
(1, 'Lot B-3', 'Alice Jones', '2015-11-10 05:29:14', '2015-11-10 15:18:42'),
(3, 'Lot B-3', 'Alice Jones', '2015-11-11 07:11:27', '2015-11-11 12:43:34'),
(7, 'Lot B-3', 'Alice Jones', '2015-12-10 07:11:27', '2015-12-11 12:43:34'),
(2, 'Lot B-3', 'Bill Mayhew', '2015-11-10 10:29:14', '2015-11-10 12:18:42'),
(4, 'Lot B-3', 'Bill Mayhew', '2015-11-12 09:10:27', '2015-11-13 02:43:34'),
(8, 'Lot B-3', 'Bill Mayhew', '2015-11-12 09:10:27', '2015-11-13 02:43:34'),
(5, 'Lot B-3', 'Charlotte Ahn', '2015-12-01 05:29:14', NULL),
(6, 'Lot B-3', 'Dennis Lowe', '2015-12-10 10:29:14', '2015-12-10 12:18:42');
CREATE TABLE IF NOT EXISTS `buildingNotes` (
`building` varchar(16) NOT NULL,
`observer` varchar(16) NOT NULL,
`observationDate` datetime NOT NULL,
`note` varchar(64) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `buildingNotes` (`building`, `observer`, `observationDate`, `note`) VALUES
('Lot B-3', 'Alice Jones', '2015-11-10 05:32:12', 'burned out light on pole South-22'),
('Lot B-3', 'Alice Jones', '2015-11-10 05:39:12', 'burned out light on pole West-7'),
('Lot B-3', 'Alice Jones', '2015-11-10 05:42:12', 'overfull trash can near pole North-11'),
('Lot B-3', 'Charlotte Ahn', '2015-12-01 06:09:14', 'change drawr running low at gate 3');
ALTER TABLE `buildingAccess`
ADD PRIMARY KEY (`id`), ADD KEY `building` (`building`,`person`,`enteryDate`,`exitDate`);
ALTER TABLE `buildingNotes`
ADD KEY `building` (`building`,`observer`,`observationDate`,`note`);
ALTER TABLE `buildingAccess`
MODIFY `id` int(10) unsigned NOT NULL AUTO_INCREMENT,AUTO_INCREMENT=9;
My goal is a query that returns a list of all records in the buildingAccess table. Each should have a notes field that is the GROUP_CONCAT of all of the buildingNotes.note entries made during that record's dates/times bracketed by buildingAccess.enteryDate and buildingAccess.exitDate.
I have tried a few things but am stuck at:
select
BA.building,
BA.person,
BA.enteryDate,
IFNULL(BA.exitDate, NOW()),
IFNULL(
GROUP_CONCAT(
'<p>',
BN.observationDate, ': ',
BN.observer, ': ', BN.note,
'</p>'
ORDER BY BN.observationDate ASC
SEPARATOR ''
),
''
)
from
buildingAccess BA
LEFT JOIN buildingNotes BN ON
BN.building = BA.building
AND BN.observationDate BETWEEN BA.enteryDate AND BA.exitDate
group by BN.building
This returns:
+----------+---------------+---------------------+---------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| building | person | enteryDate | exitDate | observations |
+----------+---------------+---------------------+---------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Lot B-3 | Charlotte Ahn | 2015-12-01 05:29:14 | 2016-01-23 23:04:04 | |
| Lot B-3 | Alice Jones | 2015-11-10 05:29:14 | 2015-11-10 15:18:42 | <p>2015-11-10 05:32:12: Alice Jones: burned out light on pole South-22</p><p>2015-11-10 05:39:12: Alice Jones: burned out light on pole West-7</p><p>2015-11-10 05:42:12: Alice Jones: overfull trash can near pole North-11</p> |
+----------+---------------+---------------------+---------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
I expected to see all of the other buildingAccess records even if there were no buildingNotes records.
I am assuming that I am not "grouping by" the right things but i have not found the right combination yet.
Pointers?
I believe it is due to use BN.building which is from the outer joined table in the GROUP BY clause. Try the following:
SELECT
BA.building
, BA.person
, BA.enteryDate
, IFNULL(BA.exitDate, NOW())
, IFNULL (
GROUP_CONCAT (
'<p>',
BN.observationDate, ': ',
BN.observer, ': ', BN.note,
'</p>'
ORDER BY BN.observationDate ASC
SEPARATOR ''
)
,''
)
FROM buildingAccess BA
LEFT JOIN buildingNotes BN ON BN.building = BA.building
AND BN.observationDate BETWEEN BA.enteryDate AND BA.exitDate
GROUP BY
BA.building
, BA.person
, BA.enteryDate
, IFNULL(BA.exitDate, NOW())
It s possible this is too many rows, and perhaps you need some other way to handle (for example) only getting the date rather than full datetime. However you should routinely specify ALL the non-aggregating columns of a query in the group by caluse. See MySQL GROUP BY Extension

mysql select with time range and condition

error_tbl success_tbl
id | creationTime id|creationTime
1 2014-09-23 10:03:40 1212 2014-09-23 10:02:40
1213 2014-09-23 10:03:40
1214 2014-09-23 10:10:40
After run of query I want to have this:
result table
creationTime | fail_ids | succ_ids
2014-09-23 10:03:40, 1, NULL
2014-09-23 10:02:40, NULL, 1212
2014-09-23 10:03:40, NULL, 1213
I want to select all data (success and fail) within time range of EACH AND EVERY 5 minutes only if any record exists in error_tbl within that period.
(SELECT
creationTime,
id as fail_ids,null as succ_ids
FROM error_tbl a
UNION
SELECT
creationTime,
null as fail_ids ,id as succ_ids
FROM success_tbl b
) s
Given the simplified schema with minor changes to make it look clean.
CREATE TABLE error(
`error_id` INT,
`creation_time` DATETIME
);
INSERT INTO error(`error_id`, `creation_time`) VALUES
(1, '2014-09-23 10:03:40'),
(2, '2014-09-23 10:04:05'),
(3, '2014-09-24 10:08:04');
CREATE TABLE success(
`success_id` INT,
`creation_time` DATETIME
);
INSERT INTO success(`success_id`, `creation_time`) VALUES
(1212, '2014-09-23 10:02:40'),
(1213, '2014-09-23 10:03:40'),
(1214, '2014-09-23 10:10:40');
This should work, though I don't expect it to perform well on big datasets.
SELECT CONCAT(
DATE_FORMAT(creation_time, '%Y-%m-%d %k:'),
LPAD(FLOOR(MINUTE(creation_time) / 5) * 5, 2, 0),
':00'
) `creation_time`,
GROUP_CONCAT(error_id) `error_ids`,
GROUP_CONCAT(success_id) `success_ids`
FROM (
(
SELECT NULL success_id, error_id, creation_time
FROM error
)
UNION
(
SELECT success_id, NULL error_id, creation_time
FROM success
)
) un
GROUP BY FLOOR(UNIX_TIMESTAMP(creation_time) / (5 * 60))
HAVING error_ids IS NOT NULL
SQL Fiddle snippet.