Sorting sub-queries in SQL - sql

I've been trying to a query working in SQL 2012 which I'm almost certain I am over complicating
I have a table which stores an order number, item numbers (multiple per order), status codes (multiple per item) and a timestamp
So basically something like this
Order Item Status
1 1 1
1 1 2
2 1 1
2 1 2
2 1 3
3 1 3
3 2 1
3 2 2
Within my query (using this table as the example), I need see the following 1 entry for each line and item but only showing the highest available status... BUT not if the status is 3
So in this case, I'd want to see
Order Item Status
1 1 2
3 2 2
The issue I had is that the query itself works... but it returns the FIRST status code it finds. Not the highest one. So I end up with
Order Item Status
1 1 1
3 2 1
Here's the full expanded code snippet
with summary as (
select a.order_no as order_no, a.item_no as item_no, a.timestamp as timestamp,
max(a.status_code) as status_code, row_number() over (partition by order_no
order by item_no asc) as rn
from db.ordhist a
where a.order_no > 120400000 and a.order_no < 120800000
and a.timestamp < Dateadd(DD,-3,GETDATE() )
and a.status_code >= 133
and not exists (
select b.order_no, b.item_no
from db.ordhist b
where b.status_code in (137,170,201,999)
and b.order_no = a.order_no
and b.item_no = a.item_no)
and not exists (
select c.order_no
from db.ordhist c
where c.status_code = 6
and c.order_no = a.order_no)
group by a.order_no, a.item_no, a.timestamp)
select * from summary where rn = 1

I think you don't need ROW_NUMBER just use a GROUP BY with HAVING MAX([Status])<>3:
SELECT [Order],[Item],MAX([Status])
FROM Table_Name
GROUP BY [Order],[Item]
HAVING MAX([Status])<>3

Ok I think I may have answered my own question.... by removing the grouping within the "summary" and doing grouping in the final results query instead
-- Produced (or higher) but not Delivery Noted --
with summary as (
select a.order_no as order_no, a.item_no as item_no, a.timestamp as timestamp, a.status_code as status_code
from db.ordhist a
where a.order_no > 120400000 and a.order_no < 120800000
and a.timestamp < Dateadd(DD,-3,GETDATE() )
and a.status_code >= 133
and not exists (
select b.order_no, b.item_no
from db.ordhist b
where b.status_code in (137,170,201,999)
and b.order_no = a.order_no
and b.item_no = a.item_no)
and not exists (
select c.order_no
from db.ordhist c
where c.status_code = 6
and c.order_no = a.order_no))
select order_no, item_no, timestamp, max(status_code) from summary
group by order_no, item_no, timestamp
order by status_code

Related

Compare the same id with 2 values in string in one table

I have a table like this:
id
status
grade
123
Overall
A
123
Current
B
234
Overall
B
234
Current
D
345
Overall
C
345
Current
A
May I know how can I display how many ids is fitting with the condition:
The grade is sorted like this A > B > C > D > F,
and the Overall grade must be greater than or equal to the Current grade
Is it need to use CASE() to switch the grade to a number first?
e.g. A = 4, B = 3, C = 2, D = 1, F = 0
In the table, there should be 345 is not match the condition. How can I display the tables below:
qty_pass_the_condition
qty_fail_the_condition
total_ids
2
1
3
and\
fail_id
345
Thanks.
As grade is sequential you can do order by desc to make the number. for the first result you can do something like below
select
sum(case when GradeRankO >= GradeRankC then 1 else 0 end) AS
qty_pass_the_condition,
sum(case when GradeRankO < GradeRankC then 1 else 0 end) AS
qty_fail_the_condition,
count(*) AS total_ids
from
(
select * from (
select Id,Status,
Rank() over (partition by Id order by grade desc) GradeRankO
from YourTbale
) as a where Status='Overall'
) as b
inner join
(
select * from (
select Id,Status,
Rank() over (partition by Id order by grade desc) GradeRankC
from YourTbale
) as a where Status='Current'
) as c on b.Id=c.Id
For second one you can do below
select
b.Id fail_id
from
(
select * from (
select Id,Status,
Rank() over (partition by Id order by grade desc) GradeRankO
from Grade
) as a where Status='Overall'
) as b
inner join
(
select * from (
select Id,Status,
Rank() over (partition by Id order by grade desc) GradeRankC
from Grade
) as a where Status='Current'
) as c on b.Id=c.Id
where GradeRankO < GradeRankC
You can use pretty simple conditional aggregation for this, there is no need for window functions.
A Pass is when the row of Overall has grade which is less than or equal to Current, with "less than" being in A-Z order.
Then aggregate again over the whole table, and qty_pass_the_condition is simply the number of non-nulls in Pass. And qty_fail_the_condition is the inverse of that.
SELECT
qty_pass_the_condition = COUNT(t.Pass),
qty_fail_the_condition = COUNT(*) - COUNT(t.Pass),
total_ids = COUNT(*)
FROM (
SELECT
t.id,
Pass = CASE WHEN MIN(CASE WHEN t.status = 'Overall' THEN t.grade END) <=
MIN(CASE WHEN t.status = 'Current' THEN t.grade END)
THEN 1 END
FROM YourTable t
GROUP BY
t.id
) t;
To query the actual failed IDs, simply use a HAVING clause:
SELECT
t.id
FROM YourTable t
GROUP BY
t.id
HAVING MIN(CASE WHEN t.status = 'Overall' THEN t.grade END) >
MIN(CASE WHEN t.status = 'Current' THEN t.grade END);
db<>fiddle

