Już wiesz, że możesz przetestować wyrażenie user_info->>'username'
dla NULL. Ale Twoja funkcja jest nadal bardzo nieefektywna . I nadal istnieją niejasności .
Lepsze rozwiązanie w Postgresie 9.3
Wielokrotne aktualizowanie wiersza dla wielu kolumn jest kosztowne. Postgres zapisuje nową wersję wiersza dla każdej aktualizacji. Użyj singla UPDATE
jeśli to w ogóle możliwe:
CREATE OR REPLACE FUNCTION sp_update_user(_user_id int, _user_info json)
RETURNS json AS
$func$
BEGIN
UPDATE users u
SET firstname = COALESCE(_user_info->>'firstname', u.firstname)
, lastname = COALESCE(_user_info->>'lastname' , u.lastname)
WHERE id = sp_update_user._user_id
AND ((_user_info->>'firstname') IS NOT NULL OR
(_user_info->>'lastname') IS NOT NULL);
IF FOUND THEN
RETURN '{"success":true}'::json;
ELSE
RETURN '{"success":false}'::json;
END IF;
END
$func$ LANGUAGE plpgsql;
Zadzwoń:
SELECT sp_update_user(123, '{"firstname": "jon", "lastname": "doe"}')
-
Jest to znacznie szybsze w przypadku wielu kolumn, ponieważ tylko jedna
UPDATE
(co najwyżej) jest wykonywany. JeśliWHERE
klauzula nie ma wartościtrue
, nie następuje żadna aktualizacja i otrzymujesz'{"success":false}'
w rezultacie. -
Jeśli czasami wartości w tabeli są już takie, na jakie są zmieniane, możliwa jest kolejna optymalizacja. Rozważ ostatni akapit tej powiązanej odpowiedzi:
-
Zmienna / parametr
user_id
brakuje w oryginale. -
Nadal istnieje przypadek narożny niejednoznaczność . Jeśli element istnieje i jest ustawiony na JSON
null
, otrzymasz również SQLNULL
w wyniku. Rozważ:SELECT ('{"b": null}'::json->>'b') IS NULL AS b_is_null , ('{"c": 2}'::json->>'b') IS NULL AS b_missing;
-
Nie wiem, dlaczego używasz danych typu
json
jako typ zwrotu, po prostu go zachowałem. Ale jeśli funkcja się nie aktualizuje, nie możesz być pewien, dlaczego otrzymujeszfalse
. Może nie być wiersza o podanymid
, nazwy kluczy'firstname'
i'lastname'
może brakować - lub byćnull
...
Doskonałe rozwiązanie w Postgresie 9.4
Jest czysty i proste rozwiązanie w Postgresie 9.4 z jsonb
z ?
operator „istnienia”
- który może nawet używać indeksu dla większych tabel (nie dotyczy Twojej funkcji):
SELECT ('{"b": null}'::jsonb ? 'b') AS b_is_null
, ('{"c": 2}'::jsonb ? 'b') AS b_missing;
A ?|
i ?&
warianty
aby sprawdzić wiele kluczy naraz.
Więc możemy zaimplementować:
CREATE OR REPLACE FUNCTION sp_update_user(_user_id int, _user_info jsonb)
RETURNS jsonb AS
$func$
BEGIN
UPDATE users u
SET firstname = CASE WHEN _user_info ? 'firstname' THEN _user_info->>'firstname' ELSE u.firstname END
, lastname = CASE WHEN _user_info ? 'lastname' THEN _user_info->>'lastname' ELSE u.lastname END
WHERE id = sp_update_user._user_id
AND _user_info ?| '{firstname,lastname}';
IF FOUND THEN
RETURN '{"success":true}'::jsonb;
ELSE
RETURN '{"success":false}'::jsonb;
END IF;
END
$func$ LANGUAGE plpgsql;
Te połączenia działają teraz zgodnie z oczekiwaniami:
SELECT sp_update_user(123, '{"firstname": null, "lastname": "doe1"}'::jsonb);
SELECT sp_update_user(123, '{"firstname": "doris"}'::jsonb);