Sql data retrieval issue - sql

I have below tables:
Order
Order_id orde_number Order_name
1 12345 iphone
2 67891 samsung
order_event
order_event_no status
1 D
1 C
2 C
I wrote below query to retrieve status not in ('D') like below ,But it gave me 2 records ,
But query should not return because order_no 1 already as status D, even though it has second record C it should not include.
select o.order_number,o.order_name
from order o
join order_event oe
on (o.order_id=oe.order_event_no) where oe.status not in ('D')
Regards,
Chaitu

This will accomplish what you want with your given schema / data...
SELECT order_number, order_name
FROM order
WHERE order_id NOT IN (SELECT order_event_no FROM order_event WHERE status = 'D')

If you want to exclude any order who has a status like 'D' you need a subquery.
select o.order_number,o.order_name
from order o
where oe.order_event_no
NOT IN
(SELECT order_event_no FROM order_event_no WHERE status = 'D')

This is equivalent. Some RDBMs will execute it faster:
Select
o.order_number,
o.order_name
from
order o
where
not exists (
select
'x'
from
order_event oe
where
oe.order_event_no = o.order_id And
oe.status = 'D'
);

Related

ORACLE SQL - multiple JOINs from same table

I have data related material transactions in one table and log history header data related to materials is in another table and detailed log history data in third table. I'm trying to get different status update dates matched to material table but I get duplicate rows for one material transaction
Original material transaction table:
ORDER_NO
MATERIAL
QTY
0001
MAT01
2
0002
MAT02  
5
Original Log History Header transaction table:
ORDER_NO
LOG_ID
0001
1001
0001
1002
Status code 1 refers to Opened and code 2 to Closed
Detailed Log History table:
LOG_ID
STATUS_CODE
DATE
1001
1
11/12/2021
1002
2  
15/12/2021
With following SQL query:
SELECT
TO_CHAR (m.order_no) order_no,
m.material,
a.date opened_date,
ab.closed_date
FROM MATERIAL_TRANSACTIONS m
INNER JOIN HISTORY_LOG t
ON m.ORDER_NO = t.ORDER_NO
INNER JOIN HISTORY_LOG_DETAILED a
ON t.LOG_ID = a.LOG_ID
AND a.STATUS_CODE = '1'
INNER JOIN HISTORY_LOG_DETAILED ab
ON t.LOG_ID = ab.LOG_ID
AND ab.STATUS_CODE = '2'
I get following result:
ORDER_NO
MATERIAL
QTY
OPENED_DATE
CLOSED_DATE
0001
MAT01
2
11/12/2021
0001
MAT01  
2
15/12/2021
And I would like to get the status dates to the same row as below:
ORDER_NO
MATERIAL
QTY
OPENED_DATE
CLOSED_DATE
0001
MAT01
2
11/12/2021
15/12/2021
I would appreciate all the help I can get and am very sorry if there already is topic for similar issue.
Your problem occurs because you join the history table, which holds 2 records for the order. You could flatten this if you use 2 inline tables that hold exactly 1 record.
with opened_dates as (
select h.order_id, d.date
from history h
inner join details d on h.log_id = d.log_id and d.status_code = '1'
), closed_dates as (
select h.order_id, d.date
from history h
inner join details d on h.log_id = d.log_id and d.status_code = '2'
)
select to_char (m.order_no) order_no,
m.material,
o.date opened_date,
c.date closed_date
from material_transactions m
join opened_dates o on m.order_no = o.order_no
join closed_dates c on m.order_no = c.order_no
;
Just an idea :
I joined HISTORY_LOG and HISTORY_LOG_DETAILED tables to get dates for specific status, and set as OPENED_DATE and CLOSED_DATE (if status 1 , then opened date is DATE column, otherwise set it as 01.01.0001)
After that grouped those records by ORDER_NO and maxed the date values to get actual OPENED_DATE and CLOSED_DATE .
Finally joined this subquery with MATERIAL_TRANSACTIONS table :
SELECT
TO_CHAR (M.ORDER_NO) ORDER_NO,
M.MATERIAL,
QTY,
L_T.OPENED_DATE,
L_T.CLOSED_DATE
FROM MATERIAL_TRANSACTIONS M
INNER JOIN
(
SELECT L.ORDER_NO ,
MAX( CASE WHEN LD.STATUS_CODE = 1 THEN LD.DATE ELSE TO_DATE('01.01.0001','dd.mm.yyyy') END ) OPENED_DATE
MAX( CASE WHEN LD.STATUS_CODE = 2 THEN LD.DATE ELSE TO_DATE('01.01.0001','dd.mm.yyyy') END ) CLOSED_DATE
FROM
HISTORY_LOG L
INNER JOIN HISTORY_LOG_DETAILED LD ON LD.LOG_ID = L.LOG_ID
GROUP BY L.ORDER_NO
) L_T on L_T.ORDER_NO = M.ORDER_NO
Note: I didnt test it. So there can be small syntax errors. Please check it and for better help add a fiddle so i can test my query

