MySQL Truncating of result when using Group_Concat and Concat - mysql

I have the following SQL (I have removed some of the selesct fi:
SELECT node_revisions.title AS 'Task',
node_revisions.body AS 'Description',
Date_format(field_due_date_value, '%e/%c/%Y') AS 'Due Date',
users.name AS 'User Name',
(SELECT GROUP_CONCAT(Concat(CHAR(10),Concat_ws( ' - ', name, From_unixtime( TIMESTAMP,
'%e/%c/%Y' )),CHAR(10),COMMENT))
FROM comments
WHERE comments.nid = content_type_task.nid) AS 'Comments'
FROM content_type_task
INNER JOIN users
ON content_type_task.field_assigned_to_uid = users.uid
INNER JOIN node_revisions
ON content_type_task.vid = node_revisions.vid
ORDER BY content_type_task.nid DESC
This pulls back all my tasks and all comments associated with a task. The problem I am having is that the comments field; created using the *GROUP_CONCAT*, is truncating the output. I don't know why and I don't know how to overcome this. (It looks to be at 341ish chars)

GROUP_CONCAT() is, by default, limited to 1024 bytes.
To work around this limitation and allow up to 100 KBytes of data,
add group_concat_max_len=102400 in my.cnf
or query the server using SET GLOBAL group_concat_max_len=102400.

As Andre mentioned, the result of GROUP_CONCAT() is limited to group_concat_max_len bytes.
However, when using GROUP_CONCAT with ORDER BY, the result is further truncated to one third of group_concat_max_len. This is why your original result was being truncated to 341 (= 1024/3) bytes. If you remove the ORDER BY clause, this should return up to the full 1024 bytes for Comments. For example:
CREATE TABLE MyTable
(
`Id` INTEGER,
`Type` VARCHAR(10),
`Data` TEXT
);
INSERT INTO MyTable VALUES
(0, 'Alpha', 'ABCDEF'),
(1, 'Alpha', 'GHIJKL'),
(2, 'Alpha', 'MNOPQR'),
(3, 'Alpha', 'STUVWX'),
(4, 'Alpha', 'YZ'),
(5, 'Numeric', '12345'),
(6, 'Numeric', '67890');
SET SESSION group_concat_max_len = 26;
-- Returns 26 bytes of data
SELECT Type, GROUP_CONCAT(Data SEPARATOR '') AS AllData_Unordered
FROM MyTable
GROUP BY Type;
-- Returns 26/3 = 8 bytes of data
SELECT Type, GROUP_CONCAT(Data SEPARATOR '') AS AllData_Ordered
FROM MyTable
GROUP BY Type
ORDER BY Id;
DROP TABLE MyTable;
Will return
Type AllData_Unordered
Alpha ABCDEFGHIJKLMNOPQRSTUVWXYZ
Numeric 1234567890
Type AllData_Ordered
Alpha ABCDEFGH
Numeric 12345678
I have not found this interaction between GROUP_CONCAT() and ORDER BY mentioned in the MySQL Manual, but it affects at least MySQL Server 5.1.

Related

How to declare a variable in MySQL

I should set VARIABLE in mysql query but not work: can you help me, where is the error?
SET #variabile = ( SELECT `submit_time` AS 'Submitted',
max(if(`field_name`='N_PROGRESSIVO', `field_value`, null )) AS 'N_PROGRESSIVO',
max(if(`field_name`='Submitted From', `field_value`, null )) AS 'Submitted From',
GROUP_CONCAT(if(`file` is null or length(`file`) = 0, null, `field_name`)) AS 'fields_with_file'
FROM `wpcust_cf7dbplugin_submits`
WHERE `form_name` = 'Modulo SDO 2022 BS'
GROUP BY `submit_time`
ORDER BY `submit_time` DESC
) as variabile);
SELECT col1 INTO #variable1 FROM ... LIMIT 1
SELECT col1, col2 INTO #variable1, #variable2 FROM ... LIMIT 1
The number of variables must be the same as the number of columns or
expressions in the select list. In addition, the query must returns
zero or one row.
If the query return no rows, MySQL issues a warning of no data and the
value of the variables remain unchanged.
In case the query returns multiple rows, MySQL issues an error. To
ensure that the query always returns maximum one row, you use the
LIMIT 1 clause to limit the result set to a single row.
Source
https://dev.mysql.com/doc/refman/5.7/en/select-into.html
https://www.mysqltutorial.org/mysql-select-into-variable/

How to group a table by user id and get max for another column?

