Using rollup with a view gives me an empty field? - mysql

I've been smashing my head against this for a few days, so I decided to create a sample data set to prove to myself it wasn't our monolithic schema that's breaking things. But nope, it still happens.
CREATE TABLE test_data (
`id` int(11) NOT NULL AUTO_INCREMENT,
`label` varchar(10) NOT NULL,
PRIMARY KEY (`id`)
);
CREATE VIEW test_view AS
SELECT id, label
FROM test_data;
INSERT INTO test_data VALUES
(1, 'red'),
(2, 'red'),
(3, 'red'),
(4, 'red'),
(5, 'green'),
(6, 'green'),
(7, 'green'),
(8, 'yellow'),
(9, 'yellow'),
(10, 'yellow'),
(11, 'blue'),
(12, 'blue'),
(13, 'blue'),
(14, 'blue'),
(15, 'blue'),
(16, 'blue');
https://www.db-fiddle.com/f/vMwvXDtReEwfAtGjYw4xEt/0
So we have a table with some data, and then we select it using group by with rollup:
SELECT label, COUNT(id) as cnt FROM test_data GROUP BY label WITH ROLLUP
Then there's another query wrapped around this (which in the real world does other things, in this example is just pulling data from the select).
SELECT COALESCE(label, 'TOTAL') as wrap_label, label, cnt FROM
(
SELECT label, COUNT(id) as cnt FROM test_data GROUP BY label WITH ROLLUP
) as d;
When I pull directly from a table, it works perfectly, we get the final line with TOTAL as the wrap_label. However if there's a view (which is essentially a SELECT * from test_data) involved it stops pulling out the wrap_label:
SELECT COALESCE(label, 'TOTAL') as wrap_label, label, cnt FROM
(
SELECT label, COUNT(id) as cnt FROM test_view GROUP BY label WITH ROLLUP
) as d;
If you run the fiddle above you'll see that Query #2 has TOTAL, Query #4 has a blank identifier. weirdly the subqueries (#1 from the table, #3 from the view) both seem to return the exact same data!
Is this expected behaviour? I'm guessing it's something to do with execution order and the select or the group by from the view is happening AFTER the subquery is trying to pull?
Obviously this is easily fixed by ditching the subselect and just doing
SELECT COALESCE(label, 'TOTAL') as label, COUNT(id) as cnt FROM test_view GROUP BY label WITH ROLLUP;
but this is the real world and I can't do that I'm afraid, the outer query does more and is in many places in our massive codebase.
I'd appreciate any insight anyone might have.
Cheers,
Si.

Related

GROUP CONCAT with ORDER in MEMSQL

Here is a toy example:
CREATE TABLE TEST
(
ID INT,
AGG NVARCHAR(20),
GRP NVARCHAR(20)
);
INSERT INTO TEST VALUES
(1, 'AB', 'X'), (2, 'BC', 'X'), (3, 'AC', 'X'),
(4, 'EF', 'Y'), (5, 'FG', 'Y'), (6, 'DC', 'Y'),
(7, 'JI', 'Z'), (8, 'IJ', 'Z'), (9, 'JK', 'Z');
Now, I would like to do this (this is a valid code in MySQL, but not in MEMSQL):
SELECT
COUNT(*),
SUM(ID),
GROUP_CONCAT(AGG ORDER BY AGG),
GRP
FROM TEST
GROUP BY GRP
So that the output looks like this (Required Output):
3 6 AB,AC,BC X
3 15 DC,EF,FG Y
3 24 IJ,JI,JK Z
Note that the values in the third column are sorted for each row. My output looks like this (Current Wrong Output):
3 6 BC,AB,AC X
3 15 DC,EF,FG Y
3 24 IJ,JI,JK Z
Compare each row in the third column, the lists are sorted.
However, since the above query is not valid in MEMSQL, I have to remove the ORDER BY AGG part in GROUP_CONCAT which causes the third column to not be sorted.
As per the documentation of GROUP_CONCAT, the expression can also be a function, however, there is no built in function to sort. I have tried many combinations of SELECT ... ORDER BY statements in GROUP_CONCAT without success. Is this impossible to do, or am I missing something?
I think this works for my case.
SELECT
COUNT(*),
SUM(T.ID),
GROUP_CONCAT(T.AGG),
T.GRP
FROM (
SELECT
*,
RANK() OVER(PARTITION BY GRP ORDER BY AGG) AS R
FROM TEST
) T
GROUP BY T.GRP
ORDER BY T.R
It is rather convoluted, so I hope someone can suggest an improvement.
Try this:
SELECT
COUNT(*),
SUM(ID),
GROUP_CONCAT(AGG),
GRP
FROM TEST
GROUP BY GRP
ORDER BY GROUP_CONCAT(AGG)

