Select query by filtering multiple rows - oracle

I have a table like this:
col | status
----+-------
1 | 0
1 | 1
2 | 1
2 | 2
3 | 1
3 | 0
I want to select only the row with having max status value. But also want to ignore if there is any 0 in status. So expected rows would be as follows (I am ignoring 1 and 3 for having status = 0).
col | status
----+-------
2 | 2
I only can pick the rows based on max(). But when I add another clause to filter the zero, it doesn't work.
SELECT col, max(status)
FROM my_table
WHERE
(select count(*)
from my_table t1
where t1.col = col
and status = 0) = 0
GROUP BY col;
Any guide will do for me.

Use HAVING clause:
SELECT col, MAX(STATUS)
FROM tab
GROUP BY col
HAVING SUM(CASE WHEN STATUS = 0 THEN 1 ELSE 0 END) = 0;
DBFiddle
If your minimal value for STATUS is 0 then you could use:
SELECT col, MAX(STATUS)
FROM tab
GROUP BY col
HAVING MIN(STATUS) > 0;

I'm a novice at SQL, but I'm sure rank() will serve the purpose.
select tabl.col, tabl.status from
(select col, status, rank over(status desc) as rnk
from tab where status = 0) tabl
where tabl.rnk = 1
and rownum = 1;

Related

How to force a set of possible values in conditional aggregation?

In Oracle I have a query that uses conditional aggregation to display totals.
See http://sqlfiddle.com/#!4/ab1915/2
select ts.description,
sum(case when ta.group_number = 1 then 1 else 0 end) group_one
,sum(case when ta.group_number = 2 then 1 else 0 end) group_two
,sum(case when ta.group_number = 3 then 1 else 0 end) group_three
from ta
join ts on ta.status_id=ts.status_id
group by ts.description;
However, I need the results to also show another row for the status "cancelled", which is not currently showed because there are not any record with that status, but I need to display "cancelled" with 0 counts anyway,
Any idea how I would do that?
The DDL for this example is
create table ta (group_number number, status_id number);
create table ts (status_id number, description varchar(111));
insert into ta (group_number,status_id) values (1,1);
insert into ta (group_number,status_id) values (2,1);
insert into ta (group_number,status_id) values (3,2);
insert into ta (group_number,status_id) values (3,3);
insert into ta (group_number,status_id) values (3,3);
Use an outer join so you pick up all of the ts valueswhich means you need to reverse the order of the tables; and I'd use count() instead of sum():
select ts.description,
count(case when ta.group_number = 1 then 1 end) group_one
,count(case when ta.group_number = 2 then 1 end) group_two
,count(case when ta.group_number = 3 then 1 end) group_three
,count(case when ta.group_number = 4 then 1 end) group_four
from ts
left join ta on ta.status_id=ts.status_id
group by ts.description;
DESCRIPTION GROUP_ONE GROUP_TWO GROUP_THREE
-------------------- ---------- ---------- -----------
started 1 1 0
finished 0 0 2
cancelled 0 0 0
progressing 0 0 1
Updated SQL Fiddle
The case expression can evaluate to anything when matched, it just has to be non-null. From the docs:
If you specify expr, then COUNT returns the number of rows where expr is not null.
so where the case is not matched it evaluates to null, which is not counted. (You can have else null if you prefer to be explicit, but I tend to prefer brevity...)

Count and Sum inside a select with multiple sums and counts

