conditional where clause mysql - mysql

I want to find in my MySQL table a specific time-period.
I want to find the period that
1. started in the past (cursusperiode_van < now())
2. and will last for at least 4 weeks (cursusperiode_tot - INTERVAL 28 DAY > now())
3. If i don't find this one, i would like to find the one that is the first to start after this one (cursusperiode_van > now() order by cursusperiode_van asc limit 1).
Periods do not overlap, at least not for more than 1 day.
Let's say i have
id:1 - period: 1 jan 2011 to 1 aug 2011
id:2 - period: 1 aug 2011 to 31 dec 2011
If i query that database on July 1, i want to find id 1, if i query on July 23, i want to find id 2.
Not likely to be relevant, but this query is to be embedded in another query like
select * from cursus where cursus_periode_id in (
select cursusperiode_id from cursusperiode_id where ...[panic!] ...
) and so on
I tried to use IFNULL, CASE, but i can't seem to get my head around it. How do i paste these two together?
Any hint or help is appreciated. Thank you.

You could UNION both queries, add a dummy column to aid in sorting and retain the first one found.
SQL Statement
SELECT c1.*
FROM cursus c1
INNER JOIN (
SELECT 1 AS Dummy
, cursusperiode_id
FROM cursusperiode_id
WHERE cursusperiode_tot - INTERVAL 28 DAY > now()
UNION ALL
SELECT 2 AS Dummy
, cursusperiode_id
FROM cursusperiode_id
WHERE cursusperiode_van > now()
) c2 ON c2.cursusperiod_id = c1.cursusperiod_id
ORDER BY
c2.Dummy
, c1.cursusperiode_van ASC
LIMIT 1

Related

count method returns different values depending on date in condition

