2016-04-12 18 views
1

aşağıdaki tabloyu aşağıdaki verilerePerformans değerleri

INSERT INTO my_table (column_1, column_2, column_3, price) VALUES 
(1, NULL, 1, 54.99), 
(1, NULL, 1, 69.50), 
(NULL, 2,  2, 54.99), 
(NULL, 2,  2, 69.50), 
(3, 3,  NULL, 54.99), 
(3, 3,  NULL, 69.50); 

ile

CREATE TABLE my_table 
(
    record_id SERIAL, 
    column_1 INTEGER, 
    column_2 INTEGER, 
    column_3 INTEGER, 
    price NUMERIC 
); 

var Diyelim Şimdi tek yapmamız

CREATE TABLE my_table_aggregations AS 
SELECT 
    ROW_NUMBER() OVER() AS aggregation_id, 
    column_1, 
    column_2, 
    column_3 
FROM my_table 
GROUP BY 
    column_1, 
    column_2, 
    column_3; 

gibi bir şey Şimdi yapmak istediğim, my_table içindeki her record_id dosyasına bir aggregation_id atar. NULL değerlerim olduğu için, NULL = NULL NULL olduğundan ve bu kayıtlar bu kayıtları hariç tutacağından, t1.column_1 = t2.column_1'a bağlanamıyorum.

Şimdi ben buradaki sorun ben kayıtları yüz milyonlarca uğraşan ve çalıştırmak için sonsuza kadar sürer gibi görünüyor katılmak içinde bir ameliyathane yaşıyorum ki bu

SELECT 
    t.record_id, 
    agg.aggregation_id 
FROM my_table t 
JOIN my_table_aggregations agg ON 
(
    ((t.column_1 IS NULL AND agg.column_1 IS NULL) OR t.column_1 = agg.column_1) AND 
    ((t.column_2 IS NULL AND agg.column_2 IS NULL) OR t.column_2 = agg.column_2) AND 
    ((t.column_3 IS NULL AND agg.column_3 IS NULL) OR t.column_3 = agg.column_3) 
); 

gibi bir şey kullanması gerektiğini biliyoruz.

bu

SELECT 
    t.record_id, 
    agg.aggregation_id 
FROM my_table t 
JOIN my_table_aggregations agg ON 
(
    COALESCE(t.column_1, -1) = COALESCE(agg.column_1, -1) AND 
    COALESCE(t.column_2, -1) = COALESCE(agg.column_2, -1) AND 
    COALESCE(t.column_3, -1) = COALESCE(agg.column_3, -1) 
); 

gibi bir şey Ama bu sorun ben -1 bu sütunların herhangi birinde hiçbir değer yoktur varsayıyorum olmasıdır olan alternatif vardır.

Not, bu aynı sonucu elde etmek için DENSE_RANK kullanabileceğimin farkında olduğum bir örnektir. Öyleyse bunun bir seçenek olmadığını varsayalım.

COALESCE'u kullanmaktan zevk almanın, ancak OR'un doğru yolunu kullanarak performansını sürdürmenin bazı çılgın harika yolu var mı? Testleri çalıştırıyorum ve COALESCE, OR'dan 10 kat daha hızlı.

Bunu bir Greenplum veritabanında çalıştırıyorum, dolayısıyla bu performans farkının standart Postgres veritabanında aynı olup olmadığından emin değilim.

cevap

1

biraz düşündüm sonra, bunun ne bu dinamik kullanılabilir her sütun için bir değer bulmak için en iyi yaklaşım karar Bir COALESCE ikinci param olarak katıl. Fonksiyon oldukça uzun, ama ihtiyacım olanı yapar ve daha da önemlisi, bu şekilde COALESCE performansını korur, sadece aşağı tarafta MIN değerini almak ek bir zaman maliyeti, ama bir dakika konuşuyoruz.Aşağıda

CREATE OR REPLACE FUNCTION pg_temp.get_null_join_int_value 
(
    left_table_schema TEXT, 
    left_table_name TEXT, 
    left_table_columns TEXT[], 
    right_table_schema TEXT, 
    right_table_name TEXT, 
    right_table_columns TEXT[], 
    output_table_schema TEXT, 
    output_table_name TEXT 
) RETURNS TEXT AS 
$$ 
DECLARE 
    colum_name TEXT; 
    sql TEXT; 
    complete_sql TEXT; 
    full_left_table TEXT; 
    full_right_table TEXT; 
    full_output_table TEXT; 
