Unpack GROUP BY query for results that lack the key? - mysql

I'm trying to run a GROUP BY query to select all the items in a table grouping them by a collection id.
| id | collection | date |
| 1 | x | ... |
| 2 | x | ... |
| 3 | y | ... |
| 4 | | ... |
| 5 | | ... |
I'd like to obtain a list like this:
[
{
collection: x,
items: [1, 2]
},
{
collection: y,
items: [3]
},
{
collection: null,
items: [4]
},
{
collection: null,
items: [5]
}
]
My query right now is the following, but I need a way to unpack items that lack the collection ID so that they all end up in a separate group, how can I do?
SELECT id, collection FROM items ORDER BY date DESC GROUP BY collection
I'm using MySQL but any SQL syntax would still be helpful.

Here I have shared two query. One with conditional group by clause and the other one is using union all. I would prefer first one.
CREATE TABLE items( id int, collection varchar(10));
insert into items values( 1 , 'x');
insert into items values( 2 , 'x');
insert into items values( 3 , 'y');
insert into items (id)values( 4 );
insert into items (id)values( 5 );
Query#1 (conditional group by clause)
SELECT collection,group_concat(id) id FROM items
GROUP BY collection,
case when collection is not null then collection else id end
Output:
collection
id
null
4
null
5
x
1,2
y
3
Query#2 (using union all)
SELECT collection,group_concat(id) id FROM items where collection is not null
GROUP BY collection
union all
select collection,id from items where collection is null
Output:
collection
id
x
1,2
y
3
null
4
null
5
db<fiddle here
Sorted by collection and id:
Query#1
SELECT collection,group_concat(id) id FROM items
GROUP BY collection,
case when collection is not null then collection else id end
order by collection,id
Output:
collection
id
null
4
null
5
x
1,2
y
3
Query#2
select collection,id from
(
SELECT collection,group_concat(id) id FROM items where collection is not null
GROUP BY collection
union all
select collection,id from items where collection is null
)t order by collection,id
Output:
collection
id
null
4
null
5
x
1,2
y
3
db<fiddle here

SELECT
CASE WHEN collection is null THEN id ELSE collection END as id,
GROUP_CONCAT(collection) as collection
FROM items
GROUP BY 1
I see, you have an ORDER BY date?

Related

Lead function for missing records

I am using below query
select id,
number_sequ,
startvalue
lead(startvalue,1,0) over (partition by id order by number_sequ) AS End_value
from mytable
to populate the following output
id number_sequ startvalue End_value
---- ----- ---------- -----------
AAA 1 30 20
AAA 2 20 10
AAA 4 10 15
AAA 5 15 0
BBB 1 12 23
BBB 3 23 34
BBB 4 34 0
But there are missing records in sequence
id number_sequ startvalue End_value
---- ----- ---------- -----------
AAA 3
BBB 2
I have tried different ways to find out missing numbers in Sequence and try to insert with 0 values. after that i can use lead function. unable to find out efficient way
INSERT INTO mytable (id, number_sequ, startvalue)
select id ,number_sequ ,'0'
from mytable
where (some condition to specify missing data)
Can any one help me to solve above issue.
You can get the missing values with the following approach: generate all possible values and then filter out the ones that exist.
select i.id, n.n, 0 as start_value
from (select id, min(number_seq) as min_ns, max(number_seq) as max_ns
from mytable
group by id
) i join
(select row_number() over (partition by number_seq) as n
from mytable
) n
on n.n <= i.max_ns left join -- just a bunch of numbers
mytable t
on t.id = i.id and
t.number_seq = n.n
where t.id is null;
You can pop the insert before the select to insert these values into your table.
Note that this generates the sequence numbers you need using the original data. So it assumes that your table has enough rows for the numbers you need.
If the missing values are always in between existing values, you can find the gaps using the Snowflake's JavaScript UDTFs
For example, here's a function that finds gaps in a sequence, and then we use it to generate "empty" rows:
create or replace table x(id int, seq int, startVal int) as select * from
values(1,1,11),(1,2,12),(1,4,14),(2,2,22),(2,5,25);
CREATE OR REPLACE FUNCTION find_gaps(SEQ float)
RETURNS TABLE (GAP float)
LANGUAGE JAVASCRIPT
AS '
{
initialize: function(argumentInfo, context) {
this.lastRow = null;
},
processRow: function (row, rowWriter, context) {
let curRow = row.SEQ;
if (this.lastRow == null || this.lastRow + 1 == curRow) {
this.lastRow = curRow;
} else {
while (this.lastRow + 1 < curRow) {
this.lastRow++;
rowWriter.writeRow({GAP: this.lastRow});
}
}
}
}'
;
select id, seq, startVal from x
union all
select id, gap, 0 from x,
table(find_gaps(seq::float)
over (partition by id order by seq));
----+-----+----------+
ID | SEQ | STARTVAL |
----+-----+----------+
1 | 1 | 11 |
1 | 2 | 12 |
1 | 4 | 14 |
2 | 2 | 22 |
2 | 5 | 25 |
2 | 3 | 0 |
2 | 4 | 0 |
1 | 3 | 0 |
----+-----+----------+
You can use variations of this function also e.g. if you know the range of your values per id, just feed it expected min/max as well. Also, you might need something special if your input contains NULL values (but then - what should be the result? :))
Apart from the suggested solutions, if you still want to stick to Lead function,
Lead function analyses data that has values, the result of it can have null values based on partition but the data it uses for analyses should have values. In my view, What you did is right to include the missing sequence in your result.
http://www.mysqltutorial.org/mysql-window-functions/mysql-lead-function/

