There is a table which has following data:
student subject code
student1 maths 312
student1 physics 785
student2 english 900
student3 geography 317
I am trying to restrict access to each student in the table to view data specific to their chosen subject. But there is one restriction to show maths data to student2. Thereby both student1 and student2 both would be able to see maths data, and this mapping has to be done without altering the master data. So only while displaying the table, student2 should be mapped to both english and maths.
Thanks for the help here!
One option is to - as you said - temporarily use UNION set operator. Something like this:
SQL> WITH
2 test (student, subject, code)
3 AS
4 (SELECT 'student1', 'maths', 312 FROM DUAL
5 UNION ALL
6 SELECT 'student1', 'physics', 785 FROM DUAL
7 UNION ALL
8 SELECT 'student2', 'english', 900 FROM DUAL
9 UNION ALL
10 SELECT 'student3', 'geography', 317 FROM DUAL)
11 SELECT *
12 FROM test
13 WHERE student = '&&par_student'
14 -- add this to your query
15 UNION
16 SELECT 'student2', 'maths', NULL
17 FROM DUAL
18 WHERE '&&par_student' = 'student2';
Enter value for par_student: student1 --> student1 is OK, it has two subjects
STUDENT SUBJECT CODE
-------- --------- ----------
student1 maths 312
student1 physics 785
SQL> undefine par_student
SQL> /
Enter value for par_student: student2 --> for student2, UNION is used
STUDENT SUBJECT CODE
-------- --------- ----------
student2 english 900
student2 maths
SQL> undefine par_student
SQL> /
Enter value for par_student: student3 --> nothing new for student3
STUDENT SUBJECT CODE
-------- --------- ----------
student3 geography 317
SQL>
Depending on tool you use, parameter might look as this (in e.g. TOAD):
WHERE student = :par_student
or any other way parameters are used in that tool of yours.
Something like:
SELECT student, subject, code
FROM (
SELECT t.*,
COUNT(
CASE
WHEN student = :your_student
OR (:your_student, subject) IN (('student2', 'maths'))
THEN 1
END
) OVER (PARTITION BY subject) AS has_access
FROM table_name t
)
WHERE has_access > 0
Then, for the sample data:
CREATE TABLE table_name (student, subject, code) AS
SELECT 'student1', 'maths', 312 FROM DUAL UNION ALL
SELECT 'student1', 'physics', 785 FROM DUAL UNION ALL
SELECT 'student2', 'english', 900 FROM DUAL UNION ALL
SELECT 'student3', 'geography', 317 FROM DUAL;
If :your_student is student1 then the output is:
STUDENT
SUBJECT
CODE
student1
maths
312
student1
physics
785
and if :your_student is student2 then the output is:
STUDENT
SUBJECT
CODE
student2
english
900
student1
maths
312
db<>fiddle here
Related
I have the following table structure in Oracle database.
I want to find how many marks has ABC scored in Maths? The subject name is dynamic. It can change depending upon the input of the user.
Student Table:
Student_Id Student_Name Maths English History Physics
1 ABC 93 89 90 70
2 XYZ 88 98 88 80
3 DEF 79 78 87 90
Is there a way to match the column name with a particular value fetched dynamically through User's Input?
You should know what the columns are in the table. Hence you can write the query using a case expression:
select (case when :input = 'Maths' then Maths
when :input = 'English' then English
when :input = 'History' then History
when :input = 'Physics' then Physics
end)
from students
were Student_Name = 'ABC';
In fact, this question helps explain why it is better to store such data in rows rather than columns. If your table were structured as:
Students:
Student_Id Student_Name
1 ABC
. . .
StudentMarks
Student_Id Subject Marks
1 Maths 93
1 English 89
1 History 90
1 Physics 70
. . .
(Note that you might also want a separate reference table for subjects as well.)
The query would be trivial with this data structure:
select sm.marks
from studentmarks sm join
students s
on sm.student_id = s.student_id
where subject = :input;
Such a structure would make it easy to add new subjects as well.
Dynamic SQL is what you need. Here's an example:
Sample data:
SQL> select * from student;
ID NAM MATHS ENGLISH
---------- --- ---------- ----------
1 ABC 93 89
2 XYZ 88 98
3 DEF 79 78
Function:
SQL> CREATE OR REPLACE FUNCTION f_sum (par_subject IN VARCHAR2)
2 RETURN NUMBER
3 IS
4 l_str VARCHAR2 (200);
5 retval NUMBER;
6 BEGIN
7 l_str :=
8 'select sum('
9 || DBMS_ASSERT.simple_sql_name (par_subject)
10 || ') from student';
11
12 EXECUTE IMMEDIATE l_str INTO retval;
13
14 RETURN retval;
15 END;
16 /
Function created.
Testing:
SQL> SELECT f_sum ('maths') FROM DUAL;
F_SUM('MATHS')
--------------
260
SQL>
You do not need dynamic SQL or even PL/SQL. You can do it in an SQL query using UNPIVOT:
Oracle Setup:
CREATE TABLE test_data ( Student_Id, Student_Name, Maths, English, History, Physics ) AS
SELECT 1, 'ABC', 93, 89, 90, 70 FROM DUAL UNION ALL
SELECT 2, 'XYZ', 88, 98, 88, 80 FROM DUAL UNION ALL
SELECT 3, 'DEF', 79, 78, 87, 90 FROM DUAL;
Query:
SELECT score
FROM test_data
UNPIVOT ( score FOR subject IN ( Maths, English, History, Physics ) )
WHERE subject = 'MATHS'
AND student_name = 'ABC'
Output:
| SCORE |
| ----: |
| 93 |
db<>fiddle here
If you want to use a PL/SQL Function then you still don't need dynamic SQL and could just wrap the above query or use a CASE statement, as below:
PL/SQL Function:
CREATE FUNCTION getScore(
i_student_name IN TEST_DATA.STUDENT_NAME%TYPE,
i_subject IN VARCHAR2
) RETURN NUMBER
IS
p_score NUMBER(3,0);
BEGIN
SELECT CASE i_subject
WHEN 'MATHS' THEN Maths
WHEN 'ENGLISH' THEN English
WHEN 'HISTORY' THEN History
WHEN 'PHYSICS' THEN Physics
END
INTO p_score
FROM test_data
WHERE student_name = i_student_name;
RETURN p_score;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RETURN NULL;
END;
/
Then:
SELECT getScore( 'ABC', 'MATHS' ) FROM DUAL;
Outputs:
| GETSCORE('ABC','MATHS') |
| ----------------------: |
| 93 |
db<>fiddle here
Also, there is one more approach using xmlquery as following:
SELECT
to_number(xmlquery('/ROWSET/ROW/C/text()'
passing xmltype(dbms_xmlgen.getxml(
'select '|| <subject_name> || ' as c '
|| 'from test_data WHERE student_name = '''
|| <student_name> || ''''))
returning content)) as marks
FROM dual;
db<>fiddle demo
Cheers!!
i need to create an oracle select statement that returns acct,name,city,splitcost from table1 and APIcost from table2. table1 splits the 90 into 3 diff. amounts because they are distributed elsewhere. table2 is the API download that only has 1 record of the total 90. if i use inner join the 90 repeats on each row linking by acct. i need the results to look like the second view only show APIcost total 90. once per acct.
hope this makes sense. if i was using sql I'm prob. do a temp table but it has to be done in Oracle which i'm not used too.
No need for a temp table, just build a rank and do a case statement on it to populate the first row with the api_cost. I don't know which row you want, so play with the "order by" clause to get that to do the row you want.
/* Building out your data to a "temp table" */
with table1 as
(select 1111 as acct, 'john' as name, 'hampton' as city, 30 as split_cost, 90 as
api_cost from dual union all
select 1111 as acct, 'john' as name, 'hampton' as city, 40 as split_cost, 90 as
api_cost from dual union all
select 1111 as acct, 'john' as name, 'hampton' as city, 20 as split_cost, 90 as
api_cost from dual union all
select 1111 as acct, 'john' as name, 'hampton' as city, 20 as split_cost, 90 as
api_cost from dual)
/* You need nothing above here, just below */
select acct, name, city, split_cost,
case when rank() over (partition by acct, name, city order by split_cost, rownum) =
1 then api_cost
else null
end as api_cost
from table1; --substitute your table name here
OUTPUT:
ACCT NAME CITY SPLIT_COST API_COST
1111 john hampton 20 90
1111 john hampton 20
1111 john hampton 30
1111 john hampton 40
I have a table abc with following rows:
emp_id_role Group_name Role_name Location_id
12 Insurance Manager Noida
12 Insurance Senior Manager Noida
13 Global Client Services Sw UP
14 Operations Management All Jobs kERALA
and another master table with all the details employee_xyz:
PERSON_ID NAME DOB START_DATE END_DATE SSN
12 DEAN 01-JAN-1990 01-JAN-2017 20-JAN-2017 847474
12 DEAN 01-JAN-1990 21-JAN-2017 03-mar-2018 847474
12 DEAN 01-JAN-1990 04-mar2018 31-DEC-4712 847474
13 SAM 20-JAN-1990 17-JAN-2016 20-JAN-2017 847474
13 SAM 20-JAN-1990 21-JAN-2017 31-DEC-4712 847474
14 JAY 29-dec-1990 21-JAN-2016 31-DEC-4712 847474
I want to fetch the full names from the table employee_xyz for the records in table abc.
When I'm joining these two using the below queries I'm getting more number of rows for an employee than in table abc,
Eg: for employee_id 12 I should get 2 rows as in table abc but I'm getting 9 rows somehow...
Query used is simple :
select * from table_abc abc,
employee_xyz xyz
where xyz.person_id=abc.emp_id_role
and trunc(sysdate) between abc.Start_date and xyz.end_date
and person_id=12;
I suppose you want to select all columns from table_abc with names from employee_xyz.
If your try with select abc.*, xyz.name, you already able to select as two rows since sysdate for person_id= 12 stays in the interval only for third row(start_date:04-mar2018/ end_date:31-DEC-4712) but if you want non-repeating even with more date values stay, you can use distinct : select distinct abc.*, xyz.name as below( where there're two rows in the interval second row's end date converted from '2018-03-03' to '2019-03-03' ) :
with table_abc(emp_id_role, Group_name, Role_name, Location_id) as
(
select 12,'Insurance','Manager','Noida' from dual union all
select 12,'Insurance','Senior Manager','Noida' from dual union all
select 13,'Global Client Services','Sw', 'UP' from dual union all
select 14,'Operations Management','All Jobs','kERALA' from dual
),
employee_xyz(person_id,name, dob, start_date, end_date, ssn) as
(
select 12,'DEAN',date'1990-01-01',date'2017-01-01',date'2017-01-20',847474 from dual union all
select 12,'DEAN',date'1990-01-01',date'2017-01-21',date'2019-03-03',847474 from dual union all
select 12,'DEAN',date'1990-01-01',date'2018-03-04',date'4712-12-31',847474 from dual
)
select distinct abc.*, xyz.name
from table_abc abc join employee_xyz xyz
on xyz.person_id = abc.emp_id_role
where trunc(sysdate) between xyz.Start_date and xyz.end_date
and person_id = 12;
EMP_ID_ROLE GROUP_NAME ROLE_NAME LOCATION_ID NAME
----------- ---------- --------------- ----------- -----
12 Insurance Manager Noida DEAN
12 Insurance Senior Manager Noida DEAN
I have two tables, students and training. Student and Training tables are as below.
Student
ID name age sex salary
1213 lavanya 18 Female 8000
1208 reshma 19 Female 14000
1207 bhavya 20 Female 15000
1212 Arshad 28 Male 20000
1209 kranthi 22 Male 22000
1210 Satish 24 Male 25000
1211 Krishna 25 Male 26000
1203 khaleel 34 Male 30000
1204 prasant 30 Male 31000
1206 laxmi 25 Female 35000
1205 kiran 20 Male 40000
1201 gopal 45 Male 50000
1202 manisha 40 Female 51000
Training
1 1201 csharp
2 1205 c
3 1201 c
4 1202 java
5 1205 java
6 1203 shell
7 1204 hadoop
8 1201 hadoop
Now I want count of males and females who have joined any course.
I tried below query-
hive> select s.sex, count(*) from student join training t on s.id=t.sid group by s.sex;
But this query is giving output as Female 2 Male 4
Though expected outcome should be Female 1 Male 2
Please note this is a sample and short form of data being used.
This looks like your query, but - returns the result you mentioned (1 female, 2 male). If possible, post your own SQL*Plus copy/paste session (take my example) so that we'd see what you exactly did).
SQL> with student (id, name, sex) as
2 (select 1, 'alex', 'm' from dual union
3 select 2, 'rita', 'f' from dual union
4 select 3, 'max', 'm' from dual union
5 select 4, 'steve', 'm' from dual
6 ),
7 training (id, sid, course) as
8 (select 1, 2, 'java' from dual union
9 select 2, 3, 'c' from dual union
10 select 3, 1, 'java' from dual
11 )
12 select s.sex, count(*)
13 from student s join training t on t.sid = s.id
14 group by s.sex;
S COUNT(*)
- ----------
m 2
f 1
I try in MySQL and in Oracle, and this query is OK.
SELECT S.sex, count(*)
FROM student s
INNER JOIN training T on S.id = T.sid
GROUP BY S.sex;
RESULT, female = 1, male = 2
If the only thing you want is a simple count by gender, why not use
select sex, count(*)
from student
group by sex
order by sex
Use exists:
select s.sex, count(*)
from students s
where exists (select 1 from training t where t.sid = s.id);
The problem with join is that it counts each student based on the number of trainings they are in.
Here i had written a code taking your data:-
SELECT
final.ct_sex as sex,count(*) as num
FROM
(SELECT tb.sex as ct_sex FROM newschema.mytable AS tb JOIN (SELECT tr.ID,GROUP_CONCAT(tr.skill) as skills FROM newschema.train AS tr GROUP BY tr.ID) AS tp ON tb.ID = tp.ID) as final
group by
final.ct_sex
Not sure why join fails here, Below subquery is giving correct output though.
select sex, count(*) from salary where salary.id in (select sid from training) group by salary.sex;
I have a table like this;
Student_Name1 mark1 Student_Name2 mark2
-------------- ------ --------------- --------
Kevin 77 Peter 78
Andrew 91 David 17
Scott 46 Bradley 28
How am i able to order mark1 and mark2 in the above table into descending order by including all the names and the points all together, like below?
Student_Name mark
-------------- ------
Andrew 91
Peter 78
Kevin 77
Scott 46
Bradley 28
David 17
I am using MSSQL Server 2008 R2
Use a UNION ALL:
Select Student_Name1 As Student_Name,
Mark1 As Mark
From YourTable
Union All
Select Student_Name2 As Student_Name,
Mark2 As Mark
From YourTable
Order By Mark Desc;
That's a strange table design but you can use UNION for this purpose like
select * from (
select Student_Name1 as Student_Name, mark1 as mark from student
union all
select Student_Name2 , mark2 from student ) xxx
order by mark desc;