Dynamic Query for Retriving the translated column value in SQL - sql

I have a table_lookup. This is the master table which has all the lookup codes like
LOOKUP_TYPE LOOKUP_CODE MEANING ENABLED_FLAG
EMP_CAT 3 Hourly with fixed hours per week Y
EMP_CAT 4 Hourly Y
EMP_CAT CAS Casual Y
EMP_CAT FR Full-time regular Y
EMP_CAT FR_01 Full-time Y
ABSENCE_CATEGORY DLHM Leave Y
ABSENCE_CATEGORY DLHNM Leave on the death of a husband Y
ABSENCE_CATEGORY DLR Leave on the death of a relative Y
ABSENCE_CATEGORY GB_ADO Adoption Y
ABSENCE_CATEGORY GB_PAT_ADO Paternity adoption Y
NATIONALITY PY Paraguayan Y
NATIONALITY QA Qatari Y
NATIONALITY RO Romanian Y
NATIONALITY RS Serbian
This table is referenced in different tables like
table_assignment
emp no. name Employee category active/inactive
1 divya 3 A
2 abc FR A
3 XYZ 4 I
4 aMY 100 A
Table table_nationality
Emp no. nationality
1 QA
2 RS
4 RO
That is the lookup_code f is translated in the table_Assignment employee_category column and nationality column in table_nationality.
I have a query like:
select emp_no.,
name,
employee_category
from table_assignment lookup_assignmen,
table_lookup lookup_stg
where lookup_stg.lookup_type = 'EMP_CAT'
AND LOOKUP_STG.LOOKUP_CODE = lookup_assignmen.employee_category;
SIMILARLY for table_nationality something like:
select emp_no.,
nationality
from TABLE_NATIONALITY lookup_NATIONALITY,
table_lookup lookup_stg
where lookup_stg.lookup_type = 'NATIONALITY'
AND LOOKUP_STG.LOOKUP_CODE = lookup_NATIONALITY.employee_category;
Now I want a dynamic query which detects if in say for example table_assignment there is any employee category which is not there in the table_lookup.
For example : In the table_assignment there is a value 100 given in the employee category column this is not there in the table_lookup.
Such values should be retrieved in a query but the query should be dynamic such that it should retrieve all the invalid lookup values in table_Assignment, table_nationality or any other
tables. I can input the lookup_type and table name in the query and the invalid values should be retrieved.
How do I change my static query to dynamic?

you can union all the invalid codes and avoid dynamic sql.
select *
from (
select emp_no as entity_id, 'emp_cat' as lookup_type, eployee_category as invalid_lookup_code
from table_assignment e
left join table_lookup r
on e.employee_category = r.lookup_code
and r.lookup_type = 'emp_cat'
where r.lookup_type is null
union all
select emp_no as entity_id, 'nationality' as lookup_type, nationality as invalid_lookup_code
from table_nationality e
left join table_lookup r
on e.employee_category = r.lookup_code
and r.lookup_type = 'nationality'
where r.lookup_type is null
) as t
where t.lookup_type = 'nationality'

You can run dynamic SQL inside SQL with a package that combines Oracle Data Cartridge with the ANY types. The code below uses my open source version of this idea.
Even using pre-built PL/SQL, this will still be a difficult task since building queries in queries is confusing. And it's not clear how the tables are mapped together, it looks like there are no foreign keys or standard names. I assume there's a mapping table somewhere, or one can be created. For the demo, I created this:
create table lookup_map(
table_name varchar2(30),
column_name varchar2(30),
lookup_type varchar2(100)
);
insert into lookup_map
select 'TABLE_ASSIGNMENT', 'EMPLOYEE_CATEGORY', 'EMP_CAT' from dual union all
select 'TABLE_NATIONALITY', 'NATIONALITY', 'NATIONALITY' from dual;
commit;
This code will build and run dynamic queries for each table, and will UNION ALL the results.
select * from table(method4.dynamic_query(
q'[
select replace(replace(replace(q'!
select '#TABLE_NAME#' table_name, emp_no, #COLUMN_NAME#
from #TABLE_NAME#
left join table_lookup
on #TABLE_NAME#.#COLUMN_NAME# = table_lookup.lookup_code
and table_lookup.lookup_type = '#LOOKUP_TYPE#'
where table_lookup.lookup_code is null
!',
'#TABLE_NAME#', table_name),
'#COLUMN_NAME#', column_name),
'#LOOKUP_TYPE#', lookup_type) v_sql
from lookup_map
]'
));
TABLE_NAME EMP_NO EMPLOYEE_CATEGORY
---------- ------ -----------------
TABLE_ASSIGNMENT 4 100
To avoid quotation-mark-hell it's best to use the alternative quoting mechanism (q') and REPLACE. The quotation marks look unmatched in the Stackoverflow syntax highlighter but it should look better in an Oracle IDE.
This is overkill for this exact query. But it gives you plenty of room to grow. You can change the queries and make them even more dynamic without any additional PL/SQL code.

