Creating new data table on existing one - mysql

Hello I've got a question, how (if it possible), I can create new datatables with close same rows but if In column value is in string "/" for example
ID
column_param
column_sym
column_value
column_val2
First
param_test1
ABC
11/12
test
Second
param_test2
CDE
22/11
test
Third
param_test3
EFG
44
teste
4'th
param_test4
HIJ
33/22
test
And here if I have param_test1 and param_test4 and if in this column value has "/" I want to create 2 other rows but if I will not set param_test2 then it stay as it is and everything should be in new datatable. Is any way to create this?
Thank you in advance.
Expected result:

As per Gordon's answer, I'm not sure what should be done with the your ID column.
I've replaced these with row numbers.
Depending on your version of MySQL/MariaDB, the ROW_NUMBER() window function may not be available. Depending on whether IDs in the output are necessary you may be able to simply omit this.
I've assumed the existence of a table called myNumbers which contains a single field num and is populated with positive integers from 1 to whatever you're likely to need.
I've included more in the output that you asked for, which will hopefully help you understand what's going on
SELECT
ROW_NUMBER() OVER (ORDER BY d.ID, n.num) as NewID,
d.ID as OriginalID,
n.num as,
d.column_param,
d.column_sym,
d.column_value as orig_value,
CASE WHEN column_param = 'param_test2' THEN d.column_value
ELSE substring_index(substring_index(d.column_value,'/',n.num),'/',-1) END as split_value,
d.column_val2
FROM
myData d
JOIN myNumbers n on char_length(d.column_value)-char_length(replace(d.column_value,'/','')) >= n.num-1
WHERE
n.num = 1 OR d.column_param <> 'param_test2'
ORDER BY
d.ID,
n.num
See this DB Fiddle (the columns output in a different order than I've specified, but I think that's a DB Fiddle quirk).
If you only want to "split" say param_test1 and param_test4 rows the code above code could be amended as follows:
SELECT
ROW_NUMBER() OVER (ORDER BY d.ID, n.num) as NewID,
d.ID as OriginalID,
d.column_param,
d.column_sym,
n.num,
d.column_value as orig_value,
CASE WHEN column_param NOT IN ('param_test1','param_test4') THEN d.column_value
ELSE substring_index(substring_index(d.column_value,'/',n.num),'/',-1) END as split_value,
d.column_val2
FROM
myData d
JOIN myNumbers n on char_length(d.column_value)-char_length(replace(d.column_value,'/','')) >= n.num-1
WHERE
n.num = 1 OR d.column_param IN ('param_test1','param_test4')
ORDER BY
d.ID,
n.num

I don't know how the id is being set, but you can do what you want using union all:
select column_param, column_sym,
substring_index(column_value, '/', 1) as column_value,
column_val2
from t
union all
select column_param, column_sym,
substring_index(column_value, '/', -1) as column_value,
column_val2
from t
where column_value = '%/%';

Related

Is there a way to use aggregate COUNT() values within CASE?

