Related
I want to build a specific calendar feature.
As a basement I have the following MySQL table:
CREATE TABLE `presence` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`eid` int(10) unsigned DEFAULT NULL,
`start` timestamp NULL DEFAULT NULL,
`end` timestamp NULL DEFAULT NULL,
`frequence` int(11) DEFAULT NULL,
`fulltime` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
with the following example data:
INSERT INTO `presence` (`id`, `eid`, `start`, `end`, `frequence`, `fulltime`) VALUES (1, 14, "2021-11-29 10:00:00", "2021-12-13 18:00:00", NULL, NULL);
INSERT INTO `presence` (`id`, `eid`, `start`, `end`, `frequence`, `fulltime`) VALUES (2, 14, "2021-11-29 10:00:00", "2021-12-13 18:00:00", 1, NULL);
INSERT INTO `presence` (`id`, `eid`, `start`, `end`, `frequence`, `fulltime`) VALUES (3, 14, "2021-11-29 10:00:00", "2021-12-13 18:00:00", 1, 1);
INSERT INTO `presence` (`id`, `eid`, `start`, `end`, `frequence`, `fulltime`) VALUES (4, 14, "2021-11-29 10:00:00", "2021-12-13 18:00:00", NULL, 1);
INSERT INTO `presence` (`id`, `eid`, `start`, `end`, `frequence`, `fulltime`) VALUES (5, 14, "2021-11-29 10:00:00", NULL, 1, 1);
In my app environment I have the following examples:
row id#1 means, that an employee (eid: 14) is present every day from 2021-11-29 to 2021-12-13. Each day from 10 am to 6 pm.
row id#2 means, that an employee (eid: 14) is present every monday from 2021-11-29 to 2021-12-13. Each Monday from 10 am to 6 pm. Monday because start is a Monday and because frequence = 1.
row id#3 means, that an employee (eid: 14) is present every monday from 2021-11-29 to 2021-12-13. Each Monday the entire day, because fullday = 1. Thus, ignoring the time.
row id#4 means, that an employee (eid: 14) is present every day from 2021-11-29 to 2021-12-13. Each day the entire day, because fullday = 1 tells again to ignore the time.
row id#5 finally has an open end for the presence. It means the employee is present every Monday from 2021-11-29 until infinity.
I am working on the following query the entire day and I'm at stage where I can't tell what I expect anymore...
SET #date = "2021-12-13 19:00:00";
SET #fullday = 0;
SELECT start, end, frequence, fulltime
FROM `presence`
WHERE (
IF (#fullday = 1,
DATE(#date) >= DATE(`start`)
,
DATE(#date) >= DATE(`start`) AND
TIME(#date) >= TIME(`start`)
)
)
AND (
IF (#fullday = 1,
DATE(#date) >= DATE(`end`)
,
DATE(#date) >= DATE(`end`) AND
TIME(#date) >= TIME(`end`)
)
)
AND (
`frequence` IS NULL OR
`frequence` = 1 AND WEEKDAY(#date) = WEEKDAY(`start`)
)
In my app I will have a php function employee_is_present(int $eid, string $date): bool where $date can be either Y-m-d H:i:s or just Y-m-d, to check if an employee is generally available on a day (not having a time) or at a Very specific time. This is what I try to mimic using SET #fullday = 0|1;
According to the input, date could be either with or without time, it is easier to separate these two situations.
We can do that since we are using function call to query
Checking date only could use this query
SET #eid =14;
SET #date = DATE("2021-12-13");
SELECT *
FROM presence
WHERE #date >= DATE(`start`) AND ( `end` IS NULL OR #date <= DATE(`end`))
AND (`frequence` IS NULL OR (`frequence` = 1 AND WEEKDAY(`start`) = WEEKDAY(#date)))
AND eid = #eid
Checking with time, we use the above query result and filter the time
SET #eid =14;
SET #date = DATE("2021-12-13 19:00:00");
SET #time = TIME(#date);
SELECT *
FROM (
SELECT *
FROM presence
WHERE #date >= DATE(`start`) AND ( `end` IS NULL OR #date <= DATE(`end`))
AND (`frequence` IS NULL OR (`frequence` = 1 AND WEEKDAY(`start`) = WEEKDAY(#date)))
AND eid = #eid
) f
WHERE `fulltime` = 1 OR (TIME(`start`) <= #time AND #time <= TIME(`end`))
https://dbfiddle.uk/?rdbms=mysql_5.7&fiddle=3e207fed77e34a6b458e79764e382156
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 |
+----+------------+
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
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
I have 2 tables and a view. In product_oper I have some products that I receive (when id_dest is 1) and that I sell (when id_src is 1). The table product_doc contains the date when the operation took place.
CREATE TABLE product_doc (
id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
doc_date date NOT NULL,
doc_no char(16) NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB;
INSERT INTO product_doc (id,doc_date,doc_no) VALUES
(1,'2009-10-07','1'),
(2,'2009-10-14','2'),
(3,'2009-10-28','4'),
(4,'2009-10-21','3');
CREATE TABLE product_oper (
id bigint(12) unsigned NOT NULL AUTO_INCREMENT,
id_document bigint(20) unsigned NOT NULL,
prod_id bigint(12) unsigned NOT NULL DEFAULT '0',
prod_quant decimal(16,4) NOT NULL DEFAULT '1.0000',
prod_value decimal(18,2) NOT NULL DEFAULT '0.00',
id_dest bigint(20) unsigned NOT NULL,
id_src bigint(20) unsigned NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB;
INSERT INTO product_oper (id,id_document,prod_id,prod_quant,prod_value,id_dest,id_src)
VALUES
(10,1,1,'2.0000', '5.00',1,0),
(11,3,1,'0.5000', '1.20',0,1),
(12,1,2,'3.0000','26.14',1,0),
(13,2,2,'0.5000','10.20',0,1),
(14,3,2,'0.3000', '2.60',0,1),
(15,4,2,'1.0000', '0.40',1,0);
In the view I want to see all the operations and the dates.
CREATE VIEW product_oper_view AS
SELECT product_oper.*, product_doc.doc_date AS doc_date, product_doc.doc_no AS doc_no
FROM product_oper JOIN product_doc ON product_oper.id_document = product_doc.id
WHERE 1;
Now I want to see the operations of a single product, and the amount and value at a specific date.
SET #amount=0.000, #balance=0.00;
SELECT product_oper_view.*,
IF(id_dest<>0, prod_quant, NULL) AS q_in,
IF(id_dest<>0, prod_value, NULL) AS v_in,
IF(id_src<>0, prod_quant, NULL) AS q_out,
IF(id_src<>0, prod_value, NULL) AS v_out,
#amount:=#amount + IF(id_dest<>0, 1, -1)*prod_quant AS q_amount,
#balance:=#balance + IF(id_dest<>0, 1, -1)*prod_value AS v_balance
FROM product_oper_view
WHERE prod_id=2 AND (id_dest=1 OR id_src=1)
ORDER BY doc_date;
The result I get is strange:
id, id_ prod_ prod_ id_ id_ doc_date, q_in, v_in, q_ v_
doc, quant,value,dest,src, q_out, v_out, amount, balance
12, 1, 3.0000, 26.14, 1, 0, '2009-10-07', 3.0000, 26.14, NULL , NULL, 3.000, 26.14
13, 2, 0.5000, 10.20, 0, 1, '2009-10-14', NULL , NULL, 0.5000, 10.20, 2.500, 15.94
15, 4, 1.0000, 0.40, 1, 0, '2009-10-21', 1.0000, 0.40, NULL , NULL, 3.200, 13.74
14, 3, 0.3000, 2.60, 0, 1, '2009-10-28', NULL , NULL, 0.3000, 2.60, 2.200, 13.34
The amount starts from zero,
at row 1: +3 => 3 (ok)
at row 2: -0.5 => 2.5 (ok)
at row 3: +1 => 3.2 (???)
at row 4: -0.3 => 2.2 (???)
It seems that MySQL doesn't take the order of rows specified in the ORDER BY clause when executing the statement, and it looks after the id: See that document with id 4 is before document with id 3 ('2009-10-21' < '2009-10-28')
Am I doing something wrong, or is it a bug of MySQL?
If I'm not totally wrong the ORDER-operation is one of the last things done when preparing the result set. Therefore your calculations are done before ordering the results. The correct way to circumvent this problem should be to use a subselect:
SET #amount=0.000, #balance=0.00;
SELECT p.*,
#amount:=#amount + IF(p.id_dest <> 0, 1, -1) * p.prod_quant AS q_amount,
#balance:=#balance + IF(p.id_dest <> 0, 1, -1) * p.prod_value AS v_balance
FROM (
SELECT product_oper_view.*,
IF(product_oper_view.id_dest <> 0, product_oper_view.prod_quant, NULL) AS q_in,
IF(product_oper_view.id_dest <> 0, product_oper_view.prod_value, NULL) AS v_in,
IF(product_oper_view.id_src <> 0, product_oper_view.prod_quant, NULL) AS q_out,
IF(product_oper_view.id_src <> 0, product_oper_view.prod_value, NULL) AS v_out
FROM product_oper_view
WHERE product_oper_view.prod_id = 2
AND (product_oper_view.id_dest = 1 OR product_oper_view.id_src = 1)
ORDER BY product_oper_view.doc_date
) AS p