I have a database table with holiday requests in it. I now want to calculate, how many days the user has requested for a certain year. So far I did this:
Table:
CREATE TABLE `holidays` (
`id` int(11) NOT NULL,
`user` int(11) NOT NULL,
`begin` date NOT NULL,
`end` date NOT NULL,
`comment_user` text NOT NULL,
`entered_at` int(11) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
Get all holidays in a year:
SELECT SUM((DATEDIFF(end, begin) + 1)) AS days
FROM holidays
WHERE user = :user AND begin LIKE '2021%'
But what can I do, if begin date is in the year before (e.g. 12/30/2020) or end date is in the next year (e.g. 01/05/2022), so the request extends over new year's eve.
EXAMPLE:
If I have a holiday request from 12/30/2020 to 01/02/2021, I wanna count just two days of that, not all four days.
Only the days in 2021
Is there any possibilty to limit the DATEDIFF calculation to 01/01/2021 AND 12/31/2021, but getting all requests affected in that year?
And how can I put that in one mysql-query? I can't find any solution on google for that.
Would be great, if someone had a similar problem and solved that.
Thank's in advance,
Tobias
I created a sample database like this (FIDDLE):
CREATE TABLE `holidays` (
`begin` date DEFAULT NULL,
`end` date DEFAULT NULL
)
INSERT INTO `holidays` VALUES
('2020-12-28','2021-01-05'),
('2020-12-05','2020-12-06'),
('2021-01-06','2021-01-06');
This SQL-statement:
select
begin,
CASE WHEN year(end)>year(begin) then ADDDATE(makedate(year(`end`),1),INTERVAL -1 DAY) else `end` end as END ,
year(begin) as year
from holidays
union all
select
makedate(year(`end`),1),
`end`,
year(`end`)
from holidays
where year(`begin`)<>year(`end`);
will have as output:
+------------+------------+------+
| begin | END | year |
+------------+------------+------+
| 2020-12-28 | 2020-12-31 | 2020 |
| 2020-12-05 | 2020-12-06 | 2020 |
| 2021-01-06 | 2021-01-06 | 2021 |
| 2021-01-01 | 2021-01-05 | 2021 |
+------------+------------+------+
BTW: One should never use, or at least try to avoid, reserved words in a table definition (like 'begin', 'end')
Related
Is it possible to create multiple querys choosing the day of week?
MYSQL query:
create table reservations (
id bigint(20) NOT NULL,
var_start datetime NOT NULL,
var_end datetime NOT NULL
)
For example
var_day = "3" // Wednesday
var_month = "11" // November
var_year = "2018"
var_start = "11:00” // 11 am
var_end = "13:00” // 1 pm
//This will create all inserts according all wednesday on november 2018.
insert into table var_start = "07-11-2018 11:00:00" var_end = "07-11-2018 13:00:00"
insert into table var_start = "14-11-2018 11:00:00" var_end = "07-11-2018 13:00:00”
insert into table var_start = "21-11-2018 11:00:00" var_end = "07-11-2018 13:00:00”
insert into table var_start = "28-11-2018 11:00:00" var_end = "07-11-2018 13:00:00”
Here is the html demo
I will appreciate some links or concepts to search the correct information about automatic querys or this kind of concepts.
Thanks in advance.
We will be basically dynamically generating all the required dates within the query itself. And then use that result-set to Insert into the reservations table.
I have changed id column to Primary Key and Auto Increment (as it should be).
In a Derived table, we will use a number generator from 0 to 4, as there can be at maximum 5 Wednesdays (and other weekdays) in a month.
Now we will try to get the first Sunday of the required month. For that, we will firstly create a date corresponding to first date of the month, using the input variable values for the month and year:
STR_TO_DATE(CONCAT('2018','11','01'), '%Y%c%d')
Concat('2018','11','01') basically generates 20181101 string. We can then use Str_to_date() function to convert it into MySQL date format. We could have use Concat() function directly to get in YYYY-MM-DD format; but this approach should be robust in case the input month is 9 instead of 09.
Now, we will use various Datetime functions to determine the nth Wednesday. I have expanded over the answer originally given here: https://stackoverflow.com/a/13405764/2469308
Number generator table will help us in calculating the 1st, 2nd, 3rd Wednesday and so on.. We can basically get the first Wednesday by adding 3 number of days to the first Sunday. Afterwards, we will basically add 7 days everytime to get next Wednesday in the month.
Eventually, we will use all these dates and AddTime() to them for determining var_start and var_end accordingly. Also, there is a possibility that in the 5th day, it may cross to next month. So we will filter those out using WHERE MONTH(..) .. AND YEAR(..) .. conditions.
Finally, INSERT INTO.. SELECT statement will be used to insert into the reservations table.
Schema (MySQL v5.7) View on DB Fiddle
create table reservations (
id bigint(20) NOT NULL PRIMARY KEY AUTO_INCREMENT,
var_start datetime NOT NULL,
var_end datetime NOT NULL
);
/*
var_day = "3" // Wednesday
var_month = "11" // November
var_year = "2018"
var_start = "11:00” // 11 am
var_end = "13:00” // 1 pm
*/
Query #1
INSERT INTO reservations (var_start, var_end)
SELECT
ADDTIME(dates.nth_date, '11:00') AS var_start,
ADDTIME(dates.nth_date, '13:00') AS var_end
FROM
(
SELECT
STR_TO_DATE(CONCAT('2018','11','01'), '%Y%c%d') +
INTERVAL (6 -
WEEKDAY(STR_TO_DATE(CONCAT('2018','11','01'), '%Y%c%d')) +
3 +
(7*nth)) DAY AS nth_date
FROM
(SELECT 0 AS nth UNION ALL
SELECT 1 UNION ALL
SELECT 2 UNION ALL
SELECT 3 UNION ALL
SELECT 4) AS num_gen
) AS dates
WHERE MONTH(dates.nth_date) = 11 AND
YEAR(dates.nth_date) = 2018;
Query #2
SELECT * FROM reservations;
| id | var_start | var_end |
| --- | ------------------- | ------------------- |
| 1 | 2018-11-07 11:00:00 | 2018-11-07 13:00:00 |
| 2 | 2018-11-14 11:00:00 | 2018-11-14 13:00:00 |
| 3 | 2018-11-21 11:00:00 | 2018-11-21 13:00:00 |
| 4 | 2018-11-28 11:00:00 | 2018-11-28 13:00:00 |
In terms of input variables (prefixed with : for parametric queries), the query would looks as follows:
INSERT INTO reservations (var_start, var_end)
SELECT
ADDTIME(dates.nth_date, :var_start) AS var_start,
ADDTIME(dates.nth_date, :var_end) AS var_end
FROM
(
SELECT
STR_TO_DATE(CONCAT(:var_year,:var_month,'01'), '%Y%c%d') +
INTERVAL (6 -
WEEKDAY(STR_TO_DATE(CONCAT(:var_year,:var_month,'01'), '%Y%c%d')) +
:var_day +
(7*nth)) DAY AS nth_date
FROM
(SELECT 0 AS nth UNION ALL
SELECT 1 UNION ALL
SELECT 2 UNION ALL
SELECT 3 UNION ALL
SELECT 4) AS num_gen
) AS dates
WHERE MONTH(dates.nth_date) = :var_month AND
YEAR(dates.nth_date) = :var_year;
I have a table with the following setup:
CREATE TABLE IF NOT EXISTS `appointment` (
`appId` tinyint(3) UNSIGNED AUTO_INCREMENT NOT NULL,
`startDateTime` datetime,
`duration` time DEFAULT NULL
);
Sample Data:
appId startDateTime duration
1 2015-05-04 16:15:00 00:14:00
2 2015-05-12 08:15:00 05:54:00
3 2015-05-12 08:35:00 02:14:00
4 2016-05-04 08:11:00 04:11:00
5 2015-05-13 19:30:00 02:50:00
Expected Output:
appId startDateTime duration
2 2015-05-12 08:15:00 05:54:00
3 2015-05-12 08:35:00 02:14:00
I need a query that is able to check every entry in the table and return and entries that collide. In the sample data above, 2 and 3 will overlap. I can convert both of the fields to unix time and calculate the end time however I am not sure how to compare each entry
Any idea?
Using Faisal's fiddle...
-- ----------------------------
-- Table structure for appointment
-- ----------------------------
DROP TABLE IF EXISTS `appointment`;
CREATE TABLE `appointment` (
`appId` tinyint(10) NOT NULL AUTO_INCREMENT,
`startDateTime` datetime DEFAULT NULL,
`duration` time DEFAULT NULL,
PRIMARY KEY (`appId`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=latin1;
-- ----------------------------
-- Records of appointment
-- ----------------------------
INSERT INTO `appointment` VALUES
('1', '2015-05-04 16:15:00', '00:14:00'),
('2', '2015-05-12 08:15:00', '05:54:00'),
('3', '2015-05-12 08:35:00', '02:14:00'),
('4', '2016-05-04 08:11:00', '04:11:00'),
('5', '2015-05-13 19:30:00', '02:50:00');
SELECT DISTINCT x.*
FROM appointment x
JOIN appointment y
ON y.startdatetime < x.startdatetime + INTERVAL TIME_TO_SEC(x.duration) SECOND
AND y.startdatetime + INTERVAL TIME_TO_SEC(y.duration) SECOND > x.startdatetime
AND y.appid <> x.appid;
appId startDateTime duration
3 12.05.2015 08:35:00 02:14:00
2 12.05.2015 08:15:00 05:54:00
http://rextester.com/YJA59081
Try with this below query. Hope it should be solve your problem.
SELECT
tbl1.*
FROM
appointment tbl1,
appointment tbl2
WHERE
tbl2.appId <> tbl1.appId
AND tbl2.startDateTime < tbl1.startDateTime + INTERVAL TIME_TO_SEC(tbl1.duration) SECOND
AND tbl2.startDateTime + INTERVAL TIME_TO_SEC(tbl2.duration) SECOND > tbl1.startDateTime;
By clicking on the below link you can see your expected result in live which you want.
SQL Fiddle Demo
Need to GROUP BY WEEKS Starting from 1st of current month
DDLs:
CREATE TABLE `group_by_week` (
`id` int(4) NOT NULL AUTO_INCREMENT,
`date` date DEFAULT NULL,
`value` int(4) DEFAULT NULL,
PRIMARY KEY (`id`)
);
INSERT INTO `group_by_week`
(`date`,`value`)
VALUES
('2016-01-01',1),
('2016-01-02',2),
('2016-01-03',3),
('2016-01-04',4),
('2016-01-05',5),
('2016-01-06',6),
('2016-01-07',7),
('2016-01-08',8),
('2016-01-09',9),
('2016-01-10',10),
('2016-01-11',11),
('2016-01-12',12),
('2016-01-13',13),
('2016-01-14',14),
('2016-01-15',15),
('2016-01-16',16);
EXPECTED RESULT:
week 1 => 28
week 2 => 77
week 3 => 31
You may need to get the week number by diving the Day part with 7 and then you may need to round the result using FLOOR.
If dates from different months are there, then its better to add month name along with the week number. So I just did that way. So the first column values would be like monthname weeknumber. And we can group by with the same first column.
Query
SELECT
CONCAT(MONTHNAME(`date`), ' week ', FLOOR(((DAY(`date`) - 1) / 7) + 1)) `month & week`,
SUM(`value`) AS `value`
FROM `group_by_week`
GROUP BY `month & week`
ORDER BY month(`date`), `month & week`;
Result
+-----------------+-------+
| month & week | value |
+-----------------+-------+
| January week 1 | 28 |
| January week 2 | 77 |
| January week 3 | 31 |
+-----------------+-------+
SQL Fiddle Demo
E.g.:
SELECT CEILING(DATE_FORMAT(date,'%d')/7) w
, SUM(value) week_total
FROM group_by_week
GROUP
BY w;
Obviously, you need to extend the logic a little if dealing with more than one month, but I'll leave that as an exercise for the reader.
Note also that the '4' in 'INT(4)' is fairly meaningless - I doubt very much that it does whatever you think it does.
A little background for the issue:
In Venezuela, there is a law that defines how an special document called Withholding Receipt (issued when a company, designated by the Tax Administration, withheld taxes to be declared by the company and not the client, really confusing legal thing) will be identified and the information it will present. It says that it have to be numbered with the following format:
YYYYMMXXXXXXXX
Where YYYY represents the year, MM the month and XXXXXXXX represents an incremental number (up to 8 digits wide) that will be refreshed (start from 0 again) if overflowed.
I could've used a plain vanilla AUTO_INCREMENT field in order to solve this puzzle, however, the real issue begins here.
According to the Agents of the Tax Administration, the incremental numbering refreshes automatically each month, meaning Receipt No. 20151200000001 and No. 20160100000001 can exist on the database and dont collide.
This means, it makes impossible to use an AUTO_INCREMENT field since its value will be resetted to 0 each month.
What options can be used to solve this puzzle? Using of course, database features only.
PS: Can be in any database (including No-SQL).
PS2: year and month can different be fields on the table/document/entity.
Edit
I did some research on MySQL based on #Gordon Linoff answer, here is a working example
CREATE TABLE IF NOT EXISTS test (
id int(11) NOT NULL AUTO_INCREMENT,
invoice_no varchar(12) NOT NULL,
year int(11) NOT NULL,
month int(11) NOT NULL,
identifier int(11) DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB;
DELIMITER //
CREATE TRIGGER ins_tr BEFORE INSERT ON test
FOR EACH ROW BEGIN
SET #maxID = (SELECT COALESCE(MAX(identifier), 0)
FROM test
WHERE CONCAT(year, lpad(month, 2, '0')) = CONCAT(NEW.year, lpad(NEW.month, 2, '0'))
);
SET NEW.identifier = #maxID +1;
END
//
DELIMITER ;
INSERT INTO test (invoice_no, year, month) VALUES (1, 2015, 12), (2, 2015, 12), (3, 2016, 1), (4, 2016, 1);
Result:
+----+------------+------+-------+------------+
| id | invoice_no | year | month | identifier |
+----+------------+------+-------+------------+
| 1 | 1 | 2015 | 12 | 1 |
| 2 | 2 | 2015 | 12 | 2 |
| 3 | 3 | 2016 | 1 | 1 |
| 4 | 4 | 2016 | 1 | 2 |
+----+------------+------+-------+------------+
In researching on a way for MongoDB or any NoSQL engine.
You would implement this in a relational database using a trigger. The trigger would implement logic such as:
select new.TaxReceiptNumber := concat(date_format(curdate(), '%Y%m',
lpad(coalesce(max(right(TaxReceiptNumber, 8) + 0), 0), 8, '0')
from t
where left(TaxReceiptNumber, 6) = date_format(curdate(), '%Y%m');
I might be tempted to store the incremental number and date of the receipt in different columns. However, given that you have to work with tax authorities, it might be better to just have the number as a single column.
First, don't make this your primary key.
Second, store the current number somewhere. When you use it, increment it by 1.
Finally, on the first of the month or when it reaches a certain value, reset it to 1.
I have a MySQL table where I display debit, credit and balance in my table. I have loaded the following definition, sample data and code into SQL Fiddle:
CREATE TABLE chequebook (
entry_date timestamp default now() PRIMARY KEY,
entry_item varchar(48) NOT NULL DEFAULT '',
entry_amount decimal(10,2) NOT NULL DEFAULT 0.00
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO chequebook (entry_date,entry_item,entry_amount) VALUES
('2010-01-02 12:34:00','Deposit A',215.56),
('2010-01-02 21:44:00','Withdrawal A' ,-23.34),
('2010-01-03 10:44:00','Withdrawal B',-150.15),
('2010-01-03 15:44:00','Deposit B',154.67),
('2010-01-04 18:44:00','Withdrawal C',-65.09),
('2010-01-05 08:44:00','Withdrawal D',-74.23),
('2010-01-06 14:44:00','Deposit C',325.12),
('2010-01-06 20:44:00','Withdrawal E',-80.12),
('2010-01-07 04:44:00','Withdrawal F',-110.34),
('2010-01-07 16:44:00','Withdrawal G',-150.25),
('2010-01-08 16:44:00','Withdrawal H',-23.90),
('2010-01-08 21:44:00','Withdrawal I',-75.66),
('2010-01-08 22:44:00','Deposit C',275.78),
('2010-01-09 11:44:00','Withdrawal K',-85.99),
('2010-01-09 21:44:00','Withdrawal J',-100.00);
set #depos=0;
set #total=0;
select
entry_date,
entry_item,
entry_amount,
if( entry_amount>0, #depos:=entry_amount, #depos:=#depos+entry_amount ) as depos_bal,
#total:=#total+entry_amount as net_bal
from chequebook
order by entry_date;
I am facing issues when I want to add an opening balance to the net_bal column from the PHP MYSQL query.
I am facing issues in adding the Opening Balance to the very FIRST COLUMN and there after it should minus or plus from the desired fields.
For example:
| entry_date | entry_item | entry_amount | depos_bal | net_bal |
|---------------------------|--------------|--------------|-----------|---------|
| January, 02 2010 12:34:00 | Deposit A | 215.56 | 5215.56 | 5215.56 | <--- 5000 is openingbalance
| January, 02 2010 21:44:00 | Withdrawal A | -23.34 | 5192.22 | 5192.22 |
| January, 03 2010 10:44:00 | Withdrawal B | -150.15 | 5042.07 | 5042.07 |
Opening Balance is fetched from different table.
How can I finish this?
You can set the initial local #Total variable to your initial balance. From your SQLFiddle:
set #depos=0;
set #total=5000;
select
entry_date,
entry_item,
entry_amount,
if( entry_amount>0, #depos:=entry_amount, #depos:=#depos+entry_amount ) as depos_bal,
#total:=#total+entry_amount as net_bal from chequebook
order by entry_date;
If it's coming from a different query, set the variable that way.