W niniejszym poście wyjaśnię dlaczego napotkaliście błąd [Error 3653] [SQLState 21S02] All select-lists do not contain the same number of expressions, znany także jako Failed [3653 : 21S02] All select-lists do not contain the same number of expressions, przedstawię przyczynę problemu oraz pokażę jak uniknąć błędu w przyszłości.
Problem
Problem może powstać podczas wykonywania operacji UNION, UNION ALL, MINUS oraz INTERSECT. Oto najprostszy sposób na otrzymanie komunikatu błędu używając operacji UNION ALL:
CREATE TABLE SUBJECT ( SUBJECT_ID INTEGER, SUBJECT_NAME VARCHAR(100), LOAD_DATE TIMESTAMP(0)); CREATE TABLE SUBJECT_HISTORY( SUBJECT_ID INTEGER, SUBJECT_NAME VARCHAR(100), LOAD_DATE TIMESTAMP(0), END_DATE TIMESTAMP(0));
Stworzyliśmy powyżej dwie tabele SUBJECT i SUBJECT_HISTORY, która jest snapshotem danych historycznych i ma dodatkowy atrybut – END_DATE przechowującą datę zamknięcia rekordu. Spróbujmy teraz uzyskać informację o wszystkich pacjentach jakich kiedykolwiek mieliśmy:
SELECT * FROM SUBJECT UNION ALL SELECT * FROM SUBJECT_HISTORY;
Nie wygląda to dobrze – dostaliśmy komunikat błędu, ale dlaczego?
Powód
Nie bez przyczyny napisałam, że tabela SUBJECT_HISTORY ma dodatkowy atrybut – END_DATE. W tym momencie tabela SUBJECT ma 3 kolumny, natomiast tabela historyczna ma ich 4. Wykonując w zapytaniu polecenie SELECT *, to tak jakbyśmy napisali:
SELECT SUBJECT_ID, SUBJECT_NAME, LOAD_DATE FROM SUBJECT UNION ALL SELECT SUBJECT_ID, SUBJECT_NAME, LOAD_DATE, END_DATE FROM SUBJECT_HISTORY;
Co Teradata ma zrobić w tym momencie z atrybutem END_DATE? Zdefiniowany jest on tylko w jednym poleceniu SELECT, więc listy naszych atrybutów są różne – i to jest właśnie przyczyna błędu 3653.
Rozwiązanie
Zawsze określaj listę atrybutów zamiast używać SELECT * podczas operacji UNION/ UNION ALL/ MINUS/ INTERSECT
Jeżeli listy atrybutów w tabelach na których wykonujesz jedno z powyższych poleceń są różne, pamiętaj aby zamiast używać polecenia SELECT * podać pełną listę kolumn, których potrzebujesz. Liczba atrybutów w zapytaniu musi być taka sama. Jeśli nie potrzebujesz atrybutu, który zakłóca Twoją listę, możesz po prostu pominąć go w zapytaniu:
SELECT SUBJECT_ID, SUBJECT_NAME, LOAD_DATE FROM SUBJECT UNION ALL SELECT SUBJECT_ID, SUBJECT_NAME, LOAD_DATE FROM SUBJECT_HISTORY;
W porządku, ale co jeśli atrybut END_DATE jest dla mnie ważny? Zwłaszcza jeśli łączymy rekordy używając UNION ALL chcielibyśmy wiedzieć, czy jest to nowy czy historyczny rekord. Spójrzmy więc na poniższe obejście problemu:
Work around
Jeśli potrzebujesz wszystkich atrybutów z tabeli, gdzie jest ich więcej, ale nie wiesz jak wtedy użyć poleceń UNION/ UNION ALL/ MINUS bądź INTERSECT, zastąp lukę w danych domyślną wartością bądź NULL’em. Spójrzmy poniżej:
SELECT SUBJECT_ID, SUBJECT_NAME, LOAD_DATE, NULL AS END_DATE FROM SUBJECT UNION ALL SELECT SUBJECT_ID, SUBJECT_NAME, LOAD_DATE, END_DATE FROM SUBJECT_HISTORY;
Powyższe zapytanie pozwala nam na otrzymanie informacji o dodatkowym atrybucie END_DATE bez otrzymania błędu 3653.