I'm running a query that looks at the number of days where an activity for a record exists over the last three days (today included) in the database using the GroupBy gem to group them by day. Here is the full query:
Record.activities.where("created_at >= ?", 2.days.ago.beginning_of_day.in_time_zone).group_by_day(:created_at).count.count
2 days ago, I have 1 activity, yesterday I have 0 and today I have 2. This should return 2 (not 3, it's counting the days not the activities) but for some reason it returns 3. What's even stranger is that if I change 2.days.ago to 1.day.ago or 0.days.ago, it returns the correct value, 1. It ignores yesterday where there were no activities and only counts the day (today) where it recognised there was an activity.
If I remove the second .count, here's what it returns for 2.days.ago...
{Tue, 21 Nov 2017=>1, Wed, 22 Nov 2017=>0, Thu, 23 Nov 2017=>2}
and if I run it for 1.day.ago, I get...
{Thu, 23 Nov 2017=>2}
Here is the raw SQL for the 2.days.ago query..
SELECT COUNT(*) AS count_all, strftime('%Y-%m-%d 00:00:00 UTC', created_at)
AS strftime_y_m_d_00_00_00_utc_created_at
FROM "activities"
WHERE "activities"."goal_id" = ? AND (created_at >= '2017-11-21 00:00:00')
AND (created_at IS NOT NULL)
GROUP BY strftime('%Y-%m-%d 00:00:00 UTC', created_at)
After some more testing, I noticed it only ignores a day with 0 activities if the x in x.days.ago falls on the day with 0. If there is more than 1 day with 0 activities, it will ignore the first one as it should but then count the other days with 0 that come after...
Not sure what I'm missing here but would appreciate any help finding the issue.
Let me know if you need any more information.
You can use .having('count(activities.id) > 0') to ignore days with no activity
untested query below:
Record.activities
.select('count(activities.id) as count_all,date(activities.created_at) as day')
.where("created_at >= ?", 2.days.ago.beginning_of_day.in_time_zone)
.group('day').having('count_all > 0')
First of all your solution is not ok, because you need to make it in sql query (while you load all the data to ruby object and then sort them. It will be a bottle neck during scale). Just put the same thing to sql. I think this one should work:
Record.activities.where("created_at >= ? AND count_all > 0", 2.days.ago.beginning_of_day.in_time_zone).group_by_day(:created_at).count
Then about why you get 3 when you expect to get 2. Sql COUNT(*) returns number of rows. In your case you will always have 3 unique days, so 3 unique rows (group_by group them by their uniqueness). You must filter days with 0 count as I made in my query to filter rows, that have 0 in count, but still get counted
I've found a way to return the right result but still curious to know why that initial query returns those results.
Here is the query that works:
Record.activities.where("created_at >= ?", 4.days.ago.beginning_of_day.in_time_zone).group_by_day(:created_at).count.reject {|k,v| v == 0 }.count

How to filter Records based on the condition from Two Columns that are dependent

I have a Table in the Database with below columns :
Month
Year
Name
xyz
abc
The table would have multiple Records but there is a unique constraint on the combination of Month, Year and Name.
The table would have data Like below :
Month Year Name
1) 1 2016 test1
2) 2 2016 test2
3) 3 2016 test3
4) 1 2017 test4
5) 2 2017 test5
Now I want records that are Greater than or Equal to Month 3 and Greater than equal to Year 2016.
So the output Should be Like Below:
Month Year Name
1) 3 2016 test3
2) 1 2017 test4
3) 2 2017 test5
How can this be accomplished. I am not getting any hint on how this can be done.I was adding a where clause with month>=3 and year>=2016 but it does not return any record and I think is correct as I am applying And condition on two expressions.
To produce the result as you have described in your second table, I would write this query:
select [Month], [Year], [Name]
from Table -- your table name
where year > 2016 or ( year = 2016 and month >= 3) -- don't forget the parentheses.
What I can understand from your result table, the query described in words will be:
Give me all the records where the year is greater than 2017 (in this case month doesn't matter, give me all the months) or if the year is 2016, give me all months greater than or equal to 3 (March included and onward). Don't give me any records where the year is smaller than 2016.
If this is correct, then the query I posted above gives the right result.
If you want to get all records starting on or after '2016-03-01' then one way would be to build your [year] and [month] back into a date datatype.
select *
from t
where convert(date,convert(varchar(10),([year]*10000)+([month]*100)+1)) >= '20160301'
or in sql server 2012 +
select *
from t
where datefromparts([year],[month],1) >= '20160301'
Your question is not clear. From the explanation you want "and" condition but in the output result it should be "or". For ex month >= 3 or year>=2016. This is your output saying.

How can I get the month that is not yet updated in SQL by inserting another row on every update?

I have a table that contains records of different transaction that is needed to be updated monthly. Once the record for a specific month has been successfully updated, it will insert a new record to that table to indicate that it is already updated. Let's take this example.
**date_of_transaction** **type**
2015-04-21 1 //A deposit record
2015-04-24 2 //A withdrawal record
2015-04-29 1
2015-04-30 2
2015-04-30 3 //3, means an update record
2015-05-14 1
2015-05-22 1
2015-05-27 2
2015-05-30 2
2015-06-09 1
2015-06-12 2
2015-06-17 2
2015-06-19 2
Let's suppose that the day today is July 23, 2015. I can only get the data one month lower than the current month, so only the data that I can get are june and downwards records.
As you can see, there is an update performed in the month of April because of the '3' in the type attribute, but in the month of May and June, there are no updates occurred, how can I get the month that is not yet updated?
This will return you months, which has no type=3 rows
SELECT MONTH([trans-date]) FROM [table] GROUP BY MONTH([trans-date]) HAVING MAX([trans-type])<3
Note: this will not work if 3 is not max value in the column
My approach would be to find all the months first, then find the months whose records were updated. Then select only those months from all months whose records werent updated (A set minus operation).
Mysql query would be something like this
select extract(MONTH,data_of_transaction) from your_table_name where month not in (select extract(MONTH,data_of_transaction) from table where type=3);
You can try this;
select *
from tbl
where date_of_transaction < 'July 23, 2015'
and
date_format(date_of_transaction, '%M-%Y') in (
select
date_format(date_of_transaction, '%M-%Y')
from tbl
group by date_format(date_of_transaction, '%M-%Y')
having max(type) != 3
)
date_format(date_of_transaction, '%M-%Y') will take month-year in consideration and filter the data having type = 3.

SQL search between two days without datetime

Trying to search in a database a date range. Problem is, I cannot use the datetime column type in my database. To compensate, date's are displayed as three columns. a Month column, a Day column, and a Year column. Here is my SQL query:
SELECT COUNT(*)
FROM `import`
WHERE `call_day` BETWEEN 29 AND 15
AND `call_month` BETWEEN 9 AND 10
AND `call_year` BETWEEN 2013 AND 2013
You can see where I run into trouble. call_day needs to search between the 29th day and the 15th day. This won't work because 15 is smaller than 29, but I need it to work because the month is in the future :)
Any thoughts/solutions? No I cannot change the database in any way. Read only.
Concat the values like yyyymmdd and then you can compare them like strings.
Besides the concatenation approach, which can be implemented in quite a few ways, e.g.
SELECT *
FROM import
WHERE STR_TO_DATE(CONCAT_WS('-', call_year, call_month, call_day), '%Y-%c-%e')
BETWEEN '2013-09-29' AND '2013-10-15'
or
SELECT *
FROM import
WHERE CONCAT(call_year, LPAD(call_month, 2, '0'), LPAD(call_day, 2, '0'))
BETWEEN '20130929' AND '20131015'
Here is SQLFiddle demo
that will always cause a full scan and assuming that date ranges in your queries usually don't span more than a few months you can also do
SELECT *
FROM import
WHERE (call_year = 2013 AND
call_month = 9 AND
call_day BETWEEN 29 AND DAY(LAST_DAY('2013-09-01'))) -- or just 30
OR (call_year = 2013 AND
call_month = 10 AND
call_day BETWEEN 1 AND 15)
Here is SQLFiddle demo
For a query that spans a year (e.g. from 2012-08-20 to 2013-10-15)
SELECT *
FROM import
WHERE (call_year = 2012 AND
call_month = 8 AND
call_day BETWEEN 20 AND 31)
OR (call_year = 2012 AND
call_month BETWEEN 9 AND 12 AND
call_day BETWEEN 1 AND 31)
OR (call_year = 2013 AND
call_month BETWEEN 1 AND 9 AND
call_day BETWEEN 1 AND 31)
OR (call_year = 2013 AND
call_month = 10 AND
call_day BETWEEN 1 AND 15)
Here is SQLFiddle demo
As PeterM said, this may play hell with performance, but if you're storing these as integers, you can do the following:
(year * 10000) + (month* 100) + day will always yield an 8 byte "date".
2013 *10000 = 20130000
9 * 100 = 900
15
20130915
It's an ugly hack, and it will be expensive, because you'll be evaluating every row in your table, if you can't limit the rows in any other way, but I think it'll work.
EDIT:
Typing is hard!
SELECT COUNT(*) FROM `import`
WHERE CONCAT(CAST(`call_year`AS CHAR(4)) , RIGHT(CONCAT('00',CAST(`call_month`AS CHAR(2))),2) , RIGHT(CONCAT('00',CAST(`call_day`AS CHAR(2))),2))
BETWEEN '20130929' AND '20131015'

Get stats for each day in a month without ignoring days with no data

I want to get stats for each day in a given month. However, if a day has no rows in the table, it doesn't show up in the results. How can I include days with no data, and show all days until the current date?
This is the query I have now:
SELECT DATE_FORMAT(FROM_UNIXTIME(timestamp), '%d'), COUNT(*)
FROM data
WHERE EXTRACT(MONTH FROM FROM_UNIXTIME(timestamp)) = 6
GROUP BY EXTRACT(DAY FROM FROM_UNIXTIME(timestamp))
So if I have
Row 1 | 01-06
Row 2 | 02-06
Row 3 | 03-06
Row 4 | 05-06
Row 5 | 05-06
(i changed timestamp values to a day/month date just to explain)
It should output
01 | 1
02 | 1
03 | 1
04 | 0
05 | 2
06 | 0
...Instead of ignoring day 4 and today (day 6).
You will need a calendar table to do something in the form
SELECT `date`, count(*)
FROM Input_Calendar c
LEFT JOIN Data d on c.date=d.date
GROUP BY `date`
I keep a full copy of a calendar table in my database and used a WHILE loop to fill it but you can populate one on the fly for use based on the different solutions out there like http://crazycoders.net/2012/03/using-a-calendar-table-in-mysql/
In MySQL, you can use MySQL variables (act like in-line programming values). You set and can manipulate as needed.
select
dayofmonth( DynamicCalendar.CalendarDay ) as `Day`,
count(*) as Entries
from
( select
#startDate := date_add( #startDate, interval 1 day ) CalendarDay
from
( select #startDate := '2013-05-31' ) sqlvars,
AnyTableThatHasAsManyDaysYouExpectToReport
limit
6 ) DynamicCalendar
LEFT JOIN Input_Calendar c
on DynamicCalendar.CalendarDay = date( from_unixtime( c.date ))
group by
DynamicCalendar.CalendarDay
In the above sample, the inner query can join against as the name implies "Any Table" in your database that has at least X number of records you are trying to generate for... in this case, you are dealing with only the current month of June and only need 6 records worth... But if you wanted to do an entire year, just make sure the "Any Table" has 365 records(or more).
The inner query will start by setting the "#startDate" to the day BEFORE June 1st (May 31). Then, by just having the other table, will result in every record joined to this variable (creates a simulated for/next loop) via a limit of 6 records (days you are generating the report for). So now, as the records are being queried, the Start Date keeps adding 1 day... first record results in June 1st, next record June 2nd, etc.
So now, you have a simulated calendar with 6 records dated from June 1 to June 6. Take that and join to your "data" table and you are already qualifying your dates via the join and get only those dates of activity. I'm joining on the DATE() of the from unix time since you care about anything that happend on June 1, and June 1 # 12:00:00AM is different than June 1 # 8:45am, so matching on the date only portion, they should remain in proper grouping.
You could expand this answer by changing the inner '2013-05-31' to some MySQL Date function to get the last day of the prior month, and the limit based on whatever day in the current month you are doing so these are not hard-coded.
Create a Time dimension. This is a standard OLAP reporting trick. You don't need a cube in order to do OLAP tricks, though. Simply find a script on the internet to generate a Calendar table and join to that table.
Also, I think your query is missing a WHERE clause.
Other useful tricks include creating a "Tally" table that is a list of numbers from 1 to N where N is usually the max of the bigint on your database management system.
No code provided here, as I am not a MySQL guru.
Pseudo-code is:
Select * from Data left join TimeDimension on data.date = timedimension.date