I have a table (scrom_scoes_track table in bitnami moodle) which includes userid and value columns. Structure of the table is as follows.
I want to find the max mark of each user. Mark of the user can be found using the following query.
SELECT
`userid`,
`value`
FROM
`mdl_scorm_scoes_track`
WHERE
`element`= 'cmi.core.score.raw'
The result of the above query is as follows.
But when I tried to get max value using the following query it won't work as expected.
SELECT
`userid`,
MAX(`value`)
FROM
`mdl_scorm_scoes_track`
WHERE
`element`= 'cmi.core.score.raw'
GROUP BY
`userid`
Result of above query.
Here first row is userid 2 and value 50. But max value for userid 2 should be 100. (As shown in second image). Any help on this problem is highly appreciated.
Do not use longtext for a numeric field.
alternatively use below
SELECT `userid`,MAX(cast(`value` as unsigned)) FROM `mdl_scorm_scoes_track` WHERE `element`= 'cmi.core.score.raw' GROUP BY `userid`
First you need to convert your string into a number. Otherwise max() is sorting alphabetically
SELECT userid, max(cast(value as unsigned))
FROM mdl_scorm_scoes_track
WHERE element = 'cmi.core.score.raw'
group by userid
You have the right idea, but value is stored as longtext, so when you apply max on it, you get the "largest" value by lexicographical ordering. Casting it to an integer should solve the problem:
SELECT `userid`,MAX(CAST(`value` AS UNSIGNED))
FROM `mdl_scorm_scoes_track`
WHERE `element`= 'cmi.core.score.raw'
GROUP BY `userid`
My guess is that the value column isn't a numeric field
Try casting the value field eg CAST(value AS UNSIGNED)
You can do that without a GROUP BY statement.
Just list all rows as usual, but only select the maximum value from a second copy of the table. Then link the 2 copies together.
SELECT
`userid`,
`value`
FROM
`mdl_scorm_scoes_track` as outer
WHERE
`element`= 'cmi.core.score.raw'
and `value` = (
select
max(`value`)
from
`mdl_scorm_scoes_track` as inner
where
inner.userid = outer.userid )
The correct solution is to convert to a number. However, this is a case where I much prefer implicit conversion over explicit conversion:
SELECT userid, max(value + 0)
FROM mdl_scorm_scoes_track
WHERE element = 'cmi.core.score.raw'
GROUP BY userid;
This should work for any type of number -- negative numbers and numbers with decimal points, in particular.

SQL: Can I refer/access data the current row in a window function?

Here is an example. Suppose I have the following table:
id | list
--------+----------
10 |
| 10,20
20 | 10,20
For each id value, I'd like to calculate the number of rows having a list value which contains that id value. The result would be look like that:
id | count of lists
--------+----------
10 | 2
| 0
20 | 2
I suggest a window function should be used, but it seems that I can't access the id value from within such a function.
I totally agree that it is BAD design. This question is about the possibility.
Any MySQL/PostgreSQL solution is fine.
Assuming that you are using MySQL, and assuming that your table has the name test, and assuming that both columns are string types:
SELECT
t1.id, count(t2.list)
FROM
(test t1 LEFT JOIN test t2 ON
(t2.list LIKE CONCAT('%,', t1.id, ',%')) OR
(t2.list LIKE CONCAT('%,', t1.id)) OR
(t2.list LIKE CONCAT(t1.id, ',%')))
GROUP BY t1.id;
Please be aware that this solution might be very slow depending on the number of records you have and depending on the average length of the strings in the list field.
If you need something faster, I think it couldn't be a single query. Perhaps we would have to write a stored procedure or some application logic for that, or use additional tables or columns and a series of multiple SQL statements.
Before I start, as mentioned above, this is a poor design. However, this is how I would query it:
CREATE TABLE #Lists (id int, list varchar(500));
INSERT INTO #Lists (id, list) VALUES
(10, NULL), (NULL, '10,20'), (20, '10,20');
WITH cte AS (
SELECT LEFT(list, INSTR(list, '%,%')-1) AS value, SUBSTRING(list, INSTR(list, '%,%') + 1, 500) AS list FROM #Lists
UNION ALL
SELECT CASE WHEN list LIKE ('%,%') THEN LEFT(list, INSTR(list, '%,%')-1) ELSE list END AS value, CASE WHEN list LIKE ('%,%') THEN SUBSTRING(list, INSTR(list, '%,%') + 1, 500) END AS list FROM cte
WHERE CHAR_LENGTH(list) > 0
)
SELECT value, COUNT(*) FROM cte GROUP BY value;
DROP TABLE #Lists;
This solution allows for any number of values in the list string (like '10,20,30').
Ideally, the list values should be stored in a separate table so that each record has a single value, such as CREATE TABLE BetterDesign (id int, value int) INSERT INTO BetterDesign (id, value) VALUES (10, NULL), (NULL, 10), (NULL, 20), (20, 10), (20, 20). Along with a million other reasons, this is better for querying SELECT value, COUNT(*) FROM BetterDesign GROUP BY value. That being said, I understand the pains of legacy systems.

My SQL query result issue when using aggregate and union all

