SQL statement not doing quite what i would like it to do - php

I am currently writing a SQL query in SQL Server Management Studio that will be used in PHP
The query is meant to get all of the records between 8AM and 8 PM yesterday and count how many of the records have the fffffff0 as the CardID currently this just spits out 355 records with the column CardCOUNT just having the number one in them not quite what I want, how should I do this?
SELECT
COUNT(CardID) AS CardCOUNT,
ReaderTime,
controllerID,
dtReading
FROM
myMachineMonitor2.dbo.ReaderData
WHERE
(controllerID = 31)
AND (ReaderTime BETWEEN '08:00:00' AND '20:00:00')
AND (CardID = 'fffffff0')
AND (DATEDIFF(DAY, DATEADD(DAY, - 1, CURRENT_TIMESTAMP), dtReading) = 0)
GROUP BY
ReaderTime, controllerID, dtReading
After playing around with it I kinda figured it out here is what I came up with
SELECT
COUNT(CardID) AS CardCOUNT
FROM
myMachineMonitor2.dbo.ReaderData
WHERE
(controllerID = 31)
AND (ReaderTime BETWEEN '08:00:00' AND '20:00:00')
AND (CardID = 'fffffff0')
AND (DATEDIFF(DAY, DATEADD(DAY, - 0, CURRENT_TIMESTAMP), dtReading) = 0)

This changes the logic to just working with the current date:
SELECT COUNT(CardID) AS CardCOUNT, ReaderTime, controllerID, dtReading
FROM myMachineMonitor2.dbo.ReaderData
WHERE (controllerID = 31) AND
(CardID = 'fffffff0') AND
dtReading >= cast(cast(getdate() as date) as datetime) - 16.0/24 and
dtReading >= cast(cast(getdate() as date) as datetime) - 4.0/24
GROUP BY ReaderTime, controllerID, dtReading ;
The cast to date puts the value at midnight. Then it subtracts 16 and 4 hours for the range.
This expression also does all the work on the current date.

I know there are some debates about the sargability of datetime columns but I would approach this more like this. I find this a lot easier to understand by separating the two conditions.
SELECT COUNT(CardID) AS CardCOUNT
, ReaderTime
, controllerID
, dtReading
FROM myMachineMonitor2.dbo.ReaderData
WHERE controllerID = 31
AND CardID = 'fffffff0'
AND ReaderTime >= dateadd(hour, 8, dateadd(day, datediff(day, 0, CURRENT_TIMESTAMP) - 1, 0)) --8am yesterday
AND ReaderTime < dateadd(hour, 19, dateadd(day, datediff(day, 0, CURRENT_TIMESTAMP) - 1, 0)) --9pm yesterday
GROUP BY ReaderTime
, controllerID
, dtReading;

The problem is by making time a grouping condition you are forcing one line per distinct time, not allowing an aggregate over a range of times. Just drop the times from your GROUP BY and you're fine.
SELECT controllerID, COUNT(CardID) AS CardCOUNT
FROM myMachineMonitor2.dbo.ReaderData
WHERE (controllerID = 31)
AND (ReaderTime BETWEEN '08:00:00' AND '20:00:00')
AND (CardID = 'fffffff0')
AND (DATEDIFF(DAY, DATEADD(DAY, - 1, CURRENT_TIMESTAMP), dtReading) = 0)
GROUP BY controllerID

Related

Mysql get tuples where date is from this month

I've written this SQL query but it's returning null although it shouldn't
I'm working with a db containing work times. So each work times has a beginning datetime and an end datetime
I'd like to get all the work times that have been recorded this month :
SELECT id, DATE_FORMAT(DATE(t.begin), "%d-%m-%Y") as date,
CONCAT(DATE_FORMAT(t.begin, "%H:%i"), " - ", DATE_FORMAT(t.end, "%H:%i")) as timerange,
CASE WHEN t.end = "0000-00-00 00:00:00"
THEN TIME_FORMAT(TIMEDIFF(DATE_ADD(NOW(), INTERVAL 1 HOUR), t.begin), "%H:%i")
ELSE TIME_FORMAT(TIMEDIFF(t.end, t.begin), "%H:%i")
END as fulltime_duration,
(
SELECT TIMEDIFF(l.end, l.begin) as duration
FROM timeslots l
WHERE l.type = 2
AND l.parent_id = t.id
) as lunch_duration
FROM timeslots t
WHERE t.user_id = '.$userId.'
AND t.approved = 1
AND t.type = '.Timeslot::DAY.'
AND DATE(begin) >= DATE_FORMAT(CURDATE(), "%Y-%m-01")
ORDER BY t.begin
when I replace this line :
AND DATE(begin) >= DATE_FORMAT(CURDATE(), "%Y-%m-01")
by :
AND WEEKOFYEAR(begin) = WEEKOFYEAR(NOW())
it returns all the times recorded this week. So everything is working except this line
AND DATE(begin) >= DATE_FORMAT(CURDATE(), "%Y-%m-01")
When I replace the "-01" by "%d" it returns all the work times of today so I guess the problem comes from this "-01". But I don't see how to do this in a different way.
Anyone to help me ?...
Here is one way:
DATE(begin) >= DATE_ADD(CURDATE(), 1 - DAY(CURDATE()))
Note: You don't need DATE(begin) (because the time component will make this bigger), so you can use:
begin >= DATE_ADD(CURDATE(), 1 - DAY(CURDATE()))
The advantage is that this can make use of an index on begin, if appropriate.