Update table column based on CTE result

I have the code shown below, and I want to update my original table to reflect the results of this query. I want each record's Route_type column to update with the corresponding value from the Route_type column in the query based on the code associated with each record. For instance, all records with code=1 should have Route_Type updated to "Other" based on the query.
With Route_Number_CTE (Code,Year_and_Week, Route_Count) As
(
Select
Code, Year_and_Week, Count(Route) AS Route_Count
From
Deliveries
Group by
Code, Year_and_Week
)
select
d.Code,
min(r.Route_Count) As Min_Count,
max(r.Route_Count) As Max_Count,
(case
When max(r.Route_Count) = 1 then 'One'
When max(r.Route_Count) <= 3 AND min(r.Route_Count) > 1 then 'Three or less'
When min(r.Route_Count) > 4 then 'Four or More'
Else 'Other'
End) As Route_Type
From
Deliveries as d
inner join
Route_Number_CTE as r on d.Code = r.Code
Group By
d.Code;
Query results:
Code Min_Count Max_Count Route_Type
----------------------------------------
1 1 4 Other
2 1 2 Three or less
3 3 3 Three or less
Deliveries:
Code Route Route_Type
-------------------------
1 A
1 C
1 D
2 A
2 C
2 B
3 A
3 C
3 D
I think that you could use window functions and an updatable cte. This is simpler, and should be more efficient as it avoids the need for aggregation and joins:
with cte as (
select route_type, max(cnt) over(partition by code) max_cnt
from (
select d.*, count(*) over(partition by code, year_and_week) cnt
from deliveries d
) d
)
update cte
set route_type = case
when max_cnt = 1 then 'One'
when max_cnt <= 3 then 'Three or less'
when max_cnt > 4 then 'Four or more'
end
I figured it out by just creating a second CTE from the second Select statement and using an update query with the result.

Oracle Remove Consecutive duplicates

I have table in which I store the evauation results of customer. Evaluation can be triggered multiple times. Below is the sample data
CUSTOMER_EVAL_RESULTS:
SEQ CUSTOMER_ID STATUS RESULT
1 100 C XYZ
3 100 C XYZ
7 100 C ABC
8 100 C PQR
11 100 C ABC
12 100 C ABC
From above data set I want only the rows with SEQ as 1,7,8,11.
I used below query suggested on other links but it is not giving the desired result. Please help
SELECT * FROM (
SELECT E.*, ROW_NUMBER() OVER(PARTITION BY CUSTOMER_ID, STATUS, RESULT ORDER BY SEQ) ROW_NUM
FROM CUSTOMER_EVAL_RESULTS E WHERE E.CUSTOMER_ID=100
) WHERE ROW_NUM=1;
You can utilize LAG to check the previous row's value:
SELECT *
FROM
(
SELECT E.*,
LAG(RESULT)
OVER(PARTITION BY CUSTOMER_ID, STATUS
ORDER BY SEQ) prevResult
FROM CUSTOMER_EVAL_RESULTS E
WHERE E.CUSTOMER_ID=100
)
WHERE prevResult IS NULL
OR prevResult <> RESULT
Please try the below
select * from CUSTOMER_EVAL_RESULTS
where not exists (select 1 from CUSTOMER_EVAL_RESULTS
a,CUSTOMER_EVAL_RESULTS b
where a.seq_no < b.seq_no and a.customer_id=b.customer_id
and a.status=b.status and a.result=b.result
and not exists(select 1 from CUSTOMER_EVAL_RESULTS c
where a.seq_no < c.seq_no and c.seq_no < b.seq_no ));

