How to assign same rownumber to group of sequence records. Refer the below example orderno.s 550 &650 are having two sets of running sequence numbers
I wanted to assign same row number for each corresponding ordernumber and set of sequence numbers(1,2,3)
Order, Item, SequenceNumber
550, AA, 1
550, AA, 2
550, AA, 3
550, AA, 1
550, AA, 2
550, AA, 3
550, AA, 4
650, AA, 1
650, AA, 2
650, AA, 3
650, AA, 1
650, AA, 2
650, AA, 3
650, AA, 4
Expected result should be with the new rownumber column populated as below.
Order, Item, SequenceNumber , Rownumber
550, AA, 1, 1
550, AA, 2, 1
550, AA, 3, 1
550, AA, 1, 2
550, AA, 2, 2
550, AA, 3, 2
550, AA, 4, 2
650, AA, 1, 1
650, AA, 2, 1
650, AA, 3, 1
650, AA, 1, 2
650, AA, 2, 2
650, AA, 3, 2
650, AA, 4, 2
You need nested OLAP functions, but of course there must be some column to order your data:
SELECT ...
Sum(flag) -- create the group number
Over (PARTITION BY Order, Item
ORDER BY whatever
ROWS Unbounded Preceding)
FROM
(
SELECT ...
-- find the row where a new group starts
CASE WHEN Min(SequenceNumber)
Over (PARTITION BY Order, Item
ORDER BY whatever
ROWS BETWEEN 1 Preceding AND 1 Preceding) + 1 = SequenceNumber
THEN 0
ELSE 1
END AS flag
FROM tab
) AS dt
From your existing table, you need to create an ALTER statement so that you can add new column.
ALTER TABLE mytableName ADD Rownumber INT;
After the statement has been successfully executed, you can now update all the records,
UPDATE myTableName
SET Rownumber = (some logic statement depending on table values)
edit:
I'd be happy to help with that (some logic statement...), but I'm not exactly sure what you're looking for from your question.
Here's the SQL:
SELECT
OrderNumber
, ItemCode
, SequenceNumber
, ROW_NUMBER() OVER(PARTITION BY OrderNumber, SequenceNumber, SequenceNumberCount) AS RowNumber
FROM
(
SELECT
OrderNumber
, ItemCode
, SequenceNumber
, COUNT(SequenceNumber) OVER(PARTITION BY OrderNumber, SequenceNumber ORDER BY OrderNumber, ItemCode) AS SequenceNumberCount
FROM
(
SELECT 550 AS OrderNumber, 'AA' As ItemCode, 1 AS SequenceNumber UNION ALL
SELECT 550 AS OrderNumber, 'AA' As ItemCode, 2 AS SequenceNumber UNION ALL
SELECT 550 AS OrderNumber, 'AA' As ItemCode, 3 AS SequenceNumber UNION ALL
SELECT 550 AS OrderNumber, 'AA' As ItemCode, 1 AS SequenceNumber UNION ALL
SELECT 550 AS OrderNumber, 'AA' As ItemCode, 2 AS SequenceNumber UNION ALL
SELECT 550 AS OrderNumber, 'AA' As ItemCode, 3 AS SequenceNumber UNION ALL
SELECT 550 AS OrderNumber, 'AA' As ItemCode, 4 AS SequenceNumber UNION ALL
SELECT 650 AS OrderNumber, 'AA' As ItemCode, 1 AS SequenceNumber UNION ALL
SELECT 650 AS OrderNumber, 'AA' As ItemCode, 2 AS SequenceNumber UNION ALL
SELECT 650 AS OrderNumber, 'AA' As ItemCode, 3 AS SequenceNumber UNION ALL
SELECT 650 AS OrderNumber, 'AA' As ItemCode, 1 AS SequenceNumber UNION ALL
SELECT 650 AS OrderNumber, 'AA' As ItemCode, 2 AS SequenceNumber UNION ALL
SELECT 650 AS OrderNumber, 'AA' As ItemCode, 3 AS SequenceNumber UNION ALL
SELECT 650 AS OrderNumber, 'AA' As ItemCode, 4 AS SequenceNumber
) AS data
) AS interim_calculation
ORDER BY
OrderNumber
, RowNumber
, SequenceNumber;
Update:
Updated SQL to remove redundant complexity based on #dnoeth's
comment:
SELECT
OrderNumber
, ItemCode
, SequenceNumber
, Row_Number() Over(PARTITION BY OrderNumber, SequenceNumber ORDER BY SequenceNumber) AS RowNumber
FROM
(
SELECT 550 AS OrderNumber, 'AA' As ItemCode, 1 AS SequenceNumber UNION ALL
SELECT 550 AS OrderNumber, 'AA' As ItemCode, 2 AS SequenceNumber UNION ALL
SELECT 550 AS OrderNumber, 'AA' As ItemCode, 3 AS SequenceNumber UNION ALL
SELECT 550 AS OrderNumber, 'AA' As ItemCode, 1 AS SequenceNumber UNION ALL
SELECT 550 AS OrderNumber, 'AA' As ItemCode, 2 AS SequenceNumber UNION ALL
SELECT 550 AS OrderNumber, 'AA' As ItemCode, 3 AS SequenceNumber UNION ALL
SELECT 550 AS OrderNumber, 'AA' As ItemCode, 4 AS SequenceNumber UNION ALL
SELECT 650 AS OrderNumber, 'AA' As ItemCode, 1 AS SequenceNumber UNION ALL
SELECT 650 AS OrderNumber, 'AA' As ItemCode, 2 AS SequenceNumber UNION ALL
SELECT 650 AS OrderNumber, 'AA' As ItemCode, 3 AS SequenceNumber UNION ALL
SELECT 650 AS OrderNumber, 'AA' As ItemCode, 1 AS SequenceNumber UNION ALL
SELECT 650 AS OrderNumber, 'AA' As ItemCode, 2 AS SequenceNumber UNION ALL
SELECT 650 AS OrderNumber, 'AA' As ItemCode, 3 AS SequenceNumber UNION ALL
SELECT 650 AS OrderNumber, 'AA' As ItemCode, 4 AS SequenceNumber
) AS data
ORDER BY
OrderNumber
, RowNumber
, SequenceNumber;
which results in:
Related
I have a table with data like the following:
DESC_E
RPTFIELD
desc 1
TITLE1
desc 2
TITLE2
desc 3
TITLE3
What is the best way to get result like this?
TITLE1
TITLE2
TITLE3
desc 1
desc 2
desc 3
A simple option is
(with sample data you posted)
SQL> with test (desc_e, rptfield) as
2 (select 'desc 1', 'TITLE1' from dual union all
3 select 'desc 2', 'TITLE2' from dual union all
4 select 'desc 3', 'TITLE3' from dual
5 )
Query:
6 select max(case when rptfield = 'TITLE1' then desc_e end) as title1,
7 max(case when rptfield = 'TITLE2' then desc_e end) as title2,
8 max(case when rptfield = 'TITLE3' then desc_e end) as title3
9 from test;
TITLE1 TITLE2 TITLE3
------ ------ ------
desc 1 desc 2 desc 3
SQL>
Littlefoot's answer is good. Just to add, you can write this more concisely with DECODE:
SELECT MAX(DECODE(rptfield,'TITLE1',desc_e)) title1,
MAX(DECODE(rptfield,'TITLE2',desc_e)) title2,
MAX(DECODE(rptfield,'TITLE3',desc_e)) title3
FROM test
I need some hints.. I got a tables which look like that:
ID
ITEM
1
XXXX
2
YYYY
3
ZZZZ
ID
ID_2
SUBITEM
1
10
AA
1
11
BB
2
12
CC
2
13
DD
3
14
EE
3
15
FF
3
16
GG
ID_2
value
10
1
11
0
12
1
13
1
14
1
15
1
16
0
I need to get all items where ALL sub-items are = 1.
for example XXXX should not be listed, because BB has value 0.
select distinct
(table1.item)
from table1,
table2,
table3
where table1.id = table2.id
and table2.id_2 = table3.id_2
and table3.value = 1
order by table1.item
my code gives me all items wherever 1 is a value
Thanks for help!
I would use an aggregation approach here:
SELECT i.ID, i.ITEM
FROM items i
INNER JOIN subitems s ON s.ID = i.ID
INNER JOIN vals v ON v.ID_2 = s.ID_2
GROUP BY i.ID, i.ITEM
HAVING COUNT(CASE WHEN v.value != 1 THEN 1 END) = 0;
The COUNT expression above will count 1 every time a value other than 1 appears for a given item. A matching item, then, is one whose non 1 count is zero, meaning all values are equal to 1.
Then zzzz shouldn't be in result set either, as GG has value 0.
Sample data:
SQL> with
2 a (id, item) as
3 (select 1, 'xxxx' from dual union all
4 select 2, 'yyyy' from dual union all
5 select 3, 'zzzz' from dual
6 ),
7 b (id, id_2, subitem) as
8 (select 1, 10, 'aa' from dual union all
9 select 1, 11, 'bb' from dual union all
10 select 2, 12, 'cc' from dual union all
11 select 2, 13, 'dd' from dual union all
12 select 3, 14, 'ee' from dual union all
13 select 3, 15, 'ff' from dual union all
14 select 3, 16, 'gg' from dual
15 ),
16 c (id_2, value) as
17 (select 10, 1 from dual union all
18 select 11, 0 from dual union all
19 select 12, 1 from dual union all
20 select 13, 1 from dual union all
21 select 14, 1 from dual union all
22 select 15, 1 from dual union all
23 select 16, 0 from dual
24 )
Query begins here:
25 select a.item
26 from a join b on a.id = b.id
27 join c on c.id_2 = b.id_2
28 group by a.item
29 having min(c.value) = max(c.value)
30 and min(c.value) = 1;
ITEM
----
yyyy
SQL>
In other words: join all three tables. As having clause is to be used, group by the item column and set conditions (you said that all values must be 1).
Hi I'm trying to do the following with a dataset I have that looks like this:
letter number
a 1
a 2
a 1
a 1
a 3
a 4
a 2
a 4
b 1
b 3
b 4
b 3
b 2
b 5
b 1
b 1
the dataset is the result of a sub query I have run. I want to format the data to look like this, so I can flag the ones that have a count higher than 3
letter number number_count
a 1 3
a 2 2
a 3 1
a 4 2
b 1 3
b 2 1
b 3 2
b 4 1
b 5 1
So for each value in the letter column, I need each unique number in col 2 counted up and displayed next to the value itself.
I've done a lot of searching to try and solve this and can't get any variation of count, count distinct, over or other code to work so I'm either underestimating the problem or more likely, I'm not sure how to phrase my search so I find the answer I'm looking for.
Any help would be greatly appreciated, I'm using sql developer to access an oracle 11g database.
So i think the following is what you are looking for:
select letter, number, count(letter) as number_count from table group by letter, number;
I added two more data lines to show an example where you'll have one result that's flagged.
WITH
sub_q AS (
SELECT 'a' AS letter_column, 1 AS value_column FROM Dual UNION ALL
SELECT 'a', 2 FROM Dual UNION ALL
SELECT 'a', 1 FROM Dual UNION ALL
SELECT 'a', 1 FROM Dual UNION ALL
SELECT 'a', 3 FROM Dual UNION ALL
SELECT 'a', 4 FROM Dual UNION ALL
SELECT 'a', 2 FROM Dual UNION ALL
SELECT 'a', 4 FROM Dual UNION ALL
SELECT 'b', 1 FROM Dual UNION ALL
SELECT 'b', 3 FROM Dual UNION ALL
SELECT 'b', 4 FROM Dual UNION ALL
SELECT 'b', 3 FROM Dual UNION ALL
SELECT 'b', 2 FROM Dual UNION ALL
SELECT 'b', 5 FROM Dual UNION ALL
SELECT 'b', 1 FROM Dual UNION ALL
SELECT 'b', 1 FROM Dual UNION ALL
SELECT 'b', 1 FROM Dual UNION ALL
SELECT 'b', 1 FROM DUal
)
SELECT
letter_column,
value_column,
COUNT(value_column) AS value_count,
CASE
WHEN COUNT(value_column) > 3 THEN 1
ELSE NULL
END AS flag_gt_3
FROM sub_q
GROUP BY
letter_column,
value_column
ORDER BY
letter_column,
value_column
;
I currently have a table where I have number of units sold and the week that they are sold in. I am trying to get the average of the prior six weeks of units sold. I am using the Fiscal EOW date as the date to show the units sold. I am stumped on this problem.
I am using the following:
select
b.FISC_EOW_DT,
a.*,
avg(net_unit_qty) over (partition by sid order by FISC_EOW_DT rows between 5
preceding and current row) as avsaleslast6wk
FROM tbl1 a
JOIN (SELECT DISTINCT FISC_WK_OF_MTH_ID,FISC_EOW_DT FROM tbl2 ) B
ON B.FISC_WK_OF_MTH_ID=A.FISC_WK_OF_MTH_ID
where sid = 12345
This works however it calculates the last 5 rows, regardless if they are the previous week or not. So for example:
if the weeks was:
12/01/2016
01/08/2017
06/01/2017
08/01/2017
It will calculate the average of these 4 weeks even though they are not consecutive. I need to know how to calculate the Average sales including weeks that are not consecutive. So for the week going back from:
01/08/2017
to
06/01/2017
there would be 0 for average sales since the last 6 weeks are not represented.
Any help would be greatly appreciated.
TBL1
SID FISC_EOW_DT NET_UNIT_QTY
1234 01/01/2017 1
1234 01/08/2017 2
1234 01/15/2017 3
1234 01/22/2017 2
1234 01/29/2017 1
1234 06/09/2017 1
Expected result:
SID FISC_EOW_DT NET_UNIT_QTY AVSALESLAST6WEEKS
1234 01/01/2017 1 0(0+0+0+0+0+0)/6
1234 01/08/2017 2 .167(1+0+0+0+0+0)/6
1234 01/15/2017 3 .50(2+1+0+0+0+0)/6
1234 01/21/2017 2 1(3+2+1+0+0+0)/6
1234 01/28/2017 1 1.33(2+3+2+1+0+0)/6
1234 06/09/2017 1 0(0+0+0+0+0+0)6<----SINCE THERE HAVE BEEN NO SALES FOR MULTIPLE WEEKS
I would like to account for the weeks where there have been no sales. So I will need to create the blank weeks through coding, however I am not sure how to do that.
SELECT
D1_PROD_11_SKU_ID
,D4_TIME_01_FISC_WK_OF_MTH_ID
,FISC_EOW_DT
,net_unit_qty
,(
CASE WHEN Min(FISC_EOW_DT)
Over (PARTITION BY D1_PROD_11_SKU_ID ORDER BY FISC_EOW_DT ROWS BETWEEN 1 Preceding AND current row) >= FISC_EOW_DT - 6*7
THEN Min(net_unit_qty)
Over (PARTITION BY D1_PROD_11_SKU_ID ORDER BY FISC_EOW_DT ROWS BETWEEN 1 Preceding AND current row)
ELSE 0
END
+
CASE WHEN Min(FISC_EOW_DT)
Over (PARTITION BY D1_PROD_11_SKU_ID ORDER BY FISC_EOW_DT ROWS BETWEEN 1 Preceding AND 1 Preceding) >= FISC_EOW_DT -6*7
THEN Min(net_unit_qty)
Over (PARTITION BY D1_PROD_11_SKU_ID ORDER BY FISC_EOW_DT ROWS BETWEEN 1 Preceding AND 1 Preceding)
ELSE 0
END
+ CASE WHEN Min(FISC_EOW_DT)
Over (PARTITION BY D1_PROD_11_SKU_ID ORDER BY FISC_EOW_DT ROWS BETWEEN 2 Preceding AND 2 Preceding) >= FISC_EOW_DT - 6*7
THEN Min(net_unit_qty)
Over (PARTITION BY D1_PROD_11_SKU_ID ORDER BY FISC_EOW_DT ROWS BETWEEN 2 Preceding AND 2 Preceding)
ELSE 0
END
+ CASE WHEN Min(FISC_EOW_DT)
Over (PARTITION BY D1_PROD_11_SKU_ID ORDER BY FISC_EOW_DT ROWS BETWEEN 3 Preceding AND 3 Preceding) >= FISC_EOW_DT - 6*7
THEN Min(net_unit_qty)
Over (PARTITION BY D1_PROD_11_SKU_ID ORDER BY FISC_EOW_DT ROWS BETWEEN 3 Preceding AND 3 Preceding)
ELSE 0
END
+ CASE WHEN Min(FISC_EOW_DT)
Over (PARTITION BY D1_PROD_11_SKU_ID ORDER BY FISC_EOW_DT ROWS BETWEEN 4 Preceding AND 4 Preceding) >= FISC_EOW_DT - 6*7
THEN Min(net_unit_qty)
Over (PARTITION BY D1_PROD_11_SKU_ID ORDER BY FISC_EOW_DT ROWS BETWEEN 4 Preceding AND 4 Preceding)
ELSE 0
END
+ CASE WHEN Min(FISC_EOW_DT)
Over (PARTITION BY D1_PROD_11_SKU_ID ORDER BY FISC_EOW_DT ROWS BETWEEN 5 Preceding AND 5 Preceding) >= FISC_EOW_DT - 6*7
THEN Min(net_unit_qty)
Over (PARTITION BY D1_PROD_11_SKU_ID ORDER BY FISC_EOW_DT ROWS BETWEEN 5 Preceding AND 5 Preceding)
ELSE 0
END
) AS ROLLING_SIX_WEEK_SALES
FROM TBL1
This utilizes EXPAND ON to create the missing rows with a zero quantity, applies the average and finally removes the added rows again:
SELECT
SID
,Begin(pd) AS eow_dt
-- set the quantity to zero for non-existing weeks
,CASE WHEN FISC_EOW_DT = Begin(pd) THEN net_unit_qty ELSE 0 END AS qty
-- finally calculate the average of the previous 5 plus the current row
,Sum(qty)
Over (PARTITION BY sid
ORDER BY Begin(pd)
ROWS 5 Preceding) / 6.000 AS avsaleslast6wk
FROM
(
SELECT
SID
,FISC_EOW_DT
,NET_UNIT_QTY
,pd
FROM
(
SELECT
SID
,FISC_EOW_DT
,NET_UNIT_QTY
-- first: find the next existing row using LEAD
,Coalesce(Min(FISC_EOW_DT)
Over (PARTITION BY SID
ORDER BY FISC_EOW_DT
ROWS BETWEEN 1 Following AND 1 Following )
,FISC_EOW_DT+7) AS next_week
FROM tbl1
) AS dt
-- then: create the missing weeks
EXPAND ON PERIOD(FISC_EOW_DT, next_week) AS pd BY INTERVAL '7' DAY
) AS dt
-- remove the non-existing weeks again
QUALIFY qty > 0
ORDER BY 1,2
Edit:
Of course this assumes that there's only a single row per week.
Another solution uses a brute force approach which is ok for a small number of weeks: check if each of the previous 6 rows is within the range of 6 weeks, then add the quantity.
SELECT
SID
,FISC_EOW_DT
,net_unit_qty
,(CASE WHEN Min(FISC_EOW_DT)
Over (PARTITION BY sid ORDER BY FISC_EOW_DT ROWS BETWEEN 1 Preceding AND 1 Preceding) >= FISC_EOW_DT - 5*7
THEN Min(net_unit_qty)
Over (PARTITION BY sid ORDER BY FISC_EOW_DT ROWS BETWEEN 1 Preceding AND 1 Preceding)
ELSE 0
END
+ CASE WHEN Min(FISC_EOW_DT)
Over (PARTITION BY sid ORDER BY FISC_EOW_DT ROWS BETWEEN 2 Preceding AND 2 Preceding) >= FISC_EOW_DT - 5*7
THEN Min(net_unit_qty)
Over (PARTITION BY sid ORDER BY FISC_EOW_DT ROWS BETWEEN 2 Preceding AND 2 Preceding)
ELSE 0
END
+ CASE WHEN Min(FISC_EOW_DT)
Over (PARTITION BY sid ORDER BY FISC_EOW_DT ROWS BETWEEN 3 Preceding AND 3 Preceding) >= FISC_EOW_DT - 5*7
THEN Min(net_unit_qty)
Over (PARTITION BY sid ORDER BY FISC_EOW_DT ROWS BETWEEN 3 Preceding AND 3 Preceding)
ELSE 0
END
+ CASE WHEN Min(FISC_EOW_DT)
Over (PARTITION BY sid ORDER BY FISC_EOW_DT ROWS BETWEEN 4 Preceding AND 4 Preceding) >= FISC_EOW_DT - 5*7
THEN Min(net_unit_qty)
Over (PARTITION BY sid ORDER BY FISC_EOW_DT ROWS BETWEEN 4 Preceding AND 4 Preceding)
ELSE 0
END
+ CASE WHEN Min(FISC_EOW_DT)
Over (PARTITION BY sid ORDER BY FISC_EOW_DT ROWS BETWEEN 5 Preceding AND 5 Preceding) >= FISC_EOW_DT - 5*7
THEN Min(net_unit_qty)
Over (PARTITION BY sid ORDER BY FISC_EOW_DT ROWS BETWEEN 5 Preceding AND 5 Preceding)
ELSE 0
END
+ net_unit_qty
) / 6.000 AS avsaleslast6wk
FROM tbl1
This is a lot of cut & paste & modify, but probably quite efficient, a single step.
Looking into your sample data and desired output, I think you are looking for sum of previous 6 weeks divided by 6 and reset when there is gap between weeks, in this case below can be an option.
WITH difference (
sid
,FISC_EOW_DT
,diff
)
AS (
SELECT t1.sid
,t1.FISC_EOW_DT
,t1.FISC_EOW_DT - Min(t1.FISC_EOW_DT) OVER (
ORDER BY t1.FISC_EOW_DT ROWS BETWEEN 1 preceding
AND 1 preceding
) AS diff
FROM table1 t1
)
SELECT t.sid
,t.FISC_EOW_DT
,t.NET_UNIT_QTY
,coalesce(cast(SUM(t.NET_UNIT_QTY) OVER (
ORDER BY t.FISC_EOW_DT RESET WHEN d.diff > 7 ROWS BETWEEN 7 preceding
AND 1 preceding
) AS DECIMAL(4, 3)) / 6, 0) AS AverageCalc
FROM difference d
INNER JOIN table1 t ON d.sid = t.sid
AND d.FISC_EOW_DT = t.FISC_EOW_DT;
what the query does is that derived table calculate the difference of dates between current and preceding row and in main query, the difference is used as reset if its more than 7 determining that weeks are not continuous.
Result:
SID FISC_EOW_DT NET_UNIT_QTY AverageCalc
---- ----------- ------------ ---------
1234 2017-01-01 1 0.000
1234 2017-01-08 2 0.167
1234 2017-01-15 3 0.500
1234 2017-01-22 2 1.000
1234 2017-01-29 1 1.333
1234 2017-06-09 1 0.000
PFB screenshot FYR.
In a Contents table, items are stored in X,Y coordinates:
Contents
-------
id
parent_id
pos_x
pos_y
Assume the container size is 3 by 3. I'd like to find which positions in a given container are free. So far I've generated a 2D matrix:
SELECT *
FROM
(SELECT rownum X FROM dual CONNECT BY LEVEL <= 3 ) xaxis
INNER JOIN
(SELECT rownum Y FROM dual CONNECT BY LEVEL <=3 ORDER BY 1) yaxis
ON xaxis.X <> yaxis.Y OR xaxis.X = yaxis.Y
Then I attempt to JOIN the queries together, excluding X,Y positions present in Contents:
SELECT X, Y
FROM
(SELECT rownum X FROM dual CONNECT BY LEVEL <= 3 ) xaxis
INNER JOIN
(SELECT rownum Y FROM dual CONNECT BY LEVEL <=3 ORDER BY 1) yaxis
ON xaxis.X <> yaxis.Y OR xaxis.X = yaxis.Y
INNER JOIN (
SELECT pos_x, pos_y FROM Contents WHERE parent_id = ?) items
ON items.posx <> xaxis.X AND items.posy <> yaxis.Y;
This doesn't treat each pair as unique, and excludes values from all rows if a position is occupied. For example, assuming that (2, 2) is occupied, the above returns:
X Y
-----
1 1
1 3
3 1
3 3
Essentially I'm trying to get the difference of the two sets. Any help appreciated.
I figured out the answer right before I posted the question, so I thought I'd post it and answer it at the same time. Stating the problem as get the difference of the two sets set me in the right direction.
The answer is the MINUS operator. Replace the final JOIN with MINUS and you get the intended results:
select X, Y
from
(select rownum X from dual CONNECT BY LEVEL <= 3 ) xaxis
inner join
(select rownum Y from dual CONNECT BY LEVEL <=3 order by 1) yaxis
on xaxis.X <> yaxis.Y OR xaxis.X = yaxis.Y
MINUS
select pos_x, pos_y FROM Contents WHERE parent_id = ?;
which returns the intended result (note the lack of (2, 2)):
X Y
-----
1 1
1 2
1 3
2 1
2 3
3 1
3 2
3 3
Today was a good day
You could do this with an outer join rather than a minus (although you'd have to test both to find out which is more performant for your data!).
If you are only doing it for a single parent_id at a time, you would do:
WITH CONTENTS AS (SELECT 1 parent_id, 2 pos_x, 2 pos_y FROM dual UNION ALL
SELECT 2 parent_id, 2 pos_x, 1 pos_y FROM dual)
SELECT xaxis.x,
yaxis.y
FROM ((SELECT LEVEL x FROM dual CONNECT BY LEVEL <= 3) xaxis
CROSS JOIN (SELECT LEVEL y FROM dual CONNECT BY LEVEL <= 3) yaxis)
LEFT OUTER JOIN CONTENTS c ON c.pos_x = xaxis.x AND c.pos_y = yaxis.y AND c.parent_id = 1
WHERE c.parent_id IS NULL
ORDER BY x, y;
X Y
---------- ----------
1 1
1 2
1 3
2 1
2 3
3 1
3 2
3 3
Alternatively, if you want to run it for all parent_ids, you could use a partitioned outer join like so:
WITH CONTENTS AS (SELECT 1 parent_id, 2 pos_x, 2 pos_y FROM dual UNION ALL
SELECT 2 parent_id, 2 pos_x, 1 pos_y FROM dual)
SELECT c.parent_id,
xaxis.x,
yaxis.y
FROM ((SELECT LEVEL x FROM dual CONNECT BY LEVEL <= 3) xaxis
CROSS JOIN (SELECT LEVEL y FROM dual CONNECT BY LEVEL <= 3) yaxis)
LEFT OUTER JOIN CONTENTS c PARTITION BY (c.parent_id) ON c.pos_x = xaxis.x AND c.pos_y = yaxis.y
WHERE c.pos_x IS NULL
AND c.pos_y IS NULL
ORDER BY c.parent_id,
xaxis.x,
yaxis.y;
PARENT_ID X Y
---------- ---------- ----------
1 1 1
1 1 2
1 1 3
1 2 1
1 2 3
1 3 1
1 3 2
1 3 3
2 1 1
2 1 2
2 1 3
2 2 2
2 2 3
2 3 1
2 3 2
2 3 3