mysql, use a result of if condition in a following if condition

Is it possible to use a resulting "column" from an if-condition in another if-condition inside the same query? I'd like to get all my documents related data once.
SELECT d.id, d.filename,
IF(d.document_valid_until_further_notice = 0,
IF(d.document_valid_until = "0000-00-00",
DATE_ADD(d.document_date, INTERVAL 12 MONTH),
d.document_valid_until
), "0000-00-00"
) AS calculated_valid_until_date,
IF(calculated_valid_until_date != "0000-00-00",
DATE_SUB(calculated_valid_until_date, INTERVAL 8 WEEK,
"0000-00-00"
) AS calculated_alert_expiring_date,
IF(calculated_valid_until_date > CURDATE() AND calculated_valid_until_date != "0000-00-00", 1, 0) AS expired FROM documents AS d WHERE 1 ORDER BY d.document_date DESC';
Now, "calculated_valid_until_date" comes out correctly, e.g. 2015-10-20, but I can't use that value in the following if-statements. with or without # -sign. (#calculated_valid_until_date). Is there even a way or do I have to do this all with separated queries or in client side?
Thanks for any ideas!
Wrap the calculated fields of the into a derived table, then reference these calculations in an outer select from this derived table to continue your calculations:
SELECT x.id, x.filename,
x.calculated_valid_until_date,
x.calculated_alert_expiring_date,
-- Use the derived table to do the second round of calculations
IF(x.calculated_valid_until_date > CURDATE()
AND x.calculated_valid_until_date != "0000-00-00", 1, 0) AS expired
FROM
-- Project the first round of calculations into a derived table
(SELECT
d.id, d.filename, d.document_date,
IF(d.document_valid_until = '0000-00-00', DATE_ADD(d.document_date, INTERVAL 12 MONTH),
d.document_valid_until) AS calculated_valid_until_date,
IF(d.calculated_valid_until_date != '0000-00-00',
DATE_SUB(d.calculated_valid_until_date, INTERVAL 8 WEEK),
'0000-00-00') AS calculated_alert_expiring_date
FROM documents AS d
) x
WHERE 1 = 1
ORDER BY x.document_date DESC;
SqlFiddle here
SELECT x.id, x.filename,
x.calculated_valid_until_date,
IF(x.calculated_valid_until_date < CURDATE() AND x.calculated_valid_until_date != "0000-00-00", 1, 0) AS expired, IF(x.calculated_valid_until_date != '0000-00-00',
DATE_SUB(x.calculated_valid_until_date, INTERVAL 8 WEEK), '0000-00-00') AS calculated_alert_expiring_date
FROM
(SELECT
d.id, d.filename, d.document_date, d.calculated_alert_expiring_date,
IF(d.document_valid_until = '0000-00-00', DATE_ADD(d.document_date, INTERVAL 12 MONTH),
d.document_valid_until) AS calculated_valid_until_date
FROM documents AS d
) x
WHERE 1 = 1
ORDER BY x.document_date DESC
CREATE TABLE documents
(
id INT,
filename VARCHAR(50),
document_valid_until_further_notice TINYINT(1),
document_valid_until_date DATE,
document_date DATE,
document_valid_until DATE,
calculated_valid_until_date DATE,
calculated_alert_expiring_date DATE
);
This is how I got it working. I didn't look at the fiddle the first time so I didn't quite realize I'd have to create the table columns for "calculated_valid_until_date" and "calculated_alert_expiring_date".
But anyhow, it is working just perfect now. THANK YOU!

How to skip other OR condition if first is matched in SELECT Query?

I am having a trouble with OR condition inside the SELECT.
I want a simple result if one condition is matched and rest OR condition should not be use.
What i want is:
I have some users shared records and i would like to email them the newest items shared on my website.
For me: Newest Items will be least two days older
Like Today is 9th so i would like to pull all records of 7th. but if i
didn't get any record of 7th then i would like to pull all record of
6th (3 days older from today). if i didn't get any records on 6th then
i would like to pull 1 day older from today.
for all this i have used OR in my SELECT query like this:
SELECT `tg`.* FROM `tblgallery` AS `tg` WHERE (
(tg.added_date BETWEEN '2014-07-07 00:00:00' AND '2014-07-08 00:00:00') OR
(tg.added_date BETWEEN '2014-07-06 00:00:00' AND '2014-07-07 00:00:00') OR
(tg.added_date BETWEEN '2014-07-08 00:00:00' AND '2014-07-09 00:00:00') )
And i have records in my database for dates:
2014-07-06
2014-07-07
and when i run this query it gives me all record of both dates.
But I need to pull only record of 2014-07-07 not of both.(I have mentioned above.)
I know i can do this by using multiple Select and i think that will not be a good idea to request to database again and again.
My Question is : How to pull data from database if the first match is true? and skip all data of rest dates?
OR
Is there any other way to do this?
Please Help
Usually one would just work with LIMIT, which is not applicable here, since there might be many rows per day. What I do is quite similar to LIMIT.
SELECT * FROM (
SELECT
tg.*,
#gn := IF(DATE(tg.added_date) != #prev_date, #gn + 1, #gn) AS my_group_number,
#prev_date := DATE(tg.added_date)
FROM tblgallery tg
, (SELECT #gn := 0, #prev_date := CURDATE()) var_init
ORDER BY FIELD(DATE(tg.added_date), CURDATE() - INTERVAL 1 DAY, CURDATE() - INTERVAL 3 DAY, CURDATE() - INTERVAL 2 DAY) DESC
) sq
WHERE my_group_number = 1;
Here's how it works.
With this line
, (SELECT #gn := 0, #prev_date := CURDATE()) var_init
the variables are initialized.
Then the ORDER BY is important! The FIELD() function sorts the rows from 2 days ago (gets value 3), to 3 days ago (gets value 2), to 1 day ago (gets value 1). Everything else gets value 0.
Then in the SELECT clause the order is also important.
With this line
#gn := IF(DATE(tg.added_date) != #prev_date, #gn + 1, #gn) AS my_group_number,
the variable #gn is incremented when the date of the current row is different from the date of the previous row.
With this line
#prev_date := DATE(tg.added_date)
the date of the current row is assigned to the variable #prev_date. In the line above it still has the value of the previous row.
Now those entries have a 1 in column my_group_number that have the most recent date in the order
2 days ago
3 days ago
yesterday
4 days ago
5 days ago
...
Try this Query:
SELECT GalleryID, PixName, A.added_date
FROM tblGallery A
INNER JOIN (
SELECT added_date FROM tblGallery
WHERE added_date <= DATE_SUB('2014-07-09 00:00:00', interval 2 day)
GROUP BY added_date
ORDER BY added_date DESC
LIMIT 1 ) B
ON A.added_date = B.added_date
See my SQL Fiddle Demo
And even if the date is more than 2 days older it will still work.
See here the Demo below wherein the latest is 4 days older from July 9, 2014
See the 2nd Demo
And if you want the current date instead of literal date like here then you could use CURDATE() function instead. Like one below:
SELECT GalleryID, PixName, A.added_date
FROM tblGallery A
INNER JOIN (
SELECT added_date FROM tblGallery
WHERE added_date <= DATE_SUB(CURDATE(), interval 2 day)
GROUP BY added_date
ORDER BY added_date DESC
LIMIT 1 ) B
ON A.added_date = B.added_date
See 3rd Demo
Well, I'm not being able to solve the multi OR issue but this is how could you get records being added last two days. Change the interval or the CURDATE() in order to fit your needs.
SELECT id, date_added
FROM gallery
WHERE date_added BETWEEN CURDATE() - INTERVAL 2 DAY AND CURDATE()
ORDER BY date_added
Check the SQL Fiddel
It is not about how OR works in MySQL.
I think you are misunderstanding where part by looking at your discussion with #B.T.
It will be executed for each record.
so if one of the record evaluates to false for the first condition then it will evaluate the second condition for that particular record and so on so if any condition evaluates to true by considering all the conditions then that will become part of your result set.
Try this query.
SELECT `tg`.* FROM `tblgallery` AS `tg` WHERE tg.added_date = (
select date (
select distinct(tg.added_date) date from tblgallery as tg
) as t1 order by case
when date between '2014-07-07 00:00:00' AND '2014-07-08 00:00:00'
then 1
when date between '2014-07-06 00:00:00' AND '2014-07-07 00:00:00'
then 2
when date between '2014-07-08 00:00:00' AND '2014-07-09 00:00:00'
then 3
else 4
end limit 1);
Here's what I am doing in this query.
I am getting all the distinct dates.
then I am ordering all the condition in order i.e if first condition is true then 1, if second is true then 2 and so on.
I am limiting the result to 1 so after the order whichever the result is the first row will be selected and which is a date and will be used in the condition.
Note: I have note tested it yes, so you may need to do some changes to the query.

SQL where date column bigger than other date column by atleast 20 days

I'm having difficulty finding rows in my Table where two date columns have a difference of at least 20 days between them (no specific date is required, just 20 days difference)
I tried this with no luck:
SELECT DocDate,DocDueDate,DocStatus
FROM dbo.OPCH
WHERE (DocDate - DocDueDate = 20) AND DocStatus = 'O' AND DocDate > '2014-01-01'
This doesn't work.
Any help would be appreciated
If your dates are actually declared as datetime, you can do:
SELECT DocDate,DocDueDate,DocStatus
FROM dbo.OPCH
WHERE (DocDate >= DocDueDate + 20) AND DocStatus = 'O' AND DocDate > '2014-01-01';
Adding a number to a date time is interpreted as adding that number of days. Unfortunately, this doesn't work for date.
For date, you can do:
WHERE datediff(day, DocDueDate, DocDate) >= 20 AND DocStatus = 'O' AND DocDate > '2014-01-01'

Find next time business is open; mysql hours calculation

I’m trying to figure out if a shop is currently within its opening hours, if not then select the next time its open.
Finally I need to be able to put the opening day as a specific date.
Can someone possible give me a tip how to construct this query?
Thanks in advance
CREATE TABLE `shop_hours` (
`id` int(11) NOT NULL,
`shop_id` int(11) unsigned NOT NULL,
`day_of_week` int(11) unsigned NOT NULL,
`open_time` time NOT NULL,
`close_time` time NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `shop_hours` (`id`, `shop_id`, `day_of_week`, `open_time`, `close_time`)
VALUES
(1, 1, 0, '08:00:00', '24:00:00'),
(2, 1, 1, '08:00:00', '24:00:00'),
(3, 1, 2, '08:00:00', '24:00:00'),
(4, 1, 3, '08:00:00', '24:00:00'),
(5, 1, 4, '08:00:00', '24:00:00'),
(6, 1, 5, '08:00:00', '24:00:00'),
(7, 1, 6, '08:00:00', '24:00:00');
Edit:
To clarify a little I'm not looking to find open shops, but only open hours for ONE specific shop. Based on the opening/closing hour, and what time it is now. I will generate some selectable timestamps incremented by 15 minutes.
E.g. if a shop has just closed (1PM), I will need to use the next open day's open/closing time instead. (the shop isn't necessarily open every day, could be closed Sundays).
To find out shop_id's, that is open for NOW()
SELECT *
FROM `shop_hours`
WHERE `day_of_week` = DATE_FORMAT(NOW(), '%w')
AND CURTIME() BETWEEN `open_time` AND `close_time`
Obsolete:
To find tomorrow's available open_times:
SELECT *
FROM `shop_hours`
WHERE `day_of_week` = DATE_FORMAT(DATE_ADD(NOW(), INTERVAL 1 DAY), '%w')
Edit 2:
To find next available open_times:
SELECT `shop_id`,
MIN(CAST(CONCAT(DATE(DATE_ADD(NOW(), INTERVAL ((7 + DATE_FORMAT(NOW(), '%w') - `day_of_week`) % 7) DAY)), ' ', `open_time`) AS DATETIME)) AS `next_open_datetime`
FROM `shop_hours`
GROUP BY `shop_id`
Edit:
DATE_FORMAT(*DATE*, '%w') uses the format 0 = Sunday ... 6 = Saturday
If you want to use the ISO format 1 = Monday ... 7 = Sunday in your day_of_week field, you should bind php's date('N') to your query (or use Mysql's if function IF(DATE_FORMAT(NOW(), '%w') = 0, 7, DATE_FORMAT(NOW(), '%w')), but that's ugly)
1) Check if shop is open. Result is empty, if shop is closed:
select * from shop_hours
where shop_id = $id
and dayofweek(curdate()) = day_of_week
and curtime() between open_time and close_time;
2) Find next open time:
(select open_time from shop_hours
where shop_id = $id and curtime() < open_time
and day_of_week >= dayofweek(curdate()))
union
(select open_time from shop_hours
where shop_id = $id and curtime() < open_time
order by day_of_week)
union
(select open_time from shop_hours
where shop_id = $id and curtime() > close_time
and day_of_week >= dayofweek(curdate()))
union
(select open_time from shop_hours
where shop_id = $id and curtime() > close_time
order by day_of_week)
limit 1;
Untested, but this should respect weekend wraparound and holes in the week (i.e. closed days).
Keep in mind, that dayofweek() numbers 1 = Sunday, 2 = Monday, ... If your table stores the weekdays in a different format, you must adjust the query accordingly.

Categories