சிக்கலான ஈஆர்பி அமைப்புகளில் பல நிறுவனங்கள் ஒரு படிநிலை தன்மையைக் கொண்டுள்ளனஒரே மாதிரியான பொருட்கள் வரிசையாக இருக்கும்போது மூதாதையர்-சந்ததி உறவுகளின் மரம் - இது நிறுவனத்தின் நிறுவன அமைப்பு (இந்த அனைத்து கிளைகள், துறைகள் மற்றும் பணிக்குழுக்கள்), மற்றும் பொருட்களின் பட்டியல், மற்றும் வேலை செய்யும் பகுதிகள் மற்றும் விற்பனை புள்ளிகளின் புவியியல்,...
உண்மையில், எதுவும் இல்லை
அத்தகைய மரத்தை DBMS இல் சேமிக்க பல வழிகள் உள்ளன, ஆனால் இன்று நாம் ஒரே ஒரு விருப்பத்தில் கவனம் செலுத்துவோம்:
CREATE TABLE hier(
id
integer
PRIMARY KEY
, pid
integer
REFERENCES hier
, data
json
);
CREATE INDEX ON hier(pid); -- не забываем, что FK не подразумевает автосоздание индекса, в отличие от PK
நீங்கள் படிநிலையின் ஆழத்தை உற்றுப் பார்க்கும்போது, அத்தகைய கட்டமைப்பில் உங்களின் "அப்பாவியான" வழிகள் எவ்வளவு பயனுள்ளதாக இருக்கும் என்பதைப் பார்க்க பொறுமையாக காத்திருக்கிறது.
எழும் பொதுவான சிக்கல்கள், SQL இல் அவற்றை செயல்படுத்துதல் மற்றும் அவற்றின் செயல்திறனை மேம்படுத்த முயற்சிப்போம்.
#1. முயல் துளை எவ்வளவு ஆழமானது?
இந்த அமைப்பு அமைப்பின் கட்டமைப்பில் உள்ள துறைகளின் கீழ்ப்படிதலை பிரதிபலிக்கும் என்பதை உறுதியாக ஏற்றுக்கொள்வோம்: துறைகள், பிரிவுகள், துறைகள், கிளைகள், பணிக்குழுக்கள்... - நீங்கள் எதை அழைத்தாலும்.
முதலில், 10K உறுப்புகளின் 'மரத்தை' உருவாக்குவோம்
INSERT INTO hier
WITH RECURSIVE T AS (
SELECT
1::integer id
, '{1}'::integer[] pids
UNION ALL
SELECT
id + 1
, pids[1:(random() * array_length(pids, 1))::integer] || (id + 1)
FROM
T
WHERE
id < 10000
)
SELECT
pids[array_length(pids, 1)] id
, pids[array_length(pids, 1) - 1] pid
FROM
T;
எளிமையான பணியுடன் தொடங்குவோம் - ஒரு குறிப்பிட்ட துறையில் பணிபுரியும் அனைத்து ஊழியர்களையும் அல்லது படிநிலையின் அடிப்படையில் - ஒரு முனையின் அனைத்து குழந்தைகளையும் கண்டறியவும். சந்ததியினரின் "ஆழம்" பெறுவதும் நன்றாக இருக்கும் ... இவை அனைத்தும் தேவைப்படலாம், எடுத்துக்காட்டாக, ஒருவித உருவாக்க
இந்த சந்ததிகளின் இரண்டு நிலைகள் மட்டுமே இருந்தால் எல்லாம் சரியாகிவிடும், மேலும் எண்ணிக்கை ஒரு டசனுக்குள் இருந்தால், ஆனால் 5 நிலைகளுக்கு மேல் இருந்தால், ஏற்கனவே டஜன் கணக்கான சந்ததியினர் இருந்தால், சிக்கல்கள் இருக்கலாம். மரத்தின் கீழ் மரத் தேடல் விருப்பங்கள் எவ்வாறு எழுதப்படுகின்றன (மற்றும் வேலை செய்யப்படுகின்றன) என்பதைப் பார்ப்போம். ஆனால் முதலில், எங்கள் ஆராய்ச்சிக்கு எந்த முனைகள் மிகவும் சுவாரஸ்யமாக இருக்கும் என்பதை தீர்மானிக்கலாம்.
சிறந்த "ஆழமான" துணை மரங்கள்:
WITH RECURSIVE T AS (
SELECT
id
, pid
, ARRAY[id] path
FROM
hier
WHERE
pid IS NULL
UNION ALL
SELECT
hier.id
, hier.pid
, T.path || hier.id
FROM
T
JOIN
hier
ON hier.pid = T.id
)
TABLE T ORDER BY array_length(path, 1) DESC;
id | pid | path
---------------------------------------------
7624 | 7623 | {7615,7620,7621,7622,7623,7624}
4995 | 4994 | {4983,4985,4988,4993,4994,4995}
4991 | 4990 | {4983,4985,4988,4989,4990,4991}
...
சிறந்த "பரந்த" துணை மரங்கள்:
...
SELECT
path[1] id
, count(*)
FROM
T
GROUP BY
1
ORDER BY
2 DESC;
id | count
------------
5300 | 30
450 | 28
1239 | 27
1573 | 25
இந்த வினவல்களுக்கு நாங்கள் வழக்கமானதைப் பயன்படுத்தினோம் சுழல்நிலை சேர்:
வெளிப்படையாக, இந்த கோரிக்கை மாதிரியுடன் மறு செய்கைகளின் எண்ணிக்கை மொத்த சந்ததியினரின் எண்ணிக்கையுடன் பொருந்தும் (மற்றும் அவற்றில் பல டஜன் உள்ளன), மேலும் இது மிகவும் குறிப்பிடத்தக்க ஆதாரங்களை எடுத்துக் கொள்ளலாம், இதன் விளைவாக, நேரம்.
"அகலமான" சப்ட்ரீயைப் பார்ப்போம்:
WITH RECURSIVE T AS (
SELECT
id
FROM
hier
WHERE
id = 5300
UNION ALL
SELECT
hier.id
FROM
T
JOIN
hier
ON hier.pid = T.id
)
TABLE T;
எதிர்பார்த்தபடி, 30 பதிவுகளையும் கண்டுபிடித்தோம். ஆனால் அவர்கள் மொத்த நேரத்தின் 60% இதற்கே செலவிட்டனர் - ஏனென்றால் அவர்கள் குறியீட்டில் 30 தேடல்களையும் செய்தனர். குறைவாக செய்ய முடியுமா?
இன்டெக்ஸ் மூலம் மொத்தமாக சரிபார்த்தல்
ஒவ்வொரு முனைக்கும் தனித்தனி குறியீட்டு வினவலை உருவாக்க வேண்டுமா? அது இல்லை என்று மாறிவிடும் - நாம் குறியீட்டிலிருந்து படிக்கலாம் ஒரு அழைப்பில் ஒரே நேரத்தில் பல விசைகளைப் பயன்படுத்துதல் உதவியுடன் = ANY(array)
.
மேலும் இதுபோன்ற ஒவ்வொரு அடையாளங்காட்டி குழுவிலும் முந்தைய படியில் காணப்படும் அனைத்து ஐடிகளையும் “நோட்கள்” மூலம் எடுக்கலாம். அதாவது, ஒவ்வொரு அடுத்த கட்டத்திலும் நாம் செய்வோம் ஒரு குறிப்பிட்ட அளவிலான அனைத்து சந்ததியினரையும் ஒரே நேரத்தில் தேடுங்கள்.
இங்கே தான் பிரச்சனை, சுழல்நிலை தேர்வில், நீங்கள் ஒரு உள்ளமை வினவலில் தன்னை அணுக முடியாது, ஆனால் முந்தைய நிலையில் காணப்பட்டதை மட்டும் எப்படியாவது தேர்ந்தெடுக்க வேண்டும்... முழுத் தேர்விற்கும் உள்ளமை வினவல் செய்வது சாத்தியமில்லை, ஆனால் அதன் குறிப்பிட்ட துறைக்கு இது சாத்தியமாகும். இந்த புலம் ஒரு வரிசையாகவும் இருக்கலாம் - இதைத்தான் நாம் பயன்படுத்த வேண்டும் ANY
.
இது கொஞ்சம் பைத்தியமாகத் தெரிகிறது, ஆனால் வரைபடத்தில் எல்லாம் எளிது.
WITH RECURSIVE T AS (
SELECT
ARRAY[id] id$
FROM
hier
WHERE
id = 5300
UNION ALL
SELECT
ARRAY(
SELECT
id
FROM
hier
WHERE
pid = ANY(T.id$)
) id$
FROM
T
WHERE
coalesce(id$, '{}') <> '{}' -- условие выхода из цикла - пустой массив
)
SELECT
unnest(id$) id
FROM
T;
இங்கே மிக முக்கியமான விஷயம் கூட இல்லை நேரத்தில் 1.5 முறை வெற்றி, மற்றும் குறைவான இடையகங்களைக் கழித்தோம், ஏனெனில் குறியீட்டிற்கு 5க்கு பதிலாக 30 அழைப்புகள் மட்டுமே உள்ளன!
இறுதி unnestக்குப் பிறகு, அடையாளங்காட்டிகள் "நிலைகள்" மூலம் வரிசைப்படுத்தப்படும் என்பது கூடுதல் போனஸ்.
முனை அடையாளம்
செயல்திறனை மேம்படுத்த உதவும் அடுத்த கருத்தில் -- "இலைகள்" குழந்தைகளைப் பெற முடியாது, அதாவது, அவர்களுக்கு "கீழே" பார்க்க வேண்டிய அவசியமில்லை. எங்கள் பணியை உருவாக்குவதில், நாங்கள் துறைகளின் சங்கிலியைப் பின்பற்றி ஒரு பணியாளரை அடைந்தால், இந்த கிளையில் மேலும் பார்க்க வேண்டிய அவசியமில்லை.
எங்கள் அட்டவணையில் நுழைவோம் கூடுதல் boolean
- புலம், இது எங்கள் மரத்தில் குறிப்பிட்ட நுழைவு ஒரு "முனை" என்பதை உடனடியாக நமக்குத் தெரிவிக்கும் - அதாவது, அது சந்ததியினரைக் கொண்டிருக்க முடியுமா.
ALTER TABLE hier
ADD COLUMN branch boolean;
UPDATE
hier T
SET
branch = TRUE
WHERE
EXISTS(
SELECT
NULL
FROM
hier
WHERE
pid = T.id
LIMIT 1
);
-- Запрос успешно выполнен: 3033 строк изменено за 42 мс.
நன்று! அனைத்து மர கூறுகளிலும் 30% க்கும் சற்று அதிகமாக மட்டுமே சந்ததியினர் உள்ளனர் என்று மாறிவிடும்.
இப்போது சற்று வித்தியாசமான மெக்கானிக்கைப் பயன்படுத்துவோம் - இதன் மூலம் சுழல்நிலைப் பகுதிக்கான இணைப்புகள் LATERAL
, இது சுழல்நிலை "அட்டவணை"யின் புலங்களை உடனடியாக அணுக அனுமதிக்கும், மேலும் விசைகளின் தொகுப்பைக் குறைக்க ஒரு முனையின் அடிப்படையில் வடிகட்டுதல் நிபந்தனையுடன் கூடிய மொத்த செயல்பாட்டைப் பயன்படுத்தவும்:
WITH RECURSIVE T AS (
SELECT
array_agg(id) id$
, array_agg(id) FILTER(WHERE branch) ns$
FROM
hier
WHERE
id = 5300
UNION ALL
SELECT
X.*
FROM
T
JOIN LATERAL (
SELECT
array_agg(id) id$
, array_agg(id) FILTER(WHERE branch) ns$
FROM
hier
WHERE
pid = ANY(T.ns$)
) X
ON coalesce(T.ns$, '{}') <> '{}'
)
SELECT
unnest(id$) id
FROM
T;
மேலும் ஒரு குறியீட்டு அழைப்பை எங்களால் குறைக்க முடிந்தது தொகுதியில் 2 முறைக்கு மேல் வெற்றி பெற்றது சரிபார்த்தல்.
#2. மீண்டும் வேர்களுக்குச் செல்வோம்
எந்த மூலத் தாள் (மற்றும் என்ன குறிகாட்டிகளுடன்) மாதிரியில் சேர்க்கப்பட்டது என்பது பற்றிய தகவலைத் தக்கவைத்துக்கொள்ளும் அதே வேளையில், "மரத்தின் மேல்" அனைத்து உறுப்புகளுக்கான பதிவுகளையும் நீங்கள் சேகரிக்க வேண்டும் என்றால், இந்த வழிமுறை பயனுள்ளதாக இருக்கும் - எடுத்துக்காட்டாக, சுருக்க அறிக்கையை உருவாக்க முனைகளில் திரட்டுதல்.
கோரிக்கை மிகவும் சிக்கலானதாக இருப்பதால், பின்வருபவை கருத்துக்கான ஆதாரமாக மட்டுமே எடுத்துக்கொள்ளப்பட வேண்டும். ஆனால் இது உங்கள் தரவுத்தளத்தில் ஆதிக்கம் செலுத்தினால், இதே போன்ற நுட்பங்களைப் பயன்படுத்துவது பற்றி நீங்கள் சிந்திக்க வேண்டும்.
இரண்டு எளிய அறிக்கைகளுடன் ஆரம்பிக்கலாம்:
- தரவுத்தளத்திலிருந்து அதே பதிவு ஒருமுறை மட்டும் படிப்பது நல்லது.
- தரவுத்தளத்திலிருந்து பதிவுகள் தொகுப்பாகப் படிப்பது மிகவும் திறமையானதுதனியாக விட.
இப்போது நமக்குத் தேவையான கோரிக்கையை உருவாக்க முயற்சிப்போம்.
1 விலக
வெளிப்படையாக, மறுநிகழ்வைத் தொடங்கும் போது (அது இல்லாமல் நாம் எங்கே இருப்போம்!) ஆரம்ப அடையாளங்காட்டிகளின் தொகுப்பின் அடிப்படையில் இலைகளின் பதிவுகளை நாம் கழிக்க வேண்டும்:
WITH RECURSIVE tree AS (
SELECT
rec -- это цельная запись таблицы
, id::text chld -- это "набор" приведших сюда исходных листьев
FROM
hier rec
WHERE
id = ANY('{1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192}'::integer[])
UNION ALL
...
“தொகுப்பு” ஒரு சரமாக சேமிக்கப்பட்டுள்ளது மற்றும் ஒரு வரிசையாக சேமிக்கப்படவில்லை என்பது ஒருவருக்கு விசித்திரமாகத் தோன்றினால், இதற்கு ஒரு எளிய விளக்கம் உள்ளது. சரங்களுக்கு உள்ளமைக்கப்பட்ட ஒருங்கிணைக்கும் "ஒட்டுதல்" செயல்பாடு உள்ளது string_agg
, ஆனால் வரிசைகளுக்கு அல்ல. அவள் என்றாலும்
2 விலக
இப்போது நாம் மேலும் படிக்க வேண்டிய பிரிவு ஐடிகளின் தொகுப்பைப் பெறுவோம். கிட்டத்தட்ட எப்போதும் அசல் தொகுப்பின் வெவ்வேறு பதிவுகளில் அவை நகலெடுக்கப்படும் - எனவே நாங்கள் செய்வோம் அவர்களை குழு, மூல இலைகள் பற்றிய தகவல்களைப் பாதுகாக்கும் போது.
ஆனால் இங்கே மூன்று பிரச்சனைகள் நமக்கு காத்திருக்கின்றன:
- வினவலின் "சப்ரெகர்சிவ்" பகுதி மொத்த செயல்பாடுகளைக் கொண்டிருக்க முடியாது
GROUP BY
. - சுழல்நிலை "அட்டவணை" பற்றிய குறிப்பு உள்ளமை துணைக் கேள்வியில் இருக்க முடியாது.
- சுழல்நிலைப் பகுதியில் உள்ள கோரிக்கையில் CTE இருக்கக்கூடாது.
அதிர்ஷ்டவசமாக, இந்த சிக்கல்கள் அனைத்தும் சமாளிக்க மிகவும் எளிதானது. முடிவில் இருந்து ஆரம்பிக்கலாம்.
சுழல்நிலை பகுதியில் CTE
இங்கே அப்படி இல்லை வேலைகள்:
WITH RECURSIVE tree AS (
...
UNION ALL
WITH T (...)
SELECT ...
)
அதனால் அது வேலை செய்கிறது, அடைப்புக்குறிக்குள் வித்தியாசம் இருக்கிறது!
WITH RECURSIVE tree AS (
...
UNION ALL
(
WITH T (...)
SELECT ...
)
)
சுழல்நிலை "அட்டவணை"க்கு எதிராக உள்ளமைக்கப்பட்ட வினவல்
ம்ம்... சுழல்நிலை CTE ஐ துணை வினவலில் அணுக முடியாது. ஆனால் அது CTE க்குள் இருக்கலாம்! உள்ளமை கோரிக்கை ஏற்கனவே இந்த CTE ஐ அணுகலாம்!
உள்ளே மறுநிகழ்வு மூலம் குழு
இது விரும்பத்தகாதது, ஆனால்... GROUP ஐப் பயன்படுத்துவதன் மூலம் எங்களிடம் ஒரு எளிய வழி உள்ளது DISTINCT ON
மற்றும் சாளர செயல்பாடுகள்!
SELECT
(rec).pid id
, string_agg(chld::text, ',') chld
FROM
tree
WHERE
(rec).pid IS NOT NULL
GROUP BY 1 -- не работает!
மேலும் இது எப்படி வேலை செய்கிறது!
SELECT DISTINCT ON((rec).pid)
(rec).pid id
, string_agg(chld::text, ',') OVER(PARTITION BY (rec).pid) chld
FROM
tree
WHERE
(rec).pid IS NOT NULL
எண் ஐடி ஏன் உரையாக மாற்றப்பட்டது என்பதை இப்போது பார்க்கிறோம் - அதனால் அவை காற்புள்ளிகளால் பிரிக்கப்பட்ட ஒன்றாக இணைக்கப்படலாம்!
3 விலக
இறுதிப் போட்டிக்கு எங்களிடம் எதுவும் இல்லை:
- குழுவாக்கப்பட்ட ஐடிகளின் தொகுப்பின் அடிப்படையில் “பிரிவு” பதிவுகளைப் படிக்கிறோம்
- கழித்த பகுதிகளை அசல் தாள்களின் "செட்" உடன் ஒப்பிடுகிறோம்
- செட்-ஸ்ட்ரிங் பயன்படுத்தி "விரிவாக்கு"
unnest(string_to_array(chld, ',')::integer[])
WITH RECURSIVE tree AS (
SELECT
rec
, id::text chld
FROM
hier rec
WHERE
id = ANY('{1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192}'::integer[])
UNION ALL
(
WITH prnt AS (
SELECT DISTINCT ON((rec).pid)
(rec).pid id
, string_agg(chld::text, ',') OVER(PARTITION BY (rec).pid) chld
FROM
tree
WHERE
(rec).pid IS NOT NULL
)
, nodes AS (
SELECT
rec
FROM
hier rec
WHERE
id = ANY(ARRAY(
SELECT
id
FROM
prnt
))
)
SELECT
nodes.rec
, prnt.chld
FROM
prnt
JOIN
nodes
ON (nodes.rec).id = prnt.id
)
)
SELECT
unnest(string_to_array(chld, ',')::integer[]) leaf
, (rec).*
FROM
tree;