Oracle conditional WHERE in stored procedure - sql

I want to write a stored procedure to select carriers, with an additional parameter 'i_showEmptyCarrier' to specify if empty carriers needs to be hidden or showed.
+---------------------------+
| Carriers |
+----+----------+-----------+
| id | label | location |
+----+----------+-----------+
| 1 | carrier1 | warehouse |
+----+----------+-----------+
| 2 | carrier2 | warehouse |
+----+----------+-----------+
| 3 | carrier3 | factory |
+----+----------+-----------+
I need to query the 'products' table to check if the carrier is empty or not.
+-------------------------------------------+
| products |
+----+-----------+----------------+---------+
| id | carrierid | productiondate | deleted |
+----+-----------+----------------+---------+
| 1 | 1 | 09/09/2020 | 1 |
+----+-----------+----------------+---------+
| 2 | 1 | 09/09/2020 | 0 |
+----+-----------+----------------+---------+
| 3 | 1 | 09/09/2020 | 0 |
+----+-----------+----------------+---------+
| 4 | 2 | 10/09/2020 | 0 |
+----+-----------+----------------+---------+
| 5 | 2 | 10/09/2020 | 0 |
+----+-----------+----------------+---------+
So in this case carrier3 is empty.
The stored procedure logic I want to have is:
PROCEDURE GetCarriers
(
i_showEmptyCarrier IN number,
c_Carriers OUT t_cursor
)
AS
BEGIN
OPEN c_Carriers FOR
SELECT
label,
( select count(*)
from products
where products.carrierid = carriers.carrierid
and records.deleted = 0
) as nrOfProducts
FROM carriers
if(i_showEmptyCarrier == 1) {
//select carriers without a product
WHERE nrOfProducts = 0 ;
}
else{
//select carriers with a product
WHERE nrOfProducts > 0 ;
}
END GetCarriers;
so if 'i_showEmptyCarrier' = 1, then empty carrier no.3 will be selected,
+----------+--------------+
| label | nrOfProducts |
+----------+--------------+
| carrier3 | 0 |
+----------+--------------+
otherwise only carrier no.1 and 2 are selected.
+----------+--------------+
| label | nrOfProducts |
+----------+--------------+
| carrier1 | 2 |
+----------+--------------+
| carrier2 | 2 |
+----------+--------------+

How about modifying the query to use left join and conditional aggregation and finally comparing the input,
SELECT label,nrOfProducts
FROM
(
SELECT c.label,SUM(CASE WHEN p.deleted = 0 THEN 1 ELSE 0 END) nrOfProducts
FROM carriers c
LEFT JOIN products p
ON p.carrierid = c.id
GROUP BY c.label
)
WHERE ((&i_showEmptyCarrier = 1 AND nrOfProducts = 0)
OR (&i_showEmptyCarrier = 0 AND nrOfProducts > 0));
You can still use the where clause for your query instead of if-else.

You want an inner join in one case and an outer join in the other. As an outer join just means that all columns of the outer-joined row are null, however, you can simply outer join in both cases and only then decide whether to dismiss or keep the outer-joined rows.
In SQL this is simply:
select *
from carriers c
left join products p on p.carrierid = c.id
where (:show_empty = 'only non-empty' and p.id is not null)
or (:show_empty = 'only empty' and p.id is null)
or (:show_empty = 'all')
(And of course you can aggregate this to count(p.id). In that case you can either group by c.id or look at one carrier where c.id = :carrier_id.)

Seems like you have a lot of Java (or other programming language) influence. I am not sure what exactly you want. But here is how you can achieve what you want.
create or replace procedure p_get_carriers(pi_carrier_flag in varchar2, po_carriers out t_cursor)
is
lt_cursor t_cursor;
begin
select *
bulk collect into lt_cursor
from records
where deleted = case when pi_carrier_flag = 'Y' then 0 else deleted end;
po_carriers := lt_cursor;
exception
when others then
po_carriers := null;
end p_get_carriers;
Meaning you don't need to have if-else block to handle that. You can do it in the where clause of the query itself.
Note: I am not aware of your table structure and the collection details. I am guessing it and posting the code. You might need to adjust it accordingly.

Related

SQL left join on multiple columns return partial matching

