Oracle Query Join issue - sql

I have an Oracle table as shown below
Orders
---------
ORDERID
DESCRIPTION
TOTALVALUE
ORDERSTATUS
I have the below mentioned query
select ORDERID,ORDERSTATUS
FROM ORDERS
WHERE ORDERID IN( 1000,1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1020,
1030,104,1040,1090,789)
Some orderIDs mentioned above are not in orders table. In spite of that I want the orderIDs to appear in the resultset with status as null.
Appreciate your help.

What about this:
SELECT T.COLUMN_VALUE AS ORDERID, ORD.ORDERSTATUS
FROM TABLE(SYS.ODCINUMBERLIST(
1000,1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1020,1030,104,1040,1090,789
)) T
LEFT JOIN ORDERS ORD ON ORD.ORDERID = T.COLUMN_VALUE;
You can also get it to work if the order IDs aren't fixed but a parameter. But the solution depends on whether you call the statement from PL/SQL or from another programming language such as C#, Java, PHP etc.
Update:
SYS.ODCINUMBERLIST is just a table type that's already defined in Oracle. You could use your own type:
CREATE TYPE NUMBER_TABLE_T AS TABLE OF NUMBER;

You can use a CTE as table for the orderIds (or store them into a temporary table), and outer join your Orders:
With tmp As (
Select 1000 As orderId From dual
Union All
Select 1001 From dual
Union All
...
)
Select tmp.orderId, o.orderStatus
From tmp
Left Join orders o On ( o.orderId = tmp.orderId )
orderStatus is NULL, when no order is found.

You would have to do an outer join to accomplish something like this :
SELECT ORDERID, ORDERSTATUS
FROM (
SELECT 1000 AS ORDERID FROM dual UNION SELECT 1001 FROM dual -- etc
) tmpOrderid
LEFT OUTER JOIN ORDERS O
ON tmpOrderid.ORDERID = O.ORDERID;
I have never used Oracle, but there is most likely a function that can generate numbers (for exemple, generate_series(1000, 1010) in PostgreSQL).

there is one more trick in oracle.
SELECT LEVEL + 1000 dt FROM DUAL CONNECT BY LEVEL < (2000 - 1000)
it generates a recordset with 1000 rows which might be left joined with your table.

Related

"Can't find table" error in SQL FROM(UNION) AS table, INNER JOIN, COUNT() FROM table

I currently have a query where inside each union select, in order to get a count of occurences, I had something like:
SELECT Order_Id, Order_Date,
C.cnt AS Order_Parts
FROM table1
INNER JOIN (SELECT Order_Id, count(Order_Id) as cnt
FROM table1
GROUP BY Order_Id) C ON table1.Order_Id = C.Order_Id
UNION SELECT Order_Id, Order_Date,
C.cnt AS Order_Parts
FROM table2
INNER JOIN (SELECT Order_Id, count(Order_Id) as cnt
FROM table2
GROUP BY Order_Id) C ON table2.Order_Id = C.Order_Id
And it worked alright, but I'm reorganising it so that the UNION is inside the Query FROM, so something like this:
SELECT
Order_Id,Order_Date,C.cnt AS Order_Parts
FROM(
SELECT Order_Id, Order_Date
FROM table1
UNION SELECT Order_Id, Order_Date
FROM table2
) AS Parts
INNER JOIN (SELECT Order_Id, count(Order_Id) as cnt
FROM Parts
GROUP BY Order_Id) C ON Parts.Order_Id = C.Order_Id
But Access throws me an error saying it can't find table or query 'Parts'. I can't for the life of me figure out why it can't use it; could someone guide me to what's wrong?
The error seems pretty clear. A table alias (i.e. parts) refers to a table or reference for the purpose of defining columns. It does provide a new "source" for data.
That is, it cannot be reused as a table in the from clause.
This is true in all databases. However, just about any other database supports common table expressions -- which do what you want. MS Access does not. Your only options are:
Repeat the UNION query for the second reference.
Use a view.
Note: There might be other approaches to writing the query that you want to write. For that, I would suggest that you ask a new question with sample data, desired results, and a clear explanation of what you want to accomplish.
Also, you may not want UNION because that removes duplicates. UNION ALL is more common.

SQL- get data from two tables in different columns without using unions

