I have a problem when I try to create a view using a procedure. I have to do that because I need to make a pivot in MySQL, converting rows of a table in columns of another.
The query works great, but when I put it in the "CREATE VIEW" statement it gives me error.
Here is the query with CREATE view
CREATE VIEW `Untitled` AS
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'MAX(IF(formazioni_persone.id_formazione = ',
formazioni.id,
', true, false)) AS "',
formazioni.titolo,'"'
)
) INTO #sql
FROM formazioni;
SET #sql = CONCAT('SELECT persone.*, ', #sql, ' FROM persone INNER JOIN formazioni_persone ON persone.id = formazioni_persone.id_persona GROUP BY persone.id');
PREPARE stmt1 FROM #sql;
EXECUTE stmt1;
DEALLOCATE PREPARE stmt1;
The query without CREATE VIEW Untitled AS works great
The query without CREATE VIEW Untitled AS works great. I already tried to create a TEMP TABLE inside the CREATE VIEW, but nothing. Also tried to use delimiters like that, but nothing
DELIMITER $$
CREATE VIEW `Untitled` AS
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'MAX(IF(formazioni_persone.id_formazione = ',
formazioni.id,
', true, false)) AS "',
formazioni.titolo,'"'
)
) INTO #sql
FROM formazioni;
SET #sql = CONCAT('SELECT persone.*, ', #sql, ' FROM persone INNER JOIN formazioni_persone ON persone.id = formazioni_persone.id_persona GROUP BY persone.id');
PREPARE stmt1 FROM #sql;
EXECUTE stmt1;
DEALLOCATE PREPARE stmt1;
END $$
DELIMITER ;
Error: 1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'SET #sql = NULL' at line 2, Time: 0.082000s
A VIEW is not a procedure. A VIEW is only a single SELECT statement, which must have fixed columns at the time you define the VIEW. You can't make a VIEW that is also a procedure.
Sorry, if you need a pivot-table, you need to specify the values for each column in the query. You can't make a SELECT query or a VIEW that dynamically adds more columns as it finds potential future values.
And you can't define a VIEW that runs an arbitrary block of procedure code anyway. That would require a procedure.
You should just use the solutions that are already shown in questions like MySQL - Rows to Columns
There are no other shortcuts or workarounds.
By the way, all SQL databases have this restriction, not just MySQL.
Re your question:
I'm looking for a solution that doesn't require manual update of the query
A pivot-table query must have as many columns in the select-list as the number of columns you want it to return. There is no way to make an SQL query that expands the number of columns dynamically as a result of the data it reads at execute time.
The only way you can make a single query that returns all the data is to NOT do a pivot-table query, and instead return all the data in rows, not columns.
SELECT p.*, f.titolo, pf.id_persona IS NOT NULL AS ha_formazioni
FROM persone AS p
CROSS JOIN formazioni AS f
LEFT OUTER JOIN formazioni_persone AS fp ON fp.id_formazioni AND fp.id_persona = p.id
This will return one row for each formazioni per persone. Then you must write code in your application to loop over all the rows of the reesult, and format the data in columns in the manner you want.
Related
I have a MySQL stored procedure (shown below) that's supposed to build a list of IDs from a table of hierarchically related records. I'm having to re-engineer an older stored procedure to switch from using a simple CONCAT function to GROUP_CONCAT because the former couldn't handle the sizes of the lists being generated (i.e., the lists are going well over the 1024 character limit of the CONCAT function).
DELIMITER $$
CREATE PROCEDURE SPTest (top_id INT)
BEGIN
DECLARE ids_all TEXT;
SET SESSION group_concat_max_len = 1000000;
SET ids_all = top_id;
SET #str = GROUP_CONCAT('SELECT GROUP_CONCAT(id SEPARATOR \', \') ',
'FROM tbl WHERE nha_id = ', top_id, ' INTO #ids_tmp' SEPARATOR '');
PREPARE stmt FROM #str;
EXECUTE stmt;
WHILE #ids_tmp != "" DO
SET ids_all = GROUP_CONCAT(ids_all, #ids_tmp SEPARATOR ', ');
SET #str = GROUP_CONCAT('SELECT GROUP_CONCAT(id SEPARATOR \', \') ',
'FROM tbl WHERE nha_id IN (', #ids_tmp, ') INTO #ids_tmp' SEPARATOR '');
PREPARE stmt FROM #str;
EXECUTE stmt;
END WHILE;
SELECT ids_all AS ids;
END $$
DELIMITER ;
The problem is this routine is generating the following error when I try to call it (and sometimes it returns this error when I try to create the Stored Procedure):
ERROR 1111 (HY000): Invalid use of group function
When I manually create the same kinds of queries this code would build and run them at the command-line, they work perfectly fine -- no errors of any kind, and I get the results I expect. I've seen posts (both here on Stack Exchange and elsewhere) which say that MySQL doesn't support nested aggregate functions, but the concatenation here is just being done to strings. So I thought that maybe somehow it was seeing the GROUP_CONCAT in the string and burping because of that, so I tried putting "XXXX" in place of "GROUP_CONCAT" in the string and then using the REPLACE function to switch it back, but that made no difference. I also tried it with WHERE and HAVING for the criteria clause, but neither one worked. I've also done an extensive web search and was unable to find anything that was in any way helpful.
I don't know what else to try, mainly because I can't see what's wrong with the syntax here. Any help would be appreciated.
Update 1:
I have since tried a modified version of the script where the GROUP_CONCAT merges data from a subquery like this:
SET #qrystr = GROUP_CONCAT('SELECT GROUP_CONCAT(c SEPARATOR ", ") ',
'FROM (SELECT id AS c FROM tbl WHERE nha_id IN (', #ids_tmp,
') AS d INTO #ids_tmp' SEPARATOR '');
but that made no difference either.
You can't use GROUP_CONCAT() as a scalar function; it must be used in the context of a set of rows. Similarly, you can't use any other aggregate function without a table reference:
SET #x = MAX(<expr>); -- makes no sense
Ideally you should upgrade to MySQL 8.0 if you haven't already, and use a recursive CTE query instead:
WITH RECURSIVE hierarchy AS (
SELECT top_id AS id
UNION
SELECT tbl.id FROM tbl JOIN hierarchy ON tbl.nha_id = hierarchy.id
)
SELECT GROUP_CONCAT(id SEPARATOR ', ') AS ids_all FROM hierarchy;
That would eliminate the need for using loops or prepare/execute or temp variables.
Thanks to Bill Karwin's answer regarding the need to use SELECT with GROUP_CONCAT, I was able to see how my code needed to be changed. Rather than using a SET statement to assign the values to the variables, I need to use a SELECT ... INTO ... construct, as shown here:
DELIMITER $$
CREATE PROCEDURE ASSEMBLY_LIST_TEST (top_id INT)
BEGIN
DECLARE ids_all TEXT DEFAULT '';
SET SESSION group_concat_max_len = 1000000;
SET ids_all = top_id;
# Find the 1st-level children of 'top_id' to start building the list
SELECT GROUP_CONCAT('SELECT GROUP_CONCAT(id SEPARATOR ", ") FROM tbl ',
'WHERE nha_id = ', top_id, ' INTO #ids_tmp' SEPARATOR '') INTO #qrystr;
PREPARE stmt FROM #qrystr;
EXECUTE stmt;
# Recursively find the children of each level of children of the previous loop & add to the list
WHILE #ids_tmp != '' DO
SELECT GROUP_CONCAT(ids_all, ', ', #ids_tmp SEPARATOR '') INTO ids_all;
SELECT GROUP_CONCAT('SELECT GROUP_CONCAT(id SEPARATOR ", ") FROM tbl ',
'WHERE nha_id IN (', #ids_tmp, ') INTO #ids_tmp' SEPARATOR '') INTO #qrystr;
PREPARE stmt FROM #qrystr;
EXECUTE stmt;
END WHILE;
SELECT ids_all AS ids;
END $$
DELIMITER ;
This now produces exactly the result I was looking for. Of course, the recursive query approach is a much better solution for what I want this to do, but maybe my solution will help someone else.
We built a piece of dynamic sql that generates a wide view from data in long format. Seen here:
CREATE PROCEDURE `selectPivotedTermpoints`(studyid varchar(300))
BEGIN
SET SESSION group_concat_max_len = 10000000;
SET #psql = NULL;
SET #finalSQL = NULL;
SET #StudyID = studyid;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT('SUM(CASE WHEN terminate = ''', REPLACE(Terminate,'''', ''''''), ''' THEN 1 ELSE 0 END) AS `', REPLACE(Terminate,'''', ''), '`')
) INTO #psql
FROM Dashboard
WHERE studyid = #StudyID
AND completion_status = 'terminate';
SET #finalSQL = CONCAT('
SELECT Sample_provider as Provider,
completion_status as `Status`,',
#psql,'
FROM Dashboard
WHERE studyid = ''', #StudyID, '''
AND completion_status = ''terminate''
GROUP BY Sample_provider');
SELECT #finalSQL;
PREPARE stmt FROM #finalSQL;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END
When the sql is run as a query,(from SET to DEALLOCATE)setting #StudyID manually, we return a table with only the columns for that specific study(distinct Terminate as columns for only that study), however when the query is turned into a stored procedure and run it is generating a table with columns for all studies(all distinct Terminate as columns).
It appears that the first where clause (in the select group_concat) is being ignored when run as a stored procedure, but this is not the case when run as a simple query.
Stored procedure call:
selectPivotedTermpoints('bhp_03a');
Does anyone know why this is the case and / or how I can correct the issue?
I helped someone with a similar issue recently in another question; it confused us for quite a while. Change the parameter name to something else, I am guessing that WHERE is using it instead of the field in the table.
(You might be able to get away with Dashboard.studyid as well, but changing the parameter name will cause less confusion; and I am not positive how the query in #finalSQL would behave either.)
I'm looking to do something like this:
select data AS curdate() from table;
so the resulting table would look like:
2013-04-26
data 1
data 2
data 3
I can't figure out the syntax, but it must be possible?
I've tried it without quotes of any kind, which returns an error. Single quotes and back ticks both return the SQL itself as the column header.
That's an unusual requirement, but if you insist, you'd have to use dynamic sql.
SET #curdate = CURDATE();
SET #sql = CONCAT('SELECT whatever AS "', #curdate, '" FROM whatever');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
An alias is considered an identifier and cannot be assigned to a function without the use of dynamic SQL. It would break referencing such as:
SELECT *
FROM (SELECT 1 AS curdate()) a
I'm trying to use a prepared statement in mysql workbench in a cursor. The cursor works on a very big data set so it is executed many times. Every time a new result is shown for the EXECUTE step. This results eventually in mysql workbench crashing because of too many open result windows.
In the cursor I do something like this:
PREPARE stmt2 FROM #eveningQuery;
EXECUTE stmt2;
DEALLOCATE PREPARE stmt2;
Normally I use stuff like
set aVar = (EXECUTE stmt2);
to silence the query but EXECUTE doesn't work like that.
Does anybody know how you can disable the output for the EXECUTE command in mysql?
Note: I understand how i can retrieve the data in a variable, however what I want to prevent is that it is displayed in the results overview like this
This will make mysql-workbench crash when looped too much.
edit because it was asked an example of the #eveningQuery.
SET #eveningQuery = CONCAT('select #resultNm := exists (select idSplitBill from tb_SplitDay where idSplitBill =', idSplitBillVar, ' and ', #columnNameEv ,' = 1 and softdelete = 0)');
idSplitBillVar = the id coming from the cursor.
#columnNameEv = a column that i am filling in variably.
I added this info because it was asked, however it doesn't really matter in my opinion because the question still stands even with the most simple query. When you execute a prepared statement, you will get a output result. I just want to disable this behaviour.
The query you use creates new result-set, and GUI client show it (...many times) -
SELECT #resultNm:=EXISTS(
SELECT idSplitBill FROM tb_SplitDay
WHERE idSplitBill =', idSplitBillVar, ' AND ', #columnNameEv ,' = 1 AND softdelete = 0
)
You can rewrite this query, and result-set won't be created -
SELECT EXISTS(
SELECT idSplitBill FROM tb_SplitDay
WHERE idSplitBill =', idSplitBillVar, ' AND ', #columnNameEv ,' = 1 AND softdelete = 0
)
INTO #resultNm
people know that we can use if statement to configure a query in the select statement like this
select if(var=1,amount,amount/2) from mytable;
But what if I want to achieve something like this:
select amount from if(var=1,mytable1,mytable2);
Is there any way to configure the table at run time?
SELECT amount FROM mytable1 WHERE #var = 1
UNION
SELECT amount FROM mytable2 WHERE #var = 0
UPD: Here's what MySQL EXPLAIN looks like for the part of the query which has a condition evaluating to FALSE:
Note the Impossible WHERE part. MySQL recognizes that the expression in WHERE is constantly evaluating to FALSE, so it doesn't even try executing the query. Hence, no performance overhead when using this approach.
(Upgrading to an answer)
Where did var come from?
If it's a variable in another language, you could test it in that other language and then construct different SQL as appropriate:
$sql = "SELECT amount FROM " . ($var = 1 ? "mytable1" : "mytable2");
If it's a user variable in SQL, you could similarly use an IF statement around the two alternative SELECT statements:
DELIMITER ;;
IF #var = 1 THEN
SELECT amount FROM mytable1;
ELSE
SELECT amount FROM mytable2;
END IF;;
DELIMITER ;
If it's anything else (like a field from your tables), then your question doesn't make a great deal of sense.
Based on the mysql manual pages, it appears you cannot do this with the traditional syntax.
"User variables are intended to provide data values. They cannot be used directly in an SQL statement as an identifier or as part of an identifier, such as in contexts where a table or database name is expected, or as a reserved word such as SELECT."
- [http://dev.mysql.com/doc/refman/5.6/en/user-variables.html][1]
The exception to this is that you can assemble a prepared statement, but is probably not a better solution for most programming tasks. It would be better to leave the sql string generation to the language invoking mysql.
But, if you are doing this as part of a "sql only" task, like an import, this seems to be the approach you must take.
SET #s = CONCAT("SELECT * FROM ", if(true, "table1", "table2"), " LIMIT 1");
PREPARE stmt FROM #s;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET #s = CONCAT("SELECT * FROM ", if(false, "table1", "table2"), " LIMIT 1");
PREPARE stmt FROM #s;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;