Home > other >  Trying to resolve ORA-01427: single-row subquery returns more than one row
Trying to resolve ORA-01427: single-row subquery returns more than one row

Time:11-05

I have a series of records that have multiple alternate bin definitions (BIN_LOC).

For example:

enter image description here

I have a query that works on this when there is only one alternate location. When there are more than one, it fails. I've tried several things such as using IN and replacing EXISTS, using RANK and PARTITION without any success. I have to consolidate thousands of rows with varying numbers of matching PART_NO, MFG, and LOCATION records with different BIN_LOC values.

update PART_LOCATION a
    set alt_bin_loc_1 = 
    (
     select bin_loc
     from PART_LOCATION b where a.part_no = b.part_no and a.mfg = b.mfg and a.location = b.location and a.bin_loc <> b.bin_loc
     and a.temp_id < b.temp_id
    )
    where exists
    (
     select bin_loc
     from PART_LOCATION b where a.part_no = b.part_no and a.mfg = b.mfg and a.location = b.location and a.bin_loc <> b.bin_loc
     and a.temp_id < b.temp_id
    );

What I need to accomplish to consolidate the ALT_BIN_XXX locations with the matching records on the PART_ID/PART_NO, MFG, LOCATION on the main record with the lowest TEMP_ID and then delete the record(s) with the next BIN_LOC not matching. As in the following progression.

First Iteration results: First Iteration

Second Iteration Results: second iteration

Third Iteration results: third iteration

CodePudding user response:

It is not advisable to have the values in multiple columns, if you have more number of occurrences, you will end up having many columns. You can group them into a single column and have a seperator for each occurrence. You can do that using LISTAGG as below,

select part_id, part_no, mfg, location, 
LISTAGG(bin_loc, '; ') WITHIN GROUP (ORDER BY part_id, part_no, mfg, location) as bin_loc
from part_location;

CodePudding user response:

You can do it all in a single MERGE statement by using analytic functions to find the minimum temperature row and then ordering the other rows and pivoting to find the alt. bin locations:

MERGE INTO table_name dst
USING (
  SELECT part_id,
         part_no,
         mfg,
         location,
         MAX(CASE WHEN grp = 1 AND bin_rn = 1 THEN bin_loc END) AS bin_loc,
         MAX(CASE WHEN grp = 2 AND bin_rn = 1 THEN bin_loc END) AS alt_bin_loc1,
         MAX(CASE WHEN grp = 2 AND bin_rn = 2 THEN bin_loc END) AS alt_bin_loc2,
         MAX(CASE WHEN grp = 2 AND bin_rn = 3 THEN bin_loc END) AS alt_bin_loc3,
         MIN(temp_id) AS temp_id
  FROM   (
    SELECT t.*,
           ROW_NUMBER() OVER (
             PARTITION BY part_id, part_no, mfg, location, grp
             ORDER BY bin_loc
           ) AS bin_rn
    FROM   (
      SELECT part_id,
             part_no,
             mfg,
             location,
             bin_loc,
             temp_id,
             LEAST(
               ROW_NUMBER() OVER (
                 PARTITION BY part_id, part_no, mfg, location
                 ORDER BY temp_id
               ),
               2
             ) AS grp
      FROM   table_name t
    ) t
  )
  GROUP BY
         part_id,
         part_no,
         mfg,
         location
) src
ON (    src.part_id  = dst.part_id
    AND src.part_no  = dst.part_no
    AND src.mfg      = dst.mfg
    AND src.location = dst.location)
WHEN MATCHED THEN
  UPDATE
  SET alt_bin_loc1 = src.alt_bin_loc1,
      alt_bin_loc2 = src.alt_bin_loc2,
      alt_bin_loc3 = src.alt_bin_loc3
  DELETE WHERE dst.bin_loc != src.bin_loc;

Which, for the sample data:

CREATE TABLE table_name (
  part_id      NUMBER,
  part_no      VARCHAR2(10),
  mfg          VARCHAR2(10),
  location     NUMBER,
  bin_loc      VARCHAR2(10),
  alt_bin_loc1 VARCHAR2(10),
  alt_bin_loc2 VARCHAR2(10),
  alt_bin_loc3 VARCHAR2(10),
  temp_id      NUMBER
);

INSERT INTO table_name (part_id, part_no, mfg, location, bin_loc, temp_id)
SELECT 950, 'A1234', 'MFG1', 75309, 'A07',    123 FROM DUAL UNION ALL
SELECT 950, 'A1234', 'MFG1', 75309, 'LFRONT', 129 FROM DUAL UNION ALL
SELECT 950, 'A1234', 'MFG1', 75309, 'LTOP',   128 FROM DUAL UNION ALL
SELECT 950, 'A1234', 'MFG1', 75309, 'RFRONT', 127 FROM DUAL;

Then, after the MERGE the table contains:

PART_ID PART_NO MFG LOCATION BIN_LOC ALT_BIN_LOC1 ALT_BIN_LOC2 ALT_BIN_LOC3 TEMP_ID
950 A1234 MFG1 75309 A07 LFRONT LTOP RFRONT 123

Note: If you just want to display the pivoted data (rather than modifying the table) then just use the SELECT in the USING clause.

fiddle

  • Related