MySQL only get overall ROLLUP - mysql

Performing a WITH ROLLUP when grouping by multiple fields, MySQL returns a rollup row for each group, as well as the overall summary:
CREATE TABLE test (name VARCHAR(50), number TINYINT);
INSERT INTO test VALUES
('foo', 1), ('foo', 1), ('foo', 2), ('foo', 3), ('foo', 3),
('bar', 1), ('bar', 2), ('bar', 2), ('bar', 2), ('bar', 3),
('baz', 1), ('baz', 2), ('bar', 2);
SELECT name, number, COUNT(1) FROM test GROUP BY name, number WITH ROLLUP;
+------+--------+----------+
| name | number | count(1) |
+------+--------+----------+
| bar | 1 | 1 |
| bar | 2 | 3 |
| bar | 3 | 1 |
| bar | NULL | 5 |
| baz | 1 | 1 |
| baz | 2 | 2 |
| baz | NULL | 3 |
| foo | 1 | 2 |
| foo | 2 | 1 |
| foo | 3 | 2 |
| foo | NULL | 5 |
| NULL | NULL | 13 |
+------+--------+----------+
I'm not interested in the rollups for foo/bar/baz, only the overall summary. What's the most efficient way to achieve this?
I know I can't use HAVING due to the rollup rows being added afterwards. Is the best solution to use a nested query for this, selecting where name and number are either both NOT NULL or both NULL?

HAVING can do the trick with no subquery:
SELECT `name`, number, COUNT(1) FROM test GROUP BY `name`, number WITH ROLLUP
HAVING number IS NOT NULL OR `name` IS NULL;
This filters out the post-rollup rows except for the grand total:
name number COUNT(1)
------ ------ --------
bar 1 1
bar 2 4
bar 3 1
baz 1 1
baz 2 1
foo 1 2
foo 2 1
foo 3 2
(NULL) (NULL) 13

Try to use a subquery, e.g. -
SELECT * FROM (
SELECT name, number, COUNT(1) FROM test GROUP BY name, number WITH ROLLUP) t
WHERE name IS NULL OR number IS NULL
You also may want to change NULL values with appropriate texts.

SELECT COALESCE(name, 'TOTAL') as Name, number, COUNT(1) FROM test GROUP BY name, number WITH ROLLUP;
Below Name column it would Display as Total. If you have issue with number as null same can be done for that too.

Related

SELECT WHERE in varchar with wildcard