BEGIN 

    /***************************** 
     VALIDATE PARAMS 
    ******************************/ 

    -- this section validates all of the function parameters ensuring that the values that cannot be NULL are not so 
    -- also checks for empty arrays which is not allowed and then ensures both arrays are of the same length 
    IF (left_table_name IS NULL) THEN 
     RAISE EXCEPTION 'left_table_name cannot be NULL'; 
    ELSIF (left_table_columns IS NULL) THEN 
     RAISE EXCEPTION 'left_table_columns cannot be NULL'; 
    ELSIF (right_table_name IS NULL) THEN 
     RAISE EXCEPTION 'right_table_name cannot be NULL'; 
    ELSIF (right_table_columns IS NULL) THEN 
     RAISE EXCEPTION 'right_table_columns cannot be NULL'; 
    ELSIF (output_table_name IS NULL) THEN 
     RAISE EXCEPTION 'output_table_name cannot be NULL'; 
    ELSIF (array_upper(left_table_columns, 1) IS NULL) THEN 
     RAISE EXCEPTION 'left_table_columns cannot be an empty array'; 
    ELSIF (array_upper(right_table_columns, 1) IS NULL) THEN 
     RAISE EXCEPTION 'right_table_columns cannot be an empty array'; 
    ELSIF (array_upper(left_table_columns, 1) <> array_upper(right_table_columns, 1)) THEN 
     RAISE EXCEPTION 'left_table_columns and right_table_columns must have a matching array length'; 
    END IF; 

    /************************ 
     TABLE NAMES 
    *************************/ 

    -- create the full name of the left table 
    -- the schema name can be NULL which means that the table is temporary 
    -- because of this, we need to detect if we should specify the schema 
    IF (left_table_schema IS NOT NULL) THEN 
     full_left_table = left_table_schema || '.' || left_table_name; 
    ELSE 
     full_left_table = left_table_name; 
    END IF; 

    -- create the full name of the right table 
    -- the schema name can be NULL which means that the table is temporary 
    -- because of this, we need to detect if we should specify the schema 
    IF (right_table_schema IS NOT NULL) THEN 
     full_right_table = right_table_schema || '.' || right_table_name; 
    ELSE 
     full_right_table = right_table_name; 
    END IF; 

    -- create the full name of the output table 
    -- the schema name can be NULL which means that the table is temporary 
    -- because of this, we need to detect if we should specify the schema 
    IF (output_table_schema IS NOT NULL) THEN 
     full_output_table = output_table_schema || '.' || output_table_name; 
    ELSE 
     full_output_table = output_table_name; 
    END IF; 

    /********************** 
     LEFT TABLE 
    ***********************/ 

    -- start to create the table which will store the min values from the left table 
    sql = 
     'DROP TABLE IF EXISTS temp_null_join_left_table;' || E'\n' || 
     'CREATE TEMP TABLE temp_null_join_left_table AS' || E'\n' || 
     'SELECT'; 

    -- loop through each column name in the left table column names parameter 
    FOR colum_name IN SELECT UNNEST(left_table_columns) LOOP 

     -- find the minimum value in this column and subtract one 
     -- we will use this as a value we know is not in the column of this table 
     sql = sql || E'\n\t' || 'MIN("' || colum_name || '")-1 AS "' || colum_name || '",'; 

    END LOOP; 

    -- remove the trailing comma from the SQL 
    sql = TRIM(TRAILING ',' FROM sql); 

    -- finish the SQL to create the left table min values 
    sql = sql || E'\n' || 
     'FROM ' || full_left_table || ';'; 

    -- run the query that creates the table which stores the minimum values for each column in the left table 
    EXECUTE sql; 

    -- store the sql which will be the return value of the function 
    complete_sql = sql; 

    /************************ 
     RIGHT TABLE 
    *************************/ 

    -- start to create the table which will store the min values from the right table 
    sql = 
     'DROP TABLE IF EXISTS temp_null_join_right_table;' || E'\n' || 
     'CREATE TEMP TABLE temp_null_join_right_table AS' || E'\n' || 
     'SELECT'; 

    -- loop through each column name in the right table column names parameter 
    FOR colum_name IN SELECT UNNEST(right_table_columns) LOOP 

     -- find the minimum value in this column and subtract one 
     -- we will use this as a value we know is not in the column of this table 
     sql = sql || E'\n\t' || 'MIN("' || colum_name || '")-1 AS "' || colum_name || '",'; 

    END LOOP; 

    -- remove the trailing comma from the SQL 
    sql = TRIM(TRAILING ',' FROM sql); 

    -- finish the SQL to create the right table min values 
    sql = sql || E'\n' || 
     'FROM ' || full_left_table || ';'; 

    -- run the query that creates the table which stores the minimum values for each column in the right table 
    EXECUTE sql; 

    -- store the sql which will be the return value of the function 
    complete_sql = complete_sql || E'\n\n' || sql; 

    -- start to create the final output table which will contain the column names defined in the left_table_columns parameter 
    -- each column will contain a negative value that is not present in both the left and right tables for the given column 
    sql = 
     'DROP TABLE IF EXISTS ' || full_output_table || ';' || E'\n' || 
     'CREATE ' || (CASE WHEN output_table_schema IS NULL THEN 'TEMP ' END) || 'TABLE ' || full_output_table || ' AS' || E'\n' || 
     'SELECT'; 

    -- loop through each index of the left_table_columns array 
    FOR i IN coalesce(array_lower(left_table_columns, 1), 1)..coalesce(array_upper(left_table_columns, 1), 1) LOOP 

     -- add to the sql a call to the LEAST function 
     -- this function takes an infinite number of columns and returns the smallest value within those columns 
     -- we have -1 hardcoded because the smallest minimum value may be a positive integer and so we need to ensure the number used is negative 
     -- this way we will not confuse this value with a real ID from a table 
     sql = sql || E'\n\t' || 'LEAST(l."' || left_table_columns[i] || '", r."' || right_table_columns[i] || '", -1) AS "' || left_table_columns[i] || '",'; 

    END LOOP; 

    -- remove the trailing comma from the SQL 
    sql = TRIM(TRAILING ',' FROM sql); 

    -- finish off the SQL which creates the final table 
    sql = sql || E'\n' || 
     'FROM temp_null_join_left_table l' || E'\n' || 
     'CROSS JOIN temp_null_join_right_table r' || ';'; 

    -- create the final table 
    EXECUTE sql; 

    -- store the sql which will be the return value of the function 
    complete_sql = complete_sql || E'\n\n' || sql; 

    -- we no longer need these tables 
    sql = 
     'DROP TABLE IF EXISTS temp_null_join_left_table;' || E'\n' || 
     'DROP TABLE IF EXISTS temp_null_join_right_table;'; 
    EXECUTE sql; 

    -- store the sql which will be the return value of the function 
    complete_sql = complete_sql || E'\n\n' || sql; 

    -- return the SQL that has been run, good for debugging purposes or just understanding what the function does 
    RETURN complete_sql; 
