SQL query: where any dump value is greater than 96 - sql

I recently encountered a couple of values that are causing exceptions in our application. A look into the db reveiled that we may have imported erroneous data (which must not be modified!). We now found that the cause of this error lies in unicode problems.
To find all relevant error records, I dumped the values I have already identified (manually) and could see, that the problematic vaules lie above the value 96, an example below:
Typ=96 Len=10: 83,85,49,89,36,73,219,190,159,87
Whereas 219,190,159 are problematic. This can be obtained through select dump(col) from table, however I would like to further only select records where one of the values in the dump exceed 99 -pretty much like (pseudo code) length(string(value)) for value in dump_record > 96 Is there any way to do this? Thanks folks.

This is how I understood the question. See if it helps; follow comments within code:
SQL> with
2 data (col) as
3 -- sample data:
4 -- 123LITTlefOOT results in Typ=96 Len=13: 49,50,51,76,73,84,84,108,101,102,79,79,84
5 -- where marked values are > 96 --- --- ---
6 --
7 -- BIG997FOOT results in Typ=96 Len=10: 66,73,71,57,57,55,70,79,79,84 which is OK
8 (select '123LITTlefOOT' from dual union all
9 select 'BIG997FOOT' from dual
10 ),
11 test as
12 -- DUMP of sample data
13 (select col,
14 dump(col) dmp
15 from data
16 ),
17 -- remove TYP=XX Len=yy
18 test2 as
19 (select col,
20 dmp,
21 trim(substr(dmp, instr(dmp, ':') + 1)) tdmp
22 from test
23 ),
24 -- split TDMP into rows
25 trows as
26 (select col,
27 dmp,
28 regexp_substr(tdmp, '[^,]+', 1, column_value) str
29 from test2 cross join table(cast(multiset(select level from dual
30 connect by level <= regexp_count(tdmp, ',') + 1
31 ) as sys.odcinumberlist))
32 )
33 select distinct col, dmp
34 from trows
35 where to_number(str) > 96;
COL DMP
------------- ------------------------------------------------------------
123LITTlefOOT Typ=1 Len=13: 49,50,51,76,73,84,84,108,101,102,79,79,84
SQL>

Regex totally did it, I used the simple below solution:
SELECT t.* FROM table t WHERE regexp_like(dump(t.error_col), '\d{3}');
Thanks for the quick replies!

Related

How can I create a right aligned string with n digits from a number without the character for the sign?

I have integers in the range between 1 and 999 and want to create a right aligned string from them, without a space for the sign.
This is possible, for example with
select
substr(to_char(theNumber, '999'), 2) as number3digits
from
theTable;
The substr(...,2) removes the space that is provided for the sign. I am wondering if there is a still shorter way, without the need to use substr to achieve the same result.
You didn't provide sample data so I'm just wondering what's wrong with pure TO_CHAR?
SQL> with test (col) as
2 (select 1 from dual union all
3 select 857 from dual union all
4 select 34 from dual
5 )
6 select substr(to_char(col, '999'), 2) result_rn,
7 lpad(col, 3, ' ') result_lf
8 from test;
RESULT_RN RESULT_LF
--------------- ---------------
1 1
857 857
34 34
SQL>

Oracle SQL compare strings and find matching sub-strings

