Query Optimization in oracle - sql

I need to fetch result from one tables and one view joined on 2 columns.
View: 8million records
Table: 5K Records
I went through query plan and observe that query will take very long to run and infact try to run this query but not getting any result.
Please help me in optimize the query.I am not using any hint.
SELECT coupon_upc
, sum ( loyalty_a ) a
, sum ( loyalty_b ) b
, sum ( loyalty_c ) c
, sum ( loyalty_x )
FROM ( SELECT ccd.coupon_upc AS coupon_upc
, ( CASE WHEN a.loyalty_cell = 'A' then 1 else 0 end ) AS loyalty_a
, ( CASE WHEN a.loyalty_cell = 'B' then 1 else 0 end ) AS loyalty_b
, ( CASE WHEN a.loyalty_cell = 'C1' then 1 else 0 end ) AS loyalty_c
, ( CASE WHEN a.loyalty_cell = 'X' then 1 else 0 end ) AS loyalty_x
FROM view1 a
, ( select distinct coupon_upc
, coupon_id
, division
from table2
where schedule_key = 'XXX' ) ccd
WHERE a.campaign_code = 'XXX'
AND a.coupon_id = ccd.coupon_id
AND a.division = ccd.division ) a
GROUP BY coupon_upc

Without the explain plan, or the schema/DDL, there is a limitted amount of optimisation that can be done.
Here is an alternative, but you'd need to test it to see if it makes any difference. (Replace a join with a correlated sub-query.)
SELECT
coupon_upc, sum(loyalty_a) a, sum(loyalty_b) b, sum(loyalty_c) c, sum(loyalty_x) x
FROM
(
SELECT
(
SELECT
coupon_upc
FROM
table2
WHERE
schedule_key = 'XXX'
AND coupon_id = a.coupon_id
AND division = a.division
GROUP BY
coupon_upc
) as coupon_upc,
(case when a.loyalty_cell = 'A' then 1 else 0 end) as loyalty_a,
(case when a.loyalty_cell = 'B' then 1 else 0 end) as loyalty_b,
(case when a.loyalty_cell = 'C1' then 1 else 0 end) as loyalty_c,
(case when a.loyalty_cell = 'X' then 1 else 0 end) as loyalty_x
FROM
view1 a
WHERE
a.campaign_code = 'XXX'
) a
GROUP BY
coupon_upc
Other than that, the kind of optimisations are:
- persisting the view
- indexes
- refactoring data structures
EDIT
Another possible refactor of the query... I don't know how well Oracle would optimise the 4 instances of correlated sub-queries.
SELECT
coupon_upc,
SUM((SELECT COUNT(*) FROM view1 WHERE campaign_code = 'XXX' AND loyalty_cell = 'A' AND coupon_id = map.coupon_id AND division = map.division)) AS loyalty_a,
SUM((SELECT COUNT(*) FROM view1 WHERE campaign_code = 'XXX' AND loyalty_cell = 'B' AND coupon_id = map.coupon_id AND division = map.division)) AS loyalty_b,
SUM((SELECT COUNT(*) FROM view1 WHERE campaign_code = 'XXX' AND loyalty_cell = 'C1' AND coupon_id = map.coupon_id AND division = map.division)) AS loyalty_c,
SUM((SELECT COUNT(*) FROM view1 WHERE campaign_code = 'XXX' AND loyalty_cell = 'X' AND coupon_id = map.coupon_id AND division = map.division)) AS loyalty_x
FROM
(
SELECT coupon_upc, coupon_id, division
FROM table2 WHERE schedule_key = 'xxx'
GROUP BY coupon_upc, coupon_id, division
)
AS map
GROUP BY
coupon_upc
Or maybe...
SELECT
map.coupon_upc, SUM(data.loyalty_a) AS a, SUM(data.loyalty_b) AS b, SUM(data.loyalty_c) AS c, SUM(data.loyalty_x) AS X
FROM
(
SELECT coupon_upc, coupon_id, division
FROM table2 WHERE schedule_key = 'xxx'
GROUP BY coupon_upc, coupon_id, division
)
AS map
INNER JOIN
(
SELECT
coupon_id,
division,
SUM(CASE WHEN loyalty_cell = 'A' THEN 1 ELSE 0 END) AS loyalty_a,
SUM(CASE WHEN loyalty_cell = 'B' THEN 1 ELSE 0 END) AS loyalty_b,
SUM(CASE WHEN loyalty_cell = 'C1' THEN 1 ELSE 0 END) AS loyalty_c,
SUM(CASE WHEN loyalty_cell = 'X' THEN 1 ELSE 0 END) AS loyalty_x
FROM
view1
WHERE
campaign_code = 'XXX'
)
AS data
ON data.coupon_id = map.coupon_id
AND data.division = map.division
GROUP BY
map.coupon_upc

