Альтернативи об'єднати рядки або процедурні процедури для запобігання повторення коду запиту SQL?


19

Відмова: Будь ласка, поводьтеся зі мною як з особою, яка використовує лише бази даних, невелику частину свого робочого часу. (Більшу частину часу я займаюся програмуванням на C ++ на своїй роботі, але мені потрібно щоденно шукати / виправляти / додавати щось у базу даних Oracle.)

Мені неодноразово потрібно писати складні запити SQL, як для спеціальних запитів, так і для запитів, вбудованих у додатки, де великі частини запитів, де щойно повторювався "код".

Написання подібних гидот традиційною мовою програмування загрожує глибоким неприємностям, але я ( I ) ще не зміг знайти жодної гідної методики для запобігання повторення коду запитів SQL.


Редагувати: Перше, я хочу подякувати тим, хто відповідав моїм оригінальним прикладом . Однак це питання не стосується мого прикладу. Йдеться про повторюваність у запитах SQL. Таким чином, відповіді ( JackP , Leigh ) поки що чудово працюють, показуючи, що ви можете зменшити повторюваність, написавши кращі запити . Однак навіть тоді ви стикаєтесь з деякою повторюваністю, яку, мабуть, неможливо зняти: Це завжди натякало мене на SQL. У "традиційних" мовах програмування я можу досить рефакторифікувати, щоб мінімізувати повторюваність у коді, але за допомогою SQL здається, що немає (?) Інструментів, які дозволяють це зробити, за винятком написання менш повторюваного твердження для початку.

Зауважте, що я знову видалив тег Oracle, тому що я був би щиро зацікавлений, чи немає в базі даних чи мови сценаріїв, що дозволяє щось більше.


Ось один такий дорогоцінний камінь, який я сьогодні спільно спіткав. В основному він повідомляє про різницю в наборі стовпців однієї таблиці. Прогляньте наступний код, esp. великий запит в кінці. Я продовжу нижче.

--
-- Create Table to test queries
--
CREATE TABLE TEST_ATTRIBS (
id NUMBER PRIMARY KEY,
name  VARCHAR2(300) UNIQUE,
attr1 VARCHAR2(2000),
attr2 VARCHAR2(2000),
attr3 INTEGER,
attr4 NUMBER,
attr5 VARCHAR2(2000)
);

--
-- insert some test data
--
insert into TEST_ATTRIBS values ( 1, 'Alfred',   'a', 'Foobar', 33, 44, 'e');
insert into TEST_ATTRIBS values ( 2, 'Batman',   'b', 'Foobar', 66, 44, 'e');
insert into TEST_ATTRIBS values ( 3, 'Chris',    'c', 'Foobar', 99, 44, 'e');
insert into TEST_ATTRIBS values ( 4, 'Dorothee', 'd', 'Foobar', 33, 44, 'e');
insert into TEST_ATTRIBS values ( 5, 'Emilia',   'e', 'Barfoo', 66, 44, 'e');
insert into TEST_ATTRIBS values ( 6, 'Francis',  'f', 'Barfoo', 99, 44, 'e');
insert into TEST_ATTRIBS values ( 7, 'Gustav',   'g', 'Foobar', 33, 44, 'e');
insert into TEST_ATTRIBS values ( 8, 'Homer',    'h', 'Foobar', 66, 44, 'e');
insert into TEST_ATTRIBS values ( 9, 'Ingrid',   'i', 'Foobar', 99, 44, 'e');
insert into TEST_ATTRIBS values (10, 'Jason',    'j', 'Bob',    33, 44, 'e');
insert into TEST_ATTRIBS values (12, 'Konrad',   'k', 'Bob',    66, 44, 'e');
insert into TEST_ATTRIBS values (13, 'Lucas',    'l', 'Foobar', 99, 44, 'e');

insert into TEST_ATTRIBS values (14, 'DUP_Alfred',   'a', 'FOOBAR', 33, 44, 'e');
insert into TEST_ATTRIBS values (15, 'DUP_Chris',    'c', 'Foobar', 66, 44, 'e');
insert into TEST_ATTRIBS values (16, 'DUP_Dorothee', 'd', 'Foobar', 99, 44, 'e');
insert into TEST_ATTRIBS values (17, 'DUP_Gustav',   'X', 'Foobar', 33, 44, 'e');
insert into TEST_ATTRIBS values (18, 'DUP_Homer',    'h', 'Foobar', 66, 44, 'e');
insert into TEST_ATTRIBS values (19, 'DUP_Ingrid',   'Y', 'foo',    99, 44, 'e');