We currently do validation on our data using a LEFT JOIN from a lookup table, these are on single tables to single columns, just a normal left join to one table then we do a case statement in the SELECT to say if returned value is greater than zero (not null) then it's ok, else warning. Process currently looks like the below:
SELECT
CASE WHEN LEN(C_Market.MCode) > 0 THEN 'Ok' ELSE 'Warning' END AS C_Market
CASE WHEN LEN(C_Language.LCode) > 0 THEN 'Ok' ELSE 'Warning' END AS C_Language
FROM [dbo].[CTable]
LEFT JOIN [dbo].[Lookup_Market] AS C_Market ON [dbo].CTable.[campaignNameMarket] = C_Market.MCode
LEFT JOIN [dbo].[Lookup_Language] AS C_Language ON [dbo].CTable.[campaignNameLanguage] = C_Language.LCode
What we are looking to do is validate across multiple columns so adding an AND to our LEFT JOIN statements. This will be based off mapped lookup tables. Our issue is that if one of the columns don't match, it returns all columns in LEFT JOIN/AND as NULL (which makes sense). Would there be a way of doing the multiple join but still return the value if it matches that column in the LEFT JOIN/AND statement but only return NULL on the column that does not match? Any help would be much appreciated
Example:
SELECT
CASE WHEN LEN(C_MarketLanguage.LCode) > 0 THEN 'Ok' ELSE 'Warning' END AS C_Language,
CASE WHEN LEN(C_MarketLanguage.MCode) > 0 THEN 'Ok' ELSE 'Warning' END AS C_Market
FROM [dbo].[CTable]
LEFT JOIN [dbo].[LookupMap_MarketLanguage] AS C_MarketLanguage ON [dbo].CTable.[campaignNameMarket] = C_MarketLanguage.MCode
AND [dbo].CTable.campaignNameLanguage = C_MarketLanguage.LCode
See below example of the LookupMap_MarketLanguage table. Here we will do a left join on MCode AND LCode. Lets say for example the main table had CH as the Market but CZE as the Language. The old method with individual left joins would say they are both ok as they are in each column however the new AND method would show both as warning. I would like Market to show as OK but Language to show as warning.
LookupMap_MarketLanguage Table
+----------------+-------+----------+-------+
| Market | MCode | Language | LCode |
+----------------+-------+----------+-------+
| Switzerland | CH | French | FRE |
| Switzerland | CH | German | GER |
| Czech Republic | CZ | Czech | CZE |
| Germany | DE | German | GER |
+----------------+-------+----------+-------+
Sample CTable data
+--------------------+----------------------+
| campaignNameMarket | campaignNameLanguage |
+-------------------------------------------+
| DE | GER |
| CH | CZE |
| CZ | CZE |
+--------------------+----------------------+
Desired output
+----------+-------------+
| C_Market | C_Language |
+----------+-------------+
| Ok | Ok |
| Ok | Warning |
| Ok | Ok |
+----------+-------------+
Current output with multiple left joins
+----------+-------------+
| C_Market | C_Language |
+----------+-------------+
| Ok | Ok |
| Ok | Ok |
| Ok | Ok |
+----------+-------------+
Current output with AND LEFT JOIN
+----------+-------------+
| C_Market | C_Language |
+----------+-------------+
| Ok | Ok |
| Warning | Warning |
| Ok | Ok |
+----------+-------------+
Probably you could instead do this with exist query, instead of joins. ie:
SELECT
CASE WHEN exists (select *
from [dbo].[Lookup_Market] m
where c.[campaignNameMarket] = C_Market.MCode and
LEN(m.MCode) > 0
) THEN 'Ok' ELSE 'Warning' End as C_Market,
CASE WHEN exists (select *
from [dbo].[dbo].[Lookup_Language] l
where c.[campaignNameLanguage] =m.LCode and
LEN(m.LCode) > 0
) THEN 'Ok' ELSE 'Warning' End as C_Language
FROM [dbo].[CTable] c;
EDIT:
As I said before, your samples are not covering all cases and not very helpful. However, at least now a little better than before. If that is exactly what you want:
SELECT
-- c.campaignNameMarket,
-- c.campaignNameLanguage,
CASE
WHEN EXISTS
(
SELECT *
FROM dbo.LookupMap_MarketLanguage ml
WHERE c.campaignNameMarket = ml.MCode
) THEN 'Ok' ELSE 'Warning'
END AS C_Market,
CASE
WHEN EXISTS
(
SELECT *
FROM dbo.LookupMap_MarketLanguage ml
WHERE c.campaignNameMarket = ml.MCode
AND c.campaignNameLanguage = ml.LCode
) THEN 'Ok' ELSE 'Warning'
END AS C_Language
FROM [dbo].[CTable] c;
You can see DBFiddle demo here.

