How to return a record for every item in the 'where value in ' clause? - sql

Oracle11g
I want to know if a player has ever played shortstop. However, a player
may not even be in my table, yet I'd still like to return a row for that player. In this case, player #3 is not in table, but I'd like to return a row nevertheless.
Selection Criteria
If player has at least one SHORTSTOP record then return just 1 row with YES.
If player has at least one record and none of them are SHORTSTOP then return just one row with NO.
If player has no records then return just one row with NO.
Query
with baseball_players as
(select 1 as player_id, 'SHORTSTOP' as position from dual union all
select 1 as player_id, 'FIRSTBASE' as position from dual union all
select 2 as player_id, 'FIRSTBASE' as position from dual)
select player_id, case position
when 'SHORTSTOP' then 'YES'
else 'NO'
end has_played
from baseball_players
where player_id in (1,2,3)
Question: How can I write query to get desired results?
Desired Output
PLAYER_ID HAS_PLAYED
----------------------
1 YES
2 NO
3 NO

How about this:
WITH playerList AS
(
SELECT
1 player_id
FROM
dual
UNION
SELECT
2
FROM
dual
UNION
SELECT
3
FROM
dual
)
, baseball_players as
(
select 1 as player_id, 'SHORTSTOP' as position from dual union all
select 1 as player_id, 'FIRSTBASE' as position from dual union all
select 2 as player_id, 'FIRSTBASE' as position from dual
)
SELECT
pl.player_id
, MAX(CASE WHEN bp.position = 'SHORTSTOP' THEN 'YES' ELSE 'NO' END)
FROM
playerList pl
LEFT JOIN
baseball_players bp
ON
bp.player_id = pl.player_id
GROUP BY
pl.player_id

Rather than using a where clause, create a tempoary table, insert the values in and left join on the table you created.
Use a case statement for IF NULL then NO, otherwise YES.

Related

Oracle SQL - Combining columns with 'OR' bit function

