Count maxdate with condition - sql

(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.

Related

Query SQL Select Column Matching From Another Table

edit : Sorry gurus, I have to rephrase my question since I forgot there are 3 tables in one query.
I have three tables with tbl_goods ,tbl_units and tbl_sat which looks like this :
tbl_goods, consists of sold goods
+--------+-------+-------+-------+
| goods |code |qty |unit |
+--------+-------+-------+-------+
| cigar | G001 | 1 | pack |
| cigar | G001 | 2 | pcs |
| bread | G002 | 2 | pcs |
| soap | G003 | 1 | pcs |
+--------+-------+-------+-------+
and tbl_units as below :
+--------+-------------+-------+
| code |ucode |qty |
+--------+-------------+-------+
| KG001 | U001 | 10 |
+--------+-------------+-------+
I add letter 'K' in front of code in tbl_units to differ and make sure not collide with code in tbl_goods.
and tbl_sat as below :
+--------+-------------+
| ucode | unit |
+--------+-------------+
| U001 | pack |
+--------+-------------+
| U002 | box |
+--------+-------------+
| U003 | crate | etc
so only cigar will have conversion because table units have the code
what the result I need to show as below :
+--------+-------+-------+-------+--------+
| goods |code |qty |unit | total |
+--------+-------+-------+-------+--------+
| cigar | G001 | 1 | pack | 10 |
| cigar | G001 | 2 | pcs | 2 |
| bread | G002 | 2 | pcs | 2 |
| soap | G003 | 1 | pcs | 1 |
+--------+-------+-------+-------+--------+
so if the code in goods doesn't have match in tbl_units then it will show just as qty in tbl_goods, but if they match then it will convert multiply from tbl_units
Thank you very much..really appreciated
regards
EDIT (might worked ?) :
I try to modify from #danielpr query, and this is the result
think it worked, please help to check it out
SELECT j.code,j.qty ,j.unit, IIF(j.unit=t.unit,j.qty*u.qty,j.fqty) FROM tbl_goods j
LEFT JOIN tbl_units u on u.code ='K' || j.code
LEFT JOIN tbl_sat t ON t.ucode =u.ucode [WHERE j.code='G001']
GROUP BY j.code,j.qty
[WHERE ..] optional if omitted will list all items, but if I just want to check the cigar..just put WHERE CLAUSE
If I understand correct, you are looking for a combination of LEFT JOIN and CASE WHEN or COALESCE.
Here the CASE WHEN option:
SELECT g.goods, g.code, g.qty, g.unit,
CASE WHEN u.conversion IS NULL
THEN g.qty
ELSE g.qty * u.qty
END AS total
FROM
tbl_goods g
LEFT JOIN tbl_units u
ON g.code = u.code
AND g.unit = u.conversion;
As said, COALESCE could also do and is a bit shorter:
SELECT g.goods, g.code, g.qty, g.unit,
g.qty * COALESCE(u.qty,1) AS total
FROM
tbl_goods g
LEFT JOIN tbl_units u
ON g.code = u.code
AND g.unit = u.conversion;
But I think this option has a worse readability compared to CASE WHEN.
Therefore, I would prefer CASE WHEN here.
Try out: db<>fiddle
EDIT because the authour changed the question:
According to the new description, a further table is involved and the table structure is other than described before. So, the COALESCE option is not possible at all in this case.
We require the CASE WHEN way here:
SELECT g.goods, g.code, g.qty, g.unit,
CASE WHEN u.qty IS NULL OR u.ucode IS NULL OR t.unit IS NULL
THEN g.qty
ELSE g.qty * u.qty
END AS total
FROM
tbl_goods g
LEFT JOIN tbl_units u ON u.code = CONCAT('K', g.code)
LEFT JOIN tbl_sat t ON u.ucode = t.ucode AND g.unit = t.unit;
New sample fiddle for this new situation: db<>fiddle
SELECT
tbl_goods.goods
, tbl_goods.code
, tbl_goods.qty
, tbl_goods.unit
, IF(tbl_goods.unit=tbl_units.conversion,tbl_goods.qty*tbl_units.qty,tbl_goods.qty) total
FROM tbl_goods
LEFT JOIN tbl_units ON tbl_goods.code=tbl_units.code
on total column, we can match whether the unit in tbl_goods is same with tbl_units, which is pack.
If it is the same, then we multiply the pack qty in tbl_units with the pack in tbl_goods, else, just return the qty of tbl_goods.

Oracle SQL query comparing multiple rows with same identifier

I'm honestly not sure how to title this - so apologies if it is unclear.
I have two tables I need to compare. One table contains tree names and nodes that belong to that tree. Each Tree_name/Tree_node combo will have its own line. For example:
Table: treenode
| TREE_NAME | TREE_NODE |
|-----------|-----------|
| 1 | A |
| 1 | B |
| 1 | C |
| 1 | D |
| 1 | E |
| 2 | A |
| 2 | B |
| 2 | D |
| 3 | C |
| 3 | D |
| 3 | E |
| 3 | F |
I have another table that contains names of queries and what tree_nodes they use. Example:
Table: queryrecord
| QUERY | TREE_NODE |
|---------|-----------|
| Alpha | A |
| Alpha | B |
| Alpha | D |
| BRAVO | A |
| BRAVO | B |
| BRAVO | D |
| CHARLIE | A |
| CHARLIE | B |
| CHARLIE | F |
I need to create an SQL where I input the QUERY name, and it returns any ‘TREE_NAME’ that includes all the nodes associated with the query. So if I input ‘ALPHA’, it would return TREE_NAME 1 & 2. If I ask it for CHARLIE, it would return nothing.
I only have read access, and don’t believe I can create temp tables, so I’m not sure if this is possible. Any advice would be amazing. Thank you!
You can use group by and having as follows:
Select t.tree_name
From tree_node t
join query_record q
on t.tree_node = q.tree_node
WHERE q.query = 'ALPHA'
Group by t.tree_name
Having count(distinct t.tree_node)
= (Select count(distinct q.tree_node) query_record q WHERE q.query = 'ALPHA');
Using an IN condition (a semi-join, which saves time over a join):
with prep (tree_node) as (select tree_node from queryrecord where query = :q)
select tree_name
from treenode
where tree_node in (select tree_node from prep)
group by tree_name
having count(*) = (select count(*) from prep)
;
:q in the prep subquery (in the with clause) is the bind variable to which you will assign the various QUERY values at runtime.
EDIT
I don't generally set up the test case on online engines; but in a comment below this answer, the OP said the query didn't work for him. So, I set up the example on SQLFiddle, here:
http://sqlfiddle.com/#!4/b575e/2
A couple of notes: for some reason, SQLFiddle thinks table names should be at most eight characters, so I had to change the second table name to queryrec (instead of queryrecord). I changed the name in the query, too, of course. And, second, I don't know how I can give bind values on SQLFiddle; I hard-coded the name 'Alpha'. (Note also that in the OP's sample data, this query value is not capitalized, while the other two are; of course, text values in SQL are case sensitive, so one should pay attention when testing.)
You can do this with a join and aggregation. The trick is to count the number of nodes in query_record before joining:
select qr.query, t.tree_name
from (select qr.*,
count(*) over (partition by query) as num_tree_node
from query_record qr
) qr join
tree_node t
on t.tree_node = qr.tree_node
where qr.query = 'ALPHA'
group by qr.query, t.tree_name, qr.num_tree_node
having count(*) = qr.num_tree_node;
Here is a db<>fiddle.

