SQL - Sum of two values IN a Group By - sql

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.

Related

Sql Query: How to Base on the row name to display

I have the table data as listed on below:
name | score
andy | 1
leon | 2
aaron | 3
I want to list out as below, even no jacky's data, but list his name and score set to 0
aaron 3
andy 2
jacky 0
leon 2
You didn't specify your DBMS, but the following is 100% standard ANSI SQL:
select v.name, coalesce(t.score, 0) as score
from (
values ('andy'),('leon'),('aaron'),('jacky')
) as v(name)
left join your_table t on t.name = v.name;
The values clause builds up a "virtual table" that contains the names you are interested in. Then this is used in a left join so that all names from the virtual table are returned plus the existing scores from your (unnamed table). For non-existing scores, NULL is returned which is turned to 0 using coalesce()
If you only want to specify the missing names, you can use a UNION in the virtual table:
select v.name, coalesce(t.score, 0) as score
from (
select t1.name
from your_table t1
union
select *
from ( values ('jacky')) as x
) as v(name)
left join your_table t on t.name = v.name;
fixed the query, could list out the data, but still missing jacky, only could list out as shown on below, the DBMS. In SQL is SQL2008.
data
name score scoredate
andy 1 2021-08-10 01:23:16
leon 2 2021-08-10 03:25:16
aaron 3 2021-08-10 06:25:16
andy 4 2021-08-10 11:25:16
leon 5 2021-08-10 13:25:16
result set
name | score
aaron | 1
andy | 5
leon | 7
select v.name as Name,
coalesce(sum(t.score),0) as Score
from (
values ('aaron'), ('andy'), ('jacky'), ('leon')
) as v(name)
left join Score t on t.name=v.name
where scoredate>='2021-08-10 00:00:00'
and scoredate<='2021-08-10 23:59:59'
group by v.name
order by v.name asc
Your question lacks a bunch of information, such as where "Jacky"s name comes from. If you have a list of names that you know are not in the table, just use union all:
select name, score
from t
union all
select 'Jacky', 0;

SQLite query to get table based on values of another table

I am not sure what title has to be here to correctly reflect my question, I can only describe what I want.
There is a table with fields:
id, name, city
There are next rows:
1 John London
2 Mary Paris
3 John Paris
4 Samy London
I want to get a such result:
London Paris
Total 2 2
John 1 1
Mary 0 1
Samy 1 0
So, I need to take all unique values of name and find an appropriate quantity for unique values of another field (city)
Also I want to get a total quantity of each city
Simple way to do it is:
1)Get a list of unique names
SELECT DISTINCT name FROM table
2)Get a list of unique cities
SELECT DISTINCT city FROM table
3)Create a query for every name and city
SELECT COUNT(city) FROM table WHERE name = some_name AND city = some_city
4)Get total:
SELECT COUNT(city) FROM table WHERE name = some_name
(I did't test these queries, so maybe there are some errors here but it's only to show the idea)
As there are 3 names and 2 cities -> 3 * 2 = 6 queries to DB
But for a table with 100 cities and 100 names -> 100 * 100 = 10 000 queries to DB
and it may take a lot of time to do.
Also, names and cities may be changed, so, I can't create a query with predefined names or cities as every day it's new ones, so, instead of London and Paris it may be Moscow, Turin and Berlin. The same thing with names.
How to get such table with one-two queries to original table using sqlite?
(sqlite: I do it for android)
You can get the per-name results with conditional aggregation. As for the total, unfortunately SQLite does not support the with rollup clause, that would generate it automatically.
One workaround is union all and an additional column for ordering:
select name, london, paris
from (
select name, sum(city = 'London') london, sum(city = 'Paris') paris, 1 prio
from mytable
group by name
union all
select 'Total', sum(city = 'London'), sum(city = 'Paris'), 0
from mytable
) t
order by prio, name
Actually the subquery might not be necessary:
select name, sum(city = 'London') london, sum(city = 'Paris') paris, 1 prio
from mytable
group by name
union all
select 'Total', sum(city = 'London'), sum(city = 'Paris'), 0
from mytable
order by prio, name
#GMB gave me the idea of using group by, but as I do it for SQLite on Android, so, the answer looks like:
SELECT name,
COUNT(CASE WHEN city = :london THEN 1 END) as countLondon,
COUNT(CASE WHEN city = :paris THEN 1 END) as countParis
FROM table2 GROUP BY name
where :london and :paris are passed params, and countLondon and countParis are fields of the response class

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'