Count maxdate with condition

(apologies if I don't format question correctly, this is my first post).
I have following example data:
a.label | b.recipient_id| b.action|b.creation_date
Books | Recipient1 | 0 | 01/04/2018
Books | Recipient1 | 1 | 02/04/2018
Books | Recipient1 | 0 | 03/04/2018
Books | Recipient1 | 1 | 04/04/2018
Books | Recipient2 | 1 | 05/04/2018
Books | Recipient2 | 0 | 06/04/2018
Books | Recipient2 | 0 | 07/04/2018
Books | Recipient2 | 1 | 08/04/2018
Books | Recipient3 | 1 | 13/04/2018
Books | Recipient3 | 0 | 14/04/2018
Books | Recipient3 | 1 | 15/04/2018
Books | Recipient3 | 0 | 16/04/2018
I would like to count recipient_ids for which max(creation_date) action = 1
If for max(creation_date) action is equal to 0 I do not want to count this recipient at all.
So in my example data above I would like to count Recipient1 and Recipient2 but not Recipient3.
I tried variations of following but nothing worked so far
SELECT count(b.recipient_id)
FROM Services a, Subscriptions b,
(SELECT recipient_id,MAX(creation_date)
FROM Subscriptions
GROUP BY recipient_id) c
WHERE a.SERVICE_ID = b.SERVICE_ID
AND b.recipient_id = c.recipient_id
AND b."ACTION" = '1'
AND a.LABEL = 'Books'
Thanks in advance
Here is one method, using two levels of aggregation:
select count(*)
from (select su.recipient_id
from services se join
subscriptions su
on se.service_id = su.service_id
where se.label = 'BOOKS'
group by su.recipient_id
having max(su.creation_date) = max(case when su.action = 1 then su.creation_date)
) ss;
The having clause checks that the final status is 1.
Notes:
Never use commas in the FROM clause.
Always use proper, explicit, standard JOIN syntax.
Use meaningful table aliases, such as abbreviations of the table name, rather than arbitrary letters.
Use single quotes to delimit strings. Double quotes can be used to escape identifiers, if that is necessary.

SQL Query manipulation

I have three tables :
BookingNode , Booking AirTrip
AirTrip :
+----+------------+
| ID | Name |
+----+------------+
| 0 | One way |
| 1 | Round trip |
| 2 | Circle |
| 3 | Other |
+----+------------+
When ever we make a booking we store the data as :
BookingNode table
+--------+-------------------+------------+----------------------+
| ID | CustomerGivenName | IPAddress | Email |
+--------+-------------------+------------+----------------------+
| 177022 | xfghfh | 2130706473 | mikehussey#gmail.com |
| 177021 | cfggjfj | 2130706473 | mikehussey#gmail.com |
+--------+-------------------+------------+----------------------+
Booking Table :
+--------+---------------+-----------+------------+------------+
| ID | BookingNodeID | AirTripID | AirLineId | Provider |
+--------+---------------+-----------+------------+------------+
| 181251 | 177020 | 1 | 978 | Jet |
| 181252 | 177021 | 0 | 982 | Go |
| 181253 | 177021 | 0 | 978 | Jet |
+--------+---------------+-----------+------------+------------+
If round trip flight is booked and ProviderID is same then a single entry is done in Booking Table with AirTripID value as 1.(Booking ID : 181251 and Provider Jet )
But if providers are different for both the legs then two entries are done in Booking Table with AirTripID for both entries are one(Booking ID : 181252 and 181253 Provider Go,Jet ).In this case BookingNodeID value being same.
Prob : I have to write a query to get different type of Bookings.(Oneway, RoundTrip,Circle).But when I apply join on AirTripID , it is giving me incorrect results.
How can I write my query to give correct results knowing that BookingNodeID is going to be the same for roundtrip (both entries in Booking Table)
Sample Output
+-------------+---------------+-------------------+------------+
| AirTripName | BookingNodeID | CustomerGivenName | IPAddress |
+-------------+---------------+-------------------+------------+
| TwoWay | 177020 | xfghfh | 2130706473 |
| TwoWay | 177021 | cfggjfj | 2130706473 |
+-------------+---------------+-------------------+------------+
Basically, this code might have an error due to my laziness syntom of data entry. But, the logic of the query is, if b.AirTripID is 0, add extra condition which group by Booking. if result return more than 1 row, is actually 2 way. so AirTripType will become 1, otherwise, remain the same as b.AirTripID. You may copy below on and try fix if theres any error. i believe the logic should work based on your expected result.
select
bd.ID,
bd.CustomerGivenName,
case b.AirTripID
when 1 then 1
when 2 then 2
when 3 then 3
when 0 then
case select BookingNodeID
from Booking
where Booking.BookingNodeID = bd.ID group by BookingNodeID having Count(BookingNodeID)
when 1 then 1
else 0 end as AirTripType,
bd.IPAddress
from BookingNode bd
inner join (select BookingNodeID ,AirTripID from Booking group by BookingNodeID ,AirTripID) as b on b.BookingNodeID = bd.ID
where id=177021
Try This
WITH CTE
AS
(
SELECT
SeqNo = ROW_NUMBER() OVER(PARTITION BY BN.ID ORDER BY B.ID),
B.BookingNodeID,
BN.CustomerGivenName,
BN.IPAddress,
AirTripId = A.ID,
AirTripNm = A.Name
FROM Booking B
INNER JOIN AirTrip A
ON A.ID = B.AirTripID
LEFT JOIN BookingNode BN
ON B.BookingNodeID = BN.id
)
SELECT
C1.SeqNo,
AirTripName = CASE WHEN C2.SeqNo IS NOT NULL
THEN 'Round trip'
ELSE C1.AirTripNm END,
C1.BookingNodeID,
C1.CustomerGivenName,
C1.IPAddress
FROM CTE C1
LEFT JOIN CTE C2
ON C1.BookingNodeID = C2.BookingNodeID
AND C2.SeqNo = 2
WHERE c1.SeqNo = 1
SQL Fiddle Link Here
Select distinct bk.bookingnodeid,cst.customername,ipaddress,
case when count(airtripid)over(partition by bookingnodeid order by bookingnodeid)=2 then 'RoundTrip' else name end As AirTripName
from booking bk
inner join airlinetrip at
on bk.airtripid=at.id
inner join customer cst
on cst.id=bk.bookingnodeid

Return list of tables and count in single query

I know about the describe command \d and select count(*) from my_schema_1.my_table_1;. However I'd like to get a neat list of the entire database, I have quite a few tables. Something like below would be nice.
my_schema_1 | mytable_1 | 12323
my_schema_2 | mytable_2 | 0
I'd basically like to loop over all the tables.
Maybe something like this (no need to execute a COUNT(*)) for each table):
EDIT new version to consider tables without projections:
SELECT
t.table_schema AS schema,
t.table_name AS table,
ZEROIFNULL(
CASE WHEN p.is_segmented IS TRUE
THEN SUM(ps.row_count) * COUNT(DISTINCT ps.node_name) // COUNT(ps.node_name)
ELSE MAX(ps.row_count)
END
) AS row_count,
CASE WHEN p.is_segmented THEN 'Yes' ELSE 'No' END AS segmented,
COUNT(DISTINCT p.projection_id) AS num_proj
FROM
v_catalog.tables t
LEFT OUTER JOIN v_monitor.projection_storage ps
ON t.table_id = ps.anchor_table_id
LEFT OUTER JOIN v_catalog.projections p
ON t.table_id = p.anchor_table_id
AND p.is_super_projection IS TRUE
GROUP BY
t.table_schema, t.table_name, p.is_segmented
ORDER BY
t.table_schema, t.table_name
;
Sample output:
schema | table | row_count | segmented | num_proj
--------+------------------------+-----------+-----------+----------
mauro | city | 5 | Yes | 2
mauro | employees | 1000000 | Yes | 2
mauro | empty | 0 | No | 0
mauro | fnames | 20 | Yes | 2
...
tpch | customer | 0 | Yes | 2
tpch | lineitem | 54010935 | Yes | 2
tpch | nation | 25 | No | 1
tpch | orders | 718277000 | Yes | 2
I did add a couple of columns: segmented (Yes/No) and num_proj. You can remove them if you want.

