Implementing mariadb's NTILE() function in MYSQL 5.7 - mysql

I can't use NTILE() because I'm currently on MYSQL 5.7 so I was wondering how would I go about converting this to be usable in 5.7 without NTILE.
Here's the query I'd like to convert:
SELECT
clientid,
ntile(4) over (
order by
last_order_date
) AS `rfm_recency`,
ntile(4) over (
order by
count_order
) AS `rfm_frequency`,
ntile(4) over (
order by
avg_amount
) AS `rfm_monetary`
FROM
(
SELECT
`clientid`,
MAX(`date`) AS `last_order_date`,
COUNT(`id`) AS `count_order`,
AVG(`price`) AS `avg_amount`
FROM
`design`
GROUP BY
`clientid`
) AS t
) AS p```

Related

MySql last record from group by item_id with order by date

My database table name is ledgers and fields are id, item_id, date, ...other fields
I Want the last record from (groupBy item_id order by date ASC). from each group.
I tried below query
select
`id`,
`item_id`,
`date`,
`opening_quantity`,
`closing_quantity`,
`item_rate`,
`item_value`,
`previous_rate`
from `ledgers`
where date(`date`) >= ? and date(`date`) <= ?
group by `item_id`
order by `date` desc
Can you guys please help.
You can filter with a correlated subquery:
select t.*
from `ledgers` t
where
date(t.`date`) >= ?
and date(t.`date`) <= ?
and t.`date` = (
select max(t1.`date`)
from `ledgers` t1
where t1.`item_id` = t.`item_id`
)
For performance, consider an index on (item_id, date).
Another option is to use rank() (available in MySQ 8.0 only):
select *
from (
select
t.*,
rank() over(partition by `item_id` order by `date` desc) rn
from `ledgers` t
where date(t.`date`) >= ? and date(t.`date`) <= ?
) t
where rn = 1

MySQL Nested Select Query?

Ok, so I have the following query:
SELECT MIN(`date`), `player_name`
FROM `player_playtime`
GROUP BY `player_name`
I then need to use this result inside the following query:
SELECT DATE(`date`) , COUNT(DISTINCT `player_name`)
FROM `player_playtime /*Use previous query result here*/`
GROUP BY DATE( `date`) DESC LIMIT 60
How would I go about doing this?
You just need to write the first query as a subquery (derived table), inside parentheses, pick an alias for it (t below) and alias the columns as well.
The DISTINCT can also be safely removed as the internal GROUP BY makes it redundant:
SELECT DATE(`date`) AS `date` , COUNT(`player_name`) AS `player_count`
FROM (
SELECT MIN(`date`) AS `date`, `player_name`
FROM `player_playtime`
GROUP BY `player_name`
) AS t
GROUP BY DATE( `date`) DESC LIMIT 60 ;
Since the COUNT is now obvious that is only counting rows of the derived table, you can replace it with COUNT(*) and further simplify the query:
SELECT t.date , COUNT(*) AS player_count
FROM (
SELECT DATE(MIN(`date`)) AS date
FROM player_playtime
GROUP BY player_name
) AS t
GROUP BY t.date DESC LIMIT 60 ;

How many different ways are there to get the second row in a SQL search?

Let's say I was looking for the second most highest record.
Sample Table:
CREATE TABLE `my_table` (
`id` int(2) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`value` int(10),
PRIMARY KEY (`id`)
);
INSERT INTO `my_table` (`id`, `name`, `value`) VALUES (NULL, 'foo', '200'), (NULL, 'bar', '100'), (NULL, 'baz', '0'), (NULL, 'quux', '300');
The second highest value is foo. How many ways can you get this result?
The obvious example is:
SELECT name FROM my_table ORDER BY value DESC LIMIT 1 OFFSET 1;
Can you think of other examples?
I was trying this one, but LIMIT & IN/ALL/ANY/SOME subquery is not supported.
SELECT name FROM my_table WHERE value IN (
SELECT MIN(value) FROM my_table ORDER BY value DESC LIMIT 1
) LIMIT 1;
Eduardo's solution in standard SQL
select *
from (
select id,
name,
value,
row_number() over (order by value) as rn
from my_table t
) t
where rn = 1 -- can pick any row using this
This works on any modern DBMS except MySQL. This solution is usually faster than solutions using sub-selects. It also can easily return the 2nd, 3rd, ... row (again this is achievable with Eduardo's solution as well).
It can also be adjusted to count by groups (adding a partition by) so the "greatest-n-per-group" problem can be solved with the same pattern.
Here is a SQLFiddle to play around with: http://sqlfiddle.com/#!12/286d0/1
This only works for exactly the second highest:
SELECT * FROM my_table two
WHERE EXISTS (
SELECT * FROM my_table one
WHERE one.value > two.value
AND NOT EXISTS (
SELECT * FROM my_table zero
WHERE zero.value > one.value
)
)
LIMIT 1
;
This one emulates a window function rank() for platforms that don't have them. It can also be adapted for ranks <> 2 by altering one constant:
SELECT one.*
-- , 1+COALESCE(agg.rnk,0) AS rnk
FROM my_table one
LEFT JOIN (
SELECT one.id , COUNT(*) AS rnk
FROM my_table one
JOIN my_table cnt ON cnt.value > one.value
GROUP BY one.id
) agg ON agg.id = one.id
WHERE agg.rnk=1 -- the aggregate starts counting at zero
;
Both solutions need functional self-joins (I don't know if mysql allows them, IIRC it only disallows them if the table is the target for updates or deletes)
The below one does not need window functions, but uses a recursive query to enumerate the rankings:
WITH RECURSIVE agg AS (
SELECT one.id
, one.value
, 1 AS rnk
FROM my_table one
WHERE NOT EXISTS (
SELECT * FROM my_table zero
WHERE zero.value > one.value
)
UNION ALL
SELECT two.id
, two.value
, agg.rnk+1 AS rnk
FROM my_table two
JOIN agg ON two.value < agg.value
WHERE NOT EXISTS (
SELECT * FROM my_table nx
WHERE nx.value > two.value
AND nx.value < agg.value
)
)
SELECT * FROM agg
WHERE rnk = 2
;
(the recursive query will not work in mysql, obviously)
You can use inline initialization like this:
select * from (
select id,
name,
value,
#curRank := #curRank + 1 AS rank
from my_table t, (SELECT #curRank := 0) r
order by value desc
) tb
where tb.rank = 2
SELECT name
FROM my_table
WHERE value < (SELECT max(value) FROM my_table)
ORDER BY value DESC
LIMIT 1
SELECT name
FROM my_table
WHERE value = (
SELECT min(r.value)
FROM (
SELECT name, value
FROM my_table
ORDER BY value DESC
LIMIT 2
) r
)
LIMIT 1

Every derived table must have its own alias - error from combination descending MySQL

I want to order one mysql table by two strtotime timestamps from two different columns. I've got the following mysql command:
SELECT * FROM (
(SELECT '1' AS `table`, `vid_req_timestamp` AS `timestamp`, `title` FROM `movies` WHERE `vid_req` = '1')
UNION
(SELECT '2' AS `table`, `ost_req_timestamp` AS `timestamp`, `title` FROM `movies` WHERE `ost_req` = '1')
)
ORDER BY `timestamp` DESC
This gives me an error:
#1248 - Every derived table must have its own alias
I want to combine vid_req_timestamp and ost_req_timestamp and make those descending. And it's important to know where the timestamp came from (somehow).
In this case, the derived table that requires an alias is the one that you are SELECTing * from.
Indentation helps make that clearer.
SELECT * FROM
(
(SELECT '1' AS `table`, `vid_req_timestamp` AS `timestamp`, `title` FROM `movies` WHERE `vid_req` = '1')
UNION
(SELECT '2' AS `table`, `ost_req_timestamp` AS `timestamp`, `title` FROM `movies` WHERE `ost_req` = '1')
) AS `some_table_name_lol_this_is_an_alias`
ORDER BY `timestamp` DESC

Doctrine subquery within subquery

I'm trying to convert a raw mysql query to use doctrine.
The table is full of rows of statistics, and my query is checking to see how far from the average the stat gain has deviated from the average increase each day.
The SQL version works exactly how I'd expect it to act. Converting to Doctrine gives me an error.
Here's the original:
SELECT
l.*,
DAY(l.created_at) as day,
MONTH(l.created_at) as month,
YEAR(l.created_at) as year,
(
MAX(l.infamyrenown) -
MIN(l.infamyrenown) -
(
SELECT AVG(infamydifference) as avginf FROM
(
SELECT (
MAX(inf.infamyrenown) -
MIN(inf.infamyrenown)
) as infamydifference
FROM lotro_record inf
GROUP BY DAY(inf.created_at)
) as p1
)
) as infamy_deviance
FROM
lotro_record l
GROUP BY
year,month,day
And here's the broken Doctrine query:
Doctrine_Core::getTable("LotroRecord")
->createQuery("l")
->select("l.*")
->addSelect("DAY(created_at)")
->addSelect("MONTH(created_at)")
->addSelect("YEAR(created_at)")
->addSelect("(
MAX(l.infamyrenown) -
MIN(l.infamyrenown) -
(
select AVG(infamydifference) as avginf FROM (
SELECT (
MAX(inf.infamyrenown) -
MIN(inf.infamyrenown)
) as infamydifference
FROM LotroRecord inf
GROUP BY DAY(inf.created_at)
) as p1
)
) as infamy_deviance")
->where("lotro_character_id = {$this->getId()}")
->groupBy("DAY(created_at)");
Which generates this SQL:
SELECT l.id AS l__id,
l.infamyrenown AS l__infamyrenown,
l.kills AS l__kills,
l.killing_blows AS l__killing_blows,
l.kills_above_rating AS l__kills_above_rating,
l.kills_below_rating AS l__kills_below_rating,
l.deaths AS l__deaths,
l.lotro_character_id AS l__lotro_character_id,
l.created_at AS l__created_at,
l.updated_at AS l__updated_at,
DAY(l.created_at) AS l__0,
MONTH(l.created_at) AS l__1,
YEAR(l.created_at) AS l__2,
( Max(l.infamyrenown) - Min(l.infamyrenown) - (SELECT
Avg(infamydifference) AS avginf
FROM
(SELECT ( Max(l2.infamyrenown) - Min(l2.infamyrenown) ) AS l__0
FROM lotro_record l2
GROUP BY DAY(l2.created_at)) AS p1) ) AS l__3
FROM lotro_record l
WHERE ( l.lotro_character_id = 1 )
GROUP BY DAY(l.created_at)
The error is:
Unknown column 'infamydifference' in 'field list'
Any ideas?
I think it wants you to call it inf.infamydifference instead of just infamydifference in the DQL query you're written:
select AVG(inf.infamydifference) as avginf