how to set an array as a mysql user variable - mysql

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;

Related

Is there a way I can optimize this query to make it shorter?

I am trying to make the code shorter and simpler. The code is working. I want to take the inner queries to a CTE or temp table to make it shorter. How do I go about this?
Create OR REPLACE view piper.v_da_areas_per_site(logistics_id, streams_label, city_id, type) as
SELECT
dataset.logistics_id,
dataset.streams_label,
dataset.city_id,
FROM
(SELECT
DISTINCT r.logistics_id,
r.streams_label,
r.city_id
FROM
top.distributions r
JOIN (
SELECT
distributions. Distribution_id,
max(distributions.event_time) AS event_time
FROM
top. distributions distributions
WHERE
distributions.stream_type = 'DA'
AND distributions. distribution_space = 'DaFilterName'
GROUP BY
distributions. distribution_id
) m ON r. distribution_id = m. distribution_id
AND r.event_time = m.event_time
AND current_date >= r. distribution_start_time
AND r. distribution_end_time >= current_date
AND r.stream_type = 'DA'
AND r. distribution_space = 'DaFilterName'
AND (
r.logistics_id IN (
SELECT
DISTINCT dev_class_hub_list.class_hub
FROM
piper.dev_class_hub_list
WHERE
dev_class_hub_list.is_3p = 'N'
)
)
)dataset;
These indexes may help:
distributions: INDEX(stream_type, distribution_space,
distribution_id, Distribution_id, event_time)
distributions: INDEX(distribution_id) -- (unless is PK)
dev_class_hub_list: INDEX(is_3p, class_hub)
Try changing
AND ( r.logistics_id IN (
SELECT DISTINCT dev_class_hub_list.class_hub
FROM dev_class_hub_list
WHERE dev_class_hub_list.is_3p = 'N' ) )
to
AND EXISTS ( SELECT 1
FROM dev_class_hub_list AS dchl
WHERE dchl.is_3p = 'N'
AND dchl.class_hub = r.logistics_id )
The DISTINCT may be causing an extra de-dup pass; the **EXISTS** is a "semi-join", so it stops when it finds the first one. The Optimizer may turn one of these into the other. Please do
EXPLAIN SELECT ...;
SHOW WARNINGS; -- to see the transformations performed
Range tests like this are notoriously difficult to optimize:
AND current_date >= r.distribution_start_time
AND r.distribution_end_time >= current_date
As for CTE -- I need to understand what the SELECT is trying to do.
As for VIEW -- Views are "syntactic sugar"; they tend to be no better than the equivalent SELECT. Will you be adding WHERE or other clauses when you SELECT from this VIEW? They may or may not be efficiently folded into the resulting Select.

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.

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.

Count Events/year in SQL

I have a list of events that have a date. I'm trying to count how many events take place in the current year, and 5 years on either side (regardless of whether any events took place) in mySQL using simple joins, selects, etc (no subqueries) in a single statement.
I have a table that produces the years and the number of events in that year, but am having problems when the year has no events taking place
Look into date functions on mysql http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html#function_datediff
You can use datediff which will give you difference in days. Ex;
WHERE abs(datediff(now(), event_date)) < 365*5
or dateadd(), if your event dates are timestamps, use timestampdiff()
Sample query
SELECT count(*) FROM mytable
WHERE abs(datediff(now(), event_date)) < 365*5
UPDATE
based on some of the comments I've read here, here's a query for you
SELECT year(event_date) as event_year, count(event_date)
FROM mytable
WHERE
abs(datediff(now(), event_date)) < 365*5
GROUP by year(event_date)
Feel free to adjust 5 in (365 * 5) for different range
UPDATE 2
This is NOT very pretty but you can try this with pure mysql. You can also modify this to be a stored proc if necessary:
SET #y6 = year(now());
SET #y5 = #y6-1;
SET #y4 = #y5-1;
SET #y3 = #y4-1;
SET #y2 = #y3-1;
SET #y1 = #y2-1;
SET #y7 = #y6+1;
SET #y8 = #y7+1;
SET #y9 = #y8+1;
SET #y10 = #y9+1;
SET #y11 = #y10+1;
CREATE TEMPORARY TABLE event_years (event_year int not null);
INSERT INTO event_years SELECT #y1;
INSERT INTO event_years SELECT #y2;
INSERT INTO event_years SELECT #y3;
INSERT INTO event_years SELECT #y4;
INSERT INTO event_years SELECT #y5;
INSERT INTO event_years SELECT #y6;
INSERT INTO event_years SELECT #y7;
INSERT INTO event_years SELECT #y8;
INSERT INTO event_years SELECT #y9;
INSERT INTO event_years SELECT #y10;
INSERT INTO event_years SELECT #y11;
SELECT ey.event_year , (SELECT count(event_date) from mytable where year(event_date) = ey.event_year)
from event_years ey;
temporary table will get dropped by itself after your connection is closed. If you add DROP TABLE after SELECT, you might not get your results back.
did you try to use join left?
MODIFIED:
SELECT tleft.YEARS, COUNT(tright.EVENTS)
FROM ONLY_YEARS tleft LEFT JOIN TABLE1 tright
ON (tleft.YEARS = tright.YEARS)
GROUP BY tleft.YEARS;
With that modification, you need to point to a table that holds all the years (ONLY_YEARS), maybe a dummy table with one column that goes from 1990 to 2020...
Left join optimization for MySQL link
To select a count of events that happened between two years, grouped by years, the following sql should suffice:
select year(event.date), count(*) from event where event.date >= '2006' and event.date <= '2016' group by year(event.date);
However, if no events occurred in a year, no result will be returned for it.
Databases are not really designed for such dynamic things and I'd suggest such logic should be put in a business (or possibly data-access) layer.

MySQL Query: Query with conditional statements

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.