View Table over Language/Client/Status Table

I would like to simplify my data with a view table, MainView but am having a hard time figuring it out.
I have a Fact table that is specific to clients, language, and status. The ID in the Fact table comes from a FactLink table that just has an FactLinkID column. The Status table has an Order column that needs to be shown in the aggregate view instead of the StatusID. My Main table references the Fact table in multiple columns.
The end goal will be to be able to query the view table by the compound index of LanguageID, StatusOrder, ClientID more simply than I was before, grabbing the largest specified StatusOrder and the specified ClientID or ClientID 1. So, that is what I was hoping to simplify with the view table.
So,
Main
ID | DescriptionID | DisclaimerID | Other
----+---------------+--------------+-------------
50 | 1 | 2 | Blah
55 | 4 | 3 | Blah Blah
Fact
FactID | LanguageID | StatusID | ClientID | Description
-------+------------+----------+----------+------------
1 | 1 | 1 | 1 | Some text
1 | 2 | 1 | 1 | Otro texto
1 | 1 | 3 | 2 | Modified text
2 | 1 | 1 | 1 | Disclaimer1
3 | 1 | 1 | 1 | Disclaimer2
4 | 1 | 1 | 1 | Some text 2
FactLink
ID
--
1
2
3
4
Status
ID | Order
---+------
1 | 10
2 | 100
3 | 20
MainView
MainID | StatusOrder | LanguageID | ClientID | Description | Disclaimer | Other
-------+-------------+------------+----------+---------------+-------------+------
50 | 10 | 1 | 1 | Some text | Disclaimer1 | Blah
50 | 10 | 2 | 1 | Otro texto | NULL | Blah
50 | 20 | 1 | 2 | Modified text | NULL | Blah
55 | 10 | 1 | 1 | Some text 2 | Disclaimer2 | Blah Blah
Here's how I implemented it with just a single column that references the Fact table:
DROP VIEW IF EXISTS dbo.KeywordView
GO
CREATE VIEW dbo.KeywordView
WITH SCHEMABINDING
AS
SELECT t.KeywordID, f.ClientID, f.Description Keyword, f.LanguageID, s.[Order] StatusOrder
FROM dbo.Keyword t
JOIN dbo.Fact f
ON f.FactLinkID = t.KeywordID
JOIN dbo.Status s
ON f.StatusID = s.StatusID
GO
CREATE UNIQUE CLUSTERED INDEX KeywordIndex
ON dbo.KeywordView (KeywordID, ClientID, LanguageID, StatusOrder)
My previous query queried for everything except for that StatusOrder. But adding in the StatusOrder seems to complicate things. Here's my previous query without the StatusOrder. When I created a view on a table with just a single Fact linked column it greatly simplified things, but extending that to two or more columns has proven difficult!
SELECT
Main.ID,
COALESCE(fDescription.Description, dfDescription.Description) Description,
COALESCE(fDisclaimer.Description, dfDisclaimer.Description) Disclaimer,
Main.Other
FROM Main
LEFT OUTER JOIN Fact fDescription
ON fDescription.FactLinkID = Main.DescriptionID
AND fDescription.ClientID = #clientID
AND fDescription.LanguageID = #langID
AND fDescription.StatusID = #statusID -- This actually needs to get the largest `StatusOrder`, not the `StatusID`.
LEFT OUTER JOIN Fact dfDescription
ON dfDescription.FactLinkID = Main.DescriptionID
AND dfDescription.ClientID = 1
AND dfDescription.LanguageID = #langID
AND dfDescription.StatusID = #statusID
... -- Same for Disclaimer
WHERE Main.ID = 50
Not sure if this the most performant or elegant way to solve this problem. But I finally thought of a way to do it. The problem with the solution below is that it can no longer be indexed. So, now to figure out how to do that without having to wrap it in a derived table.
SELECT
x.ID,
x.StatusOrder,
x.LanguageID,
x.ClientID,
x.Other,
MAX(x.Description),
MAX(x.Disclaimer)
FROM (
SELECT
Main.ID,
s.StatusOrder,
f.LanguageID,
f.ClientID,
f.Description,
NULL Disclaimer,
Main.Other
FROM Main
JOIN Fact f
ON f.FactID = Main.DescriptionID
JOIN Status s ON s.StatusID = f.StatusID
UNION ALL
SELECT
Main.ID,
s.StatusOrder,
f.LanguageID,
f.ClientID,
NULL Description,
f.Description Disclaimer,
Main.Other
FROM Main
JOIN Fact f
ON f.FactID = Main.DisclaimerID
JOIN Status s ON s.StatusID = f.StatusID
) x
GROUP BY x.ID, x.StatusOrder, x.LanguageID, x.ClientID, x.Other