Finding matches in multiple columns in different orders - sql

I am trying to merge 2 databases with the same schema together, and this is one part of it.
I have changed the subject to keep it more understandable - I cannot change this schema, it's just what I'm working with.
I have a table in both my source and target databases with the following columns:
Car
CarType1
CarType2
CarType3
CarType4
I am trying to write a query that will tell me in the target database, which rows have the same Cars between the 2 databases, but different CarTypes. All I need is a count on the rows that are different.
My query written in english would be:
Bring me back a count of rows where the Car is the same and the CarTypes between the two systems do not match. It doesn't matter if the CarType is in a different CarType field between the two, just whether all of the values are contained in one of the 4 fields or not.
So if in my source database this row:
Car: Mustang
CarType1: Fast
CarType2: Convertible
CarType3: null
CarType4: null
And in my target database I have this row:
Car: Mustang
CarType1: Fast
CarType2: Convertible
CarType3: Sports
CarType4: null
This would count as a non-match, since it's a Mustang and because the aggregate of the CarType fields is different. What order the values are in does not matter for this.
How would I write this query? I cannot get a grasp on it.

;WITH SourceT AS (
SELECT 'Toyota' AS Car, 'A' AS CarType1, 'B' AS CarType2, 'C' CarType3, 'D' CarType4 UNION ALL
SELECT 'BMW' AS Car, 'A' AS CarType1, 'B' AS CarType2, 'C' CarType3, 'D' CarType4 UNION ALL
SELECT 'Mustang' AS Car, 'Fast' AS CarType1, 'Convertible' AS CarType2, 'Sports' CarType3, NULL CarType4
),
TargetT AS (
SELECT 'Toyota' AS Car, 'D' AS CarType1, 'C' AS CarType2, 'B' CarType3, 'A' CarType4 UNION ALL
SELECT 'BMW' AS Car, 'D' AS CarType1, 'C' AS CarType2, 'B' CarType3, 'A' CarType4 UNION ALL
SELECT 'Mustang' AS Car, 'Fast' AS CarType1, 'Convertible' AS CarType2, NULL CarType3, NULL CarType4 )
SELECT *
FROM SourceT s
WHERE NOT EXISTS
(
SELECT *
FROM TargetT t
WHERE s.Car = t.Car AND 0 =
(SELECT COUNT(*) FROM
( (
(SELECT s.CarType1 AS t UNION ALL
SELECT s.CarType2 AS t UNION ALL
SELECT s.CarType3 AS t UNION ALL
SELECT s.CarType4 AS t )
EXCEPT
(SELECT t.CarType1 AS t UNION ALL
SELECT t.CarType2 AS t UNION ALL
SELECT t.CarType3 AS t UNION ALL
SELECT t.CarType4 AS t )
)
UNION ALL
(
(SELECT t.CarType1 AS t UNION ALL
SELECT t.CarType2 AS t UNION ALL
SELECT t.CarType3 AS t UNION ALL
SELECT t.CarType4 AS t )
EXCEPT
(SELECT s.CarType1 AS t UNION ALL
SELECT s.CarType2 AS t UNION ALL
SELECT s.CarType3 AS t UNION ALL
SELECT s.CarType4 AS t )
)
) T
)
)
Or a slightly shorter version
SELECT *
FROM SourceT s
WHERE NOT EXISTS
(
SELECT *
FROM TargetT t
WHERE s.Car = t.Car AND
(SELECT t FROM (SELECT s.CarType1 AS t UNION ALL
SELECT s.CarType2 AS t UNION ALL
SELECT s.CarType3 AS t UNION ALL
SELECT s.CarType4 AS t ) D ORDER BY t FOR XML PATH(''))=
(SELECT t FROM (SELECT t.CarType1 AS t UNION ALL
SELECT t.CarType2 AS t UNION ALL
SELECT t.CarType3 AS t UNION ALL
SELECT t.CarType4 AS t ) D ORDER BY t FOR XML PATH(''))
)