Related

SQL - Sum of two values IN a Group By

I am trying to add two values or get sum of two values and show it under one Exchange Name. Data below:
Table
--------------------------------------------------
EXCHANGE NAME CODE TURNOVER TRADEDATE
PARIS PA 12 14-NOV-2019
SWISS SW 14 14-NOV-2019
NULL SA 2 14-NOV-2019
NULL MI 2 14-NOV-2019
MILAN MI_1 3 14-NOV-2019
My Query
----------------------------------------------------
SELECT CE.EXCHANGE_NAME, sum(CE.TURNOVER)
FROM CE
WHERE CE.tradedate = '14-NOV-2019'
GROUP BY CE.EXCHANGE_NAME
Result
-----------------------------------------------------
EXCHANGE NAME SUM
PARIS 12
SWISS 14
MILAN 3
What I would like to achieve is that the total for SWISS to be 16 and MILAN to be 5 as MI belongs to MILAN also. There are NULL Values for EXCHANGE NAME but they belong to a certain exchange (Swiss in this case and Milan) i.e. code SA belongs to SWISS and MI belongs to MILAN.
How can I accommodate this in my query for situation like SWISS and MILAN where I know which code belongs to EXCHANGE_NAME?
Many thanks
You can use COALESCE():
SELECT COALESCE(CE.EXCHANGE_NAME, 'SWISS') as EXCHANGE_NAME, SUM(CE.TURNOVER)
FROM CE
WHERE CE.tradedate = '14-NOV-2019'
GROUP BY COALESCE(CE.EXCHANGE_NAME, 'SWISS');
As a note: I like the use of DATE for date constants:
WHERE CE.tradedate = DATE '2019-11-14'
This allows the use of ISO standard date formatting.
EDIT:
Use a CASE expression:
SELECT (CASE WHEN CE.CODE = 'SA' THEN 'SWISS'
WHEN CE.CODE = 'MI_1' THEN 'MILAN'
ELSE CE.EXCHANGE_NAME
END) as EXCHANGE_NAME,
SUM(CE.TURNOVER)
FROM CE
WHERE CE.tradedate = DATE '2019-11-14'
GROUP BY (CASE WHEN CE.CODE = 'SA' THEN 'SWISS'
WHEN CE.CODE = 'MI_1' THEN 'MILAN'
ELSE CE.EXCHANGE_NAME
END);
To me, it looks like you have to create a mapping table which will map codes to exchange names:
SQL> create table exmap
2 (exchange_name varchar2(20),
3 code varchar2(10));
Table created.
SQL> insert into exmap
2 select 'PARIS', 'PA' from dual union all
3 select 'SWISS', 'SW' from dual union all
4 select 'SWISS', 'SA' from dual union all
5 select 'MILAN', 'MI' from dual union all
6 select 'MILAN', 'MI_1' from dual;
5 rows created.
SQL>
Now, with date in the CE table (the one you posted), you'd join those two tables:
SQL> select e.exchange_name,
2 sum(c.turnover) sum_turnover
3 from ce c join exmap e on e.code = c.code
4 group by e.exchange_name;
EXCHANGE_NAME SUM_TURNOVER
-------------------- ------------
PARIS 12
MILAN 5
SWISS 16
SQL>
Why such an approach? Because sooner or later you'll add something like this to the CE table (so PARIS will now be 20):
SQL> insert into ce values ('PARIS', 'PR', 8);
1 row created.
Now, if you choose to maintain the mapping within the code, you'll have to fix it everywhere, in all your stored procedures, reports, forms ... whatever uses that table, and add yet another CASE, e.g.
case when code in ('PA', 'PR') then 'PARIS'
... ^^^^
this
That might drive you mad. But, if you simply add it to the mapping table:
SQL> insert into exmap values ('PARIS', 'PR');
1 row created.
the "old" join query will work without any further action:
SQL> select e.exchange_name,
2 sum(c.turnover) sum_turnover
3 from ce c join exmap e on e.code = c.code
4 group by e.exchange_name;
EXCHANGE_NAME SUM_TURNOVER
-------------------- ------------
PARIS 20
MILAN 5
SWISS 16
SQL>
You can use COALESCE() to turn NULL values of EXCHANGE_NAME to 'SWISS':
SELECT COALESCE(CE.EXCHANGE_NAME, 'SWISS'), sum(CE.TURNOVER)
FROM CE
WHERE CE.tradedate = '14-NOV-2019'
GROUP BY COALESCE(CE.EXCHANGE_NAME, 'SWISS')
Edit: you can use handy Oracle function decode() to map the code to a default EXCHANGE_NAME:
SELECT
COALESCE(
CE.EXCHANGE_NAME,
DECODE(CE.CODE, 'SA', 'SWISS', 'MI_1', 'MILAN')
) EXCHANGE,
SUM(CE.TURNOVER)
FROM CE
WHERE CE.tradedate = '14-NOV-2019'
GROUP BY COALESCE(
CE.EXCHANGE_NAME,
DECODE(CE.CODE, 'SA', 'SWISS', 'MI_1', 'MILAN')
)
You can expand the DECODE() argument as needed for your use case.