I need to retrieve unique yet truncated part numbers, with their description values being conditionally determined.
DATA:
Here's some simplified sample data:
(the real table has half a million rows)
create table inventory(
partnumber VARCHAR(10),
description VARCHAR(10)
);
INSERT INTO inventory (partnumber,description) VALUES
('12345','ABCDE'),
('123456','ABCDEF'),
('1234567','ABCDEFG'),
('98765','ZYXWV'),
('987654','ZYXWVU'),
('9876543','ZYXWVUT'),
('abcde',''),
('abcdef','123'),
('abcdefg','321'),
('zyxwv',NULL),
('zyxwvu','987'),
('zyxwvut','789');
TRIED:
I've tried too many things to list here.
I've finally found a way to get past all the 'unknown field' errors and at least get SOME results, but:
it's SUPER kludgy!
my results are not limited to unique prods.
Here's my current query:
SELECT
LEFT(i.partnumber, 6) AS prod,
CASE
WHEN agg.cnt > 1
OR i.description IS NULL
OR i.description = ''
THEN LEFT(i.partnumber, 6)
ELSE i.description
END AS `descrip`
FROM inventory i
INNER JOIN (SELECT LEFT(ii.partnumber, 6) t, COUNT(*) cnt
FROM inventory ii GROUP BY ii.partnumber) AS agg
ON LEFT(i.partnumber, 6) = agg.t;
GOAL:
My goal is to retrieve:
prod
descrip
12345
ABCDE
123456
123456
98765
ZYXWV
987654
987654
abcde
abcde
abcdef
abcdef
zyxwv
zyxwv
zyxwvu
zyxwvu
QUESTION:
What are some cleaner ways to use the COUNT() aggregate data with a CASE type conditional?
How can I limit my results so that all prods are UNIQUE?
You can check if a left(partnumber, 6) is not unique in the result by checking if count(*) > 1. In such a case let descrip be left(partnumber, 6). Otherwise you can use max(description) (or min(description)) to get the single description but satisfy the needs to use an aggregation function on columns not in the GROUP BY. To replace empty or NULL descriptions, nullif() and coalesce() can be used.
That would lead to the following using just one level of aggregation and no joins:
SELECT left(partnumber, 6) AS prod,
CASE
WHEN count(*) > 1 THEN
left(partnumber, 6)
ELSE
coalesce(nullif(max(description), ''), left(partnumber, 6))
END AS descrip
FROM inventory
GROUP BY left(partnumber, 6)
ORDER BY left(partnumber, 6);
But there seems to be a bug in MySQL and this query fails. The engine doesn't "see" that, in the list after SELECT partnumber is only used in the expression left(partnumber, 6), which is also in the GROUP BY. Instead the engine falsely complains about partnumber not being in the GROUP BY and not subject to an aggregation function.
As a workaround, we can use a derived table, that does the shortening of partnumber to its first six characters. We then use use that column of the derived table instead of left(partnumber, 6).
SELECT l6pn AS prod,
CASE
WHEN count(*) > 1 THEN
l6pn
ELSE
coalesce(nullif(max(description), ''), l6pn)
END AS descrip
FROM (SELECT left(partnumber, 6) AS l6pn,
description
FROM inventory) AS x
GROUP BY l6pn
ORDER BY l6pn;
Or we slap some actually pointless max()es around the left(partnumber, 6) other than the first, to work around the bug.
SELECT left(partnumber, 6) AS prod,
CASE
WHEN count(*) > 1 THEN
max(left(partnumber, 6))
ELSE
coalesce(nullif(max(description), ''), max(left(partnumber, 6)))
END AS descrip
FROM inventory
GROUP BY left(partnumber, 6)
ORDER BY left(partnumber, 6);
db<>fiddle (Change the DBMS to some other like Postgres or MariaDB to see that they also accept the first query.)

How to insert SUM() function that sums rows with similar ID in a code part of witch is unchangeable?

I am trying to write a quarry in a module for Dolibarr ERP. But module hase a part of code that is predefined and can not be changed. And I need to insert a SUM() function in it that will combine rows with similar id. That i know how to do in a regular MySQL:
SELECT fk_product AS prod, SUM(value) AS qty
FROM llx_stock_mouvement
WHERE type_mouvement = 2 AND label LIKE 'SH%'
GROUP BY fk_product
ORDER BY 1 DESC
LIMIT 26
that gives me what I want :
prod qty
1 13
2 10
BUT module has a predefined unchangeable code :
this part is predefined module writes it himself based on values provider in it:
SELECT DISTINCT
c.fk_product AS com,
c.value AS qty
THIS PART I CAN WRITE IN A MODULES GUI:
FROM
llx_stock_mouvement AS c
WHERE
type_mouvement = 2
AND label LIKE 'SH%'
And this part is predefined:
ORDER BY 1 DESC
LIMIT 26
I would appreciate any help and advice on question is there any workaround that can be done to make my desired and result ampere ? As it would using the first code I posted ?
If you can only modify the bit in the middle box then you might need to use a subquery;
--fixed part
SELECT DISTINCT
c.fk_product AS com,
c.value AS qty
--begin your editable part
FROM
(
SELECT fk_product,
SUM(value) AS value
FROM llx_stock_mouvement
WHERE type_mouvement = 2 AND label LIKE 'SH%'
GROUP BY fk_product
) c
--end your editable part
--fixed part
ORDER BY 1
DESC
LIMIT 26

