How to make an index with md5 and concat_ws? - postgresql

How to make an index with md5 and concat_ws?
CREATE INDEX psn_prev_work_hash_idx ON public.psn_prev_work (CAST(md5(concat_ws('_|_', id, end_date)) AS uuid));
SQL Error [42P17]: ERROR: functions in index expression must be marked IMMUTABLE
Version PostgreSQL 9.6.11

Assuming end_date is a date column there is no easy way to create an index on that expression as all conversions from date (or timestamp) to a text (or varchar) value are volatile due to the possible influence of locale or time zone settings.
As you know your expression is immutable (assuming end_date is indeed a date!) you can create an immutable function that you can use in the index:
create function make_uuid(p_id int, p_date date)
returns uuid
as
$$
select CAST(md5(concat_ws('_|_', p_id, p_date)) AS uuid);
$$
language sql
immutable;
CREATE INDEX psn_prev_work_hash_idx
ON psn_prev_work (make_uuid(id, end_date));
However, to make a query use that index, you need to use the function in your query, not the original expression:
select *
from psn_prev_work
where make_uuid(id, end_date) = ...;
As a side note: the index seems strange to me. I can't imagine a situation where an index on (id, end_date) wouldn't be a better alternative.

Related

In POSTGRESQL how to get some value from a table into variable of integer type first and then use it latter.?

In POSTGRESQL how to get some value from a table into variable of integer type first and then use it latter.?
Actually I first want to get value in a variable of type integer and then use it in latter select query.
I know all other solution i.e. using join , using ;with clause but I not want all that. I only want what exactly I said.
Any working example will be good. I want all that working in a pgsql
CREATE OR REPLACE FUNCTION public."GetMedLastAdmnstrationDateTime"(_medicationid integer, _alfid integer)
RETURNS TABLE("MedLastAdminDateTime" timestamp without time zone)
LANGUAGE plpgsql
AS $function$
begin
declare _partitiondate timestamp = (select date from table_abc limit 1)
Return Query
select
e."MedDateTime" as "MedLastAdminDateTime"
from public."eMAR" e
where
e."AlfId" = _alfid
and e."MedicationId" = _medicationid
and e."MedDateTime" > _partitiondate::date
order by e."MedDateTime" desc
limit 1
;
END
$function$
;

Pg sql use ilike operator to search text in array

I have query like
_search_text := 'ind'
select * from table where (_search_text ILIKE ANY (addresses))
The st.addresses have value like [india,us,uk,pak,bang]
It should return each item rows where any item of column addresses contains the string _search_text,
Currently it returns only if give full india in _search_text.
What should I make the change
I was also try to thinkin to use unnest, but since it wil be a sub clause of a very long where cluase... so avoid that.
Thanks
I afraid so it is not possible - LIKE, ILIKE supports a array only on right side, and there is searching pattern. Not string. You can write own SQL function:
CREATE OR REPLACE FUNCTION public.array_like(text[], text)
RETURNS boolean
LANGUAGE sql
IMMUTABLE STRICT
AS $function$
select bool_or(v like $2) from unnest($1) v
$function$
and the usage can looks like:
WHERE array_like(addresses, _search_text)
So the readability of this query can be well. But I afraid about performance. There cannot be used any index on this expression. It is result of little bit bad design (the data are not normalized).
ilike ignores cases (difference in upper or lower cases), it doesn't search for string containing your value.
In your case you can use:
_search_text := '%ind%'
select * from table where (_search_text ILIKE ANY (addresses))
ANY will not work here because the arguments are in the wrong order for ILIKE.
You can define a custom operator to make this work. But this will most likely suffer from poor performance:
create table addresses(id integer primary key, states varchar[]);
insert into addresses values (1,'{"belgium","france","united states"}'),
(2,'{"belgium","ireland","canada"}');
CREATE OR REPLACE FUNCTION pg_catalog."rlike"(
leftarg text,
rightarg text)
RETURNS boolean
LANGUAGE 'sql'
COST 100
IMMUTABLE STRICT PARALLEL SAFE SUPPORT pg_catalog.textlike_support
AS $BODY$
SELECT pg_catalog."like"(rightarg,leftarg);
$BODY$;
ALTER FUNCTION pg_catalog."rlike"(text, text)
OWNER TO postgres;
CREATE OPERATOR ~~~ (
leftarg = text,
rightarg = text,
procedure = rlike
);
select * from addresses where 'bel%'::text ~~~ ANY (states);
It should be possible to define this function in C to make it faster.

PostgreSQL : Cast Data Type from Text to Bigint when using WHERE IN

I've problem when try to cast data type from TEXT to BIGINT when using WHERE IN on PostgreSQL in procedure. This always gives
operator does not exist: bigint = text. Try cast the variable in the query.
But still get the same notice. This is example query:
DECLARE
-- $1 params text
BEGIN
SELECT * FROM table_a where
colId IN($1); // notice is here, colId is bigint
END
/*Call the procedure*/
SELECT my_function('1,2,3,4,5');
How do we cast the variable? Thanks!
Using strings for id list is wrong design. You can use a arrays in PostgreSQL.
For example
CREATE OR REPLACE FUNCTION foo(VARIADIC ids int[])
RETURNS SETOF table_a AS $$
SELECT * FROM table_a WHERE id = ANY($1)
$$ LANGUAGE sql;
SELECT foo(1,2,3);
But, usually wrapping simple SQL to functions looks like broken design. Procedures should not to replace views.

