SQL WHERE matching any value in any column - sql

I need a table A with 4 columns for order numbers: Order1, Order2, Order3, Order4 (I know, it's horrible, but that's a given).
And I have to find the records in table B, where the match is that any value in any order column in table A could be in any order in column B:
A.Order1 = B.Order1 OR
A.Order1 = B.Order2 OR
A.Order1 = B.Order3 OR
A.Order1 = B.Order4 OR
A.Order2 = B.Order1 etc
Is there a better way to write this?
I dread the moment they tell me they want to use 5 or 6 columns.
EDITS TO ORIGINAL QUESTION
This is for SQL Server 2008 R2
Table B also has 4 order columns with order numbers
I'm looking for any order number in any order column in Table A to match any order number in any order column in Table B.
There's no anticipated most likely finding

You can unpivot the columns in one table using a cross apply and then check if the value from the cross apply is in any of the columns from the other table.
It does not automatically work if you add new columns but you will only have to add them in one or two places.
SQL Fiddle
MS SQL Server 2014 Schema Setup:
create table A
(
Order1 int,
Order2 int,
Order3 int,
Order4 int
)
create table B
(
Order1 int,
Order2 int,
Order3 int,
Order4 int
)
insert into A values
(1, 1, 40, 10),
(2, 2, 2, 20)
insert into B values
(3, 3, 3, 30),
(4, 4, 4, 40)
Query 1:
select *
from A
where exists (
select *
from B
cross apply (values(B.Order1),(B.Order2),(B.Order3),(B.Order4)) as X(O)
where X.O in (A.Order1, A.Order2, A.Order3, A.Order4)
)
Results:
| Order1 | Order2 | Order3 | Order4 |
|--------|--------|--------|--------|
| 1 | 1 | 40 | 10 |