Try this and let me know if it works:
SELECT * FROM db1.dbo.Cars
EXCEPT
SELECT c1.* FROM db1.dbo.Cars as c1
INNER JOIN db2.dbo.Cars as c2
ON c1.Car = c2.Car
AND
--Check carType1
(c1.CarType1 = c2.CarType1 OR
c1.CarType1 = c2.CarType2 OR
c1.CarType1 = c2.CarType3 OR
c1.CarType1 = c2.CarType4)
AND
--Check carType2
(c1.CarType2 = c2.CarType1 OR
c1.CarType2 = c2.CarType2 OR
c1.CarType2 = c2.CarType3 OR
c1.CarType2 = c2.CarType4)
AND
--Check carType3
(c1.CarType3 = c2.CarType1 OR
c1.CarType3 = c2.CarType2 OR
c1.CarType3 = c2.CarType3 OR
c1.CarType3 = c2.CarType4)
AND
--Check carType4
(c1.CarType4 = c2.CarType1 OR
c1.CarType4 = c2.CarType2 OR
c1.CarType4 = c2.CarType3 OR
c1.CarType4 = c2.CarType4)
NOTE:
You will have to add ISNULLs to this to either include or exclude if a column is null.
To exclude:
ISNULL(c1.CarType4, -1) = ISNULL(c2.CarType4, -2)
To include:
ISNULL(c1.CarType4, -1) = ISNULL(c2.CarType4, -1)

SELECT c1.car
FROM target.cars c1
INNER JOIN source.cars c2
ON c2.car = c1.car
WHERE
COALESCE( c1.cartype1, '') NOT IN
( '', c2.cartype1, c2.cartype2, c2.cartype3, c2.cartype4 )
OR
COALESCE( c1.cartype2, '') NOT IN
( '', c2.cartype1, c2.cartype2, c2.cartype3, c2.cartype4 )
OR
COALESCE( c1.cartype3, '') NOT IN
( '', c2.cartype1, c2.cartype2, c2.cartype3, c2.cartype4 )
OR
COALESCE( c1.cartype4, '') NOT IN
( '', c2.cartype1, c2.cartype2, c2.cartype3, c2.cartype4 )
OR
LEN( COALESCE( c1.cartype1, '') + COALESCE( c1.cartype2, '') +
COALESCE( c1.cartype3, '') + COALESCE( c1.cartype4, '') )
<>
LEN( COALESCE( c2.cartype1, '') + COALESCE( c2.cartype2, '') + COALESCE( c2.cartype3, '') +
COALESCE( c2.cartype4, '') )
;

Related

Is it possible to replace values in row to values from the same table by the reference in Oracle SQL