Oracle conditional WHERE in stored procedure

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.

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

SQL LEFT JOIN help

My scenario: There are 3 tables for storing tv show information; season, episode and episode_translation.
My data: There are 3 seasons, with 3 episodes each one, but there is only translation for one episode.
My objetive: I want to get a list of all the seasons and episodes for a show. If there is a translation available in a specified language, show it, otherwise show null.
My attempt to get serie 1 information in language 1:
SELECT
season_number AS season,number AS episode,name
FROM
season NATURAL JOIN episode
NATURAL LEFT JOIN episode_trans
WHERE
id_serie=1 AND
id_lang=1
ORDER BY
season_number,number
result:
+--------+---------+--------------------------------+
| season | episode | name |
+--------+---------+--------------------------------+
| 3 | 3 | Episode translated into lang 1 |
+--------+---------+--------------------------------+
expected result
+-----------------+--------------------------------+
| season | episode| name |
+-----------------+--------------------------------+
| 1 | 1 | NULL |
| 1 | 2 | NULL |
| 1 | 3 | NULL |
| 2 | 1 | NULL |
| 2 | 2 | NULL |
| 2 | 3 | NULL |
| 3 | 1 | NULL |
| 3 | 2 | NULL |
| 3 | 3 | Episode translated into lang 1 |
+--------+--------+--------------------------------+
Full DB dump
http://pastebin.com/Y8yXNHrH
I tested the following on MySQL 4.1 - it returns your expected output:
SELECT s.season_number AS season,
e.number AS episode,
et.name
FROM SEASON s
JOIN EPISODE e ON e.id_season = s.id_season
LEFT JOIN EPISODE_TRANS et ON et.id_episode = e.id_episode
AND et.id_lang = 1
WHERE s.id_serie = 1
ORDER BY s.season_number, e.number
Generally, when you use ANSI-92 JOIN syntax you need to specify the join criteria in the ON clause. In MySQL, I know that not providing it for INNER JOINs results in a cross join -- a cartesian product.
LEFT JOIN episode_trans
ON episode_trans.id_episode = episode.id_episode
AND episode_trans.id_lang = 1
WHERE id_serie=1
You probably need to move the id_lang = 1 into the LEFT JOIN clause instead of the WHERE clause. Think of it this way... for all of those rows with no translation the LEFT JOIN gives you back NULLs for all of those translation columns. Then in the WHERE clause you are checking to see if that is equal to 1 - which of course evaluates to FALSE.
It would probably be easier if you included your code in the question next time instead of in a link.
Can you try using
LEFT OUTER JOIN
instead of
NATURAL LEFT JOIN