ORDER BY CASE WHEN: ORDER BY items must appear in the select list - sql-order-by

I have this example code of something I'm trying to run. Only he table names and column names were changed. What I want to do is have a result set of states and have 'NULL' be the first value and the rest of the results appear below 'NULL' in ascending order and I can't for the life of me make it work. I get the error at the bottom. This may be a very "noobish" question, but can anyone help? Much appreciated everyone!
SELECT DISTINCT
State
FROM TABLE1 (NOLOCK)
WHERE COLUMN1 NOT LIKE '%THAT%'
AND COLUMN1 NOT LIKE '%THIS%'
UNION
SELECT 'NULL'
ORDER BY ( CASE WHEN State = 'NULL' THEN 0
ELSE 1
END );
Error Message:
ORDER BY items must appear in the select list if the statement contains a UNION, INTERSECT or EXCEPT operator.

You need to select that column... even if you don't use it later, the order by requires it to be present in the select.
SELECT DISTINCT
State,
CASE WHEN State = 'NULL' THEN 0
ELSE 1 END orderId
FROM TABLE1 (NOLOCK)
WHERE COLUMN1 NOT LIKE '%THAT%'
AND COLUMN1 NOT LIKE '%THIS%'
ORDER BY ( CASE WHEN State = 'NULL' THEN 0
ELSE 1
END );

Related

Data exists within database but doesnt show up on webi report

Hi I am building a v simple report as below; when I include the customer reference number and filter for another column (resolved time) being NULL then the number of incidents are reduced. (I know they exist within the database with the same filters)
(only INC1988464 is showing when more incidents should be showing)
Is there a way to test issues such as this in webi? Or a way to resolve this? Thanks in advance
Here is the sql used to make the report:
SELECT
'INC'||TRIM(to_char(ead_incident.incident,'0000000')),
ead_incident_credit.circuit_ref,
ead_incident_credit.customer_ref,
ead_incident_credit.data_rate,
ead_incident_credit.connection_type,
ead_incident_credit.completed_date,
ead_incident_credit.wholesaler_name,
ead_incident_credit.opened_datetime,
ead_incident_credit.resolved_datetime,
ead_incident_credit.resolution_details,
ead_incident_credit.resolution_duration_seconds,
ead_incident_credit.access_circuit_core_network,
ead_incident_credit.charge_amount,
ead_incident_credit.or_cost,
ead_incident_credit.sky_cost,
ead_incident_credit.service_credit_applicable,
ead_incident_credit.service_credit_due,
CASE WHEN COALESCE(COALESCE(ead_incident.actual_end_datetime,ead_incident.impact_end_datetime),ead_incident.resolved_datetime) IS NOT NULL THEN CASE WHEN ead_incident.impact_type = 'Full Outage' AND COALESCE(ead_incident.cause_classification,'') <> 'Resolved - No fault found' AND COALESCE(odwh_data.ead_within_sla('TTR',ead_incident.opened_datetime, COALESCE(COALESCE(ead_incident.actual_end_datetime,ead_incident.impact_end_datetime),ead_incident.resolved_datetime)),'f') = 'f' THEN 'Y' ELSE 'N' END ELSE NULL END,
CASE WHEN ead_incident.correlation = 't' THEN 'Y' ELSE 'N' END,
odwh_system.kpi_nextweek_startdate(),
odwh_system.kpi_currentweek_startdate()
FROM
odwh_data.ead_incident ead_incident INNER JOIN odwh_data.ead_incident_credit ead_incident_credit ON (ead_incident_credit.incident=ead_incident.incident)
WHERE
(
CASE WHEN COALESCE(COALESCE(ead_incident.actual_end_datetime,ead_incident.impact_end_datetime),ead_incident.resolved_datetime) IS NOT NULL THEN CASE WHEN ead_incident.impact_type = 'Full Outage' AND COALESCE(ead_incident.cause_classification,'') <> 'Resolved - No fault found' AND COALESCE(odwh_data.ead_within_sla('TTR',ead_incident.opened_datetime, COALESCE(COALESCE(ead_incident.actual_end_datetime,ead_incident.impact_end_datetime),ead_incident.resolved_datetime)),'f') = 'f' THEN 'Y' ELSE 'N' END ELSE NULL END = 'Y'
AND
(
CASE WHEN ead_incident.deleted = 't' THEN 'Y' ELSE 'N' END = 'N'
AND
(
CASE WHEN ead_incident.wholesaler = 't' THEN 'Y' WHEN ead_incident.wholesaler = 'f' THEN 'N' END = 'Y'
OR
CASE WHEN ead_incident.wholesaler = 't' THEN 'Y' WHEN ead_incident.wholesaler = 'f' THEN 'N' END Is Null
)
)
)
If you can create a free-hand SQL query which exhibits your issue like this...
SELECT
'INC1988464' AS [Incident Number]
, 'SKY-COLT-003' AS [Customer Reference]
, CONVERT (DATETIME, '2022-05-08 11:17:49') AS [Resovled Time]
UNION
SELECT
'INC1988464' AS [Incident Number]
, 'SKY-COLT-005' AS [Custome rReference]
, CONVERT (DATETIME, '2022-05-09 9:03:21') AS [Resovled Time]
UNION
SELECT
'INC1988464' AS [Incident Number]
, 'SKY-COLT-007' AS [Customer Reference]
, NULL AS [Resovled Time]
UNION
SELECT
'INC1988464' AS [Incident Number]
, 'SKY-COLT-009' AS [Customer Reference]
, CONVERT (DATETIME, '2022-05-09 10:17:02') AS [Resovled Time];
Then we can take that query, put it into a WebI report, replicate the issue, and possibly resolve it.
First and easiest thing to check. Do you have any filters already on your report page or are you starting on a brand new page? The filters may be against the page or the individual report block.
If you have, then remove them and see if the issue is resolved. Note that there may also be a ranking in there, returning the top 1 incidents.
If not, then there will be something in the report logic preventing the records from being returned. Comment out the where clauses and make sure to add the columns from the where clauses into your result set. From here you have two things to check:
1/ All rows are being returned
2/ You can see which rows are going to be filtered by your where clause based on what you see on your report based on your logic. You have nested case expressions which may not be doing what you expected for example.