There is a table that contains values that are used in formulas. There are simple variables, that do not contain any expression, and also there are some variables that combined from simple variables into formula. I need to figure out if is it possible to do a SELECT query to get a readable formula based on aliases it contains. Each of these aliases could be used in other formulas.
Let's say that there are two tables:
ITEM TABLE
ID
Name
FORMULA_ID
1
Item name 1
f_3
2
Item name 2
f_26
FORMULA TABLE
ID
EXPRESSION
ALIASE
NAME
f_1
null
var_100
Ticket
f_2
null
var_200
Amount
f_3
var_100 * var_200
var_300
Some description
So is there any chance to query, with result like:
ITEM_NAME
READABLE_EXPRESSION
Item name 1
Ticket * Amount
Try this:
with items(ID,Name,Formula_Id) AS (
select 1, 'Item name 1', 'f_3' from dual union all
select 2, 'Item name 2', 'f_26' from dual
),
formulas (ID, EXPRESSION, ALIAS, NAME) as (
select 'f_1', null, 'var_100', 'Ticket' from dual union all
select 'f_2', null, 'var_200', 'Amount' from dual union all
select 'f_3', 'var_100 * var_200', 'var_300', 'Some description' from dual
),
rnformulas (id, EXPRESSION, ALIAS, NAME, rn) as (
select fm.*, row_number() over(order by id) as rn from formulas fm
),
recsubstitute( lvl, item_id, rn, expression ) as (
select 1, it.id, 0, fm.expression
from items it
join rnformulas fm on it.formula_id = fm.id
union all
select lvl+1, item_id, fm.rn, replace(r.expression, fm.alias, fm.name)
from recsubstitute r
join rnformulas fm on instr(r.expression, fm.alias) > 0 and fm.rn > r.rn
)
select item_id, expression from (
select item_id, expression, row_number() over(partition by item_id order by lvl desc, rn asc) as rn
from recsubstitute
)
where rn = 1
;
ITEM_ID EXPRESSION
---------- ------------------------------------------------------------
1 Ticket * Amount
Note that it's far to be bullet proof against all situations, especially recursion in the aliases.
Some improvement with another set of data:
with items(ID,Name,Formula_Id) AS (
select 1, 'Item name 1', 'f_3' from dual union all
select 2, 'Item name 2', 'f_4' from dual
),
formulas (ID, EXPRESSION, ALIAS, NAME) as (
select 'f_1', null, 'var_100', 'Ticket' from dual union all
select 'f_2', null, 'var_200', 'Amount' from dual union all
select 'f_3', 'var_100 * var_200', 'var_300', 'Some description' from dual union all
select 'f_4', 'var_300', null, 'Other description' from dual
),
rnformulas (id, EXPRESSION, ALIAS, NAME, rn) as (
select fm.*, row_number() over(order by id) as rn from formulas fm
),
recsubstitute( lvl, item_id, rn, expression ) as (
select 1, it.id, 0, fm.expression
from items it
join rnformulas fm on it.formula_id = fm.id
union all
select lvl+1, item_id, fm.rn, replace(r.expression, fm.alias, nvl(fm.expression,fm.name))
from recsubstitute r
join rnformulas fm on instr(r.expression, fm.alias) > 0
)
select item_id, expression from (
select item_id, expression, row_number() over(partition by item_id order by lvl desc, rn asc) as rn
from recsubstitute
)
where rn = 1
;
1 Ticket * Amount
2 Ticket * Amount
Split the space-delimited formulas into rows. Join the expression parts to the aliases and replace the alias with the name. Join this to the item_table using LISTAGG to concatenate the rows back into a single column.
WITH formula_split AS (
SELECT DISTINCT ft.id
,level lvl
,regexp_substr(ft.expression,'[^ ]+',1,level) expression_part
FROM formula_table ft
CONNECT BY ( ft.id = ft.id
AND level <= length(ft.expression) - length(replace(ft.expression,' ')) + 1 ) START WITH ft.expression IS NOT NULL
),readable_tbl AS (
SELECT ft.id
,ft.lvl
,replace(ft.expression_part,ftn1.aliase,ftn1.name) readable_expression
FROM formula_split ft
LEFT JOIN formula_table ftn1 ON ( ft.expression_part = ftn1.aliase )
)
SELECT it.name item_name
,LISTAGG(readable_expression,' ') WITHIN GROUP(ORDER BY lvl) readable_expression
FROM item_table it
JOIN readable_tbl rt ON ( it.formula_id = rt.id )
GROUP BY it.name
With sample data create CTE (calc_data) for modeling
WITH
items (ITEM_ID, ITEM_NAME, FORMULA_ID) AS
(
Select 1, 'Item name 1', 'f_3' From Dual Union All
Select 2, 'Item name 2', 'f_26' From Dual
),
formulas (FORMULA_ID, EXPRESSION, ALIAS, ELEMENT_NAME) AS
(
Select 'f_1', null, 'var_100', 'Ticket' From Dual Union All
Select 'f_2', null, 'var_200', 'Amount' From Dual Union All
Select 'f_3', 'var_100 * var_200', 'var_300', 'Some description' From Dual
),
calc_data AS
( SELECT e.ITEM_NAME, e.FORMULA_ID, e.FORMULA, e.X, e.OPERAND, e.Y,
ROW_NUMBER() OVER(Partition By e.ITEM_NAME Order By e.FORMULA_ID) "RN", f.ELEMENT_NAME
FROM( Select CAST('.' as VARCHAR2(32)) "FORMULA", i.ITEM_NAME, f.FORMULA_ID,
SubStr(Replace(f.EXPRESSION, ' ', ''), 1, InStr(Replace(f.EXPRESSION, ' ', ''), '*') - 1) "X",
CASE
WHEN InStr(f.EXPRESSION, '+') > 0 THEN '+'
WHEN InStr(f.EXPRESSION, '-') > 0 THEN '-'
WHEN InStr(f.EXPRESSION, '*') > 0 THEN '*'
WHEN InStr(f.EXPRESSION, '/') > 0 THEN '/'
END "OPERAND",
--
SubStr(Replace(f.EXPRESSION, ' ', ''), InStr(Replace(f.EXPRESSION, ' ', ''), '*') + 1) "Y"
From formulas f
Inner Join items i ON(f.FORMULA_ID = i.FORMULA_ID)
) e
Inner Join formulas f ON(f.FORMULA_ID <> e.FORMULA_ID)
)
Main SQL with MODEL clause
SELECT ITEM_NAME, FORMULA
FROM ( SELECT *
FROM calc_data
MODEL
PARTITION BY (ITEM_NAME)
DIMENSION BY (RN)
MEASURES (X, OPERAND, Y, FORMULA, ELEMENT_NAME)
RULES ( FORMULA[1] = ELEMENT_NAME[1] || ' ' || OPERAND[1] || ' ' || ELEMENT_NAME[2] )
)
WHERE RN = 1
R e s u l t :
ITEM_NAME
FORMULA
Item name 1
Amount * Ticket
Just as an option...
The same result without any analytic functions, pseudo columns, unions, etc... - just selecting over and over and over. Not readable, though...
Select
i.ITEM_NAME,
REPLACE( REPLACE( (Select EXPRESSION From formulas Where FORMULA_ID = f.FORMULA_ID),
(Select Min(ALIAS) From formulas Where FORMULA_ID <> f.FORMULA_ID),
(Select ELEMENT_NAME From formulas Where FORMULA_ID <> f.FORMULA_ID And ALIAS = (Select Min(ALIAS) From formulas Where FORMULA_ID <> f.FORMULA_ID) )
) ||
REPLACE( (Select EXPRESSION From formulas Where FORMULA_ID = f.FORMULA_ID),
(Select Max(ALIAS) From formulas Where FORMULA_ID <> f.FORMULA_ID),
(Select ELEMENT_NAME From formulas Where FORMULA_ID <> f.FORMULA_ID And ALIAS = (Select Max(ALIAS) From formulas Where FORMULA_ID <> f.FORMULA_ID) )
),
(SELECT Max(ALIAS) From formulas Where FORMULA_ID <> f.FORMULA_ID ) || (Select Min(ALIAS) From formulas Where FORMULA_ID <> f.FORMULA_ID) ||
SubStr(f.EXPRESSION, InStr(f.EXPRESSION, ' ', 1, 1), (InStr(f.EXPRESSION, ' ', 1, 2) - InStr(f.EXPRESSION, ' ', 1, 1)) + 1 ), ''
) "FORMULA"
From
formulas f
Left Join
items i ON(i.FORMULA_ID = f.FORMULA_ID)
Where i.ITEM_NAME Is Not Null
Thank you all for your answers!
I've decided to create a pl/sql function, just to modify a formula to readable row. So the function just looks for variables using regex, and uses indexes to replace every variable with a name.
CREATE OR REPLACE FUNCTION READABLE_EXPRESSION(inExpression IN VARCHAR2)
RETURN VARCHAR2
IS
matchesCount INTEGER;
toReplace VARCHAR2(32767);
readableExpression VARCHAR2(32767);
selectString VARCHAR2(32767);
BEGIN
matchesCount := REGEXP_COUNT(inExpression, '(var_)(.*?)');
IF matchesCount IS NOT NULL AND matchesCount > 0 THEN
readableExpression := inExpression;
FOR i in 1..matchesCount
LOOP
toReplace := substr(inExpression, REGEXP_INSTR(inExpression, '(var_)(.*?)', 1, i, 0),
REGEXP_INSTR(inExpression, '(var_)(.*?)', 1, i, 1) -
REGEXP_INSTR(inExpression, '(var_)(.*?)', 1, i, 0)
);
SELECT DISTINCT F.NAME
INTO selectString
FROM FORMULA F
WHERE F.ALIASE = toReplace FETCH FIRST 1 ROW ONLY;
readableExpression := REPLACE(readableExpression,
toReplace,
selectString
);
end loop;
end if;
return readableExpression;
END;
So such function returns 1 result row with replaced values for 1 input row with FORMULA. All you need to do is join the ITEM and FORMULA tables in the SELECT.
SELECT item.name, READABLE_EXPRESSION(formula.expression)
FROM item
JOIN formula ON item.formula_id = formula.id;
Please note that the tables are fictitious so as not to reveal the actual data structure, so there might be some inaccuracies. But the general idea should be clear.