SQL - how to transpose only some row values into column headers without pivot

I have a table similar to this:
stud_ID | first_name | last_name | email | col_num | user_value
1 tom smith 50 Retail
1 tom smith 60 Product
2 Sam wright 50 Retail
2 Sam wright 60 Sale
but need to convert it to: (basically transpose 'col_num' to column headers and change 50 to function, 60 to department)
stud_ID | first_name | last_name | email | Function | Department
1 tom smith Retail Product
2 Sam wright Retail Sale
Unfortunately Pivot doesn't work in my system, just wondering if there is any other way to do this please?
The code that I have so far (sorry for the long list):
SELECT c.person_id_external as stu_id,
c.lname,
c.fname,
c.mi,
a.cpnt_id,
a.cpnt_typ_id,
a.rev_dte,
a.rev_num,
cp.cpnt_title AS cpnt_desc,
a.compl_dte,
a.CMPL_STAT_ID,
b.cmpl_stat_desc,
b.PROVIDE_CRDT,
b.INITIATE_LEVEL1_SURVEY,
b.INITIATE_LEVEL3_SURVEY,
a.SCHD_ID,
a.TOTAL_HRS,
a.CREDIT_HRS,
a.CPE_HRS,
a.CONTACT_HRS,
a.TUITION,
a.INST_NAME,
--a.COMMENTS,
a.BASE_STUD_ID,
a.BASE_CPNT_TYP_ID,
a.BASE_CPNT_ID,
a.BASE_REV_DTE,
a.BASE_CMPL_STAT_ID,
a.BASE_COMPL_DTE,
a.ES_USER_NAME,
a.INTERNAL,
a.GRADE_OPT,
a.GRADE,
a.PMT_ORDER_TICKET_NO,
a.TICKET_SEQUENCE,
a.ORDER_ITEM_ID,
a.ESIG_MESSAGE,
a.ESIG_MEANING_CODE_ID,
a.ESIG_MEANING_CODE_DESC,
a.CPNT_KEY,
a.CURRENCY_CODE,
c.EMP_STAT_ID,
c.EMP_TYP_ID,
c.JL_ID,
c.JP_ID,
c.TARGET_JP_ID,
c.JOB_TITLE,
c.DMN_ID,
c.ORG_ID,
c.REGION_ID,
c.CO_ID,
c.NOTACTIVE,
c.ADDR,
c.CITY,
c.STATE,
c.POSTAL,
c.CNTRY,
c.SUPER,
c.COACH_STUD_ID,
c.HIRE_DTE,
c.TERM_DTE,
c.EMAIL_ADDR,
c.RESUME_LOCN,
c.COMMENTS,
c.SHIPPING_NAME,
c.SHIPPING_CONTACT_NAME,
c.SHIPPING_ADDR,
c.SHIPPING_ADDR1,
c.SHIPPING_CITY,
c.SHIPPING_STATE,
c.SHIPPING_POSTAL,
c.SHIPPING_CNTRY,
c.SHIPPING_PHON_NUM,
c.SHIPPING_FAX_NUM,
c.SHIPPING_EMAIL_ADDR,
c.STUD_PSWD,
c.PIN,
c.PIN_DATE,
c.ENCRYPTED,
c.HAS_ACCESS,
c.BILLING_NAME,
c.BILLING_CONTACT_NAME,
c.BILLING_ADDR,
c.BILLING_ADDR1,
c.BILLING_CITY,
c.BILLING_STATE,
c.BILLING_POSTAL,
c.BILLING_CNTRY,
c.BILLING_PHON_NUM,
c.BILLING_FAX_NUM,
c.BILLING_EMAIL_ADDR,
c.SELF_REGISTRATION,
c.SELF_REGISTRATION_DATE,
c.ACCESS_TO_ORG_FIN_ACT,
c.NOTIFY_DEV_PLAN_ITEM_ADD,
c.NOTIFY_DEV_PLAN_ITEM_MOD,
c.NOTIFY_DEV_PLAN_ITEM_REMOVE,
c.NOTIFY_WHEN_SUB_ITEM_COMPLETE,
c.NOTIFY_WHEN_SUB_ITEM_FAILURE,
c.LOCKED,
c.PASSWORD_EXP_DATE,
c.SECURITY_QUESTION,
c.SECURITY_ANSWER,
c.ROLE_ID,
c.IMAGE_ID,
c.GENDER,
c.PAST_SERVICE,
c.LST_UNLOCK_TSTMP,
c.MANAGE_SUB_SP,
c.MANAGE_OWN_SP,
d.col_num,
d.user_value
FROM pa_cpnt_evthst a,
pa_cmpl_stat b,
pa_student c,
pv_course cp,
pa_stud_user d
WHERE a.cmpl_stat_id = b.cmpl_stat_id
AND a.stud_id = c.stud_id
AND cp.cpnt_typ_id(+) = a.cpnt_typ_id
AND cp.cpnt_id(+) = a.cpnt_id
AND cp.rev_dte(+) = a.rev_dte
AND a.CPNT_TYP_ID != 'SYSTEM_PROGRAM_ENTITY'
AND c.stud_id = d.stud_id
AND d.col_num in ('10','30','50','60')
I would just use conditional aggregation:
select stud_ID, first_name, last_name, email,
max(case when col_num = 50 then user_value end) as function,
max(case when col_num = 60 then user_value end) as department
from t
group by stud_ID, first_name, last_name, email;
Your code seems to have nothing to do with the sample data. I do notice however that you are using implicit join syntax. You really need to learn how to use proper, explicit, standard JOIN syntax.
I'm assuming you have Sql Server 2000 or 2003. What you need to do in that case is create a script with one cursor.
This cursor will create a text with something like this:
string var = "CREATE TABLE #Report (Col1 VARCHAR(20), Col2, VARCHAR(20), " + ColumnName
That way you can create a temp table on the fly, at the end you will need to do a Select of your temp table to get your pivot table ready.
Its not that easy if you are not familiar with cursors.
OR
if there are only few values on your 'pivot' column and they are not going to grow you can also do something like this:
Pivot using SQL Server 2000
I'm unable to understand your code, so I'll just assume the table mentioned in the sample data as stud(because of stud_id).
So here is what I think can do the work of pivot.
SELECT ISNULL(s1.stud_ID, s2.stud_id),
ISNULL(s1.first_name, s2.first_name),
ISNULL(s1.last_name, s2.last_name),
ISNULL(s1.email, s2.email),
s1.user_value as [Function], s2.user_value as Department
FROM stud s1 OUTER JOIN stud s2
ON s1.stud_ID = s2.stud_ID -- Assuming stud_ID is primary key, else join on all primary keys
AND s1.col_num = 50 AND s2.col_num = 60
Explanation: I'm just trying to simulate here what PIVOT does. For every column you want, you create a new table in the JOIN and constaint it to only one value in your col_num column. For example, if there are no values for 50 in s1, the OUTER JOIN will get make it NULL and we need to pull records from s2.
Note: If you need more than 2 new columns, then you can use COALESCE instead of ISNULL

