Mysql 8 CASE..WHEN..THEN..END throws syntax error - mysql-8.0

I have this query which works without problems in Mysql 5.* but I recently upgraded to MySQL 8 and now the query throws a syntax error as follows:
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 'row_number, #breed'
The query is (lots of useless detail removed for simplicity):
SELECT `name`, `age`, breed
FROM (
SELECT
`dogs`.`name`,
`dogs`.`age`,
#row_number:=CASE WHEN #breed=breed
THEN #row_number+1
ELSE 1
END AS row_number
, #breed:=breed AS breed
FROM `dogs` /* other details with joins, subqueries and limits left out for simplicity*/;
breed is supposed to maintain a row count so I can get n rows for rows grouped by breed. That is, if n=2, for example, my result would be:
name | age | breed
------------------
fifi | 2 | labrador
bingo | 5 | labrador
rocket | 1 | german shepherd
sky | 1 german shepherd
My main question is why I get the syntax error. Google, is my friend, but not in this case... I tried. I also tried removing "as", adding brackets around the case/when/then/end but no joy!

I'm not certain that MySQL 8 supports user variables in the same way as MySQL 5.x does. In any case, your current syntax is at least deprecated, and you should just be using the ROW_NUMBER analytic function. For example, assuming you wanted the two youngest animals per breed, you could try:
WITH cte AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY breed ORDER BY age) rn
FROM dogs
-- maybe joins here
)
SELECT name, age, breed
FROM cte
WHERE rn <= 2;

Related

Oracle looping query to get value plus child values from same table