The only way to do this with the model design you have is something (as #GordonLinoff suggested) like this:
where b.order1 in (a.order1, a.order2, a.order3, a.order4) or
b.order2 in (a.order1, a.order2, a.order3, a.order4) or
b.order3 in (a.order1, a.order2, a.order3, a.order4) or
b.order4 in (a.order1, a.order2, a.order3, a.order4)
An interesting question you might have is how can I change my data model to make this work better? ... here is how:
First you have your two tables A and B. I'm going to assume that both A and B have an unique index ID.
You can then make a support table AOrder with the following columns
AID
ORDNUM
VALUE
If you make a similar table for BOrder then to find out if a given order is the same just join on Value and you get the AID, BID, and the two order numbers.
With this design you don't care how many order numbers there are.
You could convert your current data to this design on the fly like this and get the results you want:
SELECT aord.ID as aID, bord.ID as bID, a.num as a_ordernum, b.num as b.ordernum, v
FROM (
SELECT a.ID, 1 AS num, a.order1 as V FROM a
UNION ALL
SELECT a.ID, 2 AS num, a.order2 as V FROM a
UNION ALL
SELECT a.ID, 3 AS num, a.order3 as V FROM a
UNION ALL
SELECT a.ID, 4 AS num, a.order4 as V FROM a
) aord
JOIN (
SELECT b.ID, 1 AS num, b.order1 as V FROM b
UNION ALL
SELECT b.ID, 2 AS num, b.order2 as V FROM b
UNION ALL
SELECT b.ID, 3 AS num, b.order3 as V FROM b
UNION ALL
SELECT b.ID, 4 AS num, b.order4 as V FROM b
) bord on aord.v = bord.v

Can you just normalize them? I'm assuming you have a SetID field grouping the 4(or more) orders in A and B, so you could have a table/view like:
select ID, srctbl, seq, order
from (
select AID as ID, 'A' as srctbl, 1 as seq, order1 as order from tblA union all
select AID, 'A' as srctbl, 2, order2 from tblA union all
select AID, 'A' as srctbl, 3, order3 from tblA union all
select AID, 'A' as srctbl, 4, order4 from tblA union all
select BID, 'B' as srctbl, 1, order1 from tblB union all
select BID, 'B' as srctbl, 2, order2 from tblB union all
select BID, 'B' as srctbl, 3, order3 from tblB union all
select BID, 'B' as srctbl, 4, order4 from tblB )
then you can say
select ID, srctbl, seq, order
from (select ID, srctbl, seq, order from tbl where srctbl = 'a') ta inner join
((select ID, srctbl, seq, order from tbl where srctbl = 'b') tb on
ta.order = tb.order
Or, as a CTE:
WITH orders (ID, srctbl, SEQ, orderVal)
AS (
SELECT ID, srctbl, SEQ, orderVal
FROM (
SELECT AID AS ID, 'A' AS srctbl, 1 AS SEQ, order1 AS orderVal FROM tblA UNION ALL
SELECT AID, 'A' AS srctbl, 2, order2 FROM tblA UNION ALL
SELECT AID, 'A' AS srctbl, 3, order3 FROM tblA UNION ALL
SELECT AID, 'A' AS srctbl, 4, order4 FROM tblA UNION ALL
SELECT BID, 'B' AS srctbl, 1, order1 FROM tblB UNION ALL
SELECT BID, 'B' AS srctbl, 2, order2 FROM tblB UNION ALL
SELECT BID, 'B' AS srctbl, 3, order3 FROM tblB UNION ALL
SELECT BID, 'B' AS srctbl, 4, order4 FROM tblB )
)
SELECT ta.ID AS a_id, tb.ID AS b_ID, ta.SEQ AS a_seq, tb.SEQ AS b_seq, ta.orderVal
FROM
(SELECT ID, SEQ, orderVal FROM orders WHERE srctbl = 'a') ta INNER JOIN
(SELECT ID, SEQ, orderVal FROM orders WHERE srctbl = 'b') tb ON
ta.orderVal = tb.orderVal

Related

ORACLE get rows with condition value equals something but not equals to anything else

I have rows that look like .
OrderNo OrderStatus SomeOtherColumn
A 1
A 1
A 3
B 1 X
B 1 Y
C 2
C 3
D 2
I want to return all orders that have only one possible value of orderstatus. For e.g Here order B has only order status 1 SO result should be
B 1 X
B 1 Y
Notes:
Rows can be duplicated with same order status. For e.g. B here.
I am interested in the order having a very peculiar status for e.g. 1 here and not having any other status. So if B had a status of 3 at any point of time it is disqualified.
You can use not exists:
select t.*
from t
where not exists (select 1
from t t2
where t.orderno = t2.orderno and t.OrderStatus = t2.OrderStatus
);
If you just want the orders where this is true, you can use group by and having:
select orderno
from t
group by orderno
having min(OrderStatus) = max(OrderStatus);
If you only want a status of 1 then add max(OrderStatus) = 1 to the having clause.
Here is one way to do it. It does not handle the case where the status can be NULL; if that is possible, you will need to explain how you want it handled.
SQL> create table test_data ( orderno, status, othercol ) as (
2 select 'A', 1, null from dual union all
3 select 'A', 1, null from dual union all
4 select 'A', 3, null from dual union all
5 select 'B', 1, 'X' from dual union all
6 select 'B', 1, 'Y' from dual union all
7 select 'C', 2, null from dual union all
8 select 'C', 3, null from dual union all
9 select 'D', 2, null from dual
10 );
Table created.
SQL> variable input_status number
SQL> exec :input_status := 1
PL/SQL procedure successfully completed.
SQL> column orderno format a8
SQL> column othercol format a8
SQL> select orderno, status, othercol
2 from (
3 select t.*, count(distinct status) over (partition by orderno) as cnt
4 from test_data t
5 )
6 where status = :input_status
7 and cnt = 1
8 ;
ORDERNO STATUS OTHERCOL
-------- ---------- --------
B 1 X
B 1 Y
One way to handle NULL status (if that may happen), if in that case the orderno should be rejected (not included in the output), is to define the cnt differently:
count(case when status != :input_status or status is null then 1 end)
over (partition by orderno) as cnt
and in the outer query change the WHERE clause to a single condition,
where cnt = 0
Count distinct OrderStatus partitioned by OrderNo and show only rows where number equals one:
select OrderNo, OrderStatus, SomeOtherColumn
from ( select t.*, count(distinct orderstatus) over (partition by orderno) cnt
from t )
where cnt = 1
SQLFiddle demo
Just wanted to add something to Gordon's answer, using a stats function:
select orderno
from t
group by orderno
having variance(orderstatus) = 0;

join 2 tables with SQL

I have to join 2 tables with SQL in a special way:
TABLE1 has the fields GROUP and MEMBER, TABLE2 has the fields GROUP and MASTER.
I have to build a new TABLE3 with the fields GROUP and ID by copying TABLE1 to TABLE3 and search TABLE2 if there is a GROUP from TABLE1 and if, copy GROUP and MASTER to TABLE3.
Example:
table1:
group member
1 a
1 b
1 c
2 x
3 y
table2:
group master
3 n
3 z
1 k
9 v
2 m
7 o
8 p
Expected result, table3:
group id
1 a from table1
1 b from table1
1 c from table1
1 k from table2
2 x from table1
2 m from table2
3 y from table1
3 z from table2
3 n from table2
I hope everything's clear.
So what is the SQL query?
Thanks, Hein
The first part (copy members) should be easy:
INSERT INTO table3 (group, id) SELECT group, member FROM table1;
Then You just copy the masters, that are in groups, that are already present in table1:
INSERT INTO table3 (group, id) SELECT group, master FROM table2 WHERE group IN (SELECT DISTINCT group FROM table1);
Try this out. Of course you need to INSERT the whole selection to your new table named Table3.
WITH TABLE1(GRP,MMBR) AS
(SELECT 1, 'a' FROM DUAL UNION ALL
SELECT 1, 'b' FROM DUAL UNION ALL
SELECT 1, 'c' FROM DUAL UNION ALL
SELECT 2, 'x' FROM DUAL UNION ALL
SELECT 3, 'y' FROM DUAL),
TABLE2(GRP,MSTR) AS
(SELECT 3, 'n' FROM DUAL UNION ALL
SELECT 3, 'z' FROM DUAL UNION ALL
SELECT 1, 'k' FROM DUAL UNION ALL
SELECT 9, 'v' FROM DUAL UNION ALL
SELECT 2, 'm' FROM DUAL UNION ALL
SELECT 7, 'o' FROM DUAL UNION ALL
SELECT 8, 'p' FROM DUAL)
SELECT * FROM (
SELECT GRP, MMBR ID FROM TABLE1
UNION --UNION ALL if you need duplicates
SELECT GRP, MSTR ID FROM TABLE2
WHERE TABLE2.GRP IN (SELECT GRP FROM TABLE1)
)
ORDER BY GRP, ID
You can do it using UNION ALL and 2 simple SELECT in an INSERT as follows:
INSERT INTO table3(group,id)
SELECT group,id FROM table1
UNION ALL
SELECT group,id FROM table2
SELECT * FROM table3;
And if you don't want duplicate values,try this using UNION instead of UNION ALL:
INSERT INTO table3(group,id)
SELECT group,id FROM table1
UNION
SELECT group,id FROM table2
SELECT * FROM table3;

Counting one field of table in other table

I wrote a script in oracle. But it does not give me the result that i want.
I need this one, imagine i have two table. Order_table and book table.
My order table is like this
ORDER_TABLE Table
ID TYPE_ID VALUE_ID
1 11 null
2 11 null
3 11 null
4 12 null
5 11 null
Book Table
ID ORDER_TYPE DELETED
1 1 F
2 null F
3 5 F
4 5 F
5 4 F
6 4 F
7 3 T
My script is like this
Select *
From (
Select Newtable.Counter As Value_id,
o.Id As Id,
o.Type_id As Type_id
From (
Select (Count B.Order_Type) As Counter,
B.Order_Type As Id
From Book B
Where B.Deleted = 'F'
Group By B.Order_Type
Order By Count(B.Order_Type) Desc
) newtable,
order_table o
where o.id = newtable.id
and o.type_id = 11
)
order by id asc;
Result is like this.
Value_ID TYPE_ID ID
2 11 5
2 11 4
1 11 1
It is not showing that second and third id has 0 count, Have can i show 0 count too ?
Result should be like this.
Value_ID TYPE_ID ID
2 11 5
2 11 4
1 11 1
0 11 2
0 11 3
First, do not use implicit JOIN syntax(comma separated), that's one of the reason this mistakes are hard to catch! Use the proper JOIN syntax.
Second, your problem is that you need a left join, not an inner join , so try this:
Select *
From (Select coalesce(Newtable.Counter,0) As Value_id,
o.Id As Id,
o.Type_id As Type_id
From order_table o
LEFT JOIN (Select Count(B.Order_Type) As Counter, B.Order_Type As Id
From Book B
Where B.Deleted = 'F'
Group By B.Order_Type
Order By Count(B.Order_Type) Desc) newtable
ON(o.id = newtable.id)
WHERE o.type_id = 11)
order by id asc;
Oracle Setup:
CREATE TABLE order_table ( id, type_id, value_id ) AS
SELECT 1, 11, CAST( NULL AS INT ) FROM DUAL UNION ALL
SELECT 2, 11, CAST( NULL AS INT ) FROM DUAL UNION ALL
SELECT 3, 11, CAST( NULL AS INT ) FROM DUAL UNION ALL
SELECT 4, 12, CAST( NULL AS INT ) FROM DUAL UNION ALL
SELECT 5, 11, CAST( NULL AS INT ) FROM DUAL;
CREATE TABLE book ( id, order_type, deleted ) AS
SELECT 1, 1, 'F' FROM DUAL UNION ALL
SELECT 2, NULL, 'F' FROM DUAL UNION ALL
SELECT 3, 5, 'F' FROM DUAL UNION ALL
SELECT 4, 5, 'F' FROM DUAL UNION ALL
SELECT 5, 4, 'F' FROM DUAL UNION ALL
SELECT 6, 4, 'F' FROM DUAL UNION ALL
SELECT 7, 3, 'T' FROM DUAL;
Query:
SELECT COUNT( b.order_type ) AS value_id,
o.id,
o.order_type
FROM order_table o
LEFT OUTER JOIN
book b
ON ( o.id = b.order_type AND b.deleted = 'F' )
WHERE o.type_id = 11
GROUP BY o.id, o.type_id
ORDER BY value_id DESC, id DESC;
Output:
VALUE_ID ID TYPE_ID
-------- -- -------
2 5 11
1 1 11
0 3 11
0 2 11
However, if you did want to use the legacy Oracle comma-join syntax then you can get the same result with:
SELECT COUNT( b.order_type ) AS value_id,
o.id,
o.order_type
FROM order_table o,
book b
WHERE o.type_id = 11
AND b.order_type (+) = o.id
AND b.deleted (+) = 'F'
GROUP BY o.id, o.type_id
ORDER BY value_id DESC, id DESC;
But please don't as the ANSI/ISO joins are much easier to comprehend the join conditions.
You could also do this with a scalar subquery, which may or may not be more performant than the left join versions described in the other answers. (Quite possibly, the optimizer may rewrite it to be a left join anyway!):
with order_table ( id, type_id, value_id ) as (select 1, 11, cast( null as int ) from dual union all
select 2, 11, cast( null as int ) from dual union all
select 3, 11, cast( null as int ) from dual union all
select 4, 12, cast( null as int ) from dual union all
select 5, 11, cast( null as int ) from dual),
book ( id, order_type, deleted ) as (select 1, 1, 'F' from dual union all
select 2, null, 'F' from dual union all
select 3, 5, 'F' from dual union all
select 4, 5, 'F' from dual union all
select 5, 4, 'F' from dual union all
select 6, 4, 'F' from dual union all
select 7, 3, 'T' from dual)
-- end of mimicking your tables; you wouldn't need the above subqueries as you already have the tables.
-- See SQL below:
select (select count(*) from book bk where bk.deleted = 'F' and bk.order_type = ot.id) value_id,
ot.type_id,
ot.id
from order_table ot
order by value_id desc,
id desc;
VALUE_ID TYPE_ID ID
---------- ---------- ----------
2 11 5
2 12 4
1 11 1
0 11 3
0 11 2

SQL: Earliest Date After Latest Null If Exists

Using T-Sql I am looking to return the min date after the latest null if one exists and simply the min date on any products where there are no nulls.
Table:
DateSold Product
12/31/2012 A
1/31/2013
2/28/2013 A
3/31/2013 A
4/30/2013 A
5/31/2013
6/30/2013 A
7/31/2013 A
8/31/2013 A
9/30/2013 A
12/31/2012 B
1/31/2013 B
2/28/2013 B
3/31/2013 B
4/30/2013 B
5/31/2013 B
6/30/2013 B
7/31/2013 B
8/31/2013 B
9/30/2013 B
For product “A” 6/30/2013 is the desired return while for product “B” 12/31/2012 is desired.
Result:
MinDateSold Product
6/30/2013 A
12/31/2012 B
Any solutions will greatly be appreciated. Thank you.
This does it for me, if there's a GROUP involved, otherwise how do you know whether the NULLs are in the run of A or B products? I realise this may not be exactly what you're after, but I hope it helps anyway.
WITH DATA_IN AS (
SELECT 1 as grp,
convert(DateTime,'12/31/2012') as d_Date,
'A' AS d_ch
UNION ALL
SELECT 1, '1/31/2013', NULL UNION ALL
SELECT 1, '2/28/2013', 'A' UNION ALL
SELECT 1, '3/31/2013', 'A' UNION ALL
SELECT 1, '4/30/2013', 'A' UNION ALL
SELECT 1, '5/31/2013', NULL UNION ALL
SELECT 1, '6/30/2013', 'A' UNION ALL
SELECT 1, '7/31/2013', 'A' UNION ALL
SELECT 1, '8/31/2013', 'A' UNION ALL
SELECT 1, '9/30/2013', 'A' UNION ALL
SELECT 2, '12/31/2012', 'B' UNION ALL
SELECT 2, '1/31/2013', 'B' UNION ALL
SELECT 2, '2/28/2013', 'B' UNION ALL
SELECT 2, '3/31/2013', 'B' UNION ALL
SELECT 2, '4/30/2013', 'B' UNION ALL
SELECT 2, '5/31/2013', 'B' UNION ALL
SELECT 2, '6/30/2013', 'B' UNION ALL
SELECT 2, '7/31/2013', 'B' UNION ALL
SELECT 2, '8/31/2013', 'B' UNION ALL
SELECT 2, '9/30/2013', 'B'
)
SELECT
grp as YourGroup,
(SELECT Min(d_date) -- first date after...
FROM DATA_IN
WHERE d_date>
Coalesce( -- either the latest NULL
(SELECT max(d_Date)
FROM DATA_IN d2
WHERE d2.grp=d1.grp AND d2.d_ch IS NULL
)
, '1/1/1901' -- or a base date if no NULLs
)
) as MinDateSold
FROM DATA_IN d1
GROUP BY grp
Results :
1 2013-06-30 00:00:00.000
2 2012-12-31 00:00:00.000
One approach to this is to count the number of NULL values that appear before a given row for a given value. This divides the ranges into groups. For each group, take the minimum date. And, find the largest minimum date for each product:
select product, minDate
from (select product, NumNulls, min(DateSold) as minDate,
row_number() over (partition by product order by min(DateSold) desc
) as seqnum
from (select t.*,
(select count(*)
from table t2
where t2.product is null and t2.DateSold <= t.DateSold
) as NumNulls
from table t
) t
group by Product, NumNUlls
) t
where seqnum = 1;
In your data, there is no mixing of different products in a range, so this query sort of assumes that is true as well.

How to select row based on existance of value in other column

I realise the title to this question may be vague but I am not sure how to phrase it. I have the following table:
i_id option p_id
---- ------ ----
1 A 4
1 B 8
1 C 6
2 B 3
2 C 5
3 A 7
3 B 3
4 E 11
How do I select a row based on the value of the option column for each unique i_id: if 'C' exists, select the row, else select row with 'B' else with 'A' so that result set is:
i_id option p_id
---- ------ ----
1 C 6
2 C 5
3 B 3
select i_id, option, p_id
from (
select
i_id,
option,
p_id,
row_number() over (partition by i_id order by case option when 'C' then 0 when 'B' then 1 when 'A' then 2 end) takeme
from thetable
where option in ('A', 'B', 'C')
) foo
where takeme = 1
This will give you the values ordered by C, B, A, while removing any i_id record that does not have one of these values.
WITH ranked AS
(
SELECT i_id, [option], p_id
, ROW_NUMBER() OVER (PARTITION BY i_id ORDER BY CASE [option]
WHEN 'C' THEN 1
WHEN 'B' THEN 2
WHEN 'A' THEN 3
ELSE 4
END) AS rowNumber
FROM yourTable
WHERE [option] IN ('A', 'B', 'C')
)
SELECT r.i_id, r.[option], r.p_id
FROM ranked AS r
WHERE r.rowNumber = 1
create table t2 (
id int,
options varchar(1),
pid int
)
insert into t2 values(1, 'A', 4)
insert into t2 values(1, 'B', 8)
insert into t2 values(1, 'C', 6)
insert into t2 values(1, 'E', 7)
select t2.* from t2,
(select id, MAX(options) as op from t2
where options <> 'E'
group by id) t
where t2.id = t.id and t2.options = t.op
Well, I would suggest that this problem can be made easier if you can assign a numeric "score" to each letter, such that "better" letters have higher scores. Then you can use MAX to find, for each group, the row with the highest "score" for the option. Since 'A' < 'B' < 'C', we could cheat here and use option as the score, and thus:
SELECT t1.i_id, t1.option, t1.p_id
FROM thetable t1
INNER JOIN (SELECT t2.i_id, MAX(option)
FROM thetable t2
GROUP BY t2.i_id) AS maximums
ON t1.i_id = maximums.i_id
WHERE option != 'D'
This assumes that {i_id, option} is a natural key of the table (i.e., that no two rows will have the same combination of values for those two columns; or, alternatively, that you have an uniqueness constraint on that pair of columns).