I'm using sub-query factoring and I have a query that returns invoice lines, and in the end I have this final sub-query:
I've already tried Partition but without success
SELECT
COUNT(CASE WHEN PC <> 0 THEN 1 END) AS A_LINECOUNT,
SUM(CASE WHEN PC > 0 THEN NR ELSE 0 END) AS B_PRODUCTCOUNT,
COUNT(CASE WHEN ALLOW_PAY = 1 THEN 1 END) AS C_INVOICECOUNT, --- ERROR
SUM(CASE WHEN ALLOW_PAY = 1 THEN MISSING_VALUE ELSE 0 END) AS D_INVOICETOTAL, --- ERROR
COUNT(CASE WHEN IS_NON_LIQUIDABLE_PRODUCT = 1 THEN 1 END) AS E_CONDITIONCOUNT,
COUNT(CASE WHEN IS_LIQUIDABLE_PRODUCT = 1 THEN 1 END) AS F_CONDITIONCOUNT
FROM MAIN_Q
The calculation of C_INVOICECOUNT and D_INVOICETOTAL is not correct because their values are repeated within each line of the invoice. Please consider that um MAIN_Q i also have a document_id where i can group by.
thanks
Maybe I understood correctly, maybe not, but this is too long for comment. If yes, C_INVOICECOUNT can be count as:
count(distinct case when allow_pay = 1 then document_id end)
But the problem is with D_INVOICETOTAL. You have repeated values for each invoice here and details which do not repeat. If so, add row numbering to your query:
select main_q.*, row_number() over (partition by document_id) rn from main_q
and then in problematic places use rn = 1:
select ...
count(case when rn = 1 and allow_pay = 1 then 1 end),
sum(case when rn = 1 and allow_pay = 1 then missing_value else 0 end)
...
from (select main_q.*, row_number() over (partition by document_id) rn from main_q)
Only first rows for each invoice will be analysed. Of course you can add row_number in earlier step.

Insert New Value from another field value in the same row (using oracle database)

I have two tables (MST_IP and MST_ROUTER) like this below:
MST_IP
SEQ IP_NUMBER STATUS
1 12.123.0.0 0
2 12.123.0.1 0
3 12.104.0.1 0
4 12.104.0.2 0
5 13.123.0.1 0
6 13.123.0.2 0
7 11.555.8.1 0
8 11.555.8.2 0
I want to insert IP_NUMBER from MST_IP to MST_ROUTER (IP_NUMBER and REMOTE_NUMBER). What I want the data will be like this below.
MST_ROUTER
USER_ID IP_NUMBER REMOTE_NUMBER
AA 12.123.0.0 12.123.0.1
BB 12.104.0.1 12.104.0.2
CC 13.123.0.1 13.123.0.2
DD 11.555.8.1 11.555.8.2
below is my code.
/* Formatted on 9/28/2017 11:44:11 AM (QP5 v5.115.810.9015) */ SET SERVEROUTPUT ON
DECLARE INS VARCHAR2 (100); INS2 VARCHAR2 (100); SEQ_R INTEGER; BEGIN SEQ_R := 1;
SELECT IP_NUMBER
INTO INS
FROM ( SELECT *
FROM MST_IP
WHERE STATUS = 0
ORDER BY SEQ ASC)
WHERE ROWNUM = 1;
SELECT IP_NUMBER
INTO INS2
FROM ( SELECT *
FROM MST_IP
WHERE STATUS = 0 AND SEQ = SEQ_R + 1
ORDER BY SEQ ASC)
WHERE ROWNUM = 1;
INSERT INTO MST_ROUTER (USER_ID, IP_NUMBER)
VALUES ('HMJ-BKS', INS);
COMMIT;
UPDATE MST_IP
SET STATUS = 1
WHERE IP_NUMBER = INS;
COMMIT;
UPDATE MST_ROUTER
SET REMOTE_NUMBER = INS2
WHERE USER_ID = 'HMJ-BKS';
COMMIT;
UPDATE MST_IP
SET STATUS = 1
WHERE IP_NUMBER = INS2;
COMMIT;
DBMS_OUTPUT.PUT_LINE (INS); END; /
In my code, I change STATUS on MST_IP = 1 if the data already inserted in MST_ROUTER. The problem my code is not working after for insert the second row. Any comment will be appreciate.
One trick to bring the local and remote IP addresses in line with each other is to join a subquery of local addresses to their remote counterparts, and then insert this result into the MST_ROUTER table.
INSERT INTO MST_ROUTER (USER_ID, IP_NUMBER, REMOTE_NUMBER)
SELECT
CAST(t1.SEQ / 2 AS UNSIGNED) AS SEQ, t1.IP_NUMBER, t2.IP_NUMBER
FROM
(
SELECT SEQ - 1 AS SEQ, IP_NUMBER
FROM MST_IP
WHERE MOD((SEQ - 1), 2) = 0
) t1
LEFT JOIN MST_IP t2
ON t1.SEQ = t2.SEQ - 2
Below is a link to a demo which shows the output of the select component of my query.
Demo
Output:
SEQ IP_NUMBER IP_NUMBER
0 12.123.0.0 12.123.0.1
1 12.104.0.1 12.104.0.2
2 13.123.0.1 13.123.0.2
3 11.555.8.1 11.555.8.2