Get result true if all the rows have met the condition in sql

Basically i'm looking for solution for the condition, where table has the following rows and i want to select only those where condition is met by all the rows.
ID category flag
1 A 1
2 A 1
3 A 0
4 B 1
5 C 0
Expected Result is B where flag is true for its category.
I hope that my answer would be helpful to your problem. In the subquery, a list of the categories is created by filtering the flags.
SELECT *
FROM tablename a
WHERE a.category NOT IN (
SELECT b.category
FROM tablename b
WHERE b.flag=0)
You want the categories for which the minimum flag is 1 (meaning there is no flag = 0):
select category
from tablename
group by category
having min(flag) = 1
See the demo.
Results:
| category |
| -------- |
| B |
Use correlated subquery with not exists
select * from tablename a
where not exists (select 1 from tablename b where a.category=b.cateogry and flag=0)
Try this
select distinct category,flag from test where category in
(select t.category from (select category,flag from test
group by category,flag)t group by t.category having count(*)=1)
and flag=1;
If you have a separate table of categories, then not exists is often the fastest method:
select c.*
from categories c
where not exists (select 1
from t
where t.category = c.category and
t.flag = 0
);
In particular, this can take advantage of an index on (category, flag). If you don't have such a table, then forpas's solution is quite effective.

Aggregate data from multiple rows into single row