Postgres: Function check duplicates in table

I have table with multiple duplicates and I want to make from these a function. Could you please help me to make a function from this code? thanks.
SELECT id_member,id_service,amount,date, count(*) as number_of_duplicates
from evidence
GROUP BY id_member,id_service,amount,date
HAVING COUNT(*) > 1;
CREATE OR REPLACE FUNCTION check_for_duplicates()
RETURNS VOID AS
$BODY$
BEGIN
SELECT id_member,id_service,amount,date, count(*) as number_of_duplicates
from evidence
GROUP BY id_member,id_service,amount,date
HAVING COUNT(*) > 1;
END;
$BODY$
LANGUAGE ‘plpgsql‘;
If a function should return a result set it needs to be declared as returns table () or returns setof
You also don't need a PL/pgSQL function for that, a simple SQL function will do and is more efficient:
CREATE OR REPLACE FUNCTION check_for_duplicates()
RETURNS table (id_member integer, id_service integer, amount numeric, date date, number_of_duplicates bigint)
$BODY$
SELECT id_member,id_service,amount,date, count(*) as number_of_duplicates
from evidence
GROUP BY id_member,id_service,amount,date
HAVING COUNT(*) > 1;
$BODY$
LANGUAGE sql;
You didn't show us the definition of the table evidence so I had to guess the data type of the columns. You will need to adjust the types in the returns table (...) part to match the types from the table.
Having said that: I would create a view for things like that, not a function.
Unrelated, but: date is a horrible name for a column. For one because it's also a keyword but more importantly it doesn't document what the column contains: a release date? An expiration date? A due date?

How to derive a column name in the return type from input parameters to the function?

Using Postgres 9.5 I have built this function:
CREATE or REPLACE FUNCTION func_getratio_laglag(_numeratorLAG text, _n1 int, _denominatorLAG text, _n2 int, _table text)
RETURNS TABLE (date_t timestamp without time zone, customer_code text, index text, ratio real) AS
$BODY$
BEGIN
RETURN QUERY EXECUTE
'SELECT
date_t,
customer_code,
index,
(LAG('||quote_ident(_numeratorLAG)||',' || quote_literal(_n1)||') OVER W / LAG('||quote_ident(_denominatorLAG)||','|| quote_literal(_n2)||') OVER W) '
|| ' FROM ' || quote_ident(_table)
|| ' WINDOW W AS (PARTITION BY customer_code ORDER BY date_t asc);';
END;
$BODY$ LANGUAGE plpgsql;
All the function does is allow me the ability to pick a 2 different columns from a specified table and calculate a ratio between them based on different lag windows. To execute the function above I use the following query:
SELECT * FROM func_getratio_laglag('order_first',1,'order_last',0,'customers_hist');
This returns a table with the column labels date_t, customer_code, index and ratio. I have really struggled on how to output ratio as a dynamic column label. That is, I would like to make it contingent on the input parameters e.g. if I ran the select query above then I would like the column labels date_t, customer_code, index and order_first_1_order_last_0.
I am stuck, any advice or hints?
How to derive a column name in the return type from input parameters to the function?
The short answer: Not possible.
SQL is very rigid about column data types and names. Those have to be declared before or at call time at the latest. No exceptions. No truly dynamic column names.
I can think of 3 half-way workarounds:
1. Column aliases
Use your function as is (or rather the audited version I suggest below) and add column aliases in the function call:
SELECT * FROM func_getratio_laglag('order_first',1,'order_last',0,'customers_hist')
AS f(date_t, customer_code, index, order_first_1_order_last_0)
I would do that.
2. Column definition list
Create your function to return anonymous records:
RETURNS SETOF record
Then you have to provide a column definition list with every call:
SELECT * FROM func_getratio_laglag('order_first',1,'order_last',0,'customers_hist')
AS f(date_t timestamp, customer_code text, index text, order_first_1_order_last_0 real)
I would not do that.
3. Use a registered row type as polymorphic input / output type.
Mostly useful if you happen to have row types at hand. You could register a row type on the fly by crating a temporary table, but that seems like overkill for your use case.
Details in the last chapter of this answer:
Refactor a PL/pgSQL function to return the output of various SELECT queries
Function audit
Use format() to make building query string much more safe and simple.
Read the manual if you are not familiar with it.
CREATE OR REPLACE FUNCTION func_getratio_laglag(
_numerator_lag text, _n1 int
, _denominator_lag text, _n2 int
, _table regclass)
RETURNS TABLE (date_t timestamp, customer_code text, index text, ratio real) AS
$func$
BEGIN
RETURN QUERY EXECUTE format (
'SELECT date_t, customer_code, index
, (lag(%I, %s) OVER w / lag(%I, %s) OVER w) -- data type must match
FROM %s
WINDOW w AS (PARTITION BY customer_code ORDER BY date_t)'
, _numerator_lag, _n1, _denominator_lag, _n2, _table::text
);
END
$func$ LANGUAGE plpgsql;
Note the data type regclass for the table name. That's my personal (optional) suggestion.
Table name as a PostgreSQL function parameter
Aside: I would also advise not to use mixed-case identifiers in Postgres.
Are PostgreSQL column names case-sensitive?