Get n oldest rows, but no more than x that have the same value in a column

I have a simple table
CREATE TABLE `example` (
`id` int(12) NOT NULL,
`food` varchar(250) NOT NULL
);
With the following data
INSERT INTO `example` (`id`, `food`) VALUES
(1, 'apple'),
(2, 'apple'),
(3, 'apple'),
(4, 'apple'),
(5, 'apple'),
(6, 'apple'),
(7, 'apple'),
(8, 'banana'),
(9, 'banana'),
(10, 'potato'),
(11, 'potato'),
(12, 'potato'),
(13, 'banana'),
(14, 'banana'),
(15, 'banana');
I want to get the oldest 10 rows
SELECT *
FROM example
ORDER BY id ASC
LIMIT 10
But I don't want to get more than 5 rows where food has the same value.
My current query receives 7 apple (more than I want), 2 banana, and 1 potato. In the data provided, I'd want to receive 5 apple, 2 banana, and 3 potato.
How can I accomplish this?
Update:
SQL Group BY, Top N Items for each Group is not a duplicate because it involves a different database. In particular, GROUP BY works different in sql-server than it does in MySQL
You can add a count (in reverse) for each food . . . using variables or a correlated subquery. This will use the latter:
select t.*
from (select t.*,
(select count(*) from example t2 where t2.food = t.food and t2.id >= t.id) as seqnum
from example t
) t
where seqnum <= 5
order by id desc
limit 10;
I didn't create the table and test this, but it should give you what you want. Just a different approach than the one above.
Select *
From (Select ID, Food
, Count(Food) Over(Partition By Food Order by ID) as Appearances
From Your_Table) as a
Where a.Appearances <= 5
Order By ID Asc
You can obviously put the limit if you want.

mysql- sort list based on empty and null values as last in column

I have a table of users with basic details id, their names and profile photo (link to photo, actually)
CREATE TABLE IF NOT EXISTS `users` (
`id` int(11) NOT NULL,
`userName` varchar(60) NOT NULL,
`photo` varchar(50) NULL,
`status` int(11) NOT NULL
) ENGINE=InnoDB AUTO_INCREMENT=42 DEFAULT CHARSET=latin1;
INSERT INTO `users` (`id`, `userName`,`photo`, `status`) VALUES
(1, 'John', 'john.png',1),
(2, 'Jane', 'jane.png',1),
(3, 'Ali', '',1),
(6, 'Bruce', 'bruce.png',1),
(7, 'Martha', '',1),
(8, 'Sidney', '',1),
(10, 'Charlie', 'charlie.png',1),
(12, 'Elisa', '',1),
(14, 'Samantha', 'samantha.png',1),
(15, 'Hannah', 'hannah.png',1),
(16, 'Hannah', '',1),
(17, 'Kevin', 'kevin1.png',1),
(18, 'Kevin', 'kevin2.png',1),
(19, 'Ruth', '',1);
Not all users have profile picture. I would like to list these users in alphabetic order and also show users who have profile picture in front.
This is the query that I wrote:
select * from users ORDER BY photo DESC ;
It sorts users based on photo value but they are not being shown in alphabetic order.
Is it possible to list these users such that all users who have photos will appear top on the list with [userName] in alphabetic order?
SQL FIDDLE
Once you've changed '' to NULL...
SELECT * FROM users ORDER BY photo IS NULL,username;
You have empty vlaues here. So you can use this way.
SELECT * FROM users ORDER BY if(photo = '' or photo is null,1,0), userName
But in mysql has a method to order null values with (-).
SELECT * FROM users ORDER BY -photo DESC, userName ASC
Here photo column data order as null values last because of DESC order
OR
SELECT * FROM users ORDER BY
CASE WHEN photo IS NULL THEN 1 ELSE 0 END ASC, userName ASC
If you want 1st people without picture and then other (while sorting these 2 groups alphabetically):
select * from users ORDER BY photo,userName ASC ;
Waiting your feed back if it is not exactly what you want.