Oracle 12.2 - I have a table with 3 columns... ID, ParentID and ProductList. ID is unique, with multiple IDs rolling up to a ParentID. (this is a account model... basically multiple accounts have the same parent...) ProductList is a string...also exactly 20 bytes... right now it is 20 letters of 'Y' and 'N', such as YYNYNYNYNNNY... but I can change the 'Y' and 'N' to 1 and 0 if it will help... what I need to do is within a group of ParentID, calculate a bitwise OR of the ProductList. The end result I need is a 20 byte string (or some type of 20 bits of data) that says - for each respective letter/bit - if any 'Y' then return 'Y'. Again, I can use 1/0 if easier than Y/N.
Here is pseudoCode of what I am trying to do... Any help appreciated.
with T1 as
(
select 10 as ID, 20 as ParentID, 'YYNNYNYNYNYYNNYNYNYN' as ProductList from dual
union
select 11 as ID, 20 as ParentID, 'NNNNNNNNNNYYYYYYYYYY' as ProductList from dual
union
select 22 as ID, 20 as ParentID, 'YYNNNNNNNNNNNNNNNNNN' as ProductList from dual
)
SELECT ParentID, BitWiseOr(ProductList) FROM t1
group by ParentID;
You can use the brute force method of taking the maximum of each character and then using ||:
SELECT ParentID,
(max(substr(productlist, 1, 1)) ||
max(substr(productlist, 2, 1)) ||
max(substr(productlist, 3, 1)) ||
. . .
max(substr(productlist, 20, 1)) ||
)
FROM t1
GROUP BY ParentID;
This works because 'Y' > 'N'.
Note: This is a lousy data model. You should have a separate table with one row per id and product.
You can destruct string to atomic values, compute result of or operation and assemble it back into string. (Credit to #GordonLinoff for Y>N trick.) dbfiddle here.
Unfortunately, Oracle does not allow something like unpivot (val FOR substring(ProductList,i,1 in ... and also Oracle does not have equivalent to Postgres bool_or, which would both made solution simpler. At least this solution scales with ProductList length.
Anyway you should avoid violating 1st normal form. If you cannot, it IMHO does not matter how boolean is modelled.
with T1 as
(
select 10 as ID, 20 as ParentID, 'YYNNYNYNYNYYNNYNYNYN' as ProductList from dual
union
select 11 as ID, 20 as ParentID, 'NNNNNNNNNNYYYYYYYYYY' as ProductList from dual
union
select 22 as ID, 20 as ParentID, 'YYNNNNNNNNNNNNNNNNNN' as ProductList from dual
), series (i) as (
select level as i
from dual
connect by level <= 20
), applied_or as (
select t1.parentid
, max(substr(t1.productlist, series.i, 1)) as or_result
, series.i
from t1
cross join series
group by t1.parentid, series.i
)
select parentid
, listagg(or_result) within group (order by i)
from applied_or
group by parentid

How to union a hardcoded row after each grouped result

After every group / row i want to insert a hardcoded dummy row with a bunch of 'xxxx' to act a separator.
I would like to use oracle sql to do this query. i can execute it using a loop but i don't want to use plsql.
As the others suggest, it is best to do it on the front end.
However, if you have a burning need to be done as a query, here is how.
Here I did not use the rownum function as you have already done. I assume, your data is returned by a query, and you can replace my table with your query.
I made few more assumptions, as you have data with row numbers in it.
[I am not sure what do you mean by not PL/SQL]
Select Case When MOD(rownm, 2) = 0 then ' '
Else to_char((rownm + 1) / 2) End as rownm,
name, total, column1
From
(
select (rownm * 2 - 1) rownm,name, to_char(total) total ,column1 from t
union
SELECT (rownm * 2) rownm,'XXX' name, 'XXX' total, 'The row act .... ' column1 FROM t
) Q
Order by Q.rownm;
and here is the fiddle
Since you're already grouping the data, it might be easier to use GROUPING SETS instead of a UNION.
Grouping sets let you group by multiple sets of columns, including the same set twice to duplicate rows. Then the GROUP_ID function can be used to determine when the fake values should be used. This code will be a bit smaller than a UNION approach, and should be faster since it doesn't need to reference the table multiple times.
select
case when group_id() = 0 then name else '' end name,
case when group_id() = 0 then sum(some_value) else null end total,
case when group_id() = 1 then 'this rows...' else '' end column1
from
(
select 'jack' name, 22 some_value from dual union all
select 'jack' name, 1 some_value from dual union all
select 'john' name, 44 some_value from dual union all
select 'john' name, 1 some_value from dual union all
select 'harry' name, 1 some_value from dual union all
select 'harry' name, 1 some_value from dual
) raw_data
group by grouping sets (name, name)
order by raw_data.name, group_id();
You can use row generator technique (using CONNECT BY) and then use CASE..WHEN as follows:
SQL> SELECT CASE WHEN L.LVL = 1 THEN T.ROWNM END AS ROWNM,
2 CASE WHEN L.LVL = 1 THEN T.NAME
3 ELSE 'XXX' END AS NAME,
4 CASE WHEN L.LVL = 1 THEN TO_CHAR(T.TOTAL)
5 ELSE 'XXX' END AS TOTAL,
6 CASE WHEN L.LVL = 1 THEN T.COLUMN1
7 ELSE 'This row act as separator..' END AS COLUMN1
8 FROM T CROSS JOIN (
9 SELECT LEVEL AS LVL FROM DUAL CONNECT BY LEVEL <= 2
10 ) L ORDER BY T.ROWNM, L.LVL;
ROWNM NAME TOTAL COLUMN1
---------- ---------- ----- ---------------------------
1 Jack 23
XXX XXX This row act as separator..
2 John 45
XXX XXX This row act as separator..
3 harry 2
XXX XXX This row act as separator..
4 roy 45
XXX XXX This row act as separator..
5 Jacob 26
XXX XXX This row act as separator..
10 rows selected.
SQL>

SQL: return true/false if a related record is presented

I have two tables:
ASSIGNMENTS (ID)
ASSIGNMENT_REVIEWS (ID, ASSIGNMENT_ID)
As a result of selecting I'd like to retrieve a flag if a review is already presented for the assignment. How to do it in the best way?
You are looking for the exists statement:
select
id,
case when exists (
select 1 from assignment_reviews where assignment_reviews.assignment_id = assignments.id
) then 1 else 0 end as hasReview
from
assignments
You can use a left join with nvl2() function(returns the
value in the 2nd argument if the 1st argument is not null, otherwise
returns the 3rd argument practically )
with assignments(id) as
(
select 101 from dual union all
select 102 from dual
), assignments_reviews(id,assignment_id) as
(
select 855, 101 from dual
)
select a.id,
nvl2(r.assignment_id,1,0) as already_presented
from assignments a
left join assignments_reviews r
on r.assignment_id = a.id;
ID ALREADY_PRESENTED
101 1
102 0
Demo

Excluding a row that contains a specific value

I want to exclude people who have joined a specific group. For example, if some students signed up for an Orchestra club, and I want to retrieve a list of students who did NOT sign up for orchestra, how do I do so?
I am unable to simply do a Group By clause because some students may have joined multiple clubs, and would bypass the Where condition and still show up in the query,
as shown here.
I am thinking about using a CASE statement in the SELECT clause to flag the person as '1' if they have joined Orchestra, and '0' if they have not, but I'm struggling to write an aggregate CASE function, which would cause issues from the GROUP BY clause.
Any thoughts on how to flag people with a certain row value?
Apparently my table didn't get saved onto SQLFiddle so you can paste the code below on your own screen:
CREATE TABLE activity ( PersonId, Club) as
select 1, 'Soccer' from dual union
select 1, 'Orchestra' from dual union
select 2, 'Soccer' from dual union
select 2, 'Chess' from dual union
select 2, 'Bball' from dual union
select 3, 'Orchestra' from dual union
select 3, 'Chess' from dual union
select 3, 'Bball' from dual union
select 4, 'Soccer' from dual union
select 4, 'Bball' from dual union
select 4, 'Chess' from dual;
Use the HAVING clause instead of using WHERE, with case expression :
HAVING max(case when column = ‘string’ then 1 else 0 end) = 0
Add this after your group by .
How about selecting a list of user ids from the activity table and excluding it:
SELECT * FROM users WHERE id NOT IN
(SELECT PersonId FROM activity WHERE Club = 'Orchestra');
You could use a subquery to return a list of people to exclude.
-- Returns person 2 and 4.
SELECT
PersonId
FROM
activity
WHERE
PersonId NOT IN
(
-- People to exclude.
SELECT
PersonId
FROM
activity
WHERE
Club = 'Orchestra'
)
GROUP BY
PersonId
;
EDIT Removed superfluous distinct in subquery - thanks #mathguy.
select * from
(
select a.*, case when Club ='Orchestra' then 1 else 0 end flag
from activity a
) where flag =1; --> get some students signed up for an Orchestra club
select * from
(
select a.*, case when Club ='Orchestra' then 1 else 0 end flag
from activity a
) where flag =0; --> get students not signed up for an Orchestra club

SQL query : how to check existence of multiple rows with one query

I have this table MyTable:
PROG VALUE
-------------
1 aaaaa
1 bbbbb
2 ccccc
4 ddddd
4 eeeee
now I'm checking the existence of a tuple with a certain id with a query like
SELECT COUNT(1) AS IT_EXISTS
FROM MyTable
WHERE ROWNUM = 1 AND PROG = {aProg}
For example I obtain with aProg = 1 :
IT_EXISTS
---------
1
I get with aProg = 3 :
IT_EXISTS
---------
0
The problem is that I must do multiple queries, one for every value of PROG to check.
What I want is something that with a query like
SELECT PROG, ??? AS IT_EXISTS
FROM MyTable
WHERE PROG IN {1, 2,3, 4, 5} AND {some other condition}
I can get something like
PROG IT_EXISTS
------------------
1 1
2 1
3 0
4 1
5 0
The database is Oracle...
Hope I'm clear
regards
Paolo
Take a step back and ask yourself this: Do you really need to return the rows that don't exist to solve your problem? I suspect the answer is no. Your application logic can determine that records were not returned which will allow you to simplify your query.
SELECT PROG
FROM MyTable
WHERE PROG IN (1, 2, 3, 4, 5)
If you get a row back for a given PROG value, it exists. If not, it doesn't exist.
Update:
In your comment in the question above, you stated:
the prog values are from others tables. The table of the question has only a subset of the all prog values
This suggests to me that a simple left outer join could do the trick. Assuming your other table with the PROG values you're interested in is called MyOtherTable, something like this should work:
SELECT a.PROG,
CASE WHEN b.PROG IS NOT NULL THEN 1 ELSE 0 END AS IT_EXISTS
FROM MyOtherTable AS a
LEFT OUTER JOIN MyTable AS b ON b.PROG = a.PROG
A WHERE clause could be tacked on to the end if you need to do some further filtering.
I would recommend something like this. If at most one row can match a prog in your table:
select p.prog,
(case when t.prog is null then 0 else 1 end) as it_exists
from (select 1 as prog from dual union all
select 2 as prog from dual union all
select 3 as prog from dual union all
select 4 as prog from dual union all
select 5 as prog from dual
) p left join
mytable t
on p.prog = t.prog and <some conditions>;
If more than one row could match, you'll want to use aggregation to avoid duplicates:
select p.prog,
max(case when t.prog is null then 0 else 1 end) as it_exists
from (select 1 as prog from dual union all
select 2 as prog from dual union all
select 3 as prog from dual union all
select 4 as prog from dual union all
select 5 as prog from dual
) p left join
mytable t
on p.prog = t.prog and <some conditions>
group by p.prog
order by p.prog;
One solution is to use (arguably abuse) a hierarchical query to create an arbitrarily long list of numbers (in my example, I've set the largest number to max(PROG), but you could hardcode this if you knew the top range you were looking for). Then select from that list and use EXISTS to check if it exists in MYTABLE.
select
PROG
, case when exists (select 1 from MYTABLE where PROG = A.PROG) then 1 else 0 end IT_EXISTS
from (
select level PROG
from dual
connect by level <= (select max(PROG) from MYTABLE) --Or hardcode, if you have a max range in mind
) A
;
It's still not very clear where you get the prog values to check. But if you can read them from a table, and assuming that the table doesn't contain duplicate prog values, this is the query I would use:
select a.prog, case when b.prog is null then 0 else 1 end as it_exists
from prog_values_to_check a
left join prog_values_to_check b
on a.prog = b.prog
and exists (select null
from MyTable t
where t.prog = b.prog)
If you do need to hard code the values, you can do it rather simply by taking advantage of the SYS.DBMS_DEBUG_VC2COLL function, which allows you to convert a comma-delimited list of values into rows.
with prog_values_to_check(prog) as (
select to_number(column_value) as prog
from table(SYS.DBMS_DEBUG_VC2COLL(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)) -- type your values here
)
select a.prog, case when b.prog is null then 0 else 1 end as it_exists
from prog_values_to_check a
left join prog_values_to_check b
on a.prog = b.prog
and exists (select null
from MyTable t
where t.prog = b.prog)
Note: The above queries take into account that the MyTable table may have multiple rows with the same prog value, but that you only want one row in the result. I make this assumption based the WHERE ROWNUM = 1 condition in your question.