Lets say i have a table like this
CREATE TABLE Parts (ID int, part_number varchar(100), isActive TINYINT);
and these sample records
|ID | part_number | isActive|
===============================
1 | 1N3.805.327 | 1
2 | 1N3.805.327.B | 1
3 | 1N3.804.108.B | 1
4 | 1N3.804.108.C | 1
5 | 1N3.804.107.B | 1
6 | 1N3.804.107.C | 1
7 | 1N3.804.106.A | 1
8 | 1N3.804.105.A | 1
Problem
I would like to combine a where in clause with the wildcard % operator
In my dbfiddle sample i tried the string function find_in_set and the comparison operator in(). Both do not work:
-- without wildcard the query works
SELECT * FROM Parts WHERE part_number in ('1N3.804.108.B', '1N3.804.106.A'); -- 2
-- with wildcard no records are returned
SELECT * FROM Parts WHERE part_number in ('1N3.804.108%', '1N3.804.106%'); -- 0
SELECT * FROM Parts WHERE FIND_IN_SET(part_number, '1N3.804.108%,1N3.804.106%'); -- 0
Questions
I assume i could use WHERE LEFT(part_number, 11) in ('1N3.804.108', '1N3.804.106') But i do not know if this has any disadvantages.
Is there a way to use a wildcard operator with in()?
Sample records
INSERT INTO
Parts(ID, part_number, isActive)
VALUES
(1, '1N3.805.327',1),
(2, '1N3.805.327.B',1),
(3, '1N3.804.108.B',1),
(4, '1N3.804.108.C',1),
(5, '1N3.804.107.B',1),
(6, '1N3.804.107.C',1),
(7, '1N3.804.106.A',1),
(8, '1N3.804.105.A',1);
Use REGEXP for that, when you want to use OR
CREATE TABLE Parts (ID int, part_number varchar(100), isActive TINYINT);
INSERT INTO
Parts(ID, part_number, isActive)
VALUES
(1, '1N3.805.327',1),
(2, '1N3.805.327.B',1),
(3, '1N3.804.108.B',1),
(4, '1N3.804.108.C',1),
(5, '1N3.804.107.B',1),
(6, '1N3.804.107.C',1),
(7, '1N3.804.106.A',1),
(8, '1N3.804.105.A',1);
✓
✓
SELECT * FROm Parts WHeRE part_number REGEXP '^(1N3.804.108|1N3.804.106)'
ID | part_number | isActive
-: | :------------ | -------:
3 | 1N3.804.108.B | 1
4 | 1N3.804.108.C | 1
7 | 1N3.804.106.A | 1
MySQL Can only UNION a certain number of tables. i think it is about 53.
With an index on partnumber, this will be the fastest.
SELECT * FROm Parts WHeRE part_number REGEXP '^1N3.804.108'
UNION all
SELECT * FROm Parts WHeRE part_number REGEXP '^1N3.804.106'
ID | part_number | isActive
-: | :------------ | -------:
3 | 1N3.804.108.B | 1
4 | 1N3.804.108.C | 1
7 | 1N3.804.106.A | 1
SELECT * FROm Parts WHeRE part_number LIKE '1N3.804.108%'
UNION all
SELECT * FROm Parts WHeRE part_number LIKE '1N3.804.106%'
ID | part_number | isActive
-: | :------------ | -------:
3 | 1N3.804.108.B | 1
4 | 1N3.804.108.C | 1
7 | 1N3.804.106.A | 1
db<>fiddle here
you can also try to do
select *,
REGEXP_LIKE(part_number,[pattern]) as pattern_test from Parts
where pattern_test is TRUE
here first you will create the pattern that you are interested in and apply it on the column you wish to apply it to. The pattern_test will return true if it matches with your pattern and then you can filter on that [where clause]

How to sort a list of lists using a sql query?