convert all JSON columns into new table

I currently have a table structured like:
customer_id name phoneNumbers
1 Adam [{'type':'home','number':'687-5309'} , {'type':'cell','number':'123-4567'}]
2 Bill [{'type':'home','number':'987-6543'}]
With the phoneNumbers column set as a JSON column type.
For simplicity sake though I am wanting to covert all the JSON phone numbers into a new separate table.
Something like:
phone_id customer_id type number
1 1 home 687-5309
2 1 cell 123-4567
3 2 home 987-6543
It seems like it should be do-able with OPENJSON but so far I haven't had any luck in figuring out how to declare it correctly. Any help is appreciated.
USE recursive CTE with 1 and recurse upto json_length.
SELECT c.*, JSON_LENGTH(c.phoneNumbers) as json_length
from customers c;
then use concat to pass that element_id in Extract Query:
(json_unquote(JSON_EXTRACT(phoneNumbers, CONCAT('$.type.',1))), json_unquote(JSON_EXTRACT(phoneNumbers, CONCAT('$.number.',1))))
(json_unquote(JSON_EXTRACT(phoneNumbers, CONCAT('$.type.',2))), json_unquote(JSON_EXTRACT(phoneNumbers, CONCAT('$.number.',1))))
-
-
-
(json_unquote(JSON_EXTRACT(phoneNumbers, CONCAT('$.type.',json_length))), json_unquote(JSON_EXTRACT(phoneNumbers, CONCAT('$.number.',json_length))))
You can do something like this:
SELECT id,
name,
JSON_UNQUOTE(JSON_EXTRACT(phone, CONCAT("$[", seq.i, "]", ".", "number"))) AS NUMBER,
JSON_UNQUOTE(JSON_EXTRACT(phone, CONCAT("$[", seq.i, "]", ".", "type"))) AS TYPE
FROM customer, (SELECT 0 AS I UNION ALL SELECT 1) AS seq
WHERE seq.i < json_length(phone)
The trick is (SELECT 0 as i union all SELECT 1), depends on your JSON array's length you may need to add more index. You can find out the max length by:
SELECT MAX(JSON_LENGTH(phone)) FROM customer;
Please change CTE defination syntax according to MySQL\Maria versions.
WITH RECURSIVE cte_recurse_json AS
(
SELECT customer_id, phone_numbers, 0 as recurse, JSON_LENGTH(c.phoneNumbers) as json_length,
json_unquote(JSON_EXTRACT(phoneNumbers, CONCAT('$[',0,'].type'))) as type,
json_unquote(JSON_EXTRACT(phoneNumbers, CONCAT('$[',0,'].number'))) as number
FROM table
UNION ALL
SELECT t.customer_id, t.phone_numbers, ct.recurse + 1 as recurse, t.json_length,
json_unquote(JSON_EXTRACT(ct.phoneNumbers, CONCAT('$[',ct.recurse,'].type'))) as type,
json_unquote(JSON_EXTRACT(ct.phoneNumbers, CONCAT('$[',ct.recurse,'].number'))) as number
FROM TABLE t
INNER JOIN cte_recurse_json ct ON t.customer_id = ct.customer_id
WHERE ct.recurse < json_length
)
SELECT customer_id, type, number FROM cte_recurse_json;

SQL sort column A & update column B