Ordering a MySQL Table Based on a Positive or Negative Integer in a Column

I have a MySQL table called example with columns my_order and alt_order.
Here's what I'm trying to accomplish:
If alt_order is null, order by my_order.
If alt_order is not null, order by my_order, but position the row above or below where it should be based on the integer in alt_order.
Pseudocode:
SELECT
*
FROM
example
ORDER BY
IF(alt_order IS NULL, my_order, [MOVE UP OR DOWN BASED ON alt_order])
Ideal Output:
+--------+----------+-----------+-------------------------------------+
| desc | my_order | alt_order | notes |
+--------+----------+-----------+-------------------------------------+
| Item 1 | 1 | NULL | Ordered by my_order |
| Item 3 | 122 | -1 | Ordered by my_order + alt_order | <-- Move up one row
| Item 2 | 50 | NULL | Ordered by my_order |
| Item 4 | 127 | NULL | Ordered by my_order |
| Item 5 | 205 | NULL | Ordered by my_order |
+--------+----------+-----------+-------------------------------------+
Attempt:
SELECT
*
FROM
example
ORDER BY
IF(alt_order IS NULL, my_order, my_order + alt_order)
# The problem with using this method is that the value of my_order
# has to be the same as the value of above it to work.
Edit:
If the alt_order is positive n, then it should move down n rows. On the other hand, if the alt_order is negative n, then it should move up n rows.
It looks like the result you specify could be achieved by ordering the rows twice. First, order by my_order, and assign an initial "row number". Then, take a second pass through, ordering by the initial "row number" adjusted by the "row offset". Because "row number" and "row offset" are both integers, when we add them (rn=2 + ro=0) we can get a collision (rn=3 ro=-1). To make the ordering more determinate, we can need to adjust
SELECT s.desc
, s.my_order
, s.alt_order
, s.notes
FROM ( SELECT #rn := #rn + 1 AS rn
, t.desc
, t.my_order
, t.alt_order
, t.notes
FROM `example` t
CROSS
JOIN ( SELECT #rn := 0 ) i
ORDER BY t.my_order
) s
ORDER
BY s.rn + IFNULL(s.alt_order,0.5)
The query below may work for you or send you in the right direction. By using a CASE statement in your ORDER BY you can control the way your data sorts.
SELECT * FROM `example`
ORDER BY
(CASE WHEN `alt_order` IS NULL THEN `my_order` ELSE (`my_order` + `alt_order`) END)

MySQL - select one row - then one next and one previous relative to the selected

I'll try to make this clear.
I need to select a specific row and one row previous relative from that selected row and one row next relative from that selected row without using id's. Is this possible? Previous and next one, in short.
The reason why I can't (maybe I just don't know how) use id's, is because they are not in sequential order. They have gaps as you can see from this rather immature and random example.
TABLE <-the name of the table
+----+----------------------+-------+
| id | name | value |
+----+----------------------+-------+
| 1 | some_name | asf |
+----+----------------------+-------+
| 4 | hello | A3r |
+----+----------------------+-------+
| 5 | how_do_you_do | HR5 |
+----+----------------------+-------+
| 8 | not_bad | 00D |
+----+----------------------+-------+
| 12 | i_like_women | lla |
+----+----------------------+-------+
| 13 | are_you_serious | 1Ha |
+----+----------------------+-------+
| 15 | nah_i_kid | Ad4 |
+----+----------------------+-------+
| 17 | it_is_just_the_boobs | Zc5 |
+----+----------------------+-------+
| 18 | thank_god | 102 |
+----+----------------------+-------+
| 44 | no_kidding | jjy |
+----+----------------------+-------+
First, I need to select one row based on specific value from one of its column. I know how to do that:
SELECT `value`
FROM `TABLE`
WHERE name = 'i_like_women'
This will select one row with id 12 with the value lla.
What I need is to select another at least two rows: one with the name 'not_bad' and one with the name 'are_you_serious' without specifying it. Or, in other words, previous and next one relative to this selected one.
In short, three rows should be selected based on one value. I'm new to MySQL, as you can guess.
Thanks for your time and attention. Enjoy helping me.
Here is the query which will return all three records.
SELECT *
FROM `TABLE`
WHERE id >= (
SELECT id
FROM `TABLE`
WHERE id < (SELECT id FROM `TABLE` WHERE name = 'i_like_women')
ORDER BY id DESC
LIMIT 1
)
ORDER BY id ASC
LIMIT 3
The simplest way to do this is to exploit the fact that, although not continuous, your ids are in ascending order.
For example:
SELECT * FROM Table WHERE id = 8
UNION
--Select the first item less than 8
SELECT * FROM Table WHERE id = (SELECT MAX(id) FROM Table WHERE id < 8)
UNION
--select the first item greater than 8
SELECT * FROM Table WHERE id = (SELECT MIN(id) FROM Table WHERE id > 8)
If you only know the string, then:
DECLARE _id INT
SELECT _id = id FROM Table WHERE value = 'i_like_women'
Then you can simply feed this _id into the above query, instead of 8.
Note you don't need to use ` to demarcate the table and column names.
The one before can be retrieved with:
SELECT `value`
FROM `TABLE`
WHERE id < (SELECT id FROM `TABLE` WHERE name = 'i_like_women')
ORDER BY id DESC
LIMIT 1
You can do the opposite query to find the next one
this query is working fine on first and last record as well
SELECT * FROM `products` WHERE `ProductId` = (SELECT MAX(ProductId) FROM `products` WHERE ProductId < 1) AND SubCategoryId=1
UNION
SELECT * FROM `products` WHERE `ProductId` = (SELECT MIN(ProductId) FROM `products` WHERE ProductId > 1) AND SubCategoryId=1
UNION
SELECT * FROM `products` WHERE `ProductId` = (SELECT MIN(ProductId) FROM `products` WHERE ProductId < 1) AND SubCategoryId=1
UNION
SELECT * FROM `products` WHERE `ProductId` = (SELECT MAX(ProductId) FROM `products` WHERE ProductId > 1) AND SubCategoryId=1
ORDER BY ProductId ASC
i Hope this will solve your Problem :)
I have found better and easy answer for the below question(finding next and previous row ),
$neighbors = $this->WorkDescription->find('neighbors',array('field' => 'id', 'value' => 3));
it will gives output like this-
Array
(
[prev] => Array
(
[WorkDescription] => Array
(
[id] => 1
[title] => Twenty
[ter_id] => 1
[cat_id] => 4
[writer] => abk
[director] => Dir
[producer] => pro
)
)
[next] => Array
(
[WorkDescription] => Array
(
[id] => 3
[title] => The Piper
[ter_id] => 1
[cat_id] => 3
[writer] => abk
[director] => Dir
[producer] => pro
)
)
)