Query to exclude row based on another row's filter

I'm using Oracle 10g.
Question: How can I write query to return just ID only if ALL the codes for that ID end in 6? I don't want ID=1 because not all its codes end in 6.
TABLE_A
ID Code
===============
1 100
1 106
2 206
3 316
3 326
4 444
Desired Result:
ID
==
2
3
You simply want each ID where the count of rows for that id is the same as the count of rows where the third digit is six.
SELECT ID
FROM TABLE_A
GROUP BY ID
HAVING COUNT(*) = COUNT(CASE WHEN SUBSTR(code,3,1) = '6' THEN 1 END)
Try this:
SELECT DISTINCT b.id
FROM (
SELECT id,
COUNT(1) cnt
FROM table_a
GROUP BY id
) a,
(
SELECT id,
COUNT(1) cnt
FROM table_a
WHERE CODE LIKE '%6'
GROUP BY id
)b
WHERE a.id = b.id
AND a.cnt = b.cnt
Alternative using ANALYTIC functions:
SELECT DISTINCT id
FROM
(
SELECT id,
COUNT(1) OVER(PARTITION BY id) cnt,
SUM(CASE WHEN code LIKE '%6' THEN 1 ELSE 0 END) OVER(PARTITION BY id) sm
FROM table_a
)
WHERE cnt = sm

Oracle Procedure to join two tables with latest status

Please help me make an oracle stored procedure ; I have two tables
tblLead:
lead_id Name
1 x
2 y
3 z
tblTransaction:
Tran_id lead_id date status
1 1 04/20/2010 call Later
2 1 05/05/2010 confirmed
I want a result like
lead_id Name status
1 x confirmed
2 y not available !
3 z not available !
Use an outer join to the relevant rows of tblTransaction:
SQL> SELECT l.lead_id, l.NAME,
2 CASE
3 WHEN t.status IS NULL THEN
4 'N/A'
5 ELSE
6 t.status
7 END status
8 FROM tbllead l
9 LEFT JOIN (SELECT lead_id,
10 MAX(status) KEEP(DENSE_RANK FIRST
11 ORDER BY adate DESC) status
12 FROM tbltransaction
13 GROUP BY lead_id) t ON l.lead_id = t.lead_id;
LEAD_ID NAME STATUS
---------- ---- ----------
1 x confirmed
2 y N/A
3 z N/A
Alternatively you can use analytics:
SQL> SELECT lead_id, NAME, status
2 FROM (SELECT l.lead_id, l.NAME,
3 CASE
4 WHEN t.status IS NULL THEN
5 'N/A'
6 ELSE
7 t.status
8 END status,
9 row_number()
10 over(PARTITION BY l.lead_id ORDER BY t.adate DESC) rn
11 FROM tbllead l
12 LEFT JOIN tbltransaction t ON l.lead_id = t.lead_id)
13 WHERE rn = 1;
LEAD_ID NAME STATUS
---------- ---- ----------
1 x confirmed
2 y N/A
3 z N/A
It can be written in plain SQL as follows,
SELECT lead_id, name, NVL(status,'not available !')
FROM (
SELECT tblLead.lead_id, tblLead.name, tblTransaction.status,
rank ( ) OVER (PARTITION BY tblTransaction.lead_id ORDER BY tblTransaction.datee DESC, tblTransaction.tran_id DESC) rank
FROM tblLead
LEFT JOIN tblTransaction ON tblLead.lead_id = tblTransaction.lead_id
)
WHERE rank = 1
ORDER BY lead_id;
Or you may think of writing a view as follows,
CREATE VIEW trx_view AS
------
------;
Personally I think stored procedure is not necessary for scenarios like this.

Resources