CTE recursive query in select statement - mysql

I have two MySQL server version 8.0, one for local development and another on an Heroku Instance, more precisely on Heroku i'm using a service called JAWSDB.
For my project I have to use the following CTE query, because the structure of the table tree_structure is hierarchical.
The purpose of the query is that for every row in tree_structure I have to get all of its child, and then count how many user in user_roles table are present in that particular row and its child.
SELECT mtr.id,
mtr.parent_id,
mtr.name,
mtr.manager_id,
CONCAT(users.nome, ' ', users.cognome) as resp_name,
(
with recursive cte (id, name, parent_id) as (
select id,
name,
parent_id
from tree_structure as tr_rec
where tr_rec.parent_id = mtr.id
and tr_rec.session_id = '2018'
union all
select tr.id,
tr.name,
tr.parent_id
from tree_structure as tr
inner join cte
on tr.parent_id = cte.id
WHERE tr.session_id = '2018'
)
select count(distinct (user_id))
from user_roles as ur_count
where ur_count.structure_id in (select distinct(id) from cte)
) as utenti
FROM tree_structure as mtr
LEFT JOIN users ON mtr.manager_id = users.id
WHERE level = 0
The problems is that on my local server it works whereas on the heroku instance it gaves me the following error:
unknow columns mtr.id in where clause.
Has someone any ideas of what is causing this error?
Thanks in advance and sorry for my bad english.

You have an ambiguous table reference in the CTE:
SELECT
....
(with recursive cte (id, name, parent_id) as (
....
from tree_structure as tr_rec -- here you have aliased the table
where tr_rec.id = tree_structure.id -- here you refer to the table and its alias
and tr_rec.session_id = '2018'
union all
....
)
....
) as utenti
....
Table tree_structure is used in the subselect and in the outermost select. The good practice is to make an unique alias for every table reference you have used.
Also you have a typo in the condition that should check self-referencing of the hierarcy root node:
where tr_rec.id = tr_rec.parent_id
and tr_rec.session_id = '2018'

OK guys I found out why the query was wrong. Apparently since MySQL version 8.0.14 they introduced support for using external parameters within subqueries.
My local version was 8.0.16 but the online version was 8.0.11 so because of this my query didn't work.

Related

duplicate database entries

I've accidentally managed to add duplicate entries into my database. The database contains a list of telephone numbers and they are routed via the information contained in the value field. The id field is unique per entry, and the UUID and username fields should be identical but shouldn't exist in the table more than once.
Data has been blanked in the screenshot for data protection.
The following command allowed me to identify I had duplicate entries which can be seen in the screenshot above.
select * uuid, count(*) from usr_preferences group by uuid having count(*) > 1;
I'm after some help on how I could delete entries where the UUID count is more than one but one entry must remain. deleting the duplicate UUID with the highest id number would be preferred.
Is there a way to display the results before deleting them?
MySQL version - mysql Ver 14.14 Distrib 5.7.38-41, for Linux (x86_64) using 6.2
Thanks
Could you give the following bit of code a go? Please make sure you have the database backed up before running this.
DELETE b FROM `test` a, `test` b where b.uuid = a.uuid and b.id > a.id;
I've expanded on your text data to make sure it will remove both duplicates and triplicates leaving the lowest ID. You can find my testing at this DB Fiddle.
https://www.db-fiddle.com/f/sUr6V6UP9tZ1Ya8eESid33/0
Hope this sorts you issue.
Try the following for MySQL v5.7:
set #rn=0;
set #uuid=null;
delete from usr_preferences where id in
(
select D.id
from
(
select id, uuid,
case
when #uuid <> uuid then
#rn:=1
else
#rn:=#rn+1
end as rn,
#uuid:=uuid
from usr_preferences order by id,uuid
) D
where D.rn>1
);
Select * From usr_preferences;
See a demo from db-fiddle.
Important Note:
Test the query before using it on your table, and take a backup of your table before running this query on it.
For MySQL v8.0 and above you may try the following:
with cte as
(
select id, row_number() over (partition by uuid order by id) as rn
from usr_preferences
)
delete U From
usr_preferences U join cte C
On U.id = C.id
where C.rn > 1;

MySQL Correlated sub query table name out of scope