I have a table STOCK that looks like this:
PRODUCT SALES_CODE STOCK_1 STOCK_2 STOCK_3
-----------------------------------------------------
A 6-10 0 1 2
There are many STOCK_X buckets but for simplicity's sake, I've excluded.
Now I have another table SIZE_GRID:
SALES_CODE SIZE_1 SIZE_2 SIZE_3
--------------------------------------
6-10 6 8 10
As you might have guessed, these are stock on hand for a certain product, by size.
I need to get the STOCK values from the first table, and the size from the second table.
Originally, I was doing the following
SELECT
STOCK.PRODUCT,
SIZE_GRID.SIZE_1,
STOCK.STOCK_1
FROM
STOCK
INNER JOIN
SIZE_GRID ON
SIZE_GRID.SALES_CODE = STOCK.SALES_CODE
UNION ALL
SELECT
STOCK.PRODUCT,
SIZE_GRID.SIZE_2,
STOCK.STOCK_2
FROM
STOCK
INNER JOIN
SIZE_GRID ON
SIZE_GRID.SALES_CODE = STOCK.SALES_CODE
UNION ALL
SELECT
STOCK.PRODUCT,
SIZE_GRID.SIZE_3,
STOCK.STOCK_3
FROM
STOCK
INNER JOIN
SIZE_GRID ON
SIZE_GRID.SALES_CODE = STOCK.SALES_CODE
I have around 40 STOCK_X that I need to retrieve, so wandering if there is a much easier way to do this? Preferably I want to use pure SQL and no UDF/SP's.
http://sqlfiddle.com/#!6/f323e
If you are on SQL Server 2008 or later version, you could try the following method (found here):
SELECT
STOCK.PRODUCT,
X.SIZE,
X.STOCK
FROM
STOCK
INNER JOIN
SIZE_GRID ON
SIZE_GRID.SALES_CODE = STOCK.SALES_CODE
CROSS APPLY (
VALUES
(SIZE_GRID.SIZE_1, STOCK.STOCK_1),
(SIZE_GRID.SIZE_2, STOCK.STOCK_2),
(SIZE_GRID.SIZE_3, STOCK.STOCK_3)
) X (SIZE, STOCK)
;
With a small tweak you could make it work in SQL Server 2005 as well:
SELECT
STOCK.PRODUCT,
X.SIZE,
X.STOCK
FROM
STOCK
INNER JOIN
SIZE_GRID ON
SIZE_GRID.SALES_CODE = STOCK.SALES_CODE
CROSS APPLY (
SELECT SIZE_GRID.SIZE_1, STOCK.STOCK_1
UNION ALL
SELECT SIZE_GRID.SIZE_2, STOCK.STOCK_2
UNION ALL
SELECT SIZE_GRID.SIZE_3, STOCK.STOCK_3
) X (SIZE, STOCK)
;
However, if you are using an even earlier version, this might be of help:
SELECT
STOCK.PRODUCT,
SIZE = CASE X.N
WHEN 1 THEN SIZE_GRID.SIZE_1
WHEN 2 THEN SIZE_GRID.SIZE_2
WHEN 3 THEN SIZE_GRID.SIZE_3
END,
STOCK = CASE X.N
WHEN 1 THEN STOCK.STOCK_1
WHEN 2 THEN STOCK.STOCK_2
WHEN 3 THEN STOCK.STOCK_3
END,
FROM
STOCK
INNER JOIN
SIZE_GRID ON
SIZE_GRID.SALES_CODE = STOCK.SALES_CODE
CROSS JOIN (
SELECT 1
UNION ALL
SELECT 2
UNION ALL
SELECT 3
) X (N)
;
Although the last two options use UNION ALL, they are combining single rows only, not entire subsets
Consider normalizing the table. Instead of a repeating column:
PRODUCT SALES_CODE STOCK_1 STOCK_2 STOCK_3
Use a normalized table:
PRODUCT SALES_CODE STOCK_NO STOCK
And the same for the SIZE_GRID table:
SALES_CODE SIZE_NO SIZE
Now you can query without the need to list 40 columns:
select *
from STOCK s
join SIZE_GRID sg
on sg.SALES_CODE = s.SALES_CODE
and sg.SIZE_NO = s.STOCK_NO
Here are some alternatives you can use:
Execute each SQL separately and merge and sort the result sets within your program
Join the tables.
Use a scalar subquery.
select
select col1, col2, col3 from Table_1 q1,
select col1, col2,
col3 from Table_2 q2 from dual;
Try UNION using FULL OUTER JOIN with the NVL function: It is suggested that this has faster performance than the UNION operator.
select
empno,
ename,
nvl(dept.deptno,emp.deptno) deptno, dname from emp
full outer join
dept
on
(emp.deptno = dept.deptno)
order by 1,2,3,4;

Duplicate entries with different timestamp