insert into TEST_ATTRIBS values (20, 'Martha',   'm', 'Bob',    33, 88, 'f');

-- Create comparison view
CREATE OR REPLACE VIEW TA_SELFCMP as
select 
t1.id as id_1, t2.id as id_2, t1.name as name, t2.name as name_dup,
t1.attr1 as attr1_1, t1.attr2 as attr2_1, t1.attr3 as attr3_1, t1.attr4 as attr4_1, t1.attr5 as attr5_1,
t2.attr1 as attr1_2, t2.attr2 as attr2_2, t2.attr3 as attr3_2, t2.attr4 as attr4_2, t2.attr5 as attr5_2
from TEST_ATTRIBS t1, TEST_ATTRIBS t2
where t1.id <> t2.id
and t1.name <> t2.name
and t1.name = REPLACE(t2.name, 'DUP_', '')
;

-- NOTE THIS PIECE OF HORRIBLE CODE REPETITION --
-- Create comparison report
-- compare 1st attribute
select 'attr1' as Different,
id_1, id_2, name, name_dup,
CAST(attr1_1 AS VARCHAR2(2000)) as Val1, CAST(attr1_2 AS VARCHAR2(2000)) as Val2
from TA_SELFCMP
where attr1_1 <> attr1_2
or (attr1_1 is null and attr1_2 is not null)
or (attr1_1 is not null and attr1_2 is null)
union
-- compare 2nd attribute
select 'attr2' as Different,
id_1, id_2, name, name_dup,
CAST(attr2_1 AS VARCHAR2(2000)) as Val1, CAST(attr2_2 AS VARCHAR2(2000)) as Val2
from TA_SELFCMP
where attr2_1 <> attr2_2
or (attr2_1 is null and attr2_2 is not null)
or (attr2_1 is not null and attr2_2 is null)
union
-- compare 3rd attribute
select 'attr3' as Different,
id_1, id_2, name, name_dup,
CAST(attr3_1 AS VARCHAR2(2000)) as Val1, CAST(attr3_2 AS VARCHAR2(2000)) as Val2
from TA_SELFCMP
where attr3_1 <> attr3_2
or (attr3_1 is null and attr3_2 is not null)
or (attr3_1 is not null and attr3_2 is null)
union
-- compare 4th attribute
select 'attr4' as Different,
id_1, id_2, name, name_dup,
CAST(attr4_1 AS VARCHAR2(2000)) as Val1, CAST(attr4_2 AS VARCHAR2(2000)) as Val2
from TA_SELFCMP
where attr4_1 <> attr4_2
or (attr4_1 is null and attr4_2 is not null)
or (attr4_1 is not null and attr4_2 is null)
union
-- compare 5th attribute
select 'attr5' as Different,
id_1, id_2, name, name_dup,
CAST(attr5_1 AS VARCHAR2(2000)) as Val1, CAST(attr5_2 AS VARCHAR2(2000)) as Val2
from TA_SELFCMP
where attr5_1 <> attr5_2
or (attr5_1 is null and attr5_2 is not null)
or (attr5_1 is not null and attr5_2 is null)
;

Як бачимо, запит для створення "звіту про різниці" використовує один і той же блок SQL SELECT 5 разів (легко може бути 42 рази!). Це вважає мене абсолютно мозком мертвим (мені це дозволяється сказати, адже я написав код), але мені не вдалося знайти жодного хорошого рішення для цього.

  • Якщо це був би запит у деякому фактичному коді програми, я міг би записати функцію, яка поєднує цей запит у вигляді рядка, а потім я б виконував запит як рядок.

    • -> Будівництво струн - жахливо і жахливо перевіряти і підтримувати. Якщо "код програми" написаний такою мовою, як PL / SQL, він відчуває себе так неправильно, боляче.
  • Крім того, якщо я використовую PL / SQL або подібне, я б припустив, що існують деякі процедурні засоби, щоб зробити цей запит більш доступним.

    • -> Розгортання чогось, що може бути виражено в одному запиті в процедурні кроки, щоб запобігти повторенню коду, теж не так.
  • Якщо цей запит знадобиться як перегляд у базі даних, тоді - наскільки я розумію - не було б іншого способу, як насправді підтримувати визначення подання, як я розміщував вище. (!!?)

    • -> Мені насправді довелося зробити певне обслуговування для визначення 2-сторінкового перегляду, коли це було не за горами вище. Очевидно, що змінити що-небудь в цьому представленні вимагало повторного пошуку тексту по визначенню перегляду для того, чи використовувався той самий підзаголовок в іншому рядку та чи потрібно там змінювати.