Get default value (and context column) when group by returns no records in Oracle

I have a query that I need for it to return a record even when there are no records. In the case where there are records, I simply want those records returned. On the other hand, when there are no records, I need it to still return a record but with the value for the "context" column (the GROUP BY column) equal to the value of the GROUP BY column that did not meet the criteria and a default value for aggregate function/column (e.g., 0). I tried a subquery:
SELECT
(
SELECT
CONTEXT,
SUM(VAL)
FROM
A_TABLE
WHERE
COL = 'absent'
GROUP BY
CONTEXT
)
FROM
DUAL;
but anything greater than one column in the subquery SELECT clause fails w/ a "too many values" message.
I also tried a UNION (with a little more context to more faithfully represent my situation):
SELECT
*
FROM
(
SELECT
CONTEXT,
SUM(VAL)
FROM
A_TABLE
WHERE
COL = 'absent'
GROUP BY
CONTEXT
UNION
SELECT
CONTEXT,
0
FROM
B_TABLE
)
AB_TABLE
INNER JOIN C_TABLE C -- just a table that I need to join to
ON
C.ID = AB_TABLE.C_ID
WHERE
C.ID = 10
AND ROWNUM = 1 -- excludes 2nd UNION subquery result when 1st returns record;
This one does work but I don't know why since the 2nd UNION subquery does not seem to be expressly connected w/ the first (I need the 2nd CONTEXT value to be the same as the 1st for the case where the 1st returns no records). The problem is that the real query does not return any records when I try to implement a similar strategy. I would like to see if there's a better way to approach this problem and perhaps get it to work for the real query (not included as it is too large and somewhat sensitive).
I am not sure I understand the question, but let's try.
I believe what you are saying is this. You have a table called A_TABLE, with columns CONTEXT, VAL, COL (and perhaps others as well).
You want to group by CONTEXT, and get the sum of VAL but only for those rows where COL = 'absent'. Otherwise you want to return a default value (let's say 0).
This can be done with conditional aggregation. The condition is in a CASE expression within the SUM, not in a WHERE clause (as you saw already, if you filter by COL='absent', in a WHERE clause, the query - past the WHERE clause - has no knowledge of the CONTEXT values that don't appear in any rows with COL = 'absent').
If the "default value" was NULL, you could do it like this:
select context, sum(case when col = 'absent' then value end) as val
from a_table
group by context
;
If the default value is anything other than NULL, the temptation may be to use NVL() around the sum. However, if VAL may be NULL, then it is possible that SUM(VAL) is NULL even when there are rows with COL = 'absent'. To address that possibility, you must leave the sum as NULL in those cases, and instead set the value to 0 (or whatever other "default value") only when there are NO rows with COL = 'absent'. Here is one way to do that. Still a standard "conditional" aggregate query:
select context,
case when count(case when col = 'absent' then 1 end) > 0
then sum(case when col = 'absent' then value end)
else 0 -- or whatever "default value" you must assign here
end as val
from a_table
group by context
;
Here's another way you could handle it that avoids the two additional tables (B_TABLE and C_TABLE).
SELECT context
, MAX(val)
FROM (
SELECT context
, SUM(val) as val
FROM a_table
WHERE col = 'absent'
GROUP BY context
UNION
SELECT context
, 0 as val
FROM a_table
) t
GROUP BY context
This assumes the default value you want to return is 0 and that any value in A_TABLE.VAL will be a positive integer.
http://sqlfiddle.com/#!4/c6ca0/20
SELECT b.context
, sum(a.val)
FROM b_table b
LEFT OUTER JOIN a_table a
ON a.context = b.context
AND a.col = 'absent'
GROUP BY b.context

How to check if a set of values exist in item table in Oracle

I have two table- 'Order' and 'Order Item'.
Order table contains-
Order Number, Order Date, etc.
Order Item table contains-
Order Number, Order Item Number, Product Name, etc.
The joining condition between these two tables is on Order Number.
In my target table I need orders and a flag. The flag should tell, if there is a predefined set of products which has been ordered as part of that order then it should be set to 'Yes'.
E.g., Suppose an order 'ORD-01' contains three products in Order Item table - 'Mobile', 'PC' and 'Tablet', then my resulting table should contain Order Number as ORD-01 and Flag as 'Yes'.
In the same way, if order 'ORD-02' contains only two prods 'Mobile' an 'Tablet', then the resulting table should contains 'ORD-02' and Flag 'No'.
Similarly, if order 'ORD-03' contains three different prods 'Notebook', 'PC' an 'Tablet', then the resulting table should contains 'ORD-03' and Flag 'No'.
As per my understanding, I have written below query-
SELECT order_number,(SELECT CASE WHEN COUNT(DISTINCT product_name)>=3
THEN 'Yes' ELSE 'No' END Prod_Flag
FROM order_item b
WHERE a.order_number=b.order_number
AND b.product_name IN ('Mobile','PC','Tablet'))
FROM order a
WHERE order_date>last_run_date;
But it takes too much of time, as the order item is a very big table (>1 Billion rows). However I need incremental data based upon order date from Order table. Even if there is an index of order number in both tables, it takes time.
Would a query like this get you to your result any quicker?
SELECT ON.ORDER_NUMBER,
CASE WHEN SET_FOUND.ORDER_NUMBER IS NOT NULL
THEN 'Yes' ELSE 'No' END PROD_FLAG
FROM ORDER ON,
(SELECT ORDER_NUMBER
FROM ORDER_ITEM
WHERE PRODUCT_NAME = 'Mobile'
INTERSECT
SELECT ORDER_NUMBER
FROM ORDER_ITEM
WHERE PRODUCT_NAME = 'PC'
INTERSECT
SELECT ORDER_NUMBER
FROM ORDER_ITEM
WHERE PRODUCT_NAME = 'Tablet') SET_FOUND
WHERE ON.ORDER_NUMBER = SET_FOUND.ORDER_NUMBER (+)
My proposal would be this one:
WITH t AS
(SELECT product_name, order_number
FROM order_item
WHERE product_name IN ('Mobile','PC','Tablet')
GROUP BY order_number, product_name)
SELECT order_number,
CASE WHEN COUNT(DISTINCT product_name) >= 3 THEN 'Yes' ELSE 'No' END
FROM t
JOIN order USING (order_number)
GROUP BY order_number
Is the order number an increasing sequence number? If so the one approach would be to limit data selected from the order_item, which you said is a large table, by putting condition on order_number, which you said is an indexed column. I assume last_run_date signifficantly limits number of concerned orders.
If so you can:
select min(order_number) into order_num_from from Order where order_date>last_run_date
and then make your query
SELECT order_number,(SELECT CASE WHEN COUNT(DISTINCT product_name)>=3
THEN 'Yes' ELSE 'No' END Prod_Flag
FROM order_item b
WHERE a.order_number=b.order_number
AND b.order_number> order_num_from
AND b.product_name IN ('Mobile','PC','Tablet'))
FROM order a
WHERE order_date>last_run_date;
If this runs significantly faster (I didn't see explain plan, so this is just an idea how to avoid full table scan ), put an index on order_date column and eventually make finding order_num_from into subquery to have one single query.
Generally, your query is right. As I understood, you wish to raise it's speed. If so, there are several ways you can try.
You can consider to put these tables into indexed cluster. It will store the data physically joined so querying would require less physical reads.
For this query, server should scan two tables: one for appropriate dates (eigther full table scan or index scan), other for products and joins the results by reading ORDER_NUMBER via rowid. It isn't very fast anyway. The simpliest way is to add (ORDER_DATE, ORDER_NUMBER) index for ORDERs and (ORDER_NUMBER, PRODUCT_NAME) index for ORDER_ITEMs; it will allow to use indexes only.
Maybe it would be suitable to make a fast-refreshable materialized view, something like
create materialized view as
select
a.order_date,
a.order_number,
sum(case when b.product_name = 'Mobile' then 1 else 0 end) cnt_mobiles,
sum(case when b.product_name = 'PC' then 1 else 0 end) cnt_pcs,
sum(case when b.product_name = 'Tablet' then 1 else 0 end) cnt_tablets
from
order a, order_item b
where
a.order_number = b.order_number
group by
a.order_number, a.order_date
If it would be impossible to make this fast-refreshable, you can do equal thing manually using trigger. Anyway, in this case you'll get precalculated data ready to check.

Oracle: MIN() Statement causes empty row returns

I'm having a small issue with sorting the data returned from a query, with the aim of getting the oldest updated value in dataset so that I can update only that record. Here's what I'm doing:
WHERE ROWNUM = 1 AND TABLE1.ID != V_IGNOREID
AND TABLE1.LASTREADTIME = (SELECT MIN(TABLE1.LASTREADTIME) FROM TABLE1)
ORDER BY TABLE1.LASTREADTIME DESC;
It makes no difference as to whether the ORDER BY statement is included or not. If I only use the ROWNUM and equality checks, I get data, but it alternates between only two rows, which is why I'm trying to use the LASTREADTIME data (so that I can modify more than these two rows). Anybody have any thoughts on this, or any suggestions as to how I can use the MIN function effectively?
Cheers
select * from (
-- your original select without rownum and with order by
)
WHERE ROWNUM = 1
EDIT some explanation
I think the order by clause is applied on the resultset after the where clause. So if the rownum = 1 is in the same select statement with the order by, then it will be applied first and the order by will order only 1 row, which will be the first row of the unordered resultset.

Reference parent query column in subquery (Oracle)

How can I reference a column outside of a subquery using Oracle? I specifically need to use it in the WHERE statement of the subquery.
Basically I have this:
SELECT Item.ItemNo, Item.Group
FROM Item
LEFT OUTER JOIN (SELECT Attribute.Group, COUNT(1) CT
FROM Attribute
WHERE Attribute.ItemNo=12345) A ON A.Group = Item.Group
WHERE Item.ItemNo=12345
I'd like to change WHERE Attribute.ItemNo=12345 to WHERE Attribute.ItemNo=Item.ItemNo in the subquery, but I can't figure out if this is possible. I keep getting "ORA-00904: 'Item'.'ItemNo': Invalid Identifier"
EDIT:
Ok, this is why I need this kind of structure:
I want to be able to get a count of the "Error" records (where the item is missing a value) and the "OK" records (where the item has a value).
The way I have set it up in the fiddle returns the correct data. I think I might just end up filling in the value in each of the subqueries, since this would probably be the easiest way. Sorry if my data structures are a little convoluted. I can explain if need be.
My tables are:
create table itemcountry(
itemno number,
country nchar(3),
imgroup varchar2(10),
imtariff varchar2(20),
exgroup varchar2(10),
extariff varchar2(20) );
create table itemattribute(
attributeid varchar2(10),
tariffgroup varchar2(10),
tariffno varchar2(10) );
create table icav(
itemno number,
attributeid varchar2(10),
value varchar2(10) );
and my query so far is:
select itemno, country, imgroup, imtariff, im.error "imerror", im.ok "imok", exgroup, extariff, ex.error "exerror", ex.ok "exok"
from itemcountry
left outer join (select sum(case when icav.itemno is null then 1 else 0 end) error, sum(case when icav.itemno is not null then 1 else 0 end) ok, tariffgroup, tariffno
from itemattribute ia
left outer join icav on ia.attributeid=icav.attributeid
where (icav.itemno=12345 or icav.itemno is null)
group by tariffgroup, tariffno) im on im.tariffgroup=imgroup and imtariff=im.tariffno
left outer join (select sum(case when icav.itemno is null then 1 else 0 end) error, sum(case when icav.itemno is not null then 1 else 0 end) ok, tariffgroup, tariffno
from itemattribute ia
left outer join icav on ia.attributeid=icav.attributeid
where (icav.itemno=12345 or icav.itemno is null)
group by tariffgroup, tariffno) ex on ex.tariffgroup=exgroup and extariff=ex.tariffno
where itemno=12345;
It's also set up in a SQL Fiddle.
You can do it in a sub-query but not in a join. In your case I don't see any need to. You can put it in the join condition.
select i.itemno, i.group
from item i
left outer join ( select group, itemno
from attribute b
group by group itemno ) a
on a.group = i.group
and i.itemno = a.itemno
where i.itemno = 12345
The optimizer is built to deal with this sort of situation so utilise it!
I've changed the count(1) to a group by as you need to group by all columns that aren't aggregated.
I'm assuming that your actual query is more complicated than this as with the columns you're selecting this is probably equivilent to
select itemno, group
from item
where itemno = 12345
You could also write your sub-query with an analytic function instead. Something like count(*) over ( partition by group).
As an aside using a keyword as a column name, in this case group is A Bad Idea TM. It can cause a lot of confusion. As you can see from the code above you have a lot of groups in there.
So, based on your SQL-Fiddle, which I've added to the question I think you're looking for something like the following, which doesn't look much better. I suspect, given time, I could make it simpler. On another side note explicitly lower casing queries is never worth the hassle it causes. I've followed your naming convention though.
with sub_query as (
select count(*) - count(icav.itemno) as error
, count(icav.itemno) as ok
, min(itemno) over () as itemno
, tariffgroup
, tariffno
from itemattribute ia
left outer join icav
on ia.attributeid = icav.attributeid
group by icav.itemno
, tariffgroup
, tariffno
)
select ic.itemno, ic.country, ic.imgroup, ic.imtariff
, sum(im.error) as "imerror", sum(im.ok) as "imok"
, ic.exgroup, ic.extariff
, sum(ex.error) as "exerror", sum(ex.ok) as "exok"
from itemcountry ic
left outer join sub_query im
on ic.imgroup = im.tariffgroup
and ic.imtariff = im.tariffno
and ic.itemno = im.itemno
left outer join sub_query ex
on ic.exgroup = ex.tariffgroup
and ic.extariff = ex.tariffno
and ic.itemno = ex.itemno
where ic.itemno = 12345
group by ic.itemno, ic.country
, ic.imgroup, ic.imtariff
, ic.exgroup, ic.extariff
;
You can put WHERE attribute.itemno=item.itemno inside the subquery. You are going to filter the data anyway, filtering the data inside the subquery is usually faster too.

Resources