Excluding records from table based on rules from another table

I'm using Oracle SQL and I have a product table with diffrent attributes and sales volume for each product and another table with certain exclusion rules for different level of aggregation. Let's look at the example:
Here is our main table with sales data on which we want to perform some calculations:
And the other table contains diffrent rules which are supposed to exclude certain rows from table above:
When there is an "x", this column shouldn't be considered so our rules are:
1. exclude all rows with ATTR_3 = 'no'
2. exlcude all rows with ATTR_1 = 'Europe' and ATTR_2 = 'snacks' and ATTR_3 = 'no'
3. exlcude all rows with ATTR_1 = 'Africa'
And based on that our final output should be like that:
How this could be achived in SQL? I was thinking about join but I have no idea how to handle different levels of aggregation for exclusions.
I think your expected output is wrong. None of the rules excludes the 2nd row (Europe - snacks - yes).
SQL> with
2 -- sample data
3 test (product_id, attr_1, attr_2, attr_3) as
4 (select 81928 , 'Europe', 'beverages', 'yes' from dual union all
5 select 16534 , 'Europe', 'snacks' , 'yes' from dual union all
6 select 56468 , 'USA' , 'snacks' , 'no' from dual union all
7 select 129921, 'Africa', 'drinks' , 'yes' from dual union all
8 select 123021, 'Africa', 'snacks' , 'yes' from dual union all
9 select 165132, 'USA' , 'drinks' , 'yes' from dual
10 ),
11 rules (attr_1, attr_2, attr_3) as
12 (select 'x' , 'x' , 'no' from dual union all
13 select 'Europe', 'snacks', 'no' from dual union all
14 select 'Africa', 'x' , 'x' from dual
15 )
16 -- query you need
17 select t.*
18 from test t
19 where (t.attr_1, t.attr_2, t.attr_3) not in
20 (select
21 decode(r.attr_1, 'x', t.attr_1, r.attr_1),
22 decode(r.attr_2, 'x', t.attr_2, r.attr_2),
23 decode(r.attr_3, 'x', t.attr_3, r.attr_3)
24 from rules r
25 );
PRODUCT_ID ATTR_1 ATTR_2 ATT
---------- ------ --------- ---
81928 Europe beverages yes
16534 Europe snacks yes
165132 USA drinks yes
SQL>
You can use the join using CASE .. WHEN statement as follows:
SELECT P.*
FROM PRODUCT P
JOIN RULESS R ON
(R.ATTR_1 ='X' OR P.ATTR_1 <> R.ATTR_1)
AND (R.ATTR_2 ='X' OR P.ATTR_2 <> R.ATTR_2)
AND (R.ATTR_3 ='X' OR P.ATTR_3 <> R.ATTR_3)
You can use NOT EXISTS
SELECT *
FROM sales s
WHERE NOT EXISTS (
SELECT 0
FROM attributes a
WHERE ( ( a.attr_1 = s.attr_1 AND a.attr_1 IS NOT NULL )
OR a.attr_1 IS NULL )
AND ( ( a.attr_2 = s.attr_2 AND a.attr_2 IS NOT NULL )
OR a.attr_2 IS NULL )
AND ( ( a.attr_3 = s.attr_3 AND a.attr_3 IS NOT NULL )
OR a.attr_3 IS NULL )
)
where I considered the x values within the attributes table as NULL. If you really have x characters, then you can use :
SELECT *
FROM sales s
WHERE NOT EXISTS (
SELECT 0
FROM attributes a
WHERE ( ( NVL(a.attr_1,'x') = s.attr_1 AND NVL(a.attr_1,'x')!='x' )
OR NVL(a.attr_1,'x')='x' )
AND ( ( NVL(a.attr_2,'x') = s.attr_2 AND NVL(a.attr_2,'x')!='x' )
OR NVL(a.attr_2,'x')='x' )
AND ( ( NVL(a.attr_3,'x') = s.attr_3 AND NVL(a.attr_3,'x')!='x' )
OR NVL(a.attr_3,'x')='x' )
)
instead.
Demo
I would do this with three different not exists:
select p.*
from product p
where not exists (select 1
from rules r
where r.attr_1 = p.attr_1 and r.attr_1 <> 'x'
) and
not exists (select 1
from rules r
where r.attr_2 = p.attr_2 and r.attr_2 <> 'x'
) and
not exists (select 1
from rules r
where r.attr_3 = p.attr_3 and r.attr_3 <> 'x'
) ;
In particular, this can take advantage of indexes on (attr_1), (attri_2) and (attr_3) -- something that is quite handy if you have a moderate number of rules.