I have a Table for Customers by name : Customer_SCD in SQL
I have 3 Columns present in it : Customer_Name, Customer_ID Customer_TimeStamp
There are duplicate entries in this table with different Timestamp.
For example
ABC, 1, 2012-12-05 11:58:20.370
ABC, 1, 2012-12-03 12:11:09.840
I want to eliminate this from the database and keep the first time/date available.
Thanks.
This works, try it:
DELETE Customer_SCD
OUTPUT deleted.*
FROM Customer_SCD b
JOIN (
SELECT MIN(a.Customer_TimeStamp) Customer_TimeStamp,
Customer_ID,
Customer_Name
FROM Customer_SCD a
GROUP BY a.Customer_ID, a.Customer_Name
) c ON
c.Customer_ID = b.Customer_ID
AND c.Customer_Name = b.Customer_Name
AND c.Customer_TimeStamp <> b.Customer_TimeStamp
In a subquery it determines which record is the first one for every Customer_Name,Customer_ID and then it deletes all the other records for a duplicate. I also added the OUTPUT clause which returns rows affected by the statement.
You could also do it by using ranking function ROW_NUMBER:
DELETE Customer_SCD
OUTPUT deleted.*
FROM Customer_SCD b
JOIN (
SELECT Customer_ID,
Customer_Name,
Customer_TimeStamp,
ROW_NUMBER() OVER (PARTITION BY Customer_ID, Customer_Name ORDER BY Customer_TimeStamp) num
FROM Customer_SCD
) c ON
c.Customer_ID = b.Customer_ID
AND c.Customer_Name = b.Customer_Name
AND c.Customer_TimeStamp = b.Customer_TimeStamp
AND c.num <> 1
See which one has a smaller query cost and use it, when I checked it, first approach was more efficient (it had a better execution plan).
Here's an SQL Fiddle
The following query will give you the results you want to keep.
Select Customer_Name, Customer_ID, MIN(Customer_TimeStamp) as Customer_TimeStamp
from Customer_SCD
group by Customer_Name, Customer_ID
store the result in a table variable, say #correctTbl
then join with this table and remove duplicates
delete
from Customer_SCD a
inner join #correctTbl b on a.Customer_Name = b.Customer_Name and a.Customer_ID = b.Customer_ID and a.Customer_TimeStamp != b.Customer_TimeStamp

Refactoring a tsql view which uses row_number() to return rows with a unique column value