left join without duplicate values using MIN()

I have a table_1:
id custno
1 1
2 2
3 3
and a table_2:
id custno qty descr
1 1 10 a
2 1 7 b
3 2 4 c
4 3 7 d
5 1 5 e
6 1 5 f
When I run this query to show the minimum order quantities from every customer:
SELECT DISTINCT table_1.custno,table_2.qty,table_2.descr
FROM table_1
LEFT OUTER JOIN table_2
ON table_1.custno = table_2.custno AND qty = (SELECT MIN(qty) FROM table_2
WHERE table_2.custno = table_1.custno )
Then I get this result:
custno qty descr
1 5 e
1 5 f
2 4 c
3 7 d
Customer 1 appears twice each time with the same minimum qty (& a different description) but I only want to see customer 1 appear once. I don't care if that is the record with 'e' as a description or 'f' as a description.
First of all... I'm not sure why you need to include table_1 in the queries to begin with:
select custno, min(qty) as min_qty
from table_2
group by custno;
But just in case there is other information that you need that wasn't included in the question:
select table_1.custno, ifnull(min(qty),0) as min_qty
from table_1
left outer join table_2
on table_1.custno = table_2.custno
group by table_1.custno;
"Generic" SQL way:
SELECT table_1.custno,table_2.qty,table_2.descr
FROM table_1, table_2
WHERE table_2.id = (SELECT TOP 1 id
FROM table_2
WHERE custno = table_1.custno
ORDER BY qty )
SQL 2008 way (probably faster):
SELECT custno, qty, descr
FROM
(SELECT
custno,
qty,
descr,
ROW_NUMBER() OVER (PARTITION BY custno ORDER BY qty) RowNum
FROM table_2
) A
WHERE RowNum = 1
If you use SQL-Server you could use ROW_NUMBER and a CTE:
WITH CTE AS
(
SELECT table_1.custno,table_2.qty,table_2.descr,
RN = ROW_NUMBER() OVER ( PARTITION BY table_1.custno
Order By table_2.qty ASC)
FROM table_1
LEFT OUTER JOIN table_2
ON table_1.custno = table_2.custno
)
SELECT custno, qty,descr
FROM CTE
WHERE RN = 1
Demolink

Row Filter Using Distinct

This is master table Query
Select *
from AC_TAB
where AC_ID = 7 ;
AC_PK AC_ID TYPE STATUS INS_DATE VALID
102 7 0 0 3/21/2012 3:35:08 PM 0
103 7 1 0 3/21/2012 3:35:08 PM
104 7 2 1 3/21/2012 3:35:08 PM
I am joining this table with txn table using ac_id. Since here its having 3 rows for ac_id 7 , my txn table returning 3 times. how to restrict this. since i want to return only one irrespective of type
MY Txn Query
Select txn_id, amount
from txn_hdr , ac_tab
where txn_ac_id = ac_id ;
txn_id amount
1 200
1 200
3 100
3 100
It is not actually clear what you need but it sounds like you only want to return one record per ac_id from the AC_TAB. If so, then there are a few ways that you can do this.
Using a subquery:
select *
from
(
select max(INS_DATE) INS_DATE, AC_ID
from ac_tab
group by AC_ID
) a
inner join txn_hdr t
on a.ac_id = t.ac_id;
Or with CTE using a row_number():
;with cte as
(
select a.ins_date, a.ac_id, t.amount, row_number()
over(partition by a.ac_id order by a.ins_date desc) rn
from ac_tab a
inner join txn_hdr t
on a.ac_id = t.ac_id
)
select *
from cte
where rn = 1;
Or with a row_number() in a subquery:
select *
from
(
select a.ins_date, a.ac_id, t.amount, row_number()
over(partition by a.ac_id order by a.ins_date desc) rn
from ac_tab a
inner join txn_hdr t
on a.ac_id = t.ac_id
) x
where rn = 1
You can do this way :
Select distinct txn_id, amount
from txn_hdr , ac_tab
where txn_ac_id = ac_id ;