I have a table that looks like this:
User HasPermA HasPermB HasPermC
---------------------------------------
Joe True False True
Sally True True True
And I need to transform it into the following format using SQL:
User PermissionType
-----------------------
Joe A
Joe C
Sally A
Sally B
Sally C
How would I go about doing that?
You can use UNION ALL:
select *
from
(
select user
, case when HasPermA is true then 'A' else null end as PermissionType
from table
union all
select user
, case when HasPermB is true then 'B' else null end as PermissionType
from table
union all
select user
, case when HasPermC is true then 'C' else null end as PermissionType
from table
) sub
where sub.PermissionType is not null
One method is union all, which I would phrase as:
select user, 'A' as PermissionType from t where HasPermA union all
select user, 'B' from t where HasPermB union all
select user, 'C' from t where HasPermC ;
This assumes that your dialect of SQL understands boolean variables. You might need something like HasPermA = 'true'.
Several dialects of SQL support lateral joins -- using either the lateral keyword or the apply keyword (or both). If so, I like:
select t.user, v.PermissionType
from t outer apply
(value ('A', HasPermA), ('B', HasPermA), ('C', HasPermA)) v(PermissionType, hasPerm)
where hasPerm;
Using a lateral join (or unpivot query) has the advantage of only scanning the table once.
Related
I have an SQL function that returns me a string of comma separated country codes.
I have configured some specific codes in another table and I may remove or add more later.
I want to check if the comma separated string is only the combination of those specific country codes or not. That said, if that string is having even a single country code other than the specified ones, it should return true.
Suppose I configured two rows in the static data table GB and CH. Then I need below results:
String from function
result
GB
false
CH
false
GB,CH
false
CH,GB
false
GB,FR
true
FR,ES
true
ES,CH
true
CH,GB,ES
true
I am on Oracle 19c and can use only the functions available for this version. Plus I want it to be optimised. Like I can check the number of values in string and then count for each specific code. If not matching then obviously some other codes are present. But I don't want to use loops.
Can someone please suggest me a better option.
Assuming that all country codes in the static table, as well as all tokens in the comma-separated strings, are always exactly two-letter strings, you could do something like this:
with
static_data(country_code) as (
select 'GB' from dual union all
select 'CH' from dual
)
, sample_inputs(string_from_function) as (
select 'GB' from dual union all
select 'CH' from dual union all
select 'GB,CH' from dual union all
select 'CH,GB' from dual union all
select 'GB,FR' from dual union all
select 'FR,ES' from dual union all
select 'ES,CH' from dual union all
select 'CH,GB,ES' from dual
)
select string_from_function,
case when regexp_replace(string_from_function,
',| |' || (select listagg(country_code, '|')
within group (order by null)
from static_data))
is null then 'false' else 'true' end as result
from sample_inputs
;
Output:
STRING_FROM_FUNCTION RESULT
---------------------- --------
GB false
CH false
GB,CH false
CH,GB false
GB,FR true
FR,ES true
ES,CH true
CH,GB,ES true
The regular expression replaces comma, space, and every two-letter country code from the static data table with null. If the result of the whole thing is null, then all coded in the csv are in the static table; that's what you need to test for.
The assumptions guarantee that a token like GBCH (for a country like "Great Barrier Country Heat") would not be mistakenly considered OK because GB and CH are OK separately.
You can convert a csv column to a table and use EXISTS. For example
with tbl(id,str) as
(
SELECT 1,'GB,CH' FROM DUAL UNION ALL
SELECT 2,'GB,CH,FR' FROM DUAL UNION ALL
SELECT 3,'GB' FROM DUAL
),
countries (code) as
(SELECT 'GB' FROM DUAL UNION ALL
SELECT 'CH' FROM DUAL
)
select t.* ,
case when exists (
select 1
from xmltable(('"' || REPLACE(str, ',', '","') || '"')) s
where trim(s.column_value) not in (select code from countries)
)
then 'true' else 'false' end flag
from tbl t
One option is to match the country codes one by one, and then determine whether there exists an extra non-matched country from the provided literal as parameter.
The following one with FULL JOIN would help by considering the logic above
WITH
FUNCTION with_function(i_countries VARCHAR2) RETURN VARCHAR2 IS
o_val VARCHAR2(10);
BEGIN
SELECT CASE WHEN SUM(NVL2(t.country_code,0,1))=0 THEN 'false'
ELSE 'true'
END
INTO o_val
FROM (SELECT DISTINCT REGEXP_SUBSTR(i_countries,'[^ ,]+',1,level) AS country
FROM dual
CONNECT BY level <= REGEXP_COUNT(i_countries,',')+1) tt
FULL JOIN t
ON tt.country = t.country_code;
RETURN o_val;
END;
SELECT with_function(<comma-seperated-parameter-list>) AS result
FROM dual
Demo
Here is one solution
with cte as
(select distinct
s,regexp_substr(s, '[^,]+',1, level) code from strings
connect by regexp_substr(s, '[^,]+', 1, level) is not null
)
select
s string,min(case when exists
(select * from countries
where cod = code) then 'yes'
else 'no'end) all_found
from cte
group by s
order by s;
STRING | ALL_FOUND
:----- | :--------
CH | yes
CH,GB | yes
ES | no
ES,CH | no
FR | no
GB | yes
GB,CH | yes
GB,ES | no
db<>fiddle here
If you have a small number of values in the static table then the simplest method may not be to split the values from the function but to generate all combinations of values from the static table using:
SELECT SUBSTR(SYS_CONNECT_BY_PATH(value, ','), 2) AS combination
FROM static_table
CONNECT BY NOCYCLE PRIOR value != value;
Which, for the sample data:
CREATE TABLE static_table(value) AS
SELECT 'GB' FROM DUAL UNION ALL
SELECT 'CH' FROM DUAL;
Outputs:
COMBINATION
GB
GB,CH
CH
CH,GB
Then you can use a simple CASE expression to your string output to the combinations:
SELECT function_value,
CASE
WHEN function_value IN (SELECT SUBSTR(SYS_CONNECT_BY_PATH(value, ','), 2)
FROM static_table
CONNECT BY NOCYCLE PRIOR value != value)
THEN 'false'
ELSE 'true'
END AS not_matched
FROM string_from_function;
Which, for the sample data:
CREATE TABLE string_from_function(function_value) AS
SELECT 'GB' FROM DUAL UNION ALL
SELECT 'CH' FROM DUAL UNION ALL
SELECT 'GB,CH' FROM DUAL UNION ALL
SELECT 'CH,GB' FROM DUAL UNION ALL
SELECT 'GB,FR' FROM DUAL UNION ALL
SELECT 'FR,ES' FROM DUAL UNION ALL
SELECT 'ES,CH' FROM DUAL UNION ALL
SELECT 'CH,GB,ES' FROM DUAL;
Outputs:
FUNCTION_VALUE
NOT_MATCHED
GB
false
CH
false
GB,CH
false
CH,GB
false
GB,FR
true
FR,ES
true
ES,CH
true
CH,GB,ES
true
db<>fiddle here
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
I have a master table that contains a list of strings to search for. it returns TRUE/FALSE if any string in the cell contains text from the master lookup table. Currently I use excel's
=SUMPRODUCT(--ISNUMBER(SEARCH(masterTable,[#searchString])))>0
is there a way to do something like this in SQL? LEFT JOIN or OUTER APPLY would be simple solutions if the strings were equal; but they need be contains..
SELECT *
FROM t
WHERE col1 contains(lookupString,lookupColumn)
--that 2nd table could be maintained and referenced from multiple queries
hop
bell
PRS
2017
My desired results would be a column that shows TRUE/FALSE if the row contains any string from the lookup table
SEARCH_STRING Contained_in_lookup_column
hopping TRUE
root FALSE
Job2017 TRUE
PRS_tool TRUE
hand FALSE
Sorry i dont have access to the DB now to confirm the syntax, but should be something like this:
SELECT t.name,
case when (select count(1) from data_table where data_col like '%' || t.name || '%' > 0) then 'TRUE' else 'FALSE' end
FROM t;
or
SELECT t.name,
case when exists(select null from data_table where data_col like '%' || t.name || '%') then 'TRUE' else 'FALSE' end
FROM t;
Sérgio
You can use a combination of % wildcards with LIKE and EXISTS.
Example (using Oracle syntax) - we have a v_data table containing the data and a v_queries table containing the query terms:
with v_data (pk, value) as (
select 1, 'The quick brown fox jumps over the lazy dog' from dual union all
select 2, 'Yabba dabba doo' from dual union all
select 3, 'forty-two' from dual
),
v_queries (text) as (
select 'quick' from dual union all
select 'forty' from dual
)
select * from v_data d
where exists (
select null
from v_queries q
where d.value like '%' || q.text || '%');
I've a legacy database containing a table with multiple columns of type boolean. E.g.:
Table_1
id name has_lights has_engine has_brakes has_tyres can_move
1 bullock_cart false false false true true
2 car true true true true true
3 tank true true true false true
I'd like to write an SQL query for Table1 to fetch the id and name and the attributes (represented by the name of the column) that are true.
Expected output:
id name attributes
-- ---- ----------
1 bullock_cart has_tyres
1 bullock_cart can_move
2 car has_lights
2 car has_engine
2 car has_brakes
2 car has_tyres
2 car can_move
3 tank has_lights
3 tank has_engine
3 tank has_brakes
3 tank can_move
I wrote:
SELECT id, name,
CASE
WHEN has_lights THEN 'has_lights'
WHEN has_engine THEN 'has_engine'
WHEN has_brakes THEN 'has_brakes'
WHEN has_tyres THEN 'has_tyres'
WHEN can_move THEN 'can_move'
END
FROM TABLE1;
But this gets me only the 1st matching attribute for each row in Table1 (by virtue of CASE-WHEN).
What is the correct way to retrieve the data in the format I want? Any inputs/help will be greatly appreciated?
Notes:
The table structure isn't ideal but this is a legacy system and we cannot modify the schema.
Nested queries are ok as long as they aren't too slow - say for the sample above (I understand the number of matching rows/column factor in the slow-ness).
The simplest method is union all:
select id, name, 'has_lights' as attribute from t where has_lights union all
select id, name, 'has_engine' from t where has_engine union all
select id, name, 'has_brakes' from t where has_brakes union all
select id, name, 'has_tyres' from t where has_tyres union all
select id, name, 'can_move' from t where can_move;
If you have a very large table, then a lateral join is probably more efficient:
select t.id, t.name, v.attribute
from t, lateral
(select attribute
from (values (has_lights, 'has_lights'),
(has_engine, 'has_engine'),
(has_brakes, 'has_brakes'),
(has_tyres, 'has_tyres'),
(can_move, 'can_move')
) v(flag, attribute)
where flag
) v;
You can do it using UNION ALL :
SELECT name,'has_lights' as attributes FROM YourTable where has_lights = 'TRUE'
UNION ALL
SELECT name,'has_engine' as attributes FROM YourTable where has_engine= 'TRUE'
UNION ALL
SELECT name,'has_brakes' as attributes FROM YourTable where has_brakes = 'TRUE'
UNION ALL
SELECT name,'has_tyres' as attributes FROM YourTable where has_tyres = 'TRUE'
UNION ALL
SELECT name,'can_move' as attributes FROM YourTable where can_move = 'TRUE'
This is much like the brilliant query #Gordon posted:
SELECT t.id, t.name, v.attribute
FROM table1 t
JOIN LATERAL (
VALUES (has_lights, 'has_lights')
, (has_engine, 'has_engine')
, (has_brakes, 'has_brakes')
, (has_tyres , 'has_tyres')
, (can_move , 'can_move')
) v(flag, attribute) ON v.flag;
Just a bit shorter since the VALUES expression can stand on its own.
Suppose I have a table with columns user_id, name and the table contains data like this:
user_id name
------- -----
sou souhardya
cha chanchal
swa swapan
ari arindam
ran ranadeep
If I want to know these users (sou, cha, ana, agn, swa) exists in this table or not then I want output like this:
user_id it exists or not
------- -----------------
sou y
cha y
ana n
agn n
swa y
As ana and aga do not exist in the table it must show "n" (like the above output).
Assuming your existing checklist is not on the database, you will have to assemble a query containing those. There are many ways of doing it. Using CTEs, it would look like this:
with cte as
(
select 'sou' user_id
union all
select 'cha'
union all
select 'ana'
union all
select 'agn'
union all
select 'swa'
)
select
cte.user_id,
case when yt.user_id is null then 'n' else 'y' end
from cte
left join YourTable yt on cte.user_id = yt.user_id
This also assumes user_id is unique.
Here is the SQLFiddle with the proof of concept: http://sqlfiddle.com/#!3/e023a0/4
Assuming you're just testing this manually:
DECLARE #Users TABLE
(
[user_id] VARCHAR(50)
)
INSERT INTO #Users
SELECT 'sou'
UNION SELECT 'cha'
UNION SELECT 'ana'
UNION SELECT 'agn'
UNION SELECT 'swa'
SELECT a.[user_id]
, [name]
, CASE
WHEN b.[user_id] IS NULL THEN 'N'
ELSE 'Y'
END AS [exists_or_not]
FROM [your_table] a
LEFT JOIN #Users b
ON a.[user_id] = b.[user_id]
You didn't provide quite enough information to provide a working example, but this should get you close:
select tbl1.user_id, case tbl2.user_id is null then 'n' else 'y' end
from tbl1 left outer join tbl2 on tbl1.user_id = tbl2.user_id
;with usersToCheck as
(
select 'sou' as userid
union select 'cha'
union select 'ana'
union select 'agn'
union select 'swa'
)
select utc.userid,
(case when exists ( select * from usersTable as ut where ut.user_id = utc.userid) then 'y' else 'n' end)
from usersToCheck as utc