Отже, як йдеться в заголовку - які методи існують, щоб не дати писати такі гидоти?

Відповіді:


13

Ви занадто скромні - ваш SQL добре і стисло написаний з огляду на завдання, яке ви берете на себе. Кілька покажчиків:

  • t1.name <> t2.nameзавжди правда, якщо t1.name = REPLACE(t2.name, 'DUP_', '')- можна скинути колишнє
  • зазвичай ти хочеш union all. unionзасоби, union allпотім скидайте дублікати. У цьому випадку це може не мати ніякої різниці, але завжди користуватися union allкорисною звичкою, якщо ви прямо не хочете відкинути копії.
  • якщо ви бажаєте, щоб чисельні порівняння відбувалися після викиду на варчар, можливо, варто врахувати наступне:

    create view test_attribs_cast as 
    select id, name, attr1, attr2, cast(attr3 as varchar(2000)) as attr3, 
           cast(attr4 as varchar(2000)) as attr4, attr5
    from test_attribs;
    
    create view test_attribs_unpivot as 
    select id, name, 1 as attr#, attr1 as attr from test_attribs_cast union all
    select id, name, 2, attr2 from test_attribs_cast union all
    select id, name, 3, attr3 from test_attribs_cast union all
    select id, name, 4, attr4 from test_attribs_cast union all
    select id, name, 5, attr5 from test_attribs_cast;
    
    select 'attr'||t1.attr# as different, t1.id as id_1, t2.id as id_2, t1.name, 
           t2.name as name_dup, t1.attr as val1, t2.attr as val2
    from test_attribs_unpivot t1 join test_attribs_unpivot t2 on(
           t1.id<>t2.id and 
           t1.name = replace(t2.name, 'DUP_', '') and 
           t1.attr#=t2.attr# )
    where t1.attr<>t2.attr or (t1.attr is null and t2.attr is not null)
          or (t1.attr is not null and t2.attr is null);

    другий погляд - це певна unpivotоперація - якщо у вас принаймні 11 г, ви можете зробити це більш стисло з unpivotпунктом - див. тут приклад

  • Я кажу, що не йдіть процедурним шляхом, якщо ви можете це зробити в SQL, але ...
  • Динамічний SQL, мабуть, варто розглянути, незважаючи на проблеми, які ви згадуєте з тестуванням та обслуговуванням

--EDIT--

Щоб відповісти на більш загальну сторону питання, існують методи зменшення повторення в SQL, включаючи:

Але ви не можете вносити ідеї OO у світ SQL безпосередньо - у багатьох випадках повторення чудово, якщо запит читабельний і добре написаний, і було б нерозумно вдаватися до динамічного SQL (наприклад) просто , щоб уникнути повторень.

Остаточний запит, що включає запропоновані зміни Лея та CTE замість перегляду, може виглядати приблизно так:

with t as ( select id, name, attr#, 
                   decode(attr#,1,attr1,2,attr2,3,attr3,4,attr4,attr5) attr
            from test_attribs
                 cross join (select rownum attr# from dual connect by rownum<=5))
select 'attr'||t1.attr# as different, t1.id as id_1, t2.id as id_2, t1.name, 
       t2.name as name_dup, t1.attr as val1, t2.attr as val2
from t t1 join test_attribs_unpivot t2 
               on( t1.id<>t2.id and 
                   t1.name = replace(t2.name, 'DUP_', '') and 
                   t1.attr#=t2.attr# )
where t1.attr<>t2.attr or (t1.attr is null and t2.attr is not null)
      or (t1.attr is not null and t2.attr is null);

1
+1, частково для UNION ALL. Часто UNIONбез цього ALLпризводить до отримання котушки на тимчасове зберігання для необхідної операції сортування (оскільки "UNION" фактично UNION ALLслідує за цим, DISTINCTщо передбачає сортування), тому в деяких випадках різниця в продуктивності може бути величезною.
Девід Спіллетт

7

Ось альтернатива перегляду test_attribs_unpivot, наданому JackPDouglas (+1), який працює у версіях до 11 г та робить меншу кількість сканувань повної таблиці:

CREATE OR REPLACE VIEW test_attribs_unpivot AS
   SELECT ID, Name, MyRow Attr#, CAST(
      DECODE(MyRow,1,attr1,2,attr2,3,attr3,4,attr4,attr5) AS VARCHAR2(2000)) attr
   FROM TEST_ATTRIBS 
   CROSS JOIN (SELECT level MyRow FROM dual connect by level<=5);

Його остаточний запит можна використовувати без змін у цьому режимі перегляду.


Значно краще! Думаю, ти навіть можеш скинути ролі?
Джек Дуглас

Замість SELECT rownum MyRow FROM test_attribs where rownum<=5використання select level MyRow from dual connect by level <= 5. Ви не хочете, щоб усі ці логічні виходили лише для створення 5 рядків.
Штефан Оравець

@ Štefan Oravec - У мене це було так, але я змінив його, тому що не знав, для яких версій доступні ієрархічні запити. Оскільки він доступний принаймні з версії 8, я його зміню.
Лей Ріффер

4

Я часто стикаюся з подібною проблемою, щоб порівняти дві версії таблиці для нових, видалених або змінених рядків. Десь місяць тому я опублікував рішення для SQL Server, використовуючи тут PowerShell .

Щоб адаптувати його до вашої проблеми, спершу створюю два представлення, щоб відокремити оригінал від повторюваних рядків

CREATE OR REPLACE VIEW V1_TEST_ATTRIBS AS 
select * from TEST_ATTRIBS where SUBSTR(name, 1, 4) <> 'DUP_'; 

CREATE OR REPLACE VIEW V2_TEST_ATTRIBS AS 
select id, REPLACE(name, 'DUP_', '') name, attr1, attr2, attr3, attr4, attr5 from TEST_ATTRIBS where SUBSTR(name, 1, 4) = 'DUP_'; 

а потім я перевіряю зміни

SELECT 1 SRC, NAME, ATTR1, ATTR2, ATTR3, ATTR4, ATTR5 FROM V1_TEST_ATTRIBS
MINUS
Select 1 SRC, NAME, ATTR1, ATTR2, ATTR3, ATTR4, ATTR5 from V2_TEST_ATTRIBS
UNION
SELECT 2 SRC, NAME, ATTR1, ATTR2, ATTR3, ATTR4, ATTR5 FROM V2_TEST_ATTRIBS
MINUS
SELECT 2 SRC ,NAME, ATTR1, ATTR2, ATTR3, ATTR4, ATTR5 FROM V1_TEST_ATTRIBS
ORDER BY NAME, SRC;

Звідси я можу знайти ваші оригінальні ідентифікатори

Select NVL(v1.id, v2.id) id,  t.name, t.attr1, t.attr2, t.attr3, t.attr4, t.attr5 from
(
SELECT 1 SRC, NAME, ATTR1, ATTR2, ATTR3, ATTR4, ATTR5 FROM V1_TEST_ATTRIBS
MINUS
Select 1 SRC, NAME, ATTR1, ATTR2, ATTR3, ATTR4, ATTR5 from V2_TEST_ATTRIBS
UNION
SELECT 2 SRC, NAME, ATTR1, ATTR2, ATTR3, ATTR4, ATTR5 FROM V2_TEST_ATTRIBS
MINUS
Select 2 SRC ,NAME, ATTR1, ATTR2, ATTR3, ATTR4, ATTR5 from V1_TEST_ATTRIBS
) t
LEFT JOIN V1_TEST_ATTRIBS V1 ON T.NAME = V1.NAME AND T.SRC = 1
LEFT JOIN V2_TEST_ATTRIBS V2 ON T.NAME = V2.NAME AND T.SRC = 2
ORDER by NAME, SRC;

BTW: МІНУС та Спілка та ГРУПА вважають різні NULL рівними. Використання цих операцій робить запити більш елегантними.

Підказка для користувачів SQL Server: MINUS названий ВІДКЛЮЧЕНО там, але працює аналогічно.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.