Another possible rewrite would be:
SELECT
map.coupon_upc
, COUNT(CASE WHEN a.loyalty_cell = 'A' THEN 1 ELSE NULL END) AS loyalty_a
, COUNT(CASE WHEN a.loyalty_cell = 'B' THEN 1 ELSE NULL END) AS loyalty_b
, COUNT(CASE WHEN a.loyalty_cell = 'C1' THEN 1 ELSE NULL END) AS loyalty_c
, COUNT(CASE WHEN a.loyalty_cell = 'X' THEN 1 ELSE NULL END) AS loyalty_x
FROM
( SELECT coupon_upc, coupon_id, division
FROM table2 WHERE schedule_key = 'xxx'
GROUP BY coupon_upc, coupon_id, division
) AS map
JOIN view1 a
ON a.coupon_id = map.coupon_id
AND a.division = map.division
WHERE
a.campaign_code = 'xxx'
GROUP BY
map.coupon_upc
Do you have indexes on the fields that are used in the JOIN, WHERE and the GROUP BY clauses?

Related

Stored Procedure with composite Order by

I am creating a stored procedure like this :
select *
from INVENTORYTABLE inv
join INVENTORYTABLE_New invNew on inv.Stock_ID = invNew.Stock_ID
where
(
(AvailabilityStatus ='A' Or AvailabilityStatus = 'B' Or AvailabilityStatus = 'D' Or AvailabilityStatus = 'H' or AvailabilityStatus='E' ) And
MediaCount>0
)
I want to sort the results by stockId for Availability status 'A' to 'H' and then for 'E'.
Is this possible?
What I want is to show results from A to H ordered by StockId and then E below the ordered by stock Id.
You may implement an ORDER BY clause using a CASE expression:
ORDER BY
CASE WHEN AvailabilityStatus = 'E' THEN 1 ELSE 0 END,
stockId
select *
from INVENTORYTABLE inv
join INVENTORYTABLE_New invNew on inv.Stock_ID = invNew.Stock_ID
where
(
(AvailabilityStatus ='A' Or AvailabilityStatus = 'B' Or AvailabilityStatus = 'D' Or AvailabilityStatus = 'H' or AvailabilityStatus='E' ) And
MediaCount>0
)
order by
case
when AvailabilityStatus ='A' then 1
when AvailabilityStatus ='B' then 2
when AvailabilityStatus ='D' then 3
when AvailabilityStatus = 'H' then 4
when AvailabilityStatus='E' then 5
else 100
end
You can try something like this

Need help to simplify this T-SQL query