selecting distinct SET values as a single mysql column?

Let's say I have id ( primary key column) and val column (of type SETwith some allowed values):
set('a','b','c','d','e','f')
I need top select these values as a column, with first column being id
+-----+------+
| id | val |
+-----+------+
| 102 | 'a' |
| 102 | 'e' |
| 102 | 'f' |
Not sure how can this be achieved..
select id, ???? from table where id = 102;
As mentioned in my comment above, you probably should normalize your data so that this issue goes away entirely.
However, with the status quo, one would need to join with a table that contains all of the possible SET values where such value is in the record's SET; you can either maintain a permanent copy of that table, or else construct it dynamically with a UNION subquery:
SELECT my_table.id, set_options.val
FROM my_table JOIN (
SELECT 'a' AS val
UNION ALL SELECT 'b' UNION ALL SELECT 'c' UNION ALL SELECT 'd'
UNION ALL SELECT 'e' UNION ALL SELECT 'f'
) AS set_options ON FIND_IN_SET(set_options.val, my_table.val) > 0
WHERE id = 102

mySQL hierarchical grouping sort

I have a schema that essentially looks like this:
CREATE TABLE `data` (
`id` int(10) unsigned NOT NULL,
`title` text,
`type` tinyint(4),
`parent` int(10)
)
The type field is just an enum where 1 is a parent type, and 2 is a child type (in actuality there are many types, where some should behave like parents and some like children). The parent field indicates that a record is the child of another record.
I know this is probably not ideal for the query I want to build, but this is what I have to work with.
I would like to sort and group the data so that the parent records are sorted by title, and grouped under each parent is the child records sorted by title. Like so:
ID | title |type |parent
--------------------------------
4 | ParentA | 1 |
2 | ChildA | 2 | 4
5 | ChildB | 2 | 4
7 | ParentB | 1 |
9 | ChildC | 2 | 7
1 | ChildD | 2 | 7
** Edit **
We should be able to take the type field out of the picture entirely. If parent is not null then it should be grouped underneath it's parent.
SELECT * FROM `data` ORDER BY COALESCE(`parent`, `id`), `parent`, `id`
Here's a solution tested to work on SQL Server. Should be essentially the same on MySQL
select Id, Title, [Type], Id as OrderId from Hier h1 where [Type] = 1
union
select Id, Title, [Type], Parent as OrderId from Hier h2 where [Type] = 2
order by OrderId, [Type]
You said you wanted it to sort on the titles, correct?
SELECT id, title, parent
FROM
( SELECT id, title, parent,
CASE WHEN parent is null THEN title ELSE CONCAT((SELECT title FROM `data` d2 WHERE d2.id = d.parent), '.', d.title) END AS sortkey
FROM `data` d
) subtable
ORDER BY sortkey
edit: Edited to remove type from the query.