Group accounts by 1 condition vs 2 conditions

I am trying to get a count of pat_id that were either only "Sent" or only "Sent and Received"
Here is my query so far
select pat_id,
(case when addr_msg_actn_c in(1) then 'Sent' else convert(varchar(10),'Received') end) [Status]
from pat_addr_verif
where pat_id in ('Z1000354','Z1000363','Z1000392','Z1000394')
group by pat_id, addr_msg_actn_c
order by pat_id
Which generates this output
However, I'd like to simplify the output to display like this
Here's a small proof of concept. You can generate a case statement on the sum of a value to choose which text string you want.
CREATE TABLE #SentReceive (
Pat_ID NVARCHAR(8)
,[status] TINYINT
)
INSERT INTO #SentReceive
VALUES ( 'Z1000354' ,1 )
,( 'Z1000354' ,1 )
,( 'Z1000363' ,1 )
,( 'Z1000363' ,2 )
,( 'Z1000392' ,2 )
,( 'Z1000394' ,1 )
,( 'Z1000394' ,2 )
SELECT pat_id
,sentstatus = CASE sum(distinct [status])
WHEN 1
THEN 'Sent'
WHEN 2
THEN 'Received'
WHEN 3
THEN 'Sent and Received'
END
FROM #SentReceive
GROUP BY pat_id
ORDER BY pat_id
create table SentReceive
(
Pat_ID nvarchar(8),
SentOnly nvarchar(8)
)
insert SentReceive
values
('Z1000354','Sent'),
('Z1000363','Sent'),
('Z1000363','Received'),
('Z1000392','Sent'),
('Z1000394','Sent'),
('Z1000394','Received')
select distinct a.Pat_ID,
case
when b.SentOnly is not null then
case a.SentOnly
when 'Received' then b.SentOnly + ' and ' + a.SentOnly
else a.SentOnly + ' and ' + b.SentOnly
end
else a.SentOnly
end as Status
from SentReceive a
left join SentReceive b on a.Pat_ID = b.Pat_ID and a.SentOnly <> b.SentOnly
I did a quickie with only the available data that you provided. If there are other variations in your dataset then you will need to account for that and it may or may not change what I came up with. Good luck!
Here is what I came up with:
;with cte as
(
select 1 as _order, 'Z1000354' as pat_id, 'Sent' as SentOnly
union select 2, 'Z1000363', 'Sent'
union select 3, 'Z1000363', 'Received'
union select 4, 'Z1000392', 'Sent'
union select 5, 'Z1000393', 'Received'
union select 5, 'Z1000394', 'Sent'
union select 6, 'Z1000394', 'Received'
)
select pat_id, replace(_Status,',',' and') as _Status
from(
select distinct pat_id,
STUFF((Select ', '+SentOnly
from cte T2
where T1.pat_id=T2.pat_id
FOR XML PATH('')),1,1,'') as _Status from cte T1
) a
where _Status in (' Sent', ' Sent, Received')
Final query with some help from above posters:
select distinct a.pat_id,
case when b.ADDR_MSG_ACTN_C is not null
then 'Sent and Received' else 'Sent Only' end as Status
from pat_addr_verif a
left join pat_addr_verif b on a.pat_id = b.pat_id and
a.ADDR_MSG_ACTN_C <> b.ADDR_MSG_ACTN_C
where a.pat_id in ('Z1000354','Z1000363','Z1000392','Z1000394')
Correct Final Output