In my table each row has some data columns Priority column (for example, timestamp or just an integer). I want to group my data by ID and then in each group take latest not-null column. For example I have following table:
id A B C Priority
1 NULL 3 4 1
1 5 6 NULL 2
1 8 NULL NULL 3
2 634 346 359 1
2 34 NULL 734 2
Desired result is :
id A B C
1 8 6 4
2 34 346 734
In this example table is small and has only 5 columns, but in real table it will be much larger. I really want this script to work fast. I tried do it myself, but my script works for SQLSERVER2012+ so I deleted it as not applicable.
Numbers: table could have 150k of rows, 20 columns, 20-80k of unique ids and average SELECT COUNT(id) FROM T GROUP BY ID is 2..5
Now I have a working code (thanks to #ypercubeᵀᴹ), but it runs very slowly on big tables, in my case script can take one minute or even more (with indices and so on).
How can it be speeded up?
SELECT
d.id,
d1.A,
d2.B,
d3.C
FROM
( SELECT id
FROM T
GROUP BY id
) AS d
OUTER APPLY
( SELECT TOP (1) A
FROM T
WHERE id = d.id
AND A IS NOT NULL
ORDER BY priority DESC
) AS d1
OUTER APPLY
( SELECT TOP (1) B
FROM T
WHERE id = d.id
AND B IS NOT NULL
ORDER BY priority DESC
) AS d2
OUTER APPLY
( SELECT TOP (1) C
FROM T
WHERE id = d.id
AND C IS NOT NULL
ORDER BY priority DESC
) AS d3 ;
In my test database with real amount of data I get following execution plan:
This should do the trick, everything raised to the power 0 will return 1 except null:
DECLARE #t table(id int,A int,B int,C int,Priority int)
INSERT #t
VALUES (1,NULL,3 ,4 ,1),
(1,5 ,6 ,NULL,2),(1,8 ,NULL,NULL,3),
(2,634 ,346 ,359 ,1),(2,34 ,NULL,734 ,2)
;WITH CTE as
(
SELECT id,
CASE WHEN row_number() over
(partition by id order by Priority*power(A,0) desc) = 1 THEN A END A,
CASE WHEN row_number() over
(partition by id order by Priority*power(B,0) desc) = 1 THEN B END B,
CASE WHEN row_number() over
(partition by id order by Priority*power(C,0) desc) = 1 THEN C END C
FROM #t
)
SELECT id, max(a) a, max(b) b, max(c) c
FROM CTE
GROUP BY id
Result:
id a b c
1 8 6 4
2 34 346 734
One alternative that might be faster is a multiple join approach. Get the priority for each column and then join back to the original table. For the first part:
select id,
max(case when a is not null then priority end) as pa,
max(case when b is not null then priority end) as pb,
max(case when c is not null then priority end) as pc
from t
group by id;
Then join back to this table:
with pabc as (
select id,
max(case when a is not null then priority end) as pa,
max(case when b is not null then priority end) as pb,
max(case when c is not null then priority end) as pc
from t
group by id
)
select pabc.id, ta.a, tb.b, tc.c
from pabc left join
t ta
on pabc.id = ta.id and pabc.pa = ta.priority left join
t tb
on pabc.id = tb.id and pabc.pb = tb.priority left join
t tc
on pabc.id = tc.id and pabc.pc = tc.priority ;
This can also take advantage of an index on t(id, priority).
previous code will work with following syntax:
with pabc as (
select id,
max(case when a is not null then priority end) as pa,
max(case when b is not null then priority end) as pb,
max(case when c is not null then priority end) as pc
from t
group by id
)
select pabc.Id,ta.a, tb.b, tc.c
from pabc
left join t ta on pabc.id = ta.id and pabc.pa = ta.priority
left join t tb on pabc.id = tb.id and pabc.pb = tb.priority
left join t tc on pabc.id = tc.id and pabc.pc = tc.priority ;
This looks rather strange. You have a log table for all column changes, but no associated table with current data. Now you are looking for a query to collect your current values from the log table, which is a laborious task naturally.
The solution is simple: have an additional table with the current data. You can even link the tables with a trigger (so either every time a record gets inserted in your log table you update the current table or everytime a change is written to the current table you write a log entry).
Then just query your current table:
select id, a, b, c from currenttable order by id;

SQL Query help for single table query

I have a table that records status on course progress. A new record is added for each user/course comination when a course is started. That record is updated with a 'completed' status when the course is completed. I need to find the records for users who have never completed any courses.
Example Table:
User Course Status
A 1 S
A 2 C
B 1 S
C 2 S
D 2 C
C 3 S
I need a query that finds the following:
User Course Status
B 1 S
C 2 S
C 3 S
Any help is appreciated.
select user, course, status
from your_table
where user in
(
select user
from your_table
group by user
having sum(CASE WHEN status = 'C' THEN 1 ELSE 0 END) = 0
)
Select User, Course, Status from MyTable where User not in (Select Distinct User from MyTable where Status = 'C')
SELECT User,Course,Status FROM YourTable a
LEFT JOIN
(SELECT DISTINCT User FROM YourTable WHERE Status='C') CompletedAnything
ON a.User=CompletedAnything.User
WHERE COmpletedAnything.User IS NULL
Here's a SQL Fiddle that gives you what you want:
http://sqlfiddle.com/#!2/b6988/1
Query is this:
select User, Course, Status
from mytable
where User not in
(select distinct User from mytable where status = 'C' ans User is not null)

Grouping in SQL Statement

I have the following SQL statement:
SELECT TOP 30
a.ClassAdID, -- 0
a.AdTitle, -- 1
a.ClassAdCatID, -- 2
b.ClassAdCat, -- 3
a.Img1, -- 4
e.Domain, -- 5
a.AdText, -- 6
a.RegionID, -- 7
a.IsEvent, -- 8
a.IsCoupon, -- 9
b.ParentID, -- 10
a.MemberID, -- 11
a.AdURL, -- 12
a.Location, -- 13
a.GroupID -- 14
FROM ClassAd a
INNER JOIN ClassAdCat b ON b.ClassAdCatID = a.ClassAdCatID
INNER JOIN Member d ON d.MemberID = a.MemberID
INNER JOIN Region e ON e.RegionID = a.RegionID
WHERE DATEDIFF(d, GETDATE(), a.ExpirationDate) >= 0
AND PostType <> 'CPN'
ORDER BY a.CreateDate DESC
I want to only show one from each GROUPID... How can I adjust the statement to achieve this as I am lost with DISTINCT, GROUP BY etc..
Any help would be appreciated.
Many thanks,
Paul
You can use ROW_NUMBER function to partition data set based on GroupId values thus: for every new GroupId values the counter is restarted from 1 and the first row (with ROW_NUMBER = 1) is the newest record (a.CreateDate DESC). Then, we filter all records having ROW_NUMBER = 1 .
SELECT TOP 30 *
FROM
(
SELECT
a.ClassAdID, -- 0
a.AdTitle, -- 1
a.ClassAdCatID, -- 2
b.ClassAdCat, -- 3
a.Img1, -- 4
e.Domain, -- 5
a.AdText, -- 6
a.RegionID, -- 7
a.IsEvent, -- 8
a.IsCoupon, -- 9
b.ParentID, -- 10
a.MemberID, -- 11
a.AdURL, -- 12
a.Location, -- 13
a.GroupID, -- 14
ROW_NUMBER() OVER(PARTITION BY a.GroupId ORDER BY a.CreateDate DESC) AS PseudoId
FROM ClassAd a
INNER JOIN ClassAdCat b ON b.ClassAdCatID = a.ClassAdCatID
INNER JOIN Member d ON d.MemberID = a.MemberID
INNER JOIN Region e ON e.RegionID = a.RegionID
WHERE DATEDIFF(d, GETDATE(), a.ExpirationDate) >= 0
AND PostType <> 'CPN'
) q
WHERE q.PseudoId = 1;
GROUP BY goes with an AGGREGATE function... meaning you want to add up the values in the group, or find the biggest, or smallest in the group etc.
DISTINCT will remove duplicate rows.
in your query, you may be getting a bunch of not-so-similar rows that all happen to have the same group_id... if this is so, then you need to decide which one of those rows you really want to see.
maybe you want the newest one, or the one with the longest name, or something like that.
for grouping, you would pick a column like createdon and say something like MAX( createdon ) in the select list, then group on every other column in the select list to find the rows that match each other (except for created on), and return that only once with the largest value for created on... hope that makes sense.
edit:
very simple example for group id and create date. ( you can keep adding more columns as needed - one in the group by list for every one in the select list :
SELECT groupid, max( createdate )
FROM ClassAd
GROUP BY groupId
If I understand correctly you want to get one row from each group (like groupid)
I used sql server 2005 (Nothwind)
SELECT TOP 30 Customers.CompanyName, Orders.ShipCity, Orders.Freight
FROM Customers INNER JOIN
Orders ON Customers.CustomerID = Orders.CustomerID
GROUP BY Customers.CompanyName, Orders.ShipCity, Orders.Freight