Oracle SQL: inline table within a SELECT statement? - sql

I'm configuring an integration tool which moves data from System A to System B. I provide an SQL SELECT statement which it runs against System A, and the statement's output determines what in System B gets updated. For instance, to update Team 1's international sales figure:
SELECT
'Team1_Int_Sales_Count' Code,
count(*) Count,
to_char(<integration tool date syntax>) Period
FROM
Sales
JOIN
ADozenMoreTables ON stuff
WHERE
ADozenOpaqueFields IN ADozenOpaqueReferences
AND Sales.Date BETWEEN <integration tool date syntax>
AND Team = 'Team1'
AND Sales.Type = 'International'
It uses the returned fields Code, Value, and Period to update a that code for that period with that value. (Fields identified by what order they're in, not by name.)
Then, for Team 1's domestic sales, I've copied and pasted the entire query, and changed the code and one WHERE clause:
SELECT
'Team1_Dom_Sales_Count' Code,
<otherwise as above>
AND Sales.Type = 'Domestic'
And then I copy and paste it four more times for Teams 2 and 3
SELECT
'Team2_Int_Sales_Count' Code,
<snip>
AND Team = 'Team2'
AND Sales.Type = 'International'
SELECT
'Team2_Dom_Sales_Count' Code,
<snip>
AND Team = 'Team2'
AND Sales.Type = 'Domestic'
SELECT
'Team3_Int_Sales_Count' Code,
<snip>
AND Team = 'Team3'
AND Sales.Type = 'International'
SELECT
'Team3_Dom_Sales_Count' Code,
<snip>
AND Team = 'Team3'
AND Sales.Type = 'Domestic'
The full problem has a ~60-line SELECT statement with 3x3x3 permutations and the amount of copy/pasting involved is giving me the fear.
Is there some way I can write a single SQL SELECT statement which will step through all the permutations without the copy-pasted repetition? In my mind's eye I'd have the permutations in a temporary table created inline, or as a 2-dimensional array, and the query could return the code and the value where the other two fields match:
{ {'Team1_Int_Sales_Count', 'Team1', 'International'},
{'Team1_Dom_Sales_Count', 'Team1', 'Domestic'},
{'Team2_Int_Sales_Count', 'Team2', 'International'},
{'Team2_Dom_Sales_Count', 'Team2', 'Domestic'},
{'Team3_Int_Sales_Count', 'Team3', 'International'},
{'Team3_Dom_Sales_Count', 'Team3', 'Domestic'} } Permutations
Constraints here are the integration tool requires I provide each task with a single SELECT statement. I cannot preface it with a WITH statement, or declare functions, or store the complex query as a view within the source database, or do anything that's fun or nice. And it's an Oracle ODBC connection, so it uses Oracle SQL.

You seem to want a cross join.
SELECT t.team || ct.code code,
t.team,
ct.type
FROM (SELECT '_Int_Sales_Count' code,
'International' type
FROM dual
UNION ALL
SELECT '_Dom_Sales_Count' code,
'Domestic' type
FROM dual) ct
CROSS JOIN (SELECT 'Team1' team
FROM dual
UNION ALL
SELECT 'Team2' team
FROM dual
UNION ALL
SELECT 'Team3' team
FROM dual) t;
db<>fiddle

A table of permutations can be constructed like this:
SELECT
Permutations.Code AS Code,
COUNT(*) AS Count,
...
JOIN (SELECT * FROM (
(SELECT 'Team1_Int_Sales_Count' AS Code, 'Team1' AS Team, 'International' AS Type FROM Dual),
UNION (SELECT 'Team1_Dom_Sales_Count' AS Code, 'Team1' AS Team, 'Domestic' AS Type FROM Dual),
UNION (SELECT 'Team2_Int_Sales_Count' AS Code, 'Team2' AS Team, 'International' AS Type FROM Dual),
UNION (SELECT 'Team2_Dom_Sales_Count' AS Code, 'Team2' AS Team, 'Domestic' AS Type FROM Dual),
UNION (SELECT 'Team3_Int_Sales_Count' AS Code, 'Team3' AS Team, 'International' AS Type FROM Dual),
UNION (SELECT 'Team3_Dom_Sales_Count' AS Code, 'Team3' AS Team, 'Domestic' AS Type FROM Dual)
)) AS Permutations
ON Permutations.Team = Sales.Team AND Permutations.Type = Sales.Type
WHERE...

I would do it like:
with
team as (select level lv from dual connect by level <= 3)
, bas as (select '_Int_Sales_Count' n_count, 'International' type from dual
union all select '_Dom_Sales_Count' n_count, 'Domestic' type from dual)
, permutations as
( select 'Team' || lv ||n_count as code, 'Team' || lv as team, type from team join bas on (1=1))
select * from permutations
but with the constraints:
select 'Team' || lv ||n_count as code, 'Team' || lv as team, type from
(select level lv from dual connect by level <= 3) team,
(select '_Int_Sales_Count' n_count, 'International' type from dual
union all select '_Dom_Sales_Count' n_count, 'Domestic' type from dual) bas

Related

How do I select rows from table that have one or more than one specific value in a column?

I have a table containing data such as:
BP_NUMBER,CONTRACT_TYPE
0000123, 1
0000123, 2
0000123, 3
0000123, 4
0000124, 4
0000124, 4
0000124, 4
0000125, 4
0000126, 1
0000126, 5
I want to select rows containing one or more occurrences of CONTRACT_TYPE = 4. In other words, I want to know who are the clients with one or more contracts of the same type and type 4.
I tried this query:
SELECT * FROM (
SELECT BP_NUMBER, CONTRACT_TYPE, COUNT(*) OVER (PARTITION BY BP_NUMBER) CT FROM CONTRACTS
WHERE (1=1)
AND DATE = '18/10/2022'
AND CONTRACT_TYPE = 4)
WHERE CT= 1;
But it returns rows with only one occurrence of CONTRACT_TYPE = 4.
Also tried something like:
SELECT BP_NUMBER FROM CONTRACTS
WHERE (1=1)
AND CONTRACT_TYPE = 4
AND CONTRACT_TYPE NOT IN (SELECT CONTRACT_TYPE FROM CONTRACTS WHERE CONTRACT_TYPE != 4 GROUP BY CONTRACT_TYPE);
Trying to avoid any other contract types than 4. I really don't understand why it doesn't work.
The expected result would be:
0000124 --(4 occurrences of type 4)
0000125 --(1 occurrence of type 4)
Any help? Thanks
You can try something like this:
SELECT
BP_NUMBER
FROM CONTRACTS c1
WHERE CONTRACT_TYPE = 4
AND NOT EXISTS
(SELECT 1 FROM CONTRACTS c2 WHERE c2.BP_NUMBER = c1.BP_NUMBER
AND c2.CONTRACT_TYPE <> c1.CONTRACT_TYPE)
Depending on how you actually want to see it (and what other values you might want to include), you could either do a DISTINCT on the BP_NUMBER, or group on that column (and potentially others)
A similar result could also be achieved using an outer join between two instances of the CONTRACTS table. Essentially, you need the second instance of the same table so that you can exclude output rows when there are records with the "unwanted" contract types
You can just do the aggregation like here:
WITH
tbl AS
(
Select '0000123' "BP_NUMBER", '1' "CONTRACT_TYPE" From Dual Union All
Select '0000123', '2' From Dual Union All
Select '0000123', '3' From Dual Union All
Select '0000123', '4' From Dual Union All
Select '0000124', '4' From Dual Union All
Select '0000124', '4' From Dual Union All
Select '0000124', '4' From Dual Union All
Select '0000125', '4' From Dual Union All
Select '0000126', '1' From Dual Union All
Select '0000126', '5' From Dual
)
Select
BP_NUMBER "BP_NUMBER",
Count(*) "OCCURENCIES"
From
tbl
WHERE CONTRACT_TYPE = '4'
GROUP BY BP_NUMBER
ORDER BY BP_NUMBER
--
-- R e s u l t :
--
-- BP_NUMBER OCCURENCIES
-- --------- -----------
-- 0000123 1
-- 0000124 3
-- 0000125 1

BigQuery Union Distinct Where Value not in Preceding DataSet

I am trying to reconcile some student database with GSuite emails, where usernames have been created inconsistently for years.
The gist of the query I am trying to make on BigQuery is:
Match Emails to Students from email Pattern 1 and union with
Match Emails to Students from email Pattern 2 and union with
Emails not in 1 an 2.
Or in SQL:
with mymatches as (
with emaildataset as (
select 'testA' as col
union all
select 'testB'
union all
select 'testC'
union all
select 'testD'
)
select * from emaildataset where col like '%A'
union distinct
select * from emaildataset where col like '%B'
),
emaildataset2 as (
select 'testA' as col
union all
select 'testB'
union all
select 'testC'
union all
select 'testD'
)
select * from mymatches
union distinct
select * from emaildataset2 where emaildataset2.col not in (select col from mymatches)
This runs happily, but when I run the real code, then I'm getting duplicates.
The real code is now:
with matchedEmails as (
with g as (
select * from gsuite.StudentUsers
union all
select * from gsuite.AlumniUsers
)
select
std.STDCODE,
g.*
from g
inner join quick.all_students_alumni as std
on split(lower(g.Email), '#')[offset(0)] = split(quick.studentEmail(std.FNAME, std.MNAME, std.LNAME, std.STATUSTYPE), '#')[offset(0)]
where g.OU like '/Student%' or OU like '/Alumni%'
union distinct select
std.STDCODE,
g.*
from g
inner join quick.all_students_alumni as std
on split(lower(g.Email), '#')[offset(0)] = split(quick.studentEmail(std.FNAME, '', std.LNAME, std.STATUSTYPE), '#')[offset(0)]
where g.OU like '/Student%' or OU like '/Alumni%'
)
select * from matchedEmails
union distinct select
'NOT MATCHED' as STDCODE,
g.*
from (
select * from gsuite.StudentUsers
union all
select * from gsuite.AlumniUsers
) as g
where g.Email not in (select Email from matchedEmails)
and g.OU like '/Student%' or OU like '/Alumni%'
As a result though, I am getting duplicates in the Email column, which--based on my knowledge and test above--should not be, due to the where g.Email not in (select Email from matchedEmails) clause.
Am I doing something wrong?
I think, very last WHERE clause should be fixed to look like below
where g.Email not in (select Email from matchedEmails)
and (g.OU like '/Student%' or OU like '/Alumni%')
As you can see - the brackets around g.OU like '/Student%' or OU like '/Alumni%' were missing
it might be something else too that still need to be fixed - but this answers you below questions
As a result though, I am getting duplicates in the Email column, which--based on my knowledge and test above--should not be, due to the where g.Email not in (select Email from matchedEmails) clause.

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

use SUM on certain conditions

I have a script that extracts transactions and their details from a database. But my users complain that the file size being generated is too large, and so they asked for certain transactions to be just summed up/consolidated instead if they are of a certain classification, say Checking Accounts. That means there should only be one line in the result set named "Checking" which contains the sum of all transactions under Checking Accounts. Is there a way for an SQL script to go like:
CASE
WHEN Acct_class = 'Checking'
then sum(tran_amount)
ELSE tran_amount
END
I already have the proper GROUP BY and ORDER BY statements, but I can't seem to get my desired output. There is still more than one "Checking" line in the result set. Any ideas would be very much appreciated.
Try This,
Select sum(tran_amount) From tran_amount Where Acct_class = 'Checking'
You can try to achieve this using UNION ALL
SELECT tran_amount, .... FROM table WHERE NOT Acct_class = 'Checking'
UNION ALL
SELECT SUM(tran_amount), .... FROM table WHERE Acct_class = 'Checking' GROUP BY Acct_class, ...;
hi you can try below sql
select account_class,
case when account_class = 'saving' then listagg(trans_detail, ',') within group (order by emp_name) -- will give you all details transactions
when account_class = 'checking' then to_char(sum(trans_detail)) -- will give you only sum of transactions
end as trans_det from emp group by account_class;
Or, if your desired output is getting either the sum, either the actual column value based on another column value, the solution would be to use an analytical function to get the sum together with the actual value:
select
decode(acct_class, 'Checking', tran_amount_sum, tran_amount)
from (
select
sum(tran_amount) over (partition by acct_class) as tran_amount_sum,
tran_amount,
acct_class
from
YOUR_TABLE
)
You can try something like the following, by keeping single rows for some classes, and aggregating for some others:
with test (id, class, amount) as
(
select 1, 'a' , 100 from dual union all
select 2, 'a' , 100 from dual union all
select 3, 'Checking', 100 from dual union all
select 4, 'Checking', 100 from dual union all
select 5, 'c' , 100 from dual union all
select 6, 'c' , 100 from dual union all
select 7, 'c' , 100 from dual union all
select 8, 'd' , 100 from dual
)
select sum(amount), class
from test
group by case
when class = 'Checking' then null /* aggregates elements of class 'b' */
else id /* keeps elements of other classes not aggregated */
end,
class

select statement with subqueries against two databases

I have the below code to show what I am "trying" to accomplish in a stored procedure:
select * from
(
select to_char(sum(aa.amount))
from additional_amount aa, status st
where aa.int_tran_id = st.int_tran_id
and st.stage in ('ACHPayment_Confirmed')
and aa.entry_timestamp > (
select to_date(trunc(last_day(add_months(sysdate,-1))+1), 'DD-MON-RR') AS "day 1"
from dual
)
)
UNION ALL
(
select distinct it.debit_acct as "debit_accounts"
from internal_transactions it
where it.debit_acct IN ( select texe_cnasupro
from service.kndtexe, service.kndtctc
where texe_cncclipu = tctc_cncclipu
and tctc_cntipcli = 'C'
)
)
union all
(select distinct it.credit_acct as "credit_account"
from internal_transactions it
where it.credit_acct IN (select texe_cnasupro
from service.kndtexe, service.kndtctc
where texe_cncclipu = tctc_cncclipu
and tctc_cntipcli = 'C'
)
)
;
Output:
TO_CHAR(SUM(AA.AMOUNT))
----------------------------------------
130250292.22
6710654504
0000050334
2535814905
0007049560
5 rows selected
The top row of the output is what I need in the SP as output based on the below two queries which I am guessing needs to be sub-queried against the top select statement.
The top select is to select the sum of the amount a table with a join against another table for filtering (output:130250292.22).
The second and third selects is actually to check that the accounts in the internal_transactions table are signed up for the corresponding two tables in the service db which is a different db on the same server(owned by the same application).
The tables in the "service" db do not have the same common primary keys as in the first select which is against the same database.
Thank you for your help!
I don't understand your question, but I do know you can simplify this bit:
to_date(trunc(last_day(add_months(sysdate,-1))+1), 'DD-MON-RR') AS "day 1"
to this
trunc (sysdate, 'mm')
and you don't need a SELECT from DUAL to do that either.
and aa.entry_timestamp > trunc (sysdate, 'mm')