This form of my correlated sub query comes up with the error message "Unknown column 'Invoices.TranDate' in 'where clause'"
select InvoiceID, TranDate
, ifnull(TotPayments,0) TotPayments, ifnull(CountPayments,0) CountPayments
from Invoices
left join (select DebtorID, sum(TranAmount) TotPayments, count(*) CountPayments
from CashTrans
where CashTrans.TranDate >= Invoices.TranDate
group by DebtorID) PY on PY.DebtorID = Invoices.DebtorID
Yet this version works
select InvoiceID, TranDate
, (select sum(TranAmount) from CashTrans
where CashTrans.TranDate >= Invoices.TranDate
and CashTrans.DebtorID = Invoices.DebtorID) TotPayments
, (select count(*) from CashTrans
where CashTrans.TranDate >= Invoices.TranDate
and CashTrans.DebtorID = Invoices.DebtorID) CountPayments
from Invoices;
What is wrong with the first query? The only thing I can think of is that on my Windows system I have configured lower_case_table_names=2 as I want to preserve mixed case names. Perhaps that has something to do with the first query not seeing Invoice.TranDate in scope? MySQL Documentation and internet searches have not thrown any light on the matter.
https://dev.mysql.com/doc/refman/8.0/en/lateral-derived-tables.html says:
A derived table cannot normally refer to (depend on) columns of preceding tables in the same FROM clause. As of MySQL 8.0.14, a derived table may be defined as a lateral derived table to specify that such references are permitted.
In SQL:1999, the query becomes legal if the derived tables are preceded by the LATERAL keyword (which means “this derived table depends on previous tables on its left side”):
I have not tested it, but I believe your query could be written this way:
SELECT InvoiceID, TranDate,
IFNULL(TotPayments,0) AS TotPayments,
ifnull(CountPayments,0) AS CountPayments
FROM Invoices
LEFT JOIN LATERAL (
SELECT DebtorID,
SUM(TranAmount) AS TotPayments,
COUNT(*) AS CountPayments
FROM CashTrans
WHERE CashTrans.TranDate >= Invoices.TranDate
GROUP BY DebtorID
) AS PY ON PY.DebtorID = Invoices.DebtorID;
Also be aware this requires you to use at least MySQL 8.0.14.

MySQL 'cant reopen (temporary) table)

Attempting to normalize a column (wgt) by joining to the result of an inline query as follows:
select a.monthEnd, ticker, wgt/totWgt
from hold a
inner join (
select monthEnd, sum(wgt*(1+totRet)) as totWgt
from hold
group by monthEnd ) tot
on a.monthEnd = tot.monthEnd
Get the following error:
Error Code: 1137. Can't reopen table: 'a' 0.00023 sec
I'm coming from SQL Server and not acquainted. What exactly is the issue and/or workaround here? Both tables referenced are temporary tables created within a stored proc and running MySQL 8.0.
Please use WITH clause,
WITH hold as
(SELECT monthEnd, wgt, totWgt , totRet, ticker FROM <actual table>)
select a.monthEnd, ticker, wgt/totWgt
from hold a inner join
(select monthEnd, sum(wgt*(1+totRet)) as totWgt from hold group by monthEnd ) tot
on a.monthEnd = tot.monthEnd;

Table is specified twice, both as a target for 'UPDATE' and as a separate source for data in mysql