Dynamic Query for Retriving the translated column value in 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.

Collapse Multiple Records Into a Single Record With Multiple Columns

In a program I'm maintaining we were given a massive (~500 lines) SQL statement by the customer. It is used for generating flat files with fixed length records for transmitting data to another big business. Since its a massive flat file its not relational and the standard normal forms of data are collapsed. So, if you have a record that can have multiple codes associated, in this case upto 19, they all have be written into single line, but seperate fields, in the flat file.
Note: this example is simplified.
The data might look like this, with three tables:
RECORDS
record_id firstname lastname
--------------------------------
123 Bob Schmidt
324 George Washington
325 Ronald Reagan
290 George Clooney
CODE_TABLE
code_id code_cd code_txt
--------------------------------
5 3 President
2 4 Actor
3 7 Plumber
CODES_FOR_RECORDS
record_id code_cd
-------------------
123 7
325 3
290 4
324 3
325 4
123 4
This needs to produce records like:
firstname lastname code1 code2 code3
Bob Schmidt Actor Plumber NULL
George Washington President NULL NULL
Ronald Reagon Actor President NULL
George Clooney Actor NULL NULL
The portion of the current query we were given looks like this, but with 19 code columns instead of the 5:
select
x.record_id,
max(case when x.rankk = 1 then code_txt end) as CodeColumn1,
max(case when x.rankk = 2 then code_txt end) as CodeColumn2,
max(case when x.rankk = 3 then code_txt end) as CodeColumn3,
max(case when x.rankk = 4 then code_txt end) as CodeColumn4,
max(case when x.rankk = 5 then code_txt end) as CodeColumn5,
from
(
select
r.record_id,
ct.code_txt as ctag ,
dense_rank() over (partition by r.record_id order by cfr.code_id) as rankk
from
records as r
codes_for_records as cfr,
code_table as ct
where
r.record_id = cfr.record_id
and ct.code_cd = cfr.code_cd
and cfr.code_cd is not null
and ct.code_txt not like '%V%'
) as x
where
x.record_id is not null
group by
x.record_id
I trimmed down things for simplicties sake, but the actual statment includes an inner query and a join and more where conditions, but that should get the idea across. My brain is telling me there has to be a better way, but I'm not an SQL expert. We are using DB2 v8 if that helps. And the codes have to be in seperate columns, so no coalescing things into a single string. Is there a cleaner solution than this?
Update:
I ended up just refacorting the original query, it sill uses the ugly MAX() business, but overall the query is much more readable due to reworking other parts.
It sounds like what you are looking for is pivoting.
WITH joined_table(firstname, lastname, code_txt, rankk) AS
(
SELECT
r.firstname,
r.lastname,
ct.code_txt,
dense_rank() over (partition by r.record_id order by cfr.code_id) as rankk
FROM
records r
INNER JOIN
codes_for_records cfr
ON r.record_id = cfr.record_id
INNER JOIN
codes_table ct
ON ct.code_cd = cfr.code_cd
),
decoded_table(firstname, lastname,
CodeColumn1, CodeColumn2, CodeColumn3, CodeColumn4, CodeColumn5) AS
(
SELECT
firstname,
lastname,
DECODE(rankk, 1, code_txt),
DECODE(rankk, 2, code_txt),
DECODE(rankk, 3, code_txt),
DECODE(rankk, 4, code_txt),
DECODE(rankk, 5, code_txt)
FROM
joined_table jt
)
SELECT
firstname,
lastname,
MAX(CodeColumn1),
MAX(CodeColumn2),
MAX(CodeColumn3),
MAX(CodeColumn4),
MAX(CodeColumn5)
FROM
decoded_table dt
GROUP BY
firstname,
lastname;
Note that I've never actually done this myself before. I'm relying on the linked document as a reference.
You might need to include the record_id to account for duplicate names.
Edit: Added the GROUP BY.
One of the possible solutions is using of recursive query:
with recursive_view (record_id, rankk, final) as
(
select
record_id,
rankk,
cast (ctag as varchar (100))
from inner_query t1
union all
select
t1.record_id,
t1.rankk,
/* all formatting here */
cast (t2.final || ',' || t1.ctag as varchar (100))
from
inner_query t1,
recursive_view t2
where
t2.rankk < t1.rankk
and t1.record_id = t2.record_id
and locate(t1.ctag, t2.final) = 0
)
select record_id, final from recursive_view;
Can't guarantee that it works, but hope it will be helpful. Another way is using of custom aggregate function.