I have a sql view, which I'm using to retrieve data. Lets say its a large list of products, which are linked to the customers who have bought them. The view should return only one row per product, no matter how many customers it is linked to. I'm using the row_number function to achieve this. (This example is simplified, the generic situation would be a query where there should only be one row returned for each unique value of some column X. Which row is returned is not important)
CREATE VIEW productView AS
SELECT * FROM
(SELECT
Row_number() OVER(PARTITION BY products.Id ORDER BY products.Id) AS product_numbering,
customer.Id
//various other columns
FROM products
LEFT OUTER JOIN customer ON customer.productId = prodcut.Id
//various other joins
) as temp
WHERE temp.prodcut_numbering = 1
Now lets say that the total number of rows in this view is ~1 million, and running select * from productView takes 10 seconds. Performing a query such as select * from productView where productID = 10 takes the same amount of time. I believe this is because the query gets evaluated to this
SELECT * FROM
(SELECT
Row_number() OVER(PARTITION BY products.Id ORDER BY products.Id) AS product_numbering,
customer.Id
//various other columns
FROM products
LEFT OUTER JOIN customer ON customer.productId = prodcut.Id
//various other joins
) as temp
WHERE prodcut_numbering = 1 and prodcut.Id = 10
I think this is causing the inner subquery to be evaluated in full each time. Ideally I'd like to use something along the following lines
SELECT
Row_number() OVER(PARTITION BY products.productID ORDER BY products.productID) AS product_numbering,
customer.id
//various other columns
FROM products
LEFT OUTER JOIN customer ON customer.productId = prodcut.Id
//various other joins
WHERE prodcut_numbering = 1
But this doesn't seem to be allowed. Is there any way to do something similar?
EDIT -
After much experimentation, the actual problem I believe I am having is how to force a join to return exactly 1 row. I tried to use outer apply, as suggested below. Some sample code.
CREATE TABLE Products (id int not null PRIMARY KEY)
CREATE TABLE Customers (
id int not null PRIMARY KEY,
productId int not null,
value varchar(20) NOT NULL)
declare #count int = 1
while #count <= 150000
begin
insert into Customers (id, productID, value)
values (#count,#count/2, 'Value ' + cast(#count/2 as varchar))
insert into Products (id)
values (#count)
SET #count = #count + 1
end
CREATE NONCLUSTERED INDEX productId ON Customers (productID ASC)
With the above sample set, the 'get everything' query below
select * from Products
outer apply (select top 1 *
from Customers
where Products.id = Customers.productID) Customers
takes ~1000ms to run. Adding an explicit condition:
select * from Products
outer apply (select top 1 *
from Customers
where Products.id = Customers.productID) Customers
where Customers.value = 'Value 45872'
Takes some identical amount of time. This 1000ms for a fairly simple query is already too much, and scales the wrong way (upwards) when adding additional similar joins.
Try the following approach, using a Common Table Expression (CTE). With the test data you provided, it returns specific ProductIds in less than a second.
create view ProductTest as
with cte as (
select
row_number() over (partition by p.id order by p.id) as RN,
c.*
from
Products p
inner join Customers c
on p.id = c.productid
)
select *
from cte
where RN = 1
go
select * from ProductTest where ProductId = 25
What if you did something like:
SELECT ...
FROM products
OUTER APPLY (SELECT TOP 1 * from customer where customerid = products.buyerid) as customer
...
Then the filter on productId should help. It might be worse without filtering, though.
The problem is that your data model is flawed. You should have three tables:
Customers (customerId, ...)
Products (productId,...)
ProductSales (customerId, productId)
Furthermore, the sale table should probably be split into 1-to-many (Sales and SalesDetails). Unless you fix your data model you're just going to run circles around your tail chasing red-herring problems. If the system is not your design, fix it. If the boss doesn't let your fix it, then fix it. If you cannot fix it, then fix it. There isn't a easy way out for the bad data model you're proposing.
this will probably be fast enough if you really don't care which customer you bring back
select p1.*, c1.*
FROM products p1
Left Join (
select p2.id, max( c2.id) max_customer_id
From product p2
Join customer c2 on
c2.productID = p2.id
group by 1
) product_max_customer
Left join customer c1 on
c1.id = product_max_customer.max_customer_id
;

Finding pairs that do not exist in a different table

I have a table (orders) with order id, location 1, location 2 and another table (mileage) with location 1 and location 2.
I'm using the Except action to return those location pairs in orders that are not in mileage. But I'm not sure how I can also return the corresponding order_id that belongs to those pairs (order_id doesn't exist in the mileage table). The only thing I can think of is having an outer select statement that searches orders for those location pairs. I haven't tried it but I'm looking for other options.
I have something like this.
SELECT location_id1, location_id2
FROM orders
except
SELECT lm.origin_id, lm.dest_id
from mileage
How can I also retrieve the order id for those pairs?
You might try using a Not Exists statement instead:
Select O.order_id, O.location_id1, O.location_id2
From orders As O
Where Not Exists (
Select 1
From mileage As M1
Where M1.origin_id = O.location_id1
And M1.dest_id = O.location_id2
)
Another solution if you really wanted to use Except
Select O.order_id, O.location_id1, O.location_id2
From Orders As O
Except
Select O.order_id, O.location_id1, O.location_id2
From Orders As O
Join Mileage As M
On M.origin_id = O.location_id1
And M.dest_id = O.location_id2
You could left-outer-join to the mileage table, and only return rows that don't join. Like so:
select O.order_id, O.location_id1, O.location_id2
from orders O left outer join mileage M1 on
O.location_id1 = M1.origin_id and
O.location_id2 = M1.dest_id
where M1.origin_id is NULL
If you want to get the pairs that do not exist on mileage table you can do something like
select location_id1, location_id2
from orders
where (select count(*) from mileage
where mileage.origin_id = location_id1 and mileage.dest_id = location_id2) = 0
I thought it was but as Gabe pointed, this does NOT work in SQL-Server 2008:
SELECT order_id
, location_id1
, location_id2
FROM orders
WHERE (location_id1, location_id2) NOT IN
( SELECT origin_id, dest_id
FROM mileage
)
Would this solution with EXCEPT (which actually is a JOIN between your original query and Orders) work fast or horribly? I have no idea.
SELECT o.order_id, o.location_id1, o.location_id2
FROM orders o
JOIN
( SELECT location_id1, location_id2
FROM orders
except
SELECT origin_id, dest_id
FROM mileage
) AS g
ON o.location_id1 = g.location_id1
AND o.location_id2 = g.location_id2
MySQL doesn't support Except. For anyone who comes across this question using MySQL, here's how you do it:
http://nimal.info/blog/2007/intersection-and-set-difference-in-mysql-a-workaround-for-except/