I have colon separated tags associated to two different entities in two tables. Would like to do a sub-string matching for the tags and relate the entities.
Table 1 - Table of issues
Issue ---------------- Tag
Issue 1 -------------- Dual UOM:Serial Control:Material Issue
Issue 2 -------------- Validity rule:Effectivity date
Table 2 - Table of Tests
Test ----------------- Tag
Test 1 --------------- Inventory:Outbound:Material Issue
Test 2 --------------- Items:Single UOM
Test 3 --------------- Items:Dual UOM
Test 4 --------------- Recipe:Validity Rule
Test 5 --------------- Formula:Version control:date
Test 6 --------------- Formula:Effectivity date
Now, for each issue in table 1, I need to compare its associated tag with the tags in table 2 and find applicable tests.
In above ex,
Issue 1 - Matched tests will be Test 1, Test 3
Issue 2 - Matched tests will be Test 4, Test 5
All the tags associated to the issues and tests will come from a common tag master.
Any help in providing the sql code snippet that would do this sub-string to sub-string matching is much appreciated.
Here's one option: split issues into rows and compare them to test tags, using the INSTR function. Note that letter case must match. If it doesn't (in reality), use lower or upper function.
Read comments within code (which is split into several parts, to improve readability).
Sample data first:
SQL> with
2 -- sample data
3 issues (issue, tag) as
4 (select 1, 'Dual UOM:Serial Control:Material Issue' from dual union all
5 select 2, 'Validity Rule:Effectivity date' from dual
6 ),
7 tests (test, tag) as
8 (select 1, 'Inventory:Outbound:Material Issue' from dual union all
9 select 2, 'Items:Single UOM' from dual union all
10 select 3, 'Items:Dual UOM' from dual union all
11 select 4, 'Recipe:Validity Rule' from dual union all
12 select 5, 'Formula:Version control:date' from dual union all
13 select 6, 'Formula:Effectivity date' from dual
14 ),
Split issues into rows (splitiss), compare them to test tags (temp)
15 -- split issues into rows ...
16 splitiss as
17 (select issue,
18 tag,
19 regexp_substr(tag, '[^:]+', 1, column_value) val
20 from issues cross join
21 table(cast(multiset(select level from dual
22 connect by level <= regexp_count(tag, ':') + 1
23 ) as sys.odcinumberlist))
24 ),
25 -- ... and now compare them to test tags
26 temp as
27 (select i.issue, i.tag issue_tag, i.val, t.test, t.tag test_tag,
28 instr(t.tag, i.val) ins
29 from splitiss i cross join tests t
30 )
Return the result:
31 -- return only test tags which match to separate issues (INS > 0)
32 select t.issue,
33 t.issue_tag,
34 listagg(t.test, ', ') within group (order by t.test) matched_tests
35 from temp t
36 where t.ins > 0
37 group by t.issue, t.issue_tag;
ISSUE ISSUE_TAG MATCHED_TESTS
---------- -------------------------------------- --------------------
1 Dual UOM:Serial Control:Material Issue 1, 3
2 Validity Rule:Effectivity date 4, 6
SQL>
P.S. I believe you posted wrong test tags for issue #2; should be 4, 6, not 4, 5.
Thanks, this worked
I did break the tags into rows and then used substr,instr matching.

pivot table with two rows into columns