I have this working T-SQL query, is there any way to simplify it?
The SELECT COUNT(*) FROM Tablename is repeating and only the condition is different for each query, so is there a way to simplify this more? thank you
SELECT COUNT(*) FROM Table
WHERE columnA = 'I' AND columnB = 'I' AND columnC =''
UNION ALL
SELECT COUNT(*) FROM Table
WHERE columnA = 'I' AND columnB = 'I' AND columnC <>''
UNION ALL
SELECT COUNT(*) FROM Table
WHERE columnA IN ('A','R') AND columnB = 'I' AND columnD IN (SELECT columnE FROM Table2)
UNION ALL
SELECT COUNT(*) FROM Table
WHERE columnA IN ('B') AND columnB = 'I'
UNION ALL
SELECT COUNT(*) FROM Table
WHERE columnA IN ('R') AND columnB = 'S'
UNION ALL
SELECT COUNT(*) FROM Table
WHERE columnA IN ('A') AND columnB = 'S'
This is the working query and my output is 6 rows, want to maintain that
Let me assume that BI_BRH is unique in STDHOST. This is not strictly necessary but it are simplifying.
Then you can use conditional aggregation. I think putting the values in columns is the simplest approach:
SELECT SUM(CASE WHEN STATUS = 'I' AND TRX_STATE = 'I' AND ASSIGN_TO = '' THEN 1 ELSE 0 END),
SUM(CASE WHEN STATUS = 'I' AND TRX_STATE = 'I' AND ASSIGN_TO <>'' THEN 1 ELSE 0 END)
SUM(CASE WHEN STATUS IN ('A','R') AND TRX_STATE = 'I' AND s.BI_BRH IS NOT NULL THEN 1 ELSE 0 END)
SUM(CASE WHEN STATUS IN ('B') AND TRX_STATE = 'I' THEN 1 ELSE 0 END)
SUM(CASE WHEN STATUS IN ('R') AND TRX_STATE = 'S' THEN 1 ELSE 0 END)
SUM(CASE WHEN STATUS IN ('A') AND TRX_STATE = 'S' THEN 1 ELSE 0 END)
FROM ICSCHQINFO i LEFT JOIN
STDHOST s
ON s.BI_BRH = i.BRH_ASSIGN_TO;
Your code runs 6 times over the same table, with different conditions in the WHERE clause and returns 6 rows.
Assuming that the 6 conditions you have are mutually exclusive, you can create a CTE that returns for each row the category that it belongs to and then aggregate on that category:
WITH cte AS (
SELECT CASE
WHEN STATUS = 'I' AND TRX_STATE = 'I' AND ASSIGN_TO = '' THEN 'cat1'
WHEN STATUS = 'I' AND TRX_STATE = 'I' AND ASSIGN_TO <> '' THEN 'cat2'
WHEN STATUS IN ('A','R') AND TRX_STATE = 'I' AND BRH_ASSIGN_TO IN (SELECT BI_BRH FROM STDHOST) THEN 'cat3'
WHEN STATUS IN ('B') AND TRX_STATE = 'I' THEN 'cat4'
WHEN STATUS IN ('R') AND TRX_STATE = 'S' THEN 'cat5'
WHEN STATUS IN ('A') AND TRX_STATE = 'S' THEN 'cat6'
END category
FROM ICSCHQINFO
)
SELECT category, COUNT(*) counter
FROM cte
GROUP BY category
If there are no rows for any of the categories and in that case you want a row with 0, you need another CTE that returns the categories and LEFT join to the other CTE:
WITH
categories AS (SELECT category FROM (VALUES ('cat1'), ('cat2'), ('cat3'), ('cat4'), ('cat5'), ('cat6')) v(category)),
cte AS (
SELECT CASE
WHEN STATUS = 'I' AND TRX_STATE = 'I' AND ASSIGN_TO = '' THEN 'cat1'
WHEN STATUS = 'I' AND TRX_STATE = 'I' AND ASSIGN_TO <> '' THEN 'cat2'
WHEN STATUS IN ('A','R') AND TRX_STATE = 'I' AND BRH_ASSIGN_TO IN (SELECT BI_BRH FROM STDHOST) THEN 'cat3'
WHEN STATUS IN ('B') AND TRX_STATE = 'I' THEN 'cat4'
WHEN STATUS IN ('R') AND TRX_STATE = 'S' THEN 'cat5'
WHEN STATUS IN ('A') AND TRX_STATE = 'S' THEN 'cat6'
END category
FROM ICSCHQINFO
)
SELECT c.category, COUNT(t.category) counter
FROM categories c LEFT JOIN cte t
ON t.category = c.category
GROUP BY c.category

How to convert a single column containing multiple rows into rows