I have below query in mysql where I want to check if branch id and year of finance type from branch_master are equal with branch id and year of manager then update status in manager table against branch id in manager
UPDATE manager as m1
SET m1.status = 'Y'
WHERE m1.branch_id IN (
SELECT m2.branch_id FROM manager as m2
WHERE (m2.branch_id,m2.year) IN (
(
SELECT DISTINCT branch_id,year
FROM `branch_master`
WHERE type = 'finance'
)
)
)
but getting error
Table 'm1' is specified twice, both as a target for 'UPDATE' and as a
separate source for data
This is a typical MySQL thing and can usually be circumvented by selecting from the table derived, i.e. instead of
FROM manager AS m2
use
FROM (select * from manager) AS m2
The complete statement:
UPDATE manager
SET status = 'Y'
WHERE branch_id IN
(
select branch_id
FROM (select * from manager) AS m2
WHERE (branch_id, year) IN
(
SELECT branch_id, year
FROM branch_master
WHERE type = 'finance'
)
);
The correct answer is in this SO post.
The problem with here accepted answer is - as was already mentioned multiple times - creating a full copy of the whole table. This is way far from optimal and the most space complex one. The idea is to materialize the subset of data used for update only, so in your case it would be like this:
UPDATE manager as m1
SET m1.status = 'Y'
WHERE m1.branch_id IN (
SELECT * FROM(
SELECT m2.branch_id FROM manager as m2
WHERE (m2.branch_id,m2.year) IN (
SELECT DISTINCT branch_id,year
FROM `branch_master`
WHERE type = 'finance')
) t
)
Basically you just encapsulate your previous source for data query inside of
SELECT * FROM (...) t
Try to use the EXISTS operator:
UPDATE manager as m1
SET m1.status = 'Y'
WHERE EXISTS (SELECT 1
FROM (SELECT m2.branch_id
FROM branch_master AS bm
JOIN manager AS m2
WHERE bm.type = 'finance' AND
bm.branch_id = m2.branch_id AND
bm.year = m2.year) AS t
WHERE t.branch_id = m1.branch_id);
Note: The query uses an additional nesting level, as proposed by #Thorsten, as a means to circumvent the Table is specified twice error.
Demo here
Try :::
UPDATE manager as m1
SET m1.status = 'Y'
WHERE m1.branch_id IN (
(SELECT DISTINCT branch_id
FROM branch_master
WHERE type = 'finance'))
AND m1.year IN ((SELECT DISTINCT year
FROM branch_master
WHERE type = 'finance'))
The problem I had with the accepted answer is that create a copy of the whole table, and for me wasn't an option, I tried to execute it but after several hours I had to cancel it.
A very fast way if you have a huge amount of data is create a temporary table:
Create TMP table
CREATE TEMPORARY TABLE tmp_manager
(branch_id bigint auto_increment primary key,
year datetime null);
Populate TMP table
insert into tmp_manager (branch_id, year)
select branch_id, year
from manager;
Update with join
UPDATE manager as m, tmp_manager as tmp_m
inner JOIN manager as man on tmp_m.branch_id = man.branch_id
SET status = 'Y'
WHERE m.branch_id = tmp_m.branch_id and m.year = tmp_m.year and m.type = 'finance';
This is by far the fastest way:
UPDATE manager m
INNER JOIN branch_master b on m.branch_id=b.branch_id AND m.year=b.year
SET m.status='Y'
WHERE b.type='finance'
Note that if it is a 1:n relationship the SET command will be run more than once. In this case that is no problem. But if you have something like "SET price=price+5" you cannot use this construction.
Maybe not a solution, but some thoughts about why it doesn't work in the first place:
Reading data from a table and also writing data into that same table is somewhat an ill-defined task. In what order should the data be read and written? Should newly written data be considered when reading it back from the same table? MySQL refusing to execute this isn't just because of a limitation, it's because it's not a well-defined task.
The solutions involving SELECT ... FROM (SELECT * FROM table) AS tmp just dump the entire content of a table into a temporary table, which can then be used in any further outer queries, like for example an update query. This forces the order of operations to be: Select everything first into a temporary table and then use that data (instead of the data from the original table) to do the updates.
However if the table involved is large, then this temporary copying is going to be incredibly slow. No indexes will ever speed up SELECT * FROM table.
I might have a slow day today... but isn't the original query identical to this one, which souldn't have any problems?
UPDATE manager as m1
SET m1.status = 'Y'
WHERE (m1.branch_id, m1.year) IN (
SELECT DISTINCT branch_id,year
FROM `branch_master`
WHERE type = 'finance'
)

How to transform a MSSQL CTE query to MySQL?

in my MySQL schema, I have the category(id, parentid, name) table
In the MSSQL, I have that CTE query (to build a category tree from the bottom up for a supplied category ID:
with CTE (id, pid, name)
as
(
select id, parentid as pid,name
from category
where id = 197
union all
select CTE.pid as id , category.parentid as pid, category.name
from CTE
inner join category
on category.id = CTE.pid
)
select * from CTE
How to 'transform' that query to MySQL ?
Unfortunately MySQL doesn't support CTE (Common Table Expressions). This is long overdue IMO. Often, you can just use a subquery instead, but this particular CTE is recursive: it refers to itself inside the query. Recursive CTE's are extremely useful for hierarchical data, but again: MySql doesn't support them at all. You have to implement a stored procedure to get the same results.
A previous answer of mine should provide a good starting point:
Generating Depth based tree from Hierarchical Data in MySQL (no CTEs)
Thankfully it's not necessary anymore, as MySQL starting from 8.0.1 supports CTE.
unfortunately MYSQl or XAMPP(MARIADB) mysql doesnot support CTEs(COMMON TABLE EXPRESSIONS),
for the same you will have to use nested queries.
for more information click on the below link:-
https://mariadb.com/kb/en/library/with/
Please check what version of MySQL you have using SELECT VERSION();
If it is 8 or above you can then proceed further with my comment.
With MySQL 8.0, MariaDB 10.2, and later versions, you can use recursive CTEs:
WITH RECURSIVE CTE (id, pid, name) AS (
select id, parentid as pid,name
from category
where id = 197
union all
select CTE.pid as id , category.parentid as pid, category.name
from CTE
inner join category
on category.id = CTE.pid
)
select * from CTE ;
Note that CTEs are limited by cte_max_recursion_depth (default 1000, max 4,294,967,295 (2³²−1)) in MySQL and by max_recursive_iterations (default 4,294,967,295) in MariaDB.
You can increase the limit by executing:
SET cte_max_recursion_depth = 4294967295;
It will only affect your current session and won't be persisted.