Abstract question
I have a sql-table that contains records in the following form:
(list_id, value) where the list_id is an Integer identifiing a specific list and the value is something that has an order.
I now struggle to write a sql query that returns all records of that table at first ordered by the rank the list has compared to the other lists and then ordered by the value.
The abstract problem is, that I want to sort a list of lists using sql.
Algorithm to compare two lists
The algorithm to compare two lists is the following:
data CompareRes = FirstSmaller | FirstGreater | Equal deriving Show
compareLists :: Ord a => [a] -> [a] -> CompareRes
compareLists [] [] = Equal
-- Longer lists are considered to be smaller
compareLists _ [] = FirstSmaller
compareLists [] _ = FirstGreater
compareLists (x:xs) (y:ys)
| x < y = FirstSmaller
| x > y = FirstGreater
| otherwise = compareLists xs ys
Details
In my specific case the values are all Dates.
So my table looks like this:
CREATE TABLE `list_date` (
`list_id` INT NOT NULL,
`date` DATE NOT NULL,
PRIMARY KEY (`list_id`, `date`)
);
I'm using a mysql:8.0 database so a solution using WINDOW-functions is acceptable.
Example
Data
INSERT INTO `list_date` VALUES
(1, '2019-11-02'), (1, '2019-11-03'), (1, '2019-11-04'), (1, '2019-11-05'), (1, '2019-11-07'), (1, '2019-11-08'), (1, '2019-11-09'),
(2, '2019-11-01'), (2, '2019-11-03'), (2, '2019-11-04'),
(3, '2019-11-01'), (3, '2019-11-02'), (3, '2019-11-03'),
(4, '2019-11-02'), (4, '2019-11-04'), (4, '2019-11-13'), (4, '2019-11-14'),
(5, '2019-11-03'), (5, '2019-11-04'), (5, '2019-11-05'), (5, '2019-11-10'),
(6, '2019-11-01'), (6, '2019-11-02'), (6, '2019-11-03'), (6, '2019-11-05');
Query
Where I really struggle is to create an expression that calculates the list_rank:
SELECT
`list_id`,
`date`,
<PLEASE HELP> as `list_rank`
FROM
`list_date`
ORDER BY
`list_rank`, `date`;
Expected result
| list_id | date | list_rank |
|---------|------------|-----------|
| 6 | 2019-11-01 | 1 |
| 6 | 2019-11-02 | 1 |
| 6 | 2019-11-03 | 1 |
| 6 | 2019-11-05 | 1 |
| 3 | 2019-11-01 | 2 |
| 3 | 2019-11-02 | 2 |
| 3 | 2019-11-03 | 2 |
| 2 | 2019-11-01 | 3 |
| 2 | 2019-11-03 | 3 |
| 2 | 2019-11-04 | 3 |
| 1 | 2019-11-02 | 4 |
| 1 | 2019-11-03 | 4 |
| 1 | 2019-11-04 | 4 |
| 1 | 2019-11-05 | 4 |
| 1 | 2019-11-07 | 4 |
| 1 | 2019-11-08 | 4 |
| 1 | 2019-11-09 | 4 |
| 4 | 2019-11-02 | 5 |
| 4 | 2019-11-04 | 5 |
| 4 | 2019-11-13 | 5 |
| 4 | 2019-11-14 | 5 |
| 5 | 2019-11-03 | 6 |
| 5 | 2019-11-04 | 6 |
| 5 | 2019-11-05 | 6 |
| 5 | 2019-11-10 | 6 |
or
That image is the current live result my application produces. Currently the sorting is implemented using Java.
Edit
After not receiving a better answer, I implemented a solution as suggested by #gordon-linoff:
SELECT
`list_id`,
`date`
FROM
`list_date`
INNER JOIN (
SELECT `sub`.`list_id`,
GROUP_CONCAT(`sub`.`date` ORDER BY `sub`.`date` SEPARATOR '') as `concat_dates`
FROM `list_date` as `sub`
GROUP BY `sub`.`list_id`
) `all_dates` ON (`all_dates`.`list_id` = `list_date`.`list_id`)
ORDER BY
`all_dates`.`concat_dates`, `date`;
I've also created an SQL Fiddle - So you can play around with your solution.
But this solution does not sort the lists as expected because longer lists are considered bigger than smaller lists.
So I am still hoping to receive a solution that solves 100% of my requirements :)
If I understand correctly, you can sort the lists by the dates concatenated together:
select ld.*
from list_date ld join
(select list_id, group_concat(date) as dates
from ld
group by list_id
) ldc
on ld.list_id = ldc.list_id
order by ldc.dates, ld.date;
Since it's for MySql 8 the window functions can be used for this (yay).
Here's a query that first calculates some metrics, to use in the calculation of the ranking:
SELECT
list_id,
`date`,
DENSE_RANK() OVER (ORDER BY ListMinDate ASC, ListCount DESC, ListMaxDate, list_id) AS list_rank
FROM
(
SELECT
list_id,
`date`,
COUNT(*) OVER (PARTITION BY list_id) AS ListCount,
MIN(`date`) OVER (PARTITION BY list_id) AS ListMinDate,
MAX(`date`) OVER (PARTITION BY list_id) AS ListMaxDate
FROM list_date
) q
ORDER BY list_rank, `date`
A test on db<>fiddle here

Get last mysql record only from a column

This is my existing table
id name version
| 1 | a | 1.1 |
| 2 | b | 2.1 |
| 3 | c | 3.1 |
| 4 | d | 1.2 |
| 5 | e | 4.1 |
how can I write a query to generate results where i will return all records but only the last record in the column version is selected like below?
id name version
| 4 | d | 1.2 |
| 2 | b | 2.1 |
| 3 | c | 3.1 |
| 5 | e | 4.1 |
If you prefer a slightly less laborious solution...
SELECT x.*
FROM t x
JOIN
( SELECT MAX(grade) grade
FROM t
GROUP
BY FLOOR(grade)
) y
ON y.grade = x.grade
http://sqlfiddle.com/#!9/f17db1/16
This is a bit laborious but it can be done
SELECT
SUBSTRING_INDEX(GROUP_CONCAT(id ORDER BY REPLACE(grade,'.','')*1 DESC),',',1) as id,
SUBSTRING_INDEX(GROUP_CONCAT(letter ORDER BY REPLACE(grade,'.','')*1 DESC),',',1) as letter,
MAX(grade) as grade
FROM
t
GROUP BY SUBSTRING_INDEX(grade,'.',1)
ORDER BY REPLACE(grade,'.','')*1
Assuming the last column is float you can use ORDER BY lastcol directly
FIDDLE
CREATE TABLE t
(`id` int, `letter` varchar(7), `grade` varchar(55))
;
INSERT INTO t
VALUES
(1, 'a', '1.1'),
(2, 'b', '2.1'),
(3, 'c', '3.1'),
(4, 'd', '1.2'),
(5, 'e', '4.1')