I have a column , says name Student_name and its values lets say, A, B, C, D, E, F and so on..
Now i have to convert this column into row with each alias.
select A.counts from (
select count(b.ATTND_FLAG) as counts , b.ATTND_FLAG as ATTND_FLAG
from hr_emp_notifications a, v_emp_attendance b
where a.emp_id=b.emp_id
and a.emp_id=90327
and b.ATTND_FLAG is not null
group by b.ATTND_FLAG )A
my query showing one column which has multiple values in rows.
i have to convert these values into row.
I would use conditional aggregation:
select sum(case when ea.attnd_flag = 'A' then 1 else 0 end) as num_a,
sum(case when ea.attnd_flag = 'B' then 1 else 0 end) as num_b,
sum(case when ea.attnd_flag = 'C' then 1 else 0 end) as num_c,
sum(case when ea.attnd_flag = 'D' then 1 else 0 end) as num_d,
sum(case when ea.attnd_flag = 'E' then 1 else 0 end) as num_e,
sum(case when ea.attnd_flag = 'F' then 1 else 0 end) as num_f
from v_emp_attendance ea
where ea.emp_id = 90327;
If you want multiple employees, use group byea.emp_id`.
Notice that the join is not needed.
It seems you need a pivot clause here -
SELECT *
FROM (SELECT NAME, ATTND_FLAG
FROM v_emp_attendance)
PIVOT (COUNT(ATTND_FLAG)
FOR NAME IN ('A' AS A, 'B' AS B, 'C' AS C /* AND SO ON */)
)

counting records on the same table with different values possibly none sql server 2008

I have a inventory table with a condition i.e. new, used, other, and i am query a small set of this data, and there is a possibility that all the record set contains only 1 or all the conditions. I tried using a case statement, but if one of the conditions isn't found nothing for that condition returned, and I need it to return 0
This is what I've tried so far:
select(
case
when new_used = 'N' then 'new'
when new_used = 'U' then 'used'
when new_used = 'O' then 'other'
end
)as conditions,
count(*) as count
from myDB
where something = something
group by(
case
when New_Used = 'N' then 'new'
when New_Used = 'U' then 'used'
when New_Used = 'O' then 'other'
end
)
This returns the data like:
conditions | count
------------------
new 10
used 45
I am trying to get the data to return like the following:
conditions | count
------------------
new | 10
used | 45
other | 0
Thanks in advance
;WITH constants(letter,word) AS
(
SELECT l,w FROM (VALUES('N','new'),('U','used'),('O','other')) AS x(l,w)
)
SELECT
conditions = c.word,
[count] = COUNT(x.new_used)
FROM constants AS c
LEFT OUTER JOIN dbo.myDB AS x
ON c.letter = x.new_used
AND something = something
GROUP BY c.word;
try this -
DECLARE #t TABLE (new_used CHAR(1))
INSERT INTO #t (new_used)
SELECT t = 'N'
UNION ALL
SELECT 'N'
UNION ALL
SELECT 'U'
SELECT conditions, ISNULL(r.cnt, 0) AS [count]
FROM (
VALUES('U', 'used'), ('N', 'new'), ('O', 'other')
) t(c, conditions)
LEFT JOIN (
SELECT new_used, COUNT(1) AS cnt
FROM #t
--WHERE something = something
GROUP BY new_used
) r ON r.new_used = t.c
in output -
new 2
used 1
other 0
You can do it as a cross-tab:
select
sum(case when new_used = 'N' then 1 else 0 end) as N,
sum(case when new_used = 'U' then 1 else 0 end) as U,
sum(case when new_used = 'O' then 1 else 0 end) as Other
from myDB
where something = something

SQL: selecting a value based on values from a number of rows in another table

I have a table NETWORKS where each network can have multiple CIRCUITS. Each network has an over-all status (red/yellow/green) and each circuit has an individual status (red/green). The circuit's statuses are each set manually. The network's status is as such:
If all its circuits are green --> Green
If all its circuits are red --> Red
If at least 1, but not all, circuits are green --> Yellow
If there are no circuits --> NULL(no status)
I am trying to select all of the networks with their statuses being determined dynamically by the SELECT rather than having to save and manage the status as a column in the table. I cannot figure out an efficient way to do this. What I have now works (both are small tables, < 100, rows with a relatively static amount of data), but is wildly inefficient and I'm hoping there is a better way.
SELECT
(CASE
WHEN (
SELECT COUNT(*)
FROM NETWORK_CIRCUITS
WHERE network_id = N.network_id
) = 0 THEN 'noStatus'
WHEN (
SELECT COUNT(*)
FROM NETWORK_CIRCUITS
WHERE network_id = N.network_id
AND [status] = 'greenStatus'
) = (
SELECT COUNT(*)
FROM NETWORK_CIRCUITS
WHERE network_id = SSN.network_id
) THEN 'greenStatus'
WHEN (
SELECT COUNT(*)
FROM NETWORK_CIRCUITS
WHERE network_id = N.network_id
AND [status] = 'redStatus'
) = (
SELECT COUNT(*)
FROM NETWORK_CIRCUITS
WHERE network_id = N.network_id
) THEN 'redStatus'
ELSE 'yellowStatus'
END) network_status
FROM NETWORKS N
Here's one way:
SELECT
CASE
WHEN CircuitCount IS NULL THEN 'noStatus'
WHEN GreenCount = CircuitCount THEN 'greenStatus'
WHEN GreenCount = 0 THEN 'redStatus'
ELSE 'yellowStatus'
END As network_status
FROM NETWORKS As N
LEFT JOIN
( SELECT COUNT(*) As CircuitCount,
COUNT(NULLIF([status],'redStatus')) As GreenCount,
network_id
FROM NETWORK_CIRCUITS
GROUP BY network_id
) As C ON N.network_id = C.network_id
My first thought is:
select network_id, case when green=0 and red=0 then null when green=0 then 'red' else when red=0 then 'green' else 'yellow' end as status
from
(select n.network_id,
sum(case when c.status='green' then 1 else 0 end) as green,
sum(case when c.status='red' then 1 else 0 end) as red
from network n
join circuit c on c.network_id=n.network_id
group by n.network_id)
I think this should do what you want. It should also be faster than using a bunch of subqueries.
SELECT CASE
WHEN circuits.network_id is NULL THEN 'No Status'
WHEN circuits.greenCount = circuits.totalCircuits THEN 'Green'
WHEN circuits.greenCount >= 1 and circuits.redCount >= 1 THEN 'Yellow'
WHEN circuits.redCount = circuits.totalCircuits THEN 'Red'
END as network_status
, N.network_id
FROM NETWORKS N
LEFT JOIN
(SELECT network_id
, sum(CASE WHEN [status] = 'redStatus' THEN 1 ELSE 0 END) as redCount
, sum(CASE WHEN [status] = 'greenStatus' THEN 1 ELSE 0 END) as greenCount
, count(*) as totalCircuits
FROM NETWORK_CIRCUITS
GROUP BY network_id) as circuits ON circuits.network_id = N.network_id