Best way to update MySQL table with random values from another table

For this (pseudo code) example I have two tables in MySQL:
member { id, name }
names { name }
There are 100 members in member and 10 names. I want to use a random name from names to update the member table. So far I've got this, but, not sure if there is a better method to achieve it.
UPDATE member SET name = (SELECT name FROM names ORDER BY RAND() LIMIT 1);
The code will be executed from a script so I'm looking to avoid functions etc.
Thanks in advance.
You could avoid ordering by rand() by adding id column to your names table and using:
UPDATE member SET name = (SELECT name FROM names WHERE id=floor(1 + rand()*10 ) );
With only 10 names the result won't be much faster, but you would see the difference if you wanted to choose from a bigger set of names as sorting by rand() starts being inefficient quite fast and you do it for every row in members.
Update:
Seems like rand() inside where gives unpredictable results.
Use this one instead:
UPDATE member m1
JOIN ( select id, floor(1+rand()*10) as rnd from member ) m2 on m1.id=m2.id
JOIN names n on n.id = m2.rnd
SET m1.name=n.name
Number of rows affected may vary, if random name matches the one already in the table it doesnt count as update.
Tried to improve piotrm's solution. Seems it works;-)
CREATE TABLE member (
id INT(11) NOT NULL AUTO_INCREMENT,
name VARCHAR(255) DEFAULT NULL,
PRIMARY KEY (id)
);
CREATE TABLE names (
id INT(11) NOT NULL AUTO_INCREMENT,
name VARCHAR(255) DEFAULT NULL,
PRIMARY KEY (id)
);
INSERT INTO member VALUES
(1, NULL),
(2, NULL),
(3, NULL),
(4, NULL),
(5, NULL),
(6, NULL),
(7, NULL),
(8, NULL),
(9, NULL),
(10, NULL),
(11, NULL),
(12, NULL),
(13, NULL),
(14, NULL),
(15, NULL);
INSERT INTO names VALUES
(1, 'text1'),
(2, 'text2'),
(3, 'text3'),
(4, 'text4'),
(5, 'text5'),
(6, 'text6'),
(7, 'text7'),
(8, 'text8'),
(9, 'text9'),
(10, 'text10');
UPDATE
member m1
JOIN (SELECT id, #i:=FLOOR(1 + RAND() * 10), (SELECT name FROM names n WHERE n.id = #i) name FROM member) m2
ON m1.id = m2.id
SET
m1.name = m2.name;

count for each row

What is wrong with this query?
SELECT *, (SELECT COUNT(*)
FROM
(
SELECT NULL
FROM words
WHERE project=projects.id
GROUP BY word
HAVING COUNT(*) > 1
) T1) FROM projects
MySQL returns 1054 Unknown column 'projects.id' in 'where clause'
Thanks
Does this work?
SELECT *, (SELECT COUNT(*)
FROM words
WHERE words.project=projects.id) as pCount
FROM projects
Your inner subquery knows nothing about the outer query, so the projects table is not available.
It looks like you are trying to count for each project the number of words which occur more than once.
You can run your subquery for all projects and then use a JOIN to get the rest of the data from the projects table:
SELECT projects.*, COUNT(word) AS cnt
FROM projects
LEFT JOIN (
SELECT project, word
FROM words
GROUP BY project, word
HAVING COUNT(*) > 1
) T1
ON T1.project = projects.id
GROUP BY projects.id
Result:
id cnt
1 0
2 1
3 2
Test data:
CREATE TABLE projects (id INT NOT NULL);
INSERT INTO projects (id) VALUES (1), (2), (3);
CREATE TABLE words (project INT NOT NULL, word VARCHAR(100) NOT NULL);
INSERT INTO words (project, word) VALUES
(1, 'a'),
(2, 'a'),
(2, 'b'),
(2, 'b'),
(3, 'b'),
(3, 'b'),
(3, 'c'),
(3, 'c');