MySQL Query: Query with conditional statements - mysql

i have this query
SELECT
IF(isnull(ub.user_lecture_id), 0, ub.user_lecture_id) as IsPurchased,
cs.title,cs.start_date, cs.start_time, cs.end_time
FROM campus_schedule cs
LEFT JOIN campus_bookinfo cb ON cs.bookid=cb.idx_campus_bookinfo
LEFT JOIN user_lectures ub ON ub.id_product = cs.idx_campus_schedule AND ub.id_customer = 11
WHERE cs.idx_campus = 1 and cs.title like '%%' and cs.status=1
Which Shows:
Click to view Output
Explanation: if (IsPurchased == 0) it is not yet bought my customer
My Question: if you look at the time of row with IsPurchased=1, the time range is conflicting with the time in IsPurchases=0. how can i compare and conclude that the time of the same date of the query is conflicting to the time and date of the other rows. results may be 1 or 0 in a "conflict" field name
Hope you got the point. Thanks for the help!!!

To compare times, you will find it easier to use DATETIME fields.
To check for "conflicting" rows, you'll probably need to have a subquery in the WHERE clause.

Subquery should work but will be inefficient in mysql. You should create temporary table and analyze it. Or do the same inline, like:
set #lastdate=0;
set #lasttime=0;
select IsPurchased, title, start_date, start_time, end_time, if(#lastdate = start_date, #lasttime < end_time, 1) as CONFLICT, #lastdate:=start_date, #lasttime:=start_time
from (your_query ORDER BY start_date, start_time, end_time) t ;
that is just an idea, it worked for me several times.

Related

MYSQL Check for record existence while fetching records

I've ran into some performance issues with my database structure "or better to say my query instead "
I have a the following table :
http://sqlfiddle.com/#!9/348cb
And following query trying to fetch certain data, and after that trying to check if there are other records matching my conditions, it's all in the following query.
it is working as expected, the only reason that I'm asking this question is that if there is a way I could increase its performance or use another way to get the results.
As you can see, There two ( SELECT )'s which trying to check if there are any other records containing current query data.
SELECT (
SELECT COUNT(*) FROM log AS LIKES
WHERE L.target_account=LIKES.target_account
AND LIKES.type='like'
) as liked,
(
SELECT COUNT(*) FROM log AS COMMENTS
WHERE L.target_account=COMMENTS.target_account
AND COMMENTS.type='follow_back'
) as follow_back,
(
SELECT COUNT(*) FROM log AS FOLLOW_BACK
WHERE L.target_account=FOLLOW_BACK.target_account
AND COMMENTS.type='follow_back'
) as follow_back,
L.*
FROM `log` as L
WHERE `L`.`information` = '".$target_name."'
AND `L`.`account_id` = '".$id."'
AND `L`.`date_ts` BETWEEN CURDATE() - INTERVAL ".$limit." DAY AND CURDATE()
This query takes too much time to fetch the data.
Thanks in advance.
You may be able to rewrite the query, depending on the relationship between target account and account id.
In the meantime, you want indexes. The two you want are instagram_log(target_account, type) and instagram_log(account_id, information, date_ts):
create index idx_instagram_log_1 on instagram_log(target_account, type);
create index idx_instagram_log_2 on instagram_log(account_id, information, date_ts);
SELECT SUM(LIKES) LIKES,SUM(FOLLOW_BACK) FOLLOW_BACK,SUM(COMMENTS) FROM
(
SELECT
CASE WHEN L.type='like' THEN 1 ELSE 0 END LIKES,
CASE WHEN L.type='follow_back' THEN 1 ELSE 0 END FOLLOW_BACK,
CASE WHEN L.type='comments' THEN 1 ELSE 0 END COMMENTS
FROM `log` as L
WHERE `L`.`information` = '".$target_name."'
AND `L`.`account_id` = '".$id."'
AND `L`.`date_ts` BETWEEN CURDATE() - INTERVAL ".$limit." DAY AND CURDATE()
)Z
Try the above query.

Getting previous row in MySQL

I'm stucked in a MySQL problem that I was not able to find a solution yet. I have the following query that brings to me the month-year and the number new users of each period in my platform:
select
u.period ,
u.count_new as new_users
from
(select DATE_FORMAT(u.registration_date,'%Y-%m') as period, count(distinct u.id) as count_new from users u group by DATE_FORMAT(u.registration_date,'%Y-%m')) u
order by period desc;
The result is the table:
period,new_users
2016-10,103699
2016-09,149001
2016-08,169841
2016-07,150672
2016-06,148920
2016-05,160206
2016-04,147715
2016-03,173394
2016-02,157743
2016-01,173013
So, I need to calculate for each month-year the difference between the period and the last month-year. I need a result table like this:
period,new_users
2016-10,calculate(103699 - 149001)
2016-09,calculate(149001- 169841)
2016-08,calculate(169841- 150672)
2016-07,So on...
2016-06,...
2016-05,...
2016-04,...
2016-03,...
2016-02,...
2016-01,...
Any ideas: =/
Thankss
You should be able to use a similar approach as I posted in another S/O question. You are on a good track to start. You have your inner query get the counts and have it ordered in the final direction you need. By using inline mysql variables, you can have a holding column of the previous record's value, then use that as computation base for the next result, then set the variable to the new balance to be used for each subsequent cycle.
The JOIN to the SqlVars alias does not have any "ON" condition as the SqlVars would only return a single row anyhow and would not result in any Cartesian product.
select
u.period,
if( #prevCount = -1, 0, u.count_new - #prevCount ) as new_users,
#prevCount := new_users as HoldColumnForNextCycle
from
( select
DATE_FORMAT(u.registration_date,'%Y-%m') as period,
count(distinct u.id) as count_new
from
users u
group by
DATE_FORMAT(u.registration_date,'%Y-%m') ) u
JOIN ( select #prevCount := -1 ) as SqlVars
order by
u.period desc;
You may have to play with it a little as there is no "starting" point in counts, so the first entry in either sorted direction may look strange. I am starting the "#prevCount" variable as -1. So the first record processed gets a new user count of 0 into the "new_users" column. THEN, whatever was the distinct new user count was for the record, I then assign back to the #prevCount as the basis for all subsequent records being processed. yes, it is an extra column in the result set that can be ignored, but is needed. Again, it is just a per-line place-holder and you can see in the result query how it gets its value as each line progresses...
I would create a temp table with two columns and then fill it using a cursor that
does something like this (don't remember the exact syntax - so this is just a pseudo-code):
#val = CURSOR.col2 - (select col2 from OriginalTable t2 where (t2.Period = (CURSOR.Period-1) )))
INSERT tmpTable (Period, NewUsers) Values ( CURSOR.Period, #val)

SQL: How to decrease the statement execution time?

I'm not an expert in SQL, i have an sql statement :
SELECT * FROM articles WHERE article_id IN
(SELECT distinct(content_id) FROM contents_by_cats WHERE cat_id='$cat')
AND permission='true' AND date <= '$now_date_time' ORDER BY date DESC;
Table contents_by_cats has 11000 rows.
Table articles has 2700 rows.
Variables $now_date_time and $cat are php variables.
This query takes about 10 seconds to return the values (i think because it has nested SELECT statements) , and 10 seconds is a big amount of time.
How can i achieve this in another way ? (Views or JOIN) ?
I think JOIN will help me here but i don't know how to use it properly for the SQL statement that i mentioned.
Thanks in advance.
A JOIN is exactly what you are looking for. Try something like this:
SELECT DISTINCT articles.*
FROM articles
JOIN contents_by_cats ON articles.article_id = contents_by_cats.content_id
WHERE contents_by_cats.cat_id='$cat'
AND articles.permission='true'
AND articles.date <= '$now_date_time'
ORDER BY date DESC;
If your query is still not as fast as you would like then check that you have an index on articles.article_id and contents_by_cats.content_id and contents_by_cats.cat_id. Depending on the data you may want an index on articles.date as well.
Do note that if the $cat and $now_date_time values are coming from a user then you should really be preparing and binding the query rather than just dumping these values into the query.
This is the query we are starting with:
SELECT a.*
FROM articles a
WHERE article_id IN (SELECT distinct(content_id)
FROM contents_by_cats
WHERE cat_id ='$cat'
) AND
permission ='true' AND
date <= '$now_date_time'
ORDER BY date DESC;
Two things will help this query. The first is to rewrite it using exists rather than in and to simplify the subquery:
SELECT a.*
FROM articles a
WHERE EXISTS (SELECT 1
FROM contents_by_cats cbc
WHERE cbc.content_id = a.article_id and cat_id = '$cat'
) AND
permission ='true' AND
date <= '$now_date_time'
ORDER BY date DESC;
Second, you want indexes on both articles and contents_by_cats:
create index idx_articles_3 on articles(permission, date, article_id);
create index idx_contents_by_cats_2 on contents_by_cat(content_id, cat_id);
By the way, instead of $now_date_time, you can just use the now() function in MySQL.

MySQL Function only once with group by

I have the following statement:
SELECT user_id, myMysqlFunction(user_id) FROM users_to_days GROUP BY user_id;
The table stores one row per user and day.
The problem is that MySql calls the Function "myMysqlFunction(user_id)" for every row and groups the rows after that.
My target is to execute the Function just once (for performance reasons).
I tried the following state without success:
SELECT user_id, IF(`date` = min(`date`),myMysqlFunction(user_id),0) FROM users_to_days GROUP BY user_id;
MySql still executes the statement for every row.
If I write a static date (for example '2014-09-01') - it works:
SELECT user_id, IF(`date` = '2014-09-01',myMysqlFunction(user_id),0) FROM users_to_days GROUP BY user_id;
But I don't know the min. date for the user at the time of execution. (its not the same for every user)
you could try a subquery:
SELECT user_id, myMysqlFunction(user_id)
FROM (SELECT user_id
FROM users_to_days
GROUP BY user_id);
Edit: comment got split up, I wanted to say:
I meant: try $date = date('Y-m-d');, that will get the time the user runs it. Put that in min($date), and it should be working.

how to set an array as a mysql user variable

I didn't expect to find this so difficult, but I'm trying to set a user variable in MySQL to contain an array of values. I have no clue how to do this so tried doing some research and was quite suprised to find no answer. I have tried:
SET #billable_types = ['client1','client2','client3'];
The reason being I would like to use the variable in the following statement later on:
SELECT sum((time_to_sec(timediff(tlg.time_stop, tlg.time_start))/3600)) as billable_hours
from mod_tmlog_time_log tlg, mod_tmlog_task_list mttl
where date(tlg.time_start) >= #time_start
and date(tlg.time_stop) <= #time_stop
and mttl.type IN (#billable_types)
and tlg.task_id = mttl.id
group by start_date
order by start_date desc;
Would be very grateful for help.
Fast forward a while, I ended up with the following quick and dirty solution which doesn't give me the flexibility of re-using the array elsewhere in the code but hey it's an unchargeable admin task so I don't want to spend any more time on it.
SELECT WEEKDAY(tlg.time_start) AS day_of_week, date(tlg.time_start) as start_date,
sum((time_to_sec(timediff(tlg.time_stop, tlg.time_start))/3600)) as billable_hours
from mod_tmlog_time_log tlg, mod_tmlog_task_list mttl
where date(tlg.time_start) >= #time_start
and date(tlg.time_stop) <= #time_stop
and mttl.type IN ('c1','c2','c3')
and tlg.task_id = mttl.id
group by start_date
order by start_date desc;
joostschouten seems to have found the most elegant solution (not tested it myself yet) but next time I'm writing something which calls for this I will remember to test it!
Just found the answer here: How to cycle with an array in MySQL?
set #billable_types = 'client1,client2,client3';
select * from mttl where find_in_set(mttl.type, #billable_types);
As Marc B mentioned, there is no array variable in MYSQL.
The alternative to find_in_set solution is to use SELECT with UNION to simulate the array:
SELECT billable_type FROM (
SELECT 'client1' AS billable_type UNION
SELECT 'client2' AS billable_type UNION
SELECT 'client3' AS billable_type) AS t
So your query will looks like that:
SELECT sum((time_to_sec(timediff(tlg.time_stop, tlg.time_start))/3600)) as billable_hours
from mod_tmlog_time_log tlg, mod_tmlog_task_list mttl
where date(tlg.time_start) >= #time_start
and date(tlg.time_stop) <= #time_stop
and mttl.type IN (
SELECT billable_type FROM (
SELECT 'client1' AS billable_type UNION
SELECT 'client2' AS billable_type UNION
SELECT 'client3' AS billable_type) AS t
)
and tlg.task_id = mttl.id
group by start_date
order by start_date desc;
If the user has the CREATE TABLE privilege, an array can be simulated by creating a temporary, single-column table. A value or values in the table can be retrieved with a SELECT statement. Temporary tables are dropped at the end of the session, but it's a good idea to explicitly drop them once they're no longer needed.
CREATE TEMPORARY TABLE billable_types (c VARCHAR(16));
INSERT INTO billable_types VALUES ('client1'), ('client2'), ('client3');
SELECT sum((time_to_sec(timediff(tlg.time_stop, tlg.time_start))/3600)) as billable_hours
from mod_tmlog_time_log tlg, mod_tmlog_task_list mttl
where date(tlg.time_start) >= #time_start
and date(tlg.time_stop) <= #time_stop
and mttl.type IN (SELECT * FROM billable_types)
and tlg.task_id = mttl.id
group by start_date
order by start_date desc;
DROP TABLE billable_types;