I have a table which contains values in a hierarchy structure.
I was wondering if anyone knew of a query where I could loop through each row finding it's ID and then search for rows with a PARENTID of the same value. So for example:
With a table
ID PARENTID LEVEL VALUE
-------------------------------
1 0 COUNTRY USA
2 1 CITY NYC
3 1 CITY LA
4 2 TEAM GIANTS
5 2 TEAM JETS
6 3 TEAM RAMS
7 3 TEAM CHARGERS
I could start by searching for ID:2 (NYC) and from there find all teams in that city. Something like (but I do not know the total loops I'll need to do)
SELECT ID2,VALUE FROM TABLE1 WHERE PARENTID = ID1;
Gives me:
3,LA
6,RAMS
7,CHARGERS
connect by is a common way to loop through a hierarchy like that. If you add start with, you can pick a starting point in the hierarchy.
SELECT table1.*, level
FROM table1
START WITH id = 3
CONNECT BY parentid = PRIOR id;
Please note that level is an Oracle keyword which will tell you how many loops you have stepped through so far. I wouldn't recommend using it as a column name. There's some other pseudocolumns and functions you might find helpful too.

Consolidate rows

I'm trying to cut down on rows a report has. There are 2 assets that return on this query but I want them to show up on one row.
Basically if dc.name LIKE '%CT/PT%' then I want it to be same row as the asset. The SP.SVC_PT_ID is the common field to join them.
There will be times when there is no dc.name LIKE '%CT/PT%' however I still want the DV.MFG_SERIAL_NUM to populated just with a Null to the right.
Select SP.SVC_PT_ID, SP.DEVICE_ID, DV.MFG_SERIAL_NUM, dc.name,
substr(dc.name,26)
From EIP.SVC_PT_DEVICE_REL SP,
eip.device_class dc,
EIP.DEVICE DV
Where SP.EFF_START_TIME < To_date('20170930', 'YYYYMMDD') + 1
and SP.EFF_END_TIME is null
and dc.id = DV.device_class_id
and DV.ID = SP.device_id
ORDER BY SP.SVC_PT_ID, DV.MFG_SERIAL_NUM;
I'm not sure I understand what you are saying; test case would certainly help. You said that query you posted returns two rows (only if we saw which ones ...) but you want them to be displayed as the image you attached to the message.
Generally speaking, you can do that using an aggregate function (such as MAX) on certain column(s), along with the GROUP BY clause that contains the rest of them.
Just for example:
select svc_pt_id, max(ctpt_name) ctpt_name, sum(ctpt_multipler) ctpt_multipler
from ...
group by svc_pt_id
As I said: a test case would help people who'd want to answer the question. True - someone might have understood it far better than I did and will provide assistance nevertheless.
EDIT: after you posted sample data (which, by the way, don't match screenshot you posted previously), maybe something like this might do the job: use analytic function to check whether name contains CT/PT; if so, take its data. Otherwise, display both rows.
SQL> with test as (
2 select 14 svc_pt_id, 446733 device_id, 'Generic Electric' name from dual union
3 select 14, 456517, 'Generic CT/PT, Multiplier' from dual
4 ),
5 podaci as
6 (select svc_pt_id, device_id, name,
7 rank() over (partition by svc_pt_id
8 order by case when instr(name, 'CT/PT') > 1 then 1
9 else 2
10 end) rnk
11 from test
12 )
13 select svc_pt_id, device_id, name
14 from podaci
15 where rnk = 1;
SVC_PT_ID DEVICE_ID NAME
---------- ---------- -------------------------
14 456517 Generic CT/PT, Multiplier
SQL>
My TEST table (created by WITH factoring clause) would be the result of your current query.

Removing duplicate columns in one row while merging corresponding column values

Apologies if this seems simple to some, I am still in the (very) early stages of learning!
Basically I've got a table database that has multiple users (Users_ID), each with a corresponding access name(NAME). The problem is, some Users have multiple access names, meaning when the data is pulled, there is duplicates in the User_ID column.
I need to remove the duplicates in the User column and join their corresponding access names in the NAME column, so it only takes up 1 row and no data is lost.
The current SQL query I'm using is :
select Table1_user_id, Table2.name,
from Table1
inner join Table2
on Table1.role_id = Table2.role_id
An example of what this would return:
USER_ID | NAME
------- --------------
Tim Level_1 Access
John Level 2 Access
Tim Level 2 Access
Mark Level 3 Access
Tim Level 3 Access
Ideally, I would remove the duplicates for Tim and display as following:
USER_ID | NAME
------- ----------------------------------------------
Tim Level_1 Access, Level 2 Access, Level 3 Access
John Level 2 Access
Mark Level 3 Access
Thanks in advance for any help regarding this and sorry if something similar has been asked before!
Use GROUP_CONCAT with SEPARATOR :
SELECT Table1.user_id, GROUP_CONCAT(Table2.name SEPARATOR ',') AS Ename
FROM Table1
INNER JOIN Table2 ON Table1.role_id = Table2.role_id
GROUP BY Table1.user_id
select Table1_user_id, LISTAGG(Table2.name,', ') WITHIN GROUP (ORDER BY Table2.name) as Name
from Table1
inner join Table2
on Table1.role_id = Table2.role_id

Select Distinct Query Left JOIN, Still getting duplicates in my result

I am writing a select query with distinct but I am still getting duplicates in my result. The Oracle View do have duplicates and I am trying to get back only 1 occurrence of that value.
Here is my query
select
person.person_id,
person.last_name,
person.first_name,
person.first_name,
person.middle_name,
skill.skills_id,
(case
when trim(skills.skill_description) = 'typing fast' then 'TP1'
when trim(skills.skill_description) = 'courier district 9' then 'CD9'
when trim(skills.skill_description) = 'helpdesk shift 3' then 'HD3'
when trim(skills.skill_description) = 'helpdesk shift 5' then 'HD5'
....
else ''
end) AS skill_description
from person_view person
left join (select distinct person_id, skill_id, skill_description, updated_date
from skill_view) skills
on skills.person_id = person.person_id and
((trunc(sysdate) - PHONE.UPDATED_DT <= 1)) and
trim(skills.skill_description) in ('skill1', 'skill2', 'skill3' ...)
There is a lot of values for skill_description, so I add the IN clause to filter for 15 - 20 specific skill_description values.
My case will take a the value and set the code for it.
I thought when I used the 'distinct' keyword it would filter out the duplicates but it is not working.
Here is my output so far
105 John E Doe SKILL1
105 John E Doe SKILL1
105 John E Doe SKILL2
105 John E Doe SKILL2
105 John E Doe SKILL3
105 John E Doe SKILL3
Any help is appreciated. Thanks
The problem is the DISTINCT is in an inner level and maybe the duplicates are outside the LEFT JOIN, you must put the DISTINCT clause in the first SELECT.
This should give you the desire output:
SELECT DISTINCT
person.person_id,
person.last_name,
person.first_name,
person.first_name,
person.middle_name,
skill.skills_id,
OMMITED CODE...
You are selecting updated_date in the subquery, but you don't use it in the outer query. QUESTION: Why? Did you mean to use it for something, perhaps to only select the most current info from the table?
In any case, if you have the same person_id, skill_id and skill_description values but different updated_date, DISTINCT won't help; the rows may very well be distinct in the inner query, but cease to be distinct in the outer query (if you don't include updated_date in the projection).

Oracle SQL: Select rows from table A with fallback to joined table A and B. (union, group by,...)

The requirement may seem a bit odd, but bear with me: Lets say I have a list of my employees like this:
pid name
-------------------------
1 Smith-Gordon
2 Hansen
3 Simpson
And a table of previous names (if e.g. Mrs Smith-Gordon and Mr Hansen had one or more different names before they were married, respectively), employeehist:
pid oldname
-------------------------
1 Smith
2 Taylor
2 Baker
What I want now is to be able to search for names and get results from both tables like this:
a) Search for "Simpson%" -> Get a result like "3, Simpson"
b) Search for "Hansen%" -> Get a result like "2, Hansen"
c) Search for "Taylor%" -> Get a result like "2, Hansen, matched on previous Taylor"
d) Search for "Smith%" -> Get a result like "1, Smith-Gordon"
In other words, I want the current record, plus the old name if that was where the pertinent match occurred.
What I tried so far:
1) Naively join the history to the current employees: The searches b), c) and d) will always contain something in the oldname column, so I can't tell where the match occurred. I also get duplicate hits for Mr Hansen.
2) I tried to UNION a first select on employees (containing a dummy NULL AS oldname) with a second select joining employeehist with employees which will return me a nice hit for search b) without an oldname and one with an oldname for c), but now I predictably get duplicates in d).
Any thoughts?
You can use the following query with a parameter:
SELECT e.pid,
CASE
WHEN e.name LIKE :search_key THEN e.name
WHEN eh.oldname LIKE :search_key THEN e.name || ' matched on previous ' || eh.oldname
END
FROM employees e
LEFT JOIN employeehist eh on (e.pid = eh.pid)
WHERE e.name LIKE :seach_key OR eh.oldname LIKE :search_key
I have come up with this solution:
SELECT * FROM ( /* (3) outer filter query */
SELECT e.pid, e.name, /* (1) query combining current and matching old names */
CASE
WHEN e.name LIKE :search_key THEN 'Y'
ELSE 'N'
END AS primary_match,
(
SELECT oldname /* (2) subquery that gives me one or no matching old name */
FROM employeehist eh
WHERE eh.pid = e.pid
AND eh.oldname LIKE :search_key
AND ROWNUM=1
)
FROM employees e
) combined
WHERE combined.primary_match = 'Y' OR combined.oldname IS NOT NULL;
There's one primary select (1) that gets me all current ids and names, and adds a CASE column whether the name matched. Additionally, it runs a subquery (2) that gets me one matching old name (also if there are several, or none if none). With that on hand I can use an outer select (2) that will filter away rows with no matches.
This would return e.g. for search key "Smith%"
pid | name | primary_match | oldname
1 | Smith-Gordon | Y | Smith
or for "Taylor%"
pid | name | primary_match | oldname
2 | Hansen | N | Taylor
I'm not sure how elegant it is, but it works as I want:
I get one result per matching current pid, no matter how many old names that pid has, matching or not. No duplicates.
I can distinguish between results that matched on the current name and those that ("only" or "also") matched on old names.
I don't need to define my matching condition twice because it gets rolled into that CASE column and I can filter on that.
There's obviously room for improvement: The subquery (2) could be made to return an aggregate of all matching old names (or the newest or oldest, I have a column for that).
But this works for me.
I have found a better solution than my previous one. My problem was that I couldn't GROUP BY pid and "squash" differing oldname rows. I'm quite sure I remember that this was possible in MySQL, but Oracle always ever gave me "979: not a GROUP BY expression". Strict but fair.
The solution is apparently to provide Oracle with a strategy how to deal with those rows:
SELECT pid, name,
MIN(oldname) KEEP (DENSE_RANK FIRST ORDER BY oldname NULLS FIRST) as oldname
/*(3) outer select combines current and old hits, and "squashes" duplicates, preferring current hits where available*/
FROM (
SELECT e.pid, e.name, null AS oldname /*(1) hits in current names*/
FROM employees e
WHERE e.name LIKE :search_key
UNION ALL
SELECT e.pid, e.name, eh.oldname /* (2) hits in old names*/
FROM employeehist eh
JOIN employees e ON e.pid = eh.pid
WHERE eh.oldname LIKE :search_key
) combined
GROUP BY pid, name;
The idea is simple: Run a query (1) that gives all matches in current names (plus a dummy "oldname" column with NULLs), then a query (2) that gives all matches in old names (complete with their joined current names to display). Then simply combine those, and remove the duplicates by pid (and name, because Oracle, but that's identical by definition) giving preference to rows where oldname is NULL.
This would return e.g. for search key "Smith%"
pid | name | oldname
1 | Smith-Gordon | NULL
which is exactly what I want. If there's a pid with a current and an old match, I don't care about the old one. Or for "Taylor%":
pid | name | oldname
2 | Hansen | Taylor
This query also appears to be roughly 10 times faster than my other solution - I guess because it avoids subqueries that depend on the current pid.
So the only odd thing is that I need to use MIN(oldname) instead of some form of identity. I get that Oracle needs an aggregate function here, but the whole point of the KEEP ... FIRST exercise is to only have one row anyway, no?
But it works, and it's fast, so I won't complain.

Resources