Declare variable in a SQL statement - mysql

I wonder if is possible to create variables inside a sql select query.
Explanation in the following sample, where we select the news from a city published /or updated in the past month (or older):
set #monthBack = 1; -- one month back in time
set #cityName = "Berlin"; -- take a city name
select n.title, coalesce (n.releaseDate, n.updateDate) as news_date
from news as n
inner join with cities as c on c.id = n.cityid
where
c.name = #cityName and
year(coalesce (n.releaseDate, n.updateDate)) =
year(current_date - interval #monthBack month)
month(coalesce (n.releaseDate, n.updateDate)) =
month(current_date - interval #monthBack month)
order by coalesce (n.releaseDate, n.updateDate)
If a publication date is not defined, the news date will be the updated date.
So, I would like to replace the repetition of the coalesce (n.releaseDate, n.updateDate) by a specific variable that is specific to the concrete select statement...
Is it possible in the mysql database?
PS.
As the "problem" is more about sql syntax and code readability, I would like to resolve it just by modifying the sql code, but not applying to external tools or creating additional objects like views or tables (does not worth such complexity for some syntactic optimization)

I can think of many ways to resolve your problems but I'm not sure wich one is the best.
You could do a ";With" subquery to determine the value of "coalesce (n.releaseDate, n.updateDate)" and then use it in the query below.
Or use a stored procedure ou function wich recive the right parameters
You could do a view on your table news and add a column wich is the value of "coalesce (n.releaseDate, n.updateDate)"
Or you can also do something like :
set #monthBack = 1; -- one month back in time
DECLARE #Query NVarchar(Max),
#NewsDate NVARCHAR(MAX)
SET #NewsDate = coalesce (n.releaseDate, n.updateDate) -- Determine the value here
SET #Query =
'select n.title, '+#NewsDate+' as news_date
from news as n
inner join with cities as c on c.id = n.cityid
where
c.name='Berlin' and
year('+#NewsDate+') =
year(current_date - interval #monthBack month)
month('+#NewsDate+')) =
month(current_date - interval #monthBack month)
order by '+#NewsDate
execute sp_executesql #Query

Related

how do I make this query run faster?

I have this SQL query running on a PHP website. This is an old site and the query was build by previous developer few years ago. But now as the site data is increased to around 230mb, this query has become pretty slow to execute. Take around 15-20 seconds. Is there any way I can make this run faster?
SELECT DISTINCT
NULL AS bannerID,
C1.url AS url,
LOWER(C1.Organization) AS company_name,
CONCAT(
'https://mywebsite.co.uk/logos/',
C1.userlogo
) AS logo_url
FROM
Company AS C1
INNER JOIN Vacancy AS V1 ON LOWER(V1.company_name) = LOWER(C1.Organization)
WHERE
V1.LiveDate <= CURDATE()
AND url = ''
AND V1.ClosingDate >= CURDATE()
AND C1.flag_show_logo = 1
As commented, your query is suffering from being non-sargable due to the use of lower function.
Additionally I suspect you can remove the distinct by using exists instead of joining your tables
select null as bannerID,
C1.url as url,
Lower(C1.Organization) as company_name,
Concat('https://mywebsite.co.uk/logos/', C1.userlogo) as logo_url
from Company c
where c.flag_show_logo = 1
and c.url = ''
and exists (
select * from Vacancy v
where v.LiveDate <= CURDATE()
and v.ClosingDate >= CURDATE()
and v.company_name = c.Organization
)
Avoid the sargable problem by changing to
ON V1.company_name = C1.Organization
and declaring those two columns to be the same collation, namely a collation ending with "_ci".
And have these composite indexes:
C1: INDEX(flag_show_logo, url, Organization, userlogo)
V1: INDEX(company_name, LiveDate, ClosingDate)
(These indexes should help Stu's answer, too.)

How to delete data with date value + time

I want to delete data between 'change_slot' and ('change_slot' + 2min) WHERE type = ' crash', 'pilot_death', or 'eject'
DELETE FROM pe_LogEvent
WHERE pe_LogEvent_type = 'crash'
OR pe_LogEvent_type = 'pilot_death'
OR pe_LogEvent_type = 'eject'
AND pe_LogEvent_datetime IN??? pe_LogEvent_type = 'change_slot'
BETWEEN ('change_slot datetime') AND (DATE_ADD('change_slot datetime', INTERVAL 120)))
Visual SQL DB Image with annotation of what I am trying to accomplish.:
I have another issue now, I'm trying to think of a way to solve this one as well. I was trying to use maybe a 'for each' iterative statement to filter only 1 pilotname at a time maybe? How would you go about solving this?
Problem: different pilot change_slot and causes current pilot's death not to count now ... I need to only have same pilot change_slot within 2 min pilot_death, crash, or eject does not count
DELETE FROM pe_LogEvent
WHERE pe_LogEvent_datetime BETWEEN
(SELECT pe_LogEvent_datetime FROM pe_LogEvent WHERE pe_LogEvent_type = 'change_slot' ORDER BY pe_LogEvent_datetime DESC LIMIT 1)
AND (SELECT pe_LogEvent_datetime + INTERVAL 2 MINUTES FROM pe_LogEvent WHERE pe_LogEvent_type = 'change_slot' ORDER BY pe_LogEvent_datetime DESC LIMIT 1)
AND pe_LogEvent_type IN ('crash', 'pilot_death', 'eject')
Somethink like this?
EDIT:
According to your comments below this answer, propably you want something like this, but i am not sure but if not, propably you could change this SQL at your own.
For such non-obvious queries i always use the CTE. I love it because you could use it to 'separate' your logic to simplest 'steps'. Then it's looks simple and easy to see what going on (At least for me) instead of one big query from which it is not so easy to deduce what is happening and is more difficult to edit/read.
First you could use the CTE to get all 'change_slot' type events with dates:
WITH change_slots_dates AS (
SELECT
pe_LogEvent_datetime AS date_start,
pe_LogEvent_datetime + INTERVAL 2 MINUTE AS end_time
FROM pe_LogEvent
WHERE pe_LogEvent_type = 'change_slot'
)
then another CTE to get IDs between this dates (Remember that your's CTE need to be separate by , sign:
rows_to_delete AS (
SELECT
pe.pe_LogEvent_id AS id
FROM pe_LogEvent AS pe
INNER JOIN change_slots_dates AS csd ON (pe.pe_LogEvent_datetime BETWEEN csd.date_start AND csd.end_time)
WHERE pe.pe_LogEvent_type IN ('crash', 'pilot_death', 'eject')
)
after that you can finally delete this entities:
DELETE FROM pe_LogEvent WHERE pe_LogEvent_id IN (SELECT id FROM rows_to_delete);
Notice that this is a one SQL query and you cannot use this as 3 queries. So all seems to be like this:
WITH change_slots_dates AS (
SELECT
pe_LogEvent_datetime AS date_start,
pe_LogEvent_datetime + INTERVAL 2 MINUTE AS end_time
FROM pe_LogEvent
WHERE pe_LogEvent_type = 'change_slot'
),
rows_to_delete AS (
SELECT
pe.pe_LogEvent_id AS id
FROM pe_LogEvent AS pe
INNER JOIN change_slots_dates AS csd ON (pe.pe_LogEvent_datetime BETWEEN csd.date_start AND csd.end_time)
WHERE pe.pe_LogEvent_type IN ('crash', 'pilot_death', 'eject')
)
DELETE FROM pe_LogEvent WHERE pe_LogEvent_id IN (SELECT id FROM rows_to_delete);
I hope it will be finally more helpfull.

MYSQL updating a table containing a join and subquery

I am relatively new to SQL, i am trying to update monthly salary based on employees working for a certain duration, the query displays the data using info from the person and employee table but it won't update, i keep getting a 'operand should contain 1 column' error? How would i go about displaying all the data and be able to update the monthly_salary column as well? Thanks.
UPDATE employee ep set monthly_salary = monthly_salary*1.15 = all(
SELECT p.person_id, p.name_first, p.name_last, ep.monthly_salary, ep.start_date, curdate() as today_date,
TIMESTAMPDIFF(month,ep.start_date,curdate()) as duration_months
FROM employee ep
INNER JOIN person p ON ep.person_id = p.person_id having duration_months > 24);
query result
I want this expected result but the monthly salary hasn't been updated yet, is it possible to display this and update the monthly_salary?
You are not able to do both in a single query. Typically one would run a "select query" to inspect if the desired logic appears correct, e.g.
SELECT
p.person_id
, p.name_first
, p.name_last
, ep.start_date
, curdate() as today_date
, TIMESTAMPDIFF(month,ep.start_date,curdate()) as duration_months
FROM employee ep
INNER JOIN person p ON ep.person_id = p.person_id
WHERE ep.start_date < curdate() - INTERVAL 24 MONTH
;
In that query the important piece of logic is the where clause which seeks out any employees with a start date earlier than today - 24 months.
If that logic is correct, then apply the same logic in an "update query":
UPDATE employee ep
SET monthly_salary = monthly_salary*1.15
WHERE ep.start_date < curdate() - INTERVAL 24 MONTH
;
Syntax notes:
you cannot string multiple conditions together using multiple equality operators (monthly_salary = monthly_salary*1.15 = all(...) there are 2 = signs in that
x = all() requires that all values returned by a subquery will equal x
the having clause is NOT just a substitute for a where clause. A having clause is designed for evaluating aggregated data e.g. having count(*) > 2
Finally, while it was inventive to use the having clause, what you were doing was gaining access to the alias 'duration_months', so you could simply have done this instead:
where TIMESTAMPDIFF(month,ep.start_date,curdate()) > 24
BUT this is not a good way to filter information because it requires running a function on every row of data before a decision can be reached. This has he effect of making queries slower. Compare that to the following:
WHERE ep.start_date < curdate() - INTERVAL 24 MONTH
ep.start_date is not affected by any function, and curdate() - INTERVAL 24 MONTH is just one calculation (not done every row). So this is much more efficient (also known as "sargable").

Run SQL queries by month on MySQL workbench?

I am currently working on a MySQL db with MySQL Workbench.
My objective is to retrieve the signups from a database to establish my company's KPIs on an Excel spreadsheet.
I wrote some sql queries that worked but I want to set up a very complete one in order to avoid using xxx different queries.
To get the signups for each month (based on 'created_at'), this makes the job:
SELECT year(u.created_at) year, monthname(u.created_at) month, COUNT(DISTINCT u.id) as 'New shoppers signups'
FROM users u
GROUP BY year, month
ORDER BY u.created_at
But I also wanted to have the total of previous signups for each month
Jan : 12
Feb : 14 (12 + 2 new signups)
March : 22 (14 + 8 new signups)
...
Where I get the sum of all the previous signups
I was thinking about something like:
DECLARE #month = '2012-01-01' //startdate
WHILE #month < curdate()
BEGIN
SELECT count(distinct u.id)
WHERE u.created_at < #month
dateadd(month, 1, #month) // incrementing to next month
END
But neither the while loop, the declare, set, or date function do work on MySQL Workbench.
I heard I have to declare procedures but I didn't have any more success...
I know I could use excel to get the result, but I want to improve my use of SQL and make this a very clear work.
You are actually close to the answer. Take your results and make that an inner query. Then that is basis of an outer query using MySQL variables to accumulate for each row.
select
pq.yearAdded,
pq.monthAdded,
pq.NewShoppers as 'New shoppers signups',
#runBal := #runBal + pq.NewShoppers as TotalNewShoppers
from
( SELECT
year(u.created_at) yearAdded,
monthname(u.created_at) monthAdded,
COUNT(DISTINCT u.id) as NewShoppers
from
users u
GROUP BY
year(u.created_at),
monthname(u.created_at)
ORDER BY
year(u.created_at),
monthname(u.created_at) ) pq,
( select #runBal := 0 ) sqlvars
I would just suggest having column names stay away from possible reserved words, such as Year, Month and other standard SQL commands and function names... otherwise you typically need to add tick-marks around the column names

How to get list of records from table where the time difference found using DATEDIFF function between 2 variables that are select queries themselves?

SET #startdate = (select LOG_TIME from log.time where sender='Japan' and receiver ='USA' and code=158);
SET #enddate = (select LOG_TIME from log.time where sender='Japan' and receiver ='USA' and code=189);
select * from log.time where DATEDIFF(minute, #startdate, #enddate) >= 10;
Here I want to use 2 variables (#startdate and #enddate) which are populated with multiple entries coming from the select queries used .
And for the last line , I want the select query to return a list of records where the DATEDIFF function is greater than or equal to 10 minutes by using these 2 variables with multiple values .
P.S I am using the Squirrel SQL Client 2.3 )
The issue is I have no idea if it is possible to use multiple values for variables.
Also please advise or provide any solution to the above issue such that the query works in the end.
You can't use variables this way.
Now it's hard to tell for sure not seeing your table schema and sample data but you should be able to do what you want using JOIN with a query like this
SELECT l1.*
FROM log.time l1 JOIN log.time l2
ON l1.sender = l2.sender
AND l1.receiver = l2.receiver
AND l1.code = 158
AND l2.code = 189
WHERE l1.sender = 'Japan'
AND l1.receiver = 'USA'
AND DATEDIFF(minute, l1.log_time, l2.log_time) >= 10
If you were to provide a table schema, sample data and desired output, then it'll be possible to test your query