I have a table with data similar to:
Inven DESCRIPT PrintOrder
---------------------------------
1 D 9
2 B 0
3 A 5
4 Z 0
5 X 1
. . .
. . .
. . .
I would like to sort the table on column DESCRIPT descending alpha (A - Z) and then update the column PRINTORDER so that when done, the record with PRINTORDER = 1 is the the highest alpha (A) and the record with the highest value for PRINTORDER will be the lowest in alpha (Z).
Is this possible without using temporary columns? Not a deal breaker if not, just a preference.
DESIRED RESULT:
To update the PrintOrder values based on the sorting result
Inven DESCRIPT PrintOrder
---------------------------------
1 D 3
2 B 2
3 A 1
4 Z 5
5 X 4
I am unclear whether you want to modify the table or just create a result set. Here is the solution to the latter using standard SQL:
select Inven, Descript, row_number() over (order by Descript) as PrintOrder
from table t
order by Descript;
EDIT:
In MySQL, the select would look like:
select t.*
from (select Inven, Descript, (#rn := #rn + 1) as PrintOrder
from table t cross join (select #rn := 0) params
order by Descript
) t
order by Inven;
The update is slightly trickier, because you cannot sort and initialize a variable:
update t
set PrintOrder = (#rn := coalesce(#rn + 1, 1))
order by Descript;
This should do it:
Update T
set PRINTORDER = ASCII(UPPER(DESCRIPT)) - 64
while the ASCII of A is 65 in your system. :)
EDIT:
As you said there might be something more, so we need to change it a bit by assuming you have a PK column name as Id:
UPDATE T
set PRINTORDER = T2.Order
FROM T
INNER JOIN (SELECT IDENTITY(int, 1,1) AS Order,Id FROM T Order By DESCRIPT ASC) T2 ON T.Id=T2.Id
But in this way even two record has same DESCRIPT would end up have different printorder, it could be a bit more complex if you want keep them the same.
EDIT:
Sorry just realized you are using MySql, the IDENTITY(int, 1,1) is from SQL Server, basically it generate a row number, you might need something different to have one: http://blog.sqlauthority.com/2014/03/08/mysql-generating-row-number-for-each-row-using-variable/
After arriving at my office, and chatting with my colleague, the question posed is completely inaccurate. So I'll close this thread, and an open a new one with a more accurate description of the issue.
Thank you Gordon & Simon for your responses, they are most appreciated.

MySQL multi-step GROUP BY without subquery

I'm working on improving some queries I inherited, and was curious if it was possible to do the following - given a table the_table that looks like this:
id uri
---+-------------------------
1 /foo/bar/x
1 /foo/bar/y
1 /foo/boo
2 /alpha/beta/carotine
2 /alpha/delic/ipa
3 /plastik/man/spastik
3 /plastik/man/krakpot
3 /plastik/man/helikopter
As an implicit intermediate step I'd like to group these by the 1st + 2nd tuple of uri. The results of that step would look like:
id base
---+---------------
1 /foo/bar
1 /foo/boo
2 /alpha/beta
2 /alpha/delic
3 /plastik/man
And the final result would reflect the number of unique tuple1 + tuple2 values, per unique id:
id cnt
---+-----
1 2
2 2
3 1
I can achieve these results, but not without doing a subquery (to get the results of the implicit step mentioned above), and then select/grouping out of that. Something like:
SELECT
id,
count(base) cnt
FROM (
SELECT
id,
substring_index(uri, '/', 3) AS base
FROM the_table
GROUP BY id, base
)
GROUP BY id;
My reason for wanting to avoid the subquery is that I'm working with a fairly large (20M rows) data set, and the subquery gets very expensive. Gut tells me it's not doable, but figured I'd ask SO...
There's no need for a subquery -- you can use count with distinct to achieve the same result:
SELECT
id,
count(distinct substring_index(uri, '/', 3)) AS base
FROM the_table
GROUP BY id
SQL Fiddle Demo
BTW -- this returns count of 1 for id 3 -- I assume that was a typo in your posting.