how to get combine from Two select statements

my table is,
ID First_Name Last_name manager_ID Unique_ID
12 Jon Doe 25 CN=Jon Doe, DC=test,DC=COM
25 Steve Smith 39 CN=steve smith, DC=test,dc=com
I want to write a sql that will give me manager's unique ID,
select manager_id from test where ID = '12'
this will give me users manager_ID
select unique_id from test where ID = '25'
can i combine above sql in one statement that will give me user's manager's unique_id as output?
You are looking for a self-join:
select m.unique_id
from test t join
test m
on t.manager_id = m.id
where t.ID = 12;
Note that I remove the single quotes around 12. Presumably, id is an integer. You should not be comparing an integer to a string.
Instead of joining it to the same table, you can also make a nested subquery statement like this.
SELECT unique_id FROM test WHERE ID =(SELECT manager_id FROM test WHERE ID = 12);
The inner query outputs the manager_id where id of person equals 12 and the outer query gives the unique_id of the related manager.

SQL Query to retrive translated value in output

i have a position table
pos_table with column
position_code job_code location_code pos_name BUSINESS_UNIT
1 staff delhi supervisor XYZ
2 supervor manila technical associate ABC
mapping table
table_code source_code business_unit target_code
LOC DELHI XYZ 10
loc MANILA ABC 20
job staff XYZ 01
job supervisor ABC 02
I want a query which joins mapping table and pos_table such that
for job_code staff in the output 01 from mapping table target_code should come
using business_unit and source_code as join.
output:
position_code job_code location_code pos_name BUSINESS_UNIT
1 01 10 supervisor XYZ
2 02 20 technical associate ABC
for this i wrote the query :
select POSITION_CODE,
coalesce(JOB_MAP.FUSION_HARMONIZED_CODE,JOB_CODE) JOB_CODE,
coalesce(LOC_MAP.FUSION_HARMONIZED_CODE,LOCATION_CODE)LOCATION_CODE
from pos_tab POS_STAG,
MAPPING_TAB LOC_MAP,
mapping_tab job_MAP
where 1=1
and JOB_MAP.source_code||business_unit_name = POS_STAG.JOB_CODE||business_unit_name
and LOC_MAP.TABLE_CODE ='LOC'
and job_map.table_code='JOB'
and LOC_MAP.source_code ||business_unit_name = POS_STAG.LOCATION_CODE||business_unit_name;
but this is not working and it is rerieving more number of rows
I'm not sure what "SOURCE_CORE_HR_CODE" is since you don't explain it in your question but I'm guessing the below is correct.
The problem is you are using your mapping table for two different joins so you have to join it twice.
I'm using the "new" joining syntax which has existed as a standard for over 20 years. I suggest you using this syntax. It is much easier to understand how SQL works using this syntax. I've no idea why anyone would use the old style.
SELECT P.POSITION_CODE, M1.TARGET_CODE AS JOB_CODE, M2.TARGET_CODE AS LOCATION_CODE, P.JOB_CODE AS POS_NAME, P.BUSINESS_UNIT
FROM POS_TABLE P
JOIN MAPPING_TABLE M1 ON P.JOB_CODE = M1.SOURCE_CODE AND upper(M1.TABLE_CODE) = 'JOB'
JOIN MAPPING_TABLE M2 ON P.BUSINESS_UNIT = M2.BUSINESS_UNIT AND upper(M2.TABLE_CODE) = 'LOC'