i have the next result for my table:
our_date | number_people
------------------------
23/09/19 | 26
24/09/19 | 26
ALWAYS will be just two rows
and i want pivot this result and get this:
our_date_1 | number_people_1 | our_date_2 | number_people_2
-----------------------------------------------------------------
23/09/19 | 26 | 24/09/19 | 26
to get the differences between number_people_1 and number_people_2
i try with:
select *
from table_1
pivot(
count(number_people)
for our_date in (:P_TODAY, :P_YESTERDAY)
)
and this is my actual error:
ORA-56900: la variable de enlace no está soportada en la operación PIVOT|UNPIVOT
56900. 0000 - "bind variable is not supported inside pivot|unpivot operation"
*Cause: Attempted to use bind variables inside pivot|unpivot operation.
*Action: This is not supported.
what's is wrong? how can i use dynamic values inside for clause ?
Best regards
Error says that this:
for fecha in (our_date)
can't have our_date (column name) as list of values; it (the list) has to contain constants, e.g.
for our_date in (date '2019-09-23', date '2019-09-24')
Once you fix that, query might look like this:
SQL> with table_1 (our_date, number_people) as
2 (select date '2019-09-23', 26 from dual union all
3 select date '2019-09-24', 26 from dual
4 )
5 select *
6 from table_1
7 pivot (max(number_people)
8 for our_date in (date '2019-09-23', date '2019-09-24')
9 );
TO_DATE(' 2019-09-23 00:00:00' TO_DATE(' 2019-09-24 00:00:00'
------------------------------ ------------------------------
26 26
SQL>
But, that's not exactly what you wanted.
What if there are 3, 4 or more rows in that table? Is it possible, or will there always be only 2 rows?
If it is always only 2 rows, self-join can do the job. For example:
SQL> with table_1 (our_date, number_people) as
2 (select date '2019-09-23', 26 from dual union all
3 select date '2019-09-24', 22 from dual
4 ),
5 temp as
6 (select our_date, number_people,
7 row_number() over (order by our_date) rn
8 from table_1
9 )
10 select
11 a.our_date our_date_1,
12 a.number_people number_people_1,
13 --
14 b.our_date our_date_2,
15 b.number_people number_people_2
16 from temp a cross join temp b
17 where a.rn = 1
18 and b.rn = 2;
OUR_DATE_1 NUMBER_PEOPLE_1 OUR_DATE_2 NUMBER_PEOPLE_2
---------- --------------- ---------- ---------------
23.09.2019 26 24.09.2019 22
SQL>

divide non sequential set of numbers into certain groups and return as rows in oracle database

I have a database column with non sequential numbers. Say 1 3 5 8 13 15 16 17 20 23 34 54 68. I want to divide them into groups of 3 numbers each(dividing factor is 3 for equal ranges).How can I get the below range values?
Here it is.
In the WITH clause, I create the test data. Don't worry if you don't understand what it does; it simply creates a view INPUTS with a single column COL with the values you offered for testing. The actual query (the solution) begins after the WITH clause. To test it, remove everything up to the commented line about selecting from INPUTS.
with
inputs(col) as (
select column_value
from sys.odcinumberlist(1,3,5,8,13,15,16,17,20,23,34,54,68)
)
-- select * from inputs; */
select row_num, min(col) as start_range, max(col) as end_range
from (
select col
, least( ceil(row_number() over (order by col)/3),
trunc(count(*) over () / 3)
) as row_num
from inputs
)
group by row_num
order by row_num
;
ROW_NUM START_RANGE END_RANGE
---------- ----------- ----------
1 1 5
2 8 15
3 16 20
4 23 68

How to track how many times a column changed its value?

I have a table called crewWork as follows :
CREATE TABLE crewWork(
FloorNumber int, AptNumber int, WorkType int, simTime int )
After the table was populated, I need to know how many times a change in apt occurred and how many times a change in floor occurred. Usually I expect to find 10 rows on each apt and 40-50 on each floor.
I could just write a scalar function for that, but I was wondering if there's any way to do that in t-SQL without having to write scalar functions.
Thanks
The data will look like this:
FloorNumber AptNumber WorkType simTime
1 1 12 10
1 1 12 25
1 1 13 35
1 1 13 47
1 2 12 52
1 2 12 59
1 2 13 68
1 1 14 75
1 4 12 79
1 4 12 89
1 4 13 92
1 4 14 105
1 3 12 115
1 3 13 129
1 3 14 138
2 1 12 142
2 1 12 150
2 1 14 168
2 1 14 171
2 3 12 180
2 3 13 190
2 3 13 200
2 3 14 205
3 3 14 216
3 4 12 228
3 4 12 231
3 4 14 249
3 4 13 260
3 1 12 280
3 1 13 295
2 1 14 315
2 2 12 328
2 2 14 346
I need the information for a report, I don't need to store it anywhere.
If you use the accepted answer as written now (1/6/2023), you get correct results with the OP dataset, but I think you can get wrong results with other data.
CONFIRMED: ACCEPTED ANSWER HAS A MISTAKE (as of 1/6/2023)
I explain the potential for wrong results in my comments on the accepted answer.
In this db<>fiddle, I demonstrate the wrong results. I use a slightly modified form of accepted answer (my syntax works in SQL Server and PostgreSQL). I use a slightly modified form of the OP's data (I change two rows). I demonstrate how the accepted answer can be changed slightly, to produce correct results.
The accepted answer is clever but needs a small change to produce correct results (as demonstrated in the above db<>fiddle and described here:
Instead of doing this as seen in the accepted answer COUNT(DISTINCT AptGroup)...
You should do thisCOUNT(DISTINCT CONCAT(AptGroup, '_', AptNumber))...
DDL:
SELECT * INTO crewWork FROM (VALUES
-- data from question, with a couple changes to demonstrate problems with the accepted answer
-- https://stackoverflow.com/q/8666295/1175496
--FloorNumber AptNumber WorkType simTime
(1, 1, 12, 10 ),
-- (1, 1, 12, 25 ), -- original
(2, 1, 12, 25 ), -- new, changing FloorNumber 1->2->1
(1, 1, 13, 35 ),
(1, 1, 13, 47 ),
(1, 2, 12, 52 ),
(1, 2, 12, 59 ),
(1, 2, 13, 68 ),
(1, 1, 14, 75 ),
(1, 4, 12, 79 ),
-- (1, 4, 12, 89 ), -- original
(1, 1, 12, 89 ), -- new , changing AptNumber 4->1->4 ges)
(1, 4, 13, 92 ),
(1, 4, 14, 105 ),
(1, 3, 12, 115 ),
...
DML:
;
WITH groupedWithConcats as (SELECT
*,
CONCAT(AptGroup,'_', AptNumber) as AptCombo,
CONCAT(FloorGroup,'_',FloorNumber) as FloorCombo
-- SQL SERVER doesnt have TEMPORARY keyword; Postgres doesn't understand # for temp tables
-- INTO TEMPORARY groupedWithConcats
FROM
(
SELECT
-- the columns shown in Andriy's answer:
-- https://stackoverflow.com/a/8667477/1175496
ROW_NUMBER() OVER ( ORDER BY simTime) as RN,
-- AptNumber
AptNumber,
ROW_NUMBER() OVER (PARTITION BY AptNumber ORDER BY simTime) as RN_Apt,
ROW_NUMBER() OVER ( ORDER BY simTime)
- ROW_NUMBER() OVER (PARTITION BY AptNumber ORDER BY simTime) as AptGroup,
-- FloorNumber
FloorNumber,
ROW_NUMBER() OVER (PARTITION BY FloorNumber ORDER BY simTime) as RN_Floor,
ROW_NUMBER() OVER ( ORDER BY simTime)
- ROW_NUMBER() OVER (PARTITION BY FloorNumber ORDER BY simTime) as FloorGroup
FROM crewWork
) grouped
)
-- if you want to see how the groupings work:
-- SELECT * FROM groupedWithConcats
-- otherwise just run this query to see the counts of "changes":
SELECT
COUNT(DISTINCT AptCombo)-1 as CountAptChangesWithConcat_Correct,
COUNT(DISTINCT AptGroup)-1 as CountAptChangesWithoutConcat_Wrong,
COUNT(DISTINCT FloorCombo)-1 as CountFloorChangesWithConcat_Correct,
COUNT(DISTINCT FloorGroup)-1 as CountFloorChangesWithoutConcat_Wrong
FROM groupedWithConcats;
ALTERNATIVE ANSWER
The accepted-answer may eventually get updated to remove the mistake. If that happens I can remove my warning but I still want leave you with this alternative way to produce the answer.
My approach goes like this: "check the previous row, if the value is different in previous row vs current row, then there is a change". SQL doesn't have idea or row order functions per se (at least not like in Excel for example; )
Instead, SQL has window functions. With SQL's window functions, you can use the window function RANK plus a self-JOIN technique as seen here to combine current row values and previous row values so you can compare them. Here is a db<>fiddle showing my approach, which I pasted below.
The intermediate table, showing the columns which has a value 1 if there is a change, 0 otherwise (i.e. FloorChange, AptChange), is shown at the bottom of the post...
DDL:
...same as above...
DML:
;
WITH rowNumbered AS (
SELECT
*,
ROW_NUMBER() OVER ( ORDER BY simTime) as RN
FROM crewWork
)
,joinedOnItself AS (
SELECT
rowNumbered.*,
rowNumberedRowShift.FloorNumber as FloorShift,
rowNumberedRowShift.AptNumber as AptShift,
CASE WHEN rowNumbered.FloorNumber <> rowNumberedRowShift.FloorNumber THEN 1 ELSE 0 END as FloorChange,
CASE WHEN rowNumbered.AptNumber <> rowNumberedRowShift.AptNumber THEN 1 ELSE 0 END as AptChange
FROM rowNumbered
LEFT OUTER JOIN rowNumbered as rowNumberedRowShift
ON rowNumbered.RN = (rowNumberedRowShift.RN+1)
)
-- if you want to see:
-- SELECT * FROM joinedOnItself;
SELECT
SUM(FloorChange) as FloorChanges,
SUM(AptChange) as AptChanges
FROM joinedOnItself;
Below see the first few rows of the intermediate table (joinedOnItself). This shows how my approach works. Note the last two columns, which have a value of 1 when there is a change in FloorNumber compared to FloorShift (noted in FloorChange), or a change in AptNumber compared to AptShift (noted in AptChange).
floornumber
aptnumber
worktype
simtime
rn
floorshift
aptshift
floorchange
aptchange
1
1
12
10
1
0
0
2
1
12
25
2
1
1
1
0
1
1
13
35
3
2
1
1
0
1
1
13
47
4
1
1
0
0
1
2
12
52
5
1
1
0
1
1
2
12
59
6
1
2
0
0
1
2
13
68
7
1
2
0
0
Note instead of using the window function RANK and JOIN, you could use the window function LAG to compare values in the current row to the previous row directly (no need to JOIN). I don't have that solution here, but it is described in the Wikipedia article example:
Window functions allow access to data in the records right before and after the current record.
If I am not missing anything, you could use the following method to find the number of changes:
determine groups of sequential rows with identical values;
count those groups;
subtract 1.
Apply the method individually for AptNumber and for FloorNumber.
The groups could be determined like in this answer, only there's isn't a Seq column in your case. Instead, another ROW_NUMBER() expression could be used. Here's an approximate solution:
;
WITH marked AS (
SELECT
FloorGroup = ROW_NUMBER() OVER ( ORDER BY simTime)
- ROW_NUMBER() OVER (PARTITION BY FloorNumber ORDER BY simTime),
AptGroup = ROW_NUMBER() OVER ( ORDER BY simTime)
- ROW_NUMBER() OVER (PARTITION BY AptNumber ORDER BY simTime)
FROM crewWork
)
SELECT
FloorChanges = COUNT(DISTINCT FloorGroup) - 1,
AptChanges = COUNT(DISTINCT AptGroup) - 1
FROM marked
(I'm assuming here that the simTime column defines the timeline of changes.)
UPDATE
Below is a table that shows how the distinct groups are obtained for AptNumber.
AptNumber RN RN_Apt AptGroup (= RN - RN_Apt)
--------- -- ------ ---------
1 1 1 0
1 2 2 0
1 3 3 0
1 4 4 0
2 5 1 4
2 6 2 4
2 7 3 4
1 8 5 => 3
4 9 1 8
4 10 2 8
4 11 3 8
4 12 4 8
3 13 1 12
3 14 2 12
3 15 3 12
1 16 6 10
… … … …
Here RN is a pseudo-column that stands for ROW_NUMBER() OVER (ORDER BY simTime). You can see that this is just a sequence of rankings starting from 1.
Another pseudo-column, RN_Apt contains values produces by the other ROW_NUMBER, namely ROW_NUMBER() OVER (PARTITION BY AptNumber ORDER BY simTime). It contains rankings within individual groups of identical AptNumber values. You can see that, for a newly encountered value, the sequence starts over, and for a recurring one, it continues where it stopped last time.
You can also see from the table that if we subtract RN from RN_Apt (could be the other way round, doesn't matter in this situation), we get the value that uniquely identifies every distinct group of same AptNumber values. You might as well call that value a group ID.
So, now that we've got these IDs, it only remains for us to count them (count distinct values, of course). That will be the number of groups, and the number of changes is one less (assuming the first group is not counted as a change).
add an extra column changecount
CREATE TABLE crewWork(
FloorNumber int, AptNumber int, WorkType int, simTime int ,changecount int)
increment changecount value for each updation
if want to know count for each field then add columns corresponding to it for changecount
Assuming that each record represents a different change, you can find changes per floor by:
select FloorNumber, count(*)
from crewWork
group by FloorNumber
And changes per apartment (assuming AptNumber uniquely identifies apartment) by:
select AptNumber, count(*)
from crewWork
group by AptNumber
Or (assuming AptNumber and FloorNumber together uniquely identifies apartment) by:
select FloorNumber, AptNumber, count(*)
from crewWork
group by FloorNumber, AptNumber