I have the following query on a student enrollment table, which has the student_id, first_name, enrollment_date (DATETIME) , price (Float) as the columns,
My problem is when I run this query on MySQL I get a "BLOB" value for the price column on the first part of the query when there is a value for price and in the second section of the query as well.
I want to extract the results to a csv, and do not want "NULL" in the excel sheet, hence I have used the IFNULL condition which seems to be the reason for "BLOB" value coming on the price column.
If I don't have IFNULL on the price column I get the results with "NULL" being set for price column.
If I change the IFNULL(price,'') to IFNULL(price, 0) then also things work but I am artificially putting a '0' for price when it is null this I don't want to do ... any help ?
select
student_id AS AS `student_id`,
first_name AS `first_name`,
enrolment_date AS `enrolment_date`,
IFNULL(price, '') AS `price`,
IFNULL(price * 0.1, '') AS `gst`,
IFNULL(price * 1.1, '') AS `price_ex_gst`
from
student_enrolment
where
student_id = 123 and
month(enrolment_date) = 10
union all
select
student_id AS `student_id`,
count(1) AS `count(1)`,
'Total' AS `Total`,
sum(`price`) AS `sum(price)`,
(sum(`price`) * 0.1) AS `gst`,
(sum(`price`) * 1.1) AS `price_ex_gst`
from
student_enrolment
where
student_id = 123 and
month(enrolment_date) = 10
I think the problem are the data types. Convert the price to a string and then use ifnull():
IFNULL(format(price, 4), '') AS `price`,
IFNULL(format(price * 0.1, 4), '') AS `gst`,
IFNULL(format(price * 1.1, 4), '') AS `price_ex_gst`
As a note: I would use coalesce() instead of ifnull(), because coalesce() is the ANSI standard function for this purpose.

MySQL SELECT INTO <variable> returns null but query returns a value

For some reason I cannot get this query to return a value when I do a select into.
Given a table t10company, I have a column called CompanyID which is of CHAR(8), it has a value in it of: MYCO0001
If I issue the following query:
SELECT
MAX(CONVERT(RIGHT(CompanyID, 4), UNSIGNED INTEGER))
FROM t10company
WHERE LEFT(CompanyID, 4) = 'MYCO'
GROUP BY LEFT(CompanyID, 4)
ORDER BY RIGHT(CompanyID, 4) LIMIT 1;
I get a return value of 1 which is what I would expect.
If I issue the exact same query except with INTO #myvar and then do a SELECT #myvar it always returns NULL. It does this in the stored proc I'm writing and also does it in a query window in MySQL Workbench. I dont know why?
I use this form to assign a value to a user variable in MySQL
SELECT #myvar := MAX(CONVERT(RIGHT(CompanyID, 4), UNSIGNED INTEGER)) FROM ...
According to the 5.1 documentation, this should also work:
SELECT MAX(CONVERT(RIGHT(CompanyID, 4), UNSIGNED INTEGER)) INTO #myvar FROM ...
I ran a quick test (MySQL 5.1), and the final SELECT is showing #myvar is being set to 1.
CREATE TABLE t10company (CompanyID CHAR(8));
INSERT INTO t10company VALUES ('MYCO0001');
SET #myvar := NULL;
SELECT #myvar;
SELECT
MAX(CONVERT(RIGHT(CompanyID, 4), UNSIGNED INTEGER)) INTO #myvar
FROM t10company
WHERE LEFT(CompanyID, 4) = 'MYCO'
GROUP BY LEFT(CompanyID, 4)
ORDER BY RIGHT(CompanyID, 4) LIMIT 1;
SELECT #myvar;
The expression in the ORDER BY clause is a bit odd.
Why would you be ordering by that? The aggregate is only going to return one row. It's not clear if MySQL is disregarding that though, since MySQL may not be identifying that all rows that satisfy the WHERE clause are going to be grouped together into a single.
For this particular statement, the LIMIT 1 is redundant.
I'd suggest you try it without the ORDER BY, just to see if that makes any difference.
Otherwise, I'm inclined to agree that perhaps the aggregate function and the GROUP BY might be the problem. If it is, them a possible workaround is to wrap your statement in a set of parenthesis as an inline view (give it an alias), and then selecting from the inline view.
You don't need the "group by", "order by", or "limit" in your query for a simple max(). Eliminate those, and if that doesn't work, try wrapping another query around it.
select
value into #myvar
from (
SELECT
MAX(CONVERT(RIGHT(CompanyID, 4), UNSIGNED INTEGER)) as value
FROM
t10company
WHERE
LEFT(CompanyID, 4) = 'MYCO'
)
You need to add LEFT(CompanyID, 4) to your SELECT statement (before FROM) if your are going to GROUP BY LEFT(CompanyID, 4)
This is pretty weird.
If you enclose the query into another, it seems to work.
SELECT value FROM (
SELECT MAX(CONVERT(RIGHT(CompanyID, 4), UNSIGNED INTEGER))
AS value FROM t10company
WHERE LEFT(CompanyID, 4) = 'MYCO'
GROUP BY LEFT(CompanyID, 4)
ORDER BY RIGHT(CompanyID, 4) LIMIT 1
) AS weird INTO #myval;
Judging by the EXPLAIN, the query extra cost should be negligible.