How do I write a standard SQL GROUP BY that includes columns not in the GROUP BY clause

Let's say I have a table called Customer, defined like this:
Id Name DepartmentId Hired
1 X 101 2001/01/01
2 Y 102 2002/01/01
3 Z 102 2003/01/01
And I want to retrieve the date of the last hiring in each department.
Obviously I would do this
SELECT c.DepartmentId, MAX(c.Hired)
FROM Customer c
GROUP BY c.DepartmentId
Which returns:
101 2001/01/01
102 2003/01/01
But what do I do if I want to return the name of the guy hired? I.e. I would want this result set:
101 2001/01/01 X
102 2003/01/01 Z
Note that the following does not work, as it would return three rows rather than the two I'm looking for:
SELECT c.DepartmentId, c.Name, MAX(c.Hired)
FROM Customer c
GROUP BY c.DepartmentId
I can't remember seeing a query that achieves this.
NOTE: It's not acceptable to join on the Hired field, as that would not be guaranteed to be accurate.
A subselect would do the job and would handle the case where more than one person was hired in the same department on the same day:
SELECT c.DepartmentId, c.Name, c.Hired from Customer c,
(SELECT DepartmentId, MAX(Hired) as MaxHired
FROM Customer
GROUP BY DepartmentId) as sub
WHERE c.DepartmentId = sub.DepartmentId AND c.Hired = sub.MaxHired
Standard Sql:
select *
from Customer C
where exists
(
-- Linq to Sql put NULL instead ;-)
-- In fact, you can even put 1/0 here and would not cause division by zero error
-- An RDBMS do not parse the select clause of correlated subquery
SELECT NULL
FROM Customer
where c.DepartmentId = DepartmentId
GROUP BY DepartmentId
having c.Hired = MAX(Hired)
)
If Sql Server happens to support tuple testing, this is the most succint:
select *
from Customer
where (DepartmentId, Hired) in
(select DepartmentId, MAX(Hired)
from Customer
group by DepartmentId)
SELECT a.*
FROM Customer AS a
JOIN
(SELECT DepartmentId, MAX(Hired) AS Hired
FROM Customer GROUP BY DepartmentId) AS b
USING (DepartmentId,Hired);