PL/SQL: Invalid Number error

I am creating a procedure for which I collect data by repeatedly running the following query.
SELECT ATTRIBUTE_VALUE,
COUNT(src1) CNT1,
COUNT(src2) CNT2
FROM (
SELECT a.ATTRIBUTE_VALUE,
1 src1,
TO_NUMBER(NULL) src2
FROM (
SELECT DECODE(
L,
1, IP_ADDRESS,
DECODE(
L,
2, IP_SUBNET_MASK,
DECODE(
L,
3, IP_DEFAULT_GATEWAY
)
)
) ATTRIBUTE_VALUE
FROM ( SELECT LEVEL L FROM DUAL X CONNECT BY LEVEL <= 3 ),
REUT_LOAD_IP_ADDRESSES
WHERE LIP_IPT_NAME = 'CE'
AND IP_LNT_ID IN (
SELECT LNT_ID
FROM REUT_LOAD_NTN
WHERE LNT_ID IN (
SELECT RLPN.LPN_LNT_ID
FROM REUT_LOAD_PI_NTN RLPN
WHERE LPN_LPI_ID IN (
SELECT RLPI.LPI_ID
FROM REUT_LOAD_PAC_INS RLPI
WHERE RLPI.LPI_DATE_ADDED IN (
SELECT MAX(RLPI2.LPI_DATE_ADDED)
FROM REUT_LOAD_PAC_INS RLPI2
WHERE RLPI2.PI_JOB_ID = P_ORDER_ID
)
)
)
AND IP_CEASE_DATE IS NULL
AND LNT_SERVICE_INSTANCE = 'PRIMARY'
)
It is running fine in SQL developer but when executing it as a procedure, I am getting INVALID NUMBER ERROR (ORA-01722: invalid number) at
AND IP_LNT_ID IN (
SELECT LNT_ID, in the code.
Can I get any help?
The error is pretty clear. You're comparing a number to another type of value.
Example:
SELECT 'x'
FROM DUAL
WHERE 1 IN (SELECT 'a'
FROM DUAL)
This means that IP_LNT_ID, LNT_ID, LPN_LNT_ID and LPI_ID have to be NUMBER. And LPI_DATE_ADDED and LPI_DATE_ADDED should both be date or timestamp.
If this is not possible you could compare everything as char:
SELECT ATTRIBUTE_VALUE, COUNT(src1) CNT1, COUNT(src2) CNT2
FROM (SELECT a.ATTRIBUTE_VALUE, 1 src1, TO_NUMBER(NULL) src2
FROM (SELECT
DECODE(L,1,IP_ADDRESS,DECODE(L,2,IP_SUBNET_MASK,DECODE(L,3,IP_DEFAULT_GATEWAY) ) ) ATTRIBUTE_VALUE
FROM
(
SELECT LEVEL L FROM DUAL X CONNECT BY LEVEL <= 3
),
REUT_LOAD_IP_ADDRESSES
WHERE LIP_IPT_NAME = 'CE'
AND to_char(IP_LNT_ID) IN (
SELECT LNT_ID
FROM REUT_LOAD_NTN
WHERE to_char(LNT_ID) IN (
SELECT RLPN.LPN_LNT_ID
FROM REUT_LOAD_PI_NTN RLPN
WHERE to_char(LPN_LPI_ID) IN (
SELECT RLPI.LPI_ID
FROM REUT_LOAD_PAC_INS RLPI
WHERE to_char(RLPI.LPI_DATE_ADDED) IN (
SELECT MAX(RLPI2.LPI_DATE_ADDED)
FROM REUT_LOAD_PAC_INS RLPI2
WHERE RLPI2.PI_JOB_ID = P_ORDER_ID
)
)
)
AND IP_CEASE_DATE IS NULL
AND LNT_SERVICE_INSTANCE = 'PRIMARY'
)
But this should be avoided on any cost. Unfortunately some times we have to cheat a little from time to time to work with our existing infrasructure ;-)
You need to make sure:
REUT_LOAD_IP_ADDRESSES.IP_LNT_ID
and
REUT_LOAD_NTN.LNT_ID
Have the same data type or cast/convert one or other so that they have the same data type.
There are multiple other issues:
You have aggregated and non-aggregated values:
SELECT ATTRIBUTE_VALUE,
COUNT(src1) CNT1,
COUNT(src2) CNT2
FROM ( ... )
Without a GROUP BY clause.
src2 is TO_NUMBER(NULL) which is just NULL and COUNT(NULL) will always be 0 so your query is:
SELECT ATTRIBUTE_VALUE,
COUNT(src1) CNT1,
0 CNT2
...
This code:
SELECT DECODE(
L,
1, IP_ADDRESS,
DECODE(
L,
2, IP_SUBNET_MASK,
DECODE(
L,
3, IP_DEFAULT_GATEWAY
)
)
) ATTRIBUTE_VALUE
FROM ( SELECT LEVEL L FROM DUAL X CONNECT BY LEVEL <= 3 ),
REUT_LOAD_IP_ADDRESSES
Can be rewritten as:
SELECT DECODE(
L,
1, IP_ADDRESS,
2, IP_SUBNET_MASK,
3, IP_DEFAULT_GATEWAY
) ATTRIBUTE_VALUE
FROM ( SELECT LEVEL L FROM DUAL X CONNECT BY LEVEL <= 3 ),
REUT_LOAD_IP_ADDRESSES
Or, without the join as:
SELECT attribute_value
FROM REUT_LOAD_IP_ADDRESSES
UNPIVOT ( attribute_value FOR L IN (
IP_ADDRESS AS 1,
IP_SUBNET_MASK AS 2,
IP_DEFAULT_GATEWAY AS 3
) )
The innermost query:
SELECT RLPI.LPI_ID
FROM REUT_LOAD_PAC_INS RLPI
WHERE RLPI.LPI_DATE_ADDED IN (
SELECT MAX(RLPI2.LPI_DATE_ADDED)
FROM REUT_LOAD_PAC_INS RLPI2
WHERE RLPI2.PI_JOB_ID = P_ORDER_ID
)
The inner query is restricted to have RLPI2.PI_JOB_ID = P_ORDER_ID but there is no correlation between the outer query so you can retrieve results that do not match P_ORDER_ID but just happen to have the same date as a matching row.

Show only top n records and add '...' FOR XML

I want to list only a few records (say 3) and adds ... if there are more in a single line using FOR XML PATH('').
So far I have written
;WITH SRC AS (
select 'A' grp, 'abc' rec
union select 'A', 'def'
union select 'A', 'ghi'
union select 'A', 'jkl'
union select 'B', 'mno'
)
SELECT (
SELECT STUFF((
SELECT TOP 3 ',' + rec FROM SRC
WHERE SRC.grp = tableA.grp
FOR XML PATH('')
), 1, 1, '') + CASE WHEN (SELECT COUNT(1) FROM SRC WHERE SRC.grp = tableA.grp) > 3 THEN ',...' ELSE '' END
)
FROM (SELECT 'A' grp) tableA
The above works but I want to know if there is a way not to select from SRC twice to accomplish the job (one for the data, one for the count), because the where clause could be a big one in some case.
I can't move the where clause into the CTE as well because the condition depends on the result of another select (e.g. the select from tableA).
Using MS SQL SERVER 2008 R2
Thank you
Create a XML using the first 4 rows for the group. Shred the first three nodes and return ... for the fourth row.
with SRC as
(
select 'A' grp, 'abc' rec
union select 'A', 'def'
union select 'A', 'ghi'
union select 'A', 'jkl'
union select 'B', 'mno'
union select 'B', 'pqr'
)
select (
select ','+S2.X.value('.', 'nvarchar(max)')
from (
select top(4) S.rec, '...' as eli
from SRC as S
where S.grp = tableA.grp
for xml path('X'), type
-- order by ?
) as S1(X)
cross apply S1.X.nodes('(X[position() lt 4]/rec, X[position() eq 4]/eli)/text()') as S2(X)
for xml path(''), type
).value('substring(text()[1], 2)', 'nvarchar(max)')
from (select 'A') as tableA(grp);
How does this work?
The innermost query creates an XML using the first four rows.
select top(4) S.rec, '...' as eli
from SRC as S
where S.grp = tableA.grp
for xml path('X'), type
-- order by ?
that looks like this
<X>
<rec>abc</rec>
<eli>...</eli>
</X>
<X>
<rec>def</rec>
<eli>...</eli>
</X>
<X>
<rec>ghi</rec>
<eli>...</eli>
</X>
<X>
<rec>jkl</rec>
<eli>...</eli>
</X>
The XML is then shredded using nodes()
S1.X.nodes('(X[position() lt 4]/rec, X[position() eq 4]/eli)/text()') as S2(X)
X[position() lt 4]/rec gives you the rec value from the first three Xnodes and X[position() eq 4]/eli gives you the value from eli for the fourth row.
The result of the shredded XML as a table.
abc
def
ghi
...
Then comes a regular XML concatenation using for xml path with the little twist that I use substring(text()[1], 2) in a values() clause to remove the first comma.
Another way:
You can use row_number() in a case to figure out when the fourth row i returned a. Note that I have add an order by clause both to the row_number() function and the nested query to make sure what rows are returned.
with SRC as
(
select 'A' grp, 'abc' rec
union select 'A', 'def'
union select 'A', 'ghi'
union select 'A', 'jkl'
union select 'B', 'mno'
union select 'B', 'pqr'
)
select (
select top(4) ','+case when row_number() over(order by S.rec) = 4 then '...' else S.rec end
from SRC as S
where S.grp = tableA.grp
order by S.rec
for xml path(''), type
).value('substring(text()[1], 2)', 'nvarchar(max)')
from (select 'A') as tableA(grp);