Oracle sql statement, how to created good statement for current case? - oracle

I have a table which looks like this
id
name
problem
1
A1
Good
2
A2
Bad
1
A1
Good
2
A2
Good
What I expect to recive from this table, for unique id, and name, we can have different problem,
E.g.:
If A1 = Good for all cases, I need to return 1 A1 Good,
If A2 = Good and also A2 = Bad, I need to return 2 A2 Bad
Expected result should looks like
id
name
problem
1
A1
Good
2
A2
Bad

Something like this (although I'm not 100% sure I understand the problem)
select id,
name,
case
when total_count = good_count then 'Good'
else 'Bad'
end as problem
from (
select id,
name,
count(*) as total_count,
case (when problem = 'Good' then 1 end) as good_count
from the_table
group by id, name
)
This counts the total items for each id/name combination and counts the number of "Good" items. If the total count equals the "good" count that Good will be displayed otherwise 'Bad'

Related

understanding how to add a column without having to grouping by it in sql

I have the following query:
SELECT DISTINCT
status,
CASE
WHEN status = 0 THEN 'bla'
WHEN status = 2 THEN 'bla1'
END AS "description" ,
COUNT(*) AS total
FROM
TRANSACTIONS
WHERE
status != 1
GROUP BY
status
which displays:
Status
DESCRIPTION
TOTAL
0
bla
29
2
bla1
70
3
(null)
12
4
(null)
85
now lets assume I have a table called Status_Codes which provides the Description itself, for example:
Status
DESCRIPTION
0
bla
2
bla1
I want to remove the case statement from my query that explicitly attaching the descriptions I need, and add my FROM clause the Status_Codes table, and to add Status_Codes.Description to my select.
That action cannot be done simply because I use an aggregate function in my select statement and I'd have to group by the same column( which is not something I want).
Im not sure on how to approach that problem, was thinking maybe it has something to do with partition by, but even if thats the case I dont know how to implement it in my query.
Any advices, Enlightments and whatnot will be appreciated.
thanks.
Why that irrational fear of adding yet another column into the group by clause? That's the simplest and most efficient way of doing it.
SELECT t.status, c.description, COUNT (*) AS total
FROM transactions t JOIN status_codes c ON c.status = t.status
WHERE t.status != 1
GROUP BY t.status, c.description
What benefit do you expect if you do it differently?
BTW, if you have group by clause, you don't need distinct.

join with exact match value otherwise join with default value

I have a table a
c1 c2 c3 c4 value
all all all all 5
all david all Y 6
all all cd all 7
and table b
c1 c2 c3 c4
a peter cd N
b david all Y
c all cd N
I want to have get the value from table a into table b, the desired results is like this:
c1 c2 c3 c4 Value
a david cd N 5
b david ab Y 6
c all cd N 7
That is use the default "all" value if there is no close match find.
Thanks a lot!
I see a couple of possibilities assuming you have some errors in your example result data. (C2 for value 5 and C3 for value 6 are suspect to me)
Use a CTE to union the results replace all with null and use aggregation to get max value
Use a Join on value and evaluate table a's value for each c column if it's not all use it, otherwise use B's value. (IF a and b are both all it doesn't matter which we use.) this may be problematic if both values are potentially different like if Value 6 had a Y in A, and a N in B. but no such example exits in your data so I'm trusting it doesn't happen. (or if it did, picking A's value is more appropriate if it's not all)
AS a cte: (Common Table expression)
WITH cte as (
SELECT replace(c1,'all',null)
, replace(c2,'all',null)
, replace(c3,'all',null)
, replace(c4,'all',null)
, value
FROM A
UNION ALL
SELECT replace(c1,'all',null)
, replace(c2,'all',null)
, replace(c3,'all',null)
, replace(c4,'all',null)
, value
FROM b)
/* We have to eval the max as if it's null we need to replace it with all
Might be able to avoid the replacing all provided all values of c1-c4 are
greater than all... replacing just seemed safer. at a hit to performance.*/
SELECT coalesce(max(c1),'all') as c1
,coalesce(max(c2),'all') as c2
,coalesce(max(c3),'all') as c3
,coalesce(max(c4),'all') as c4
,value
FROM cte
GROUP BY value
Using a join (simpler from a maintenance and perhaps performance standpoint)
SELECT case when A.C1 <> 'all' then A.C1 else B.c1 end as C1,
case when A.C2 <> 'all' then A.C2 else B.c2 end as C2,
case when A.C3 <> 'all' then A.C3 else B.c3 end as C3,
case when A.C4 <> 'all' then A.C4 else B.C4 end as C4,
A.value --A.val = b.val so it doesn't matter which se use.
FROM A
INNER JOIN B
on A.value = B.Value
Depending on existing indexes and data volume the first approach might be better than the second.