Conditional SUM and COUNT DISTINCT in the same query

I have a very simple table filled with about 500k records
| ID_1 | ID_2 | Date | Fields... |
+-------+-------+-------------+-----------+
| 1 | 1 | 1/1/2016 | abc |
| 2 | 1 | 2/1/2016 | abc |
| 3 | 2 | 1/2/2016 | abc |
| 4 | 3 | 1/2/2016 | abc |
| 5 | 4 | 1/2/2016 | abc |
I have to extract from that 36 values based on common parameters and differentiated by the date.
To reduce accesses to the database and increase performance I do a subquery, and then sum the results differentiating them with an if.
The not distinct sums work perfectly fine, but I need to count some distinct value and this returns me the correct value + 1.
SELECT
SUM( IF(MONTH(Date) = 1, 1, 0) ) AS A1,
SUM( IF(MONTH(Date) = 2, 1, 0) ) AS A2,
SUM( IF(MONTH(Date) = 3, 1, 0) ) AS A3,
...
COUNT(DISTINCT( IF(MONTH(Date) = '1', COALESCE(ID_cliente), 0) )) AS C1
FROM (
SELECT ID_1, ID_2, Date FROM table WHERE Fields = conditions
) AS Sub
The C1 returns 1 if the real value is 0, 2 if the real value is 1 etc
There's a solution or I must split it into two queries?

select random rows in mysql

I have one table named Mydata as follows
id name type
--------------------------------------------
1 vinu 1
2 rinu 2
3 dilu 1
4 raju 2
5 manu 3
6 saju 3
7 ragu 3
8 sonu 1
9 sam 1
10 rag 1
--------------------------------------------
I want to print records with alternating type, for example:
First row with type =1
Second row with type =2
Third row with type =3
4th row type=1
5th row type=2 and so on
Required result as follows
id name type
-----------------------------------------
1 vinu 1
2 rinu 2
5 manu 3
3 dilu 1
4 raju 2
6 saju 3
8 sonu 1
7 ragu 3
9 sam 1
10 rag 1
----------------------------------------------
Sample data:
CREATE TABLE t
(`id` int, `name` varchar(4), `type` int)
;
INSERT INTO t
(`id`, `name`, `type`)
VALUES
(1, 'vinu', 1),
(2, 'rinu', 2),
(3, 'dilu', 1),
(4, 'raju', 2),
(5, 'manu', 3),
(6, 'saju', 3),
(7, 'ragu', 3),
(8, 'sonu', 1),
(9, 'sam', 1),
(10, 'rag', 1)
;
Query:
SELECT id, name, type FROM (
SELECT
t.*,
#rn := IF(#prev_type = type, #rn + 1, 1) AS rownumber,
#prev_type := type
FROM
t
, (SELECT #rn := 0, #prev_type := NULL) var_init_subquery
ORDER BY type
) sq
ORDER BY rownumber, type
Result:
| id | name | type |
|----|------|------|
| 1 | vinu | 1 |
| 4 | raju | 2 |
| 5 | manu | 3 |
| 9 | sam | 1 |
| 2 | rinu | 2 |
| 7 | ragu | 3 |
| 8 | sonu | 1 |
| 6 | saju | 3 |
| 10 | rag | 1 |
| 3 | dilu | 1 |
see it working live in an sqlfiddle
Caveat:
Don't expect this to be performant when you have lots of data. It's doing a full table scan.
Here's a manual entry to read when you're interested about how this variables work.
This cannot be done via a raw SQL query. Extract the rows you need to display, and then sort them via your application.
Alternatively... you could write a stored procedure, but I don't recommend this. You will need a temporary table and a cursor (that transparently creates another temporary table). Too much for a query that should be executed often.