END; 
$$ 
LANGUAGE plpgsql; 

temp_null_join_values tablo bir alt yapabilirsiniz oluşturulduktan sonra

SELECT pg_temp.get_null_join_int_value 
(
    -- left table 
    'public', 
    'my_table', 
    '{"column_1", "column_2", "column_3"}', 
    -- right table 
    'public', 
    'my_table_aggregations', 
    '{"column_1", "column_2", "column_3"}', 
    -- output table 
    NULL, 
    'temp_null_join_values' 
); 

COALESCE 2 için katılmak seçmiş fonksiyon örneği kullanım şöyledir: Burada

fonksiyonudur param.

DROP TABLE IF EXISTS temp_result_table; 
CREATE TEMP TABLE temp_result_table AS 
SELECT 
    t.record_id, 
    agg.aggregation_id 
FROM public.my_table t 
JOIN my_table_aggregations agg ON 
(
    COALESCE(t.column_1, (SELECT column_1 FROM temp_null_join_values)) = COALESCE(agg.column_1, (SELECT column_1 FROM temp_null_join_values)) AND 
    COALESCE(t.column_2, (SELECT column_2 FROM temp_null_join_values)) = COALESCE(agg.column_2, (SELECT column_2 FROM temp_null_join_values)) AND 
    COALESCE(t.column_3, (SELECT column_3 FROM temp_null_join_values)) = COALESCE(agg.column_3, (SELECT column_3 FROM temp_null_join_values)) 
); 

Ben bir `OR` yaptığı gibi aynı performans sorunlarını uğrar, bu işleri yaparken bu birilerine

0

ne dersiniz:

SELECT 
    t.record_id, 
    a.aggregation_id 
FROM my_table t 
JOIN my_table_aggregations agg ON 
(
    NULLIF(t.column_1, agg.column_1) IS NULL 
    AND 
    NULLIF(agg.column_1, t.column_1) IS NULL 
    AND 
    NULLIF(t.column_2, agg.column_2) IS NULL 
    AND 
    NULLIF(agg.column_2, t.column_2) IS NULL 
    AND 
    NULLIF(t.column_3, agg.column_3) IS NULL 
    AND 
    NULLIF(agg.column_3, t.column_3) IS NULL 
); 
+0

yardımcı olur. Bunun neden Postgres'de bir sorun olduğunu anlamıyorum ... –

1

NULLIF ile benim çözüm performans sorunları vardı ve COALESCE kullanmanızın size -1 konuyla ilgilenmesi için bu çözümü verdiği deneyebilirsiniz eğer çok daha hızlı, acaba beri. Bunu yapmak için, yanlış eşleşmeleri önlemek için yayınlamayı deneyebilirsiniz. Ben performans hit olurdu emin değilim, ama gibi görünecektir:

SELECT 
    t.record_id, 
    agg.aggregation_id 
FROM my_table t 
JOIN my_table_aggregations agg ON 
(
    COALESCE(cast(t.column_1 as varchar), 'NA') = 
     COALESCE(cast(agg.column_1 as varchar), 'NA') AND 
    COALESCE(cast(t.column_2 as varchar), 'NA') = 
     COALESCE(cast(agg.column_2 as varchar), 'NA') AND 
    COALESCE(cast(t.column_3 as varchar), 'NA') = 
     COALESCE(cast(agg.column_3 as varchar), 'NA') 
); 
+0

Ortadaki bir zemini bulduk. İlk cevabınız orijinal 'coalesce' olduğu sürece 10 katın üzerinde oldu, ancak varchar'a ekleyerek, sadece 1,5 kat daha uzun sürüyor ki bence bu ücretin iyi bir bedeli olduğunu düşünüyorum. Teşekkür ederim, bunu anser olarak işaretleyeceğim –