How to efficiently evaluate rows and sub records in single PL/SQL

I'm struggling writing PL/SQL (I'm new to PL/SQL) and I'm not sure how to structure SQL and loops for something like this.
I have 128 lines of SQL to create something like the following cursor:
ID Course Grade Attend? Date
123 MATH091 B Y 5/15
123 BIOL101 F N 3/10
123 ENGL201 W Y 1/2
456 MATH091 A Y 5/16
456 CHEM101 C Y 5/16
456 POLS301 NULL NULL NULL
With each ID, I need to several comparisons across the courses (e.g. which has the latest date, or were all courses attended). These comparisons need to be done in a certain order so that when they hit one that is true, they are flagged with a code and excluded from subsequent comparisons.
For example:
All courses attended? If true, output as attended and remove from next steps.
Find and store the latest date with a passing grade.
Find and store the latest date with a non-passing grade.
Is the later date after a course with a null grade? If true, output as coming back and remove from next steps.
Etc.
Each condition can be easily written in a SQL, but I don't know/understand the appropriate structure to loop through this process.
Is there syntax that can accomplish this easily?
We're on Oracle 11g and we do not have permissions to write to a temporary table.
I don't think you need PL/SQL for this. Except for the "unknown" requirement "etc." this can all be done in a single SQL statement:
Something like:
select id, course, grade, attended, attendance_date,
count(distinct case when attended = 'Y' then course end) over (partition by id) courses_attended,
count(distinct course) over () as total_courses,
case
when count(distinct case when attended = 'Y' then course end) over (partition by id) = count(distinct course) over () then 'yes'
else 'no'
end as all_courses_attended,
max(case when attended <> 'F' then attendance_date else null end) over (partition by id) as latest_passing_date,
max(case when attended = 'F' then attendance_date else null end) over (partition by id) as latest_non_passing_date
from attendees
order by id;
Btw: the attended column is not necessary if you have an attendance_date. If that date is not NULL than obviously the student attended the course. Otherwise she/he didn't.
Of course I have no idea what the "etc." steps should do though....
SQLFiddle example: http://sqlfiddle.com/#!4/e7c95/1

Substring inside string

Suppose this is my table:
ID STRING
1 'ABC'
2 'DAE'
3 'BYYYYYY'
4 'H'
I want to select all rows that have at least one of the characters in the STRING column somewhere in another row's STRING variable.
For example, 1 and 2 have an A in common and 1 ad 3 have a B in common, but 4 does not have any characters in common with any of the other rows. So my query should return only the first three lines.
I don't need to know with which line it matched.
Thanks!
#A.B.Cade : Good solution but could be done without any distinct nor join.
SELECT * FROM test t1
WHERE EXISTS
(
SELECT * FROM test t2
WHERE t1.id<>t2.id AND
regexp_like(t1.string, '['|| replace(t2.string, '.[]', '\.\[\]')||']')
)
The query won't compare the string with extra rows since it'll stop the comparison as soon as 1 match is found for the current row...
See fiddle.
#GolezTrol's answer is a good one, but here is another approach:
select distinct t1."ID", t1."STRING"
from table1 t1, table1 t2
where t1."ID" <> t2."ID"
and regexp_like(t1."STRING", '['|| t2."STRING"||']')
First take a cartessian product of the table
Then make sure your not comparing the same string to itself
then create a regexp from one string for comparing to the other - [<string1>] means that the string must contain one of the letters in the [ ] which are all from string1
Here is a fiddle
Like this:
select distinct
id, name
from
(select distinct
x.id,
x.NAME,
length(x.NAME) as leng,
substr(x.name, level, 1) as namechar
from
YourTable x
start with
level = 0
connect by
level <= length(x.name)) y
where
exists
(select
'x'
from
YourTable z
where
instr(z.name, y.namechar) > 0 and
z.id <> y.id)
order by
id
What it does:
First, (inner select) use the table with a number generator that returns a number for each letter in the name. Now each record in YourTable is returned Length(Name) times, each with another number. That generated number is used to isolate that letter (substr).
Then (subselect in top level where clause) check if records exist that contain that isolated letter. Distinct is needed, because records are returned more than once if more than one letter matches. You could add namechar to the outer select field list to see the letter that match.

Oracle/PLSQL. Select only best match between two tables

I have two tables:
Vehicles
make model modification
Audi A 5 A 5 2010 Sportsback 2.8
Audi A 5 A 5 2012 Quattro L
Audi A 5 A 5 Cabriolet
and
matchingModel
make model modContain modEnd finalModel
Audi A 5 Sportback A5 Sportback
Audi A 5 L A5 L
Audi A 5 A5
My task is to get only best fitting finalModel by finding matches (can be seen in select below).
First i tried to join tables
(SELECT
matchingModel.finalModel
FROM vehicles
LEFT OUTER JOIN matchingModel ON
matchingModel.TEXT1 = vehicles.make
AND vehicles.model = nvl(matchingModel.model,vehicles.model)
AND vehicles.modification LIKE decode(matchingModel.modContain, NULL, vehicles.modification, '%'||matchingModel.modContain||'%')
AND vehicles.modification LIKE decode(matchingModel.modEnd, NULL, vehicles.modification, '%'||' '||matchingModel.modEnd)
)
AS bestMatch
but that did not work, because as Sportsback was found as sportsback, later its overwritten as a simple A5 because that matches too.
So next i made this happen simply by "nvling" all possible options: nvl(nvl(nvl(select where make, model fits and modContains is in the middle of Modification and option cell is empty), (select where make, model fits and modEnd is like ending of Modification and modEnd is not empty), (select where make and model fits AND so on)) AS Bestmatch
This works, but it is very slow (and both tables have more that 500k records).
This is just a part of very huge select, so its difficult to rewrite this normal way.
Anyway, the question is, are there any best practices how to get best match, only once, fast, in oracle? The problems i have run into, is performance, or values fits twice, or "where" clause does not work, because i can not know if modContain or modEnd is empty or not.
Thank You in advance.
Sorry for English.
It is not quite there yet but I worked out an example you can continue to work out for yourself: SQL Fiddle Demo
select * from (
(select
case when v.modification like '%'||m.modContain||'%' then 2
when m.modcontain is null then 1
else 0 end m1,
case when v.modification like '%'||m.modend then 2
when m.modend is null then 1
else 0 end m2
, m.make mmake, m.model mmodel, modcontain, modend, finalmodel
, v.make vmake, v.model vmodel, modification
from vehicles v, matchingmodel m
where
v.make = m.make
and soundex(v.model) = soundex(m.model) ) ) x
order by m1+m2 desc
So the sub-query adds together the matches and the highest match should be your best match. I also used soundex which may also help you because Sportback and Sportsback is not quite the same and that helped me to make A5 and A 5 make the same. Also to make it fast you will have to work a lot with assigning good indicies and watching the explain plan, especially if you have 500k records. That is not an easier undertaking.
To the idea about writing a procedure (which is a good idea) untested it might look like this:
create or replace function vehicle_matching(i_vehicles vehicles%rowtype,
i_matchingmodel matchingmodel%rowtype)
return number
is
l_return number;
begin
if i_vehicles.modification like '%'||i_matchingmodel.modContain||'%' then
l_return := 3;
elsif soundex(i_vehicles.modification) like '%'||soundex(i_matchingmodel.modContain)||'%' then
l_return := 2;
...
if i_vehicles.modification like '%'||i_matchingmodel.modend then
l_return := l_return + 1; -- there is no i++ in PL/SQL
elsif
...
return l_return;
end vehicle_matching;
Also I was thinking if it is more efficient to work with INSTR and SUBSTR than with the % but I actually do not really think this is the case.
you may consider something like this:
write a query to return 1 on any partial match
then write another query to return another 1 on another partial match - etc.
repeat this for all possible columns that count towards your 'similarity'
in the end, you will find the row with the highest sum (or count) of 1's and that will be the closest match.

Resources