This is the SQL query i used to get data from my table which contains parent-child values:
WITH RECURSIVE cte AS
(
SELECT array[r.term1_id, r.term2_id] AS path
FROM temp_table r
LEFT JOIN temp_table r0 ON r0.term1_id = r.term2_id
WHERE r0.term1_id IS NULL
UNION ALL
SELECT r.term1_id || c.path
FROM cte c
JOIN temp_table r ON r.term2_id = c.path[1]
)
SELECT path
FROM cte
ORDER BY path;
The result is something like this:
"{1,5,6,1452}"
"{1,5,6,1470,1475}"
How can i fill the last space of the first array obtaining something like this?
"{1,5,6,1452,1452}"
"{1,5,6,1470,1475}"
So i want to repeat the last non null value until i cover every level.
ANSWER:
Here's a functioning statement
CREATE OR REPLACE FUNCTION fill_with_last_element(arr anyarray, n INTEGER)
RETURNS anyarray LANGUAGE plpgsql as $$
DECLARE
l int = array_length(arr,1);
BEGIN
RETURN CASE
WHEN l > n THEN arr
ELSE arr || array_fill(arr[l], array[n- l])
END;
END $$;
WITH RECURSIVE cte(path) AS (
SELECT array[r.term1_id, r.term2_id] AS path
FROM temp_table r
LEFT JOIN temp_table r0 ON r0.term1_id = r.term2_id
WHERE r0.term1_id IS NULL
UNION ALL
SELECT r.term1_id || c.path
FROM cte c
JOIN temp_table r ON r.term2_id = c.path[1]
),
max_len AS (
SELECT max(array_length(path, 1)) max_len
FROM cte
)
SELECT fill_with_last_element(path, max_len)
FROM cte
CROSS JOIN max_len
ORDER BY path;
You cannot generate arrays with equal lengths because you do not know the length before the query terminates. You should modify the results. This function will be helpful:
create or replace function fill_with_last_element(arr anyarray, n integer)
returns anyarray language plpgsql as $$
declare
l int = array_length(arr,1);
begin
return case
when l > n then arr
else arr || array_fill(arr[l], array[n- l])
end;
end $$;
Example:
with cte(path) as (
values
('{1,5,6,1452}'::int[]),
('{1,5,6,1470,1475}')
),
max_len as (
select max(array_length(path, 1)) max_len
from cte
)
select fill_with_last_element(path, max_len)
from cte
cross join max_len
fill_with_last_element
------------------------
{1,5,6,1452,1452}
{1,5,6,1470,1475}
(2 rows)
Related
I need to create a function that would split string by element.
'a1,a2,a1,a3' and result should be:
Value
Counter
a1
2
a2
1
a3
1
This is example of my code (but it needs to work)
CREATE OR REPLACE FUNCTION SPLIT_STRING (
IN_STRING VARCHAR(1000),
IN_DELIM VARCHAR(20))
RETURNS TABLE (CNT INT)
LANGUAGE SQL
READS SQL DATA
BEGIN
DECLARE I INT DEFAULT 0;
DECLARE CNT INT DEFAULT 0;
WHILE I < LENGTH(IN_STRING)
DO
IF SUBSTR(IN_STRING, I, 1) = IN_DELIM THEN SET CNT = CNT + 1;
END IF;
SET I = I + 1;
END WHILE;
RETURN CNT;
END;
select SPLIT_STRING('a1,a2,a1,a3', ',') from SYSIBM.SYSDUMMY1
There is no need to create such a function in Db2 for LUW.
You may use a built-in functionality.
select x.tok as value, count (1) as counter
from
-- Here is your table reference
(values 'a1,a2,a1,a3') mytab (str)
, xmltable
(
'for $id in tokenize($s, ",") return <i>{string($id)}</i>'
passing
mytab.str as "s"
columns
tok varchar(4000) path '.'
) x
group by x.tok
VALUE
COUNTER
a1
2
a2
1
a3
1
Update
The same with a table function:
CREATE OR REPLACE FUNCTION SPLIT_STRING
(
IN_STRING VARCHAR(1000)
, IN_DELIM VARCHAR(20)
)
RETURNS TABLE (TOKEN VARCHAR (100), CNT INT)
LANGUAGE SQL
READS SQL DATA
RETURN
select x.tok as value, count (1) as counter
from xmltable
(
'for $id in tokenize($s, $p) return <i>{string($id)}</i>'
passing
IN_STRING as "s"
, IN_DELIM as "p"
columns
tok varchar (1000) path '.'
) x
group by x.tok
select *
from table (SPLIT_STRING ('a1,a2,a1,a3', ','))
TOKEN
CNT
a1
2
a2
1
a3
1
fiddle
I want to write a function that returns 0 or 1. How to do it, if I use WITH-construction in my code
ALTER FUNCTION [dbo].[IsBossFull] (#bossFull int, #user int)
RETURNS bit
AS
BEGIN
;WITH CTE
AS (
SELECT *
FROM [USERS] WHERE [Id] = #user
UNION ALL
SELECT U.*
FROM CTE C
INNER JOIN [USERS] u on U.[Id] = C.[Chief]
)
-- then something like
if exists(
select * from cte where id = #bossFull)
return 1
else return 0
END
You would assign the value to a variable and return that:
DECLARE #retval int;
WITH CTE AS (
SELECT *
FROM USERS
WHERE Id = #user
UNION ALL
SELECT U.*
FROM CTE C INNER JOIN
USERS u
ON U.[Id] = C.[Chief]
)
SELECT #retval = COUNT(*)
FROM cte
WHERE id = #bossfull;
RETURN(#retval);
I haven't actually simplified the code. You can stop the recursion when #bossful is actually found.
If you really want, you can use return(case when retval > 0 then 1 else 0 end). However, #retval cannot be greater than 1, because that would imply cycles in the hierarchy -- and the CTE would not return.
I tried creating a function that would return the contents of the below query where proto_location is of type text. Not sure if it matters but my java code that currently calls this query just reads each record as a string.
SELECT DISTINCT tm.proto_location
FROM track_message tm
WHERE tm.workflow_analytic_instance_id = 204
AND EXISTS ( SELECT *
FROM track_message_to_track_mapping tm2tm
JOIN track t ON t.id = tm2tm.track_id
JOIN track_item ti ON t.id = ti.track_id
JOIN track_point tp ON ti.id = tp.track_item_id
WHERE tm.id =tm2tm.track_message_id
AND ti.item_time BETWEEN 1328816277089000 AND 1328816287089000
AND ST_Intersects
(tp.track_position
, ST_GeomFromText('POLYGON((-144 59, -41 46, -75 15, -127 25, -144 59))',4326)
)
)
;
Here is my function
CREATE OR REPLACE Function getTrackMessages(workflow bigint, start_time bigint, end_time bigint) returns text[]
as $$
SELECT DISTINCT tm.proto_location
FROM track_message tm
WHERE tm.workflow_analytic_instance_id = $1AND EXISTS ( SELECT *
FROM track_message_to_track_mapping tm2tm
JOIN track t ON t.id = tm2tm.track_id
JOIN track_item ti ON t.id = ti.track_id
JOIN track_point tp ON ti.id = tp.track_item_id
WHERE tm.id =tm2tm.track_message_id
AND ti.item_time BETWEEN $2 AND $3 AND ST_Intersects
(tp.track_position
, ST_GeomFromText('POLYGON((-144 59, -41 46, -75 15, -127 25, -144 59))',4326)
)
)
;
$$ Language 'plpgsql';
I keep getting an error saying Syntax error at or near "select" and its refering to the line
SELECT DISTINCT tm.proto_location
CREATE OR REPLACE FUNCTION get_track_messages(workflow bigint, start_time bigint, end_time bigint)
RETURNS SETOF text
LANGUAGE sql AS
$func$
SELECT DISTINCT tm.proto_location
FROM track_message tm
WHERE tm.workflow_analytic_instance_id = $1
AND EXISTS (
SELECT FROM track_message_to_track_mapping tm2tm
JOIN track t ON t.id = tm2tm.track_id
JOIN track_item ti ON t.id = ti.track_id
JOIN track_point tp ON ti.id = tp.track_item_id
WHERE tm.id = tm2tm.track_message_id
AND ti.item_time BETWEEN $2 AND $3
AND ST_Intersects (tp.track_position
, ST_GeomFromText('POLYGON((-144 59, -41 46, -75 15, -127 25, -144 59))',4326))
);
$func$;
Most importantly, it would work as sql function.
For a PL/pgSQL function you'd need to change more:
CREATE OR REPLACE FUNCTION get_track_messages(workflow bigint
, start_time bigint
, end_time bigint)
RETURNS SETOF text
LANGUAGE plpqsql AS
$func$
BEGIN
RETURN QUERY
SELECT DISTINCT ... ;
END
$func$;
If the query returns more than 1 row, you also need SETOF (as you figured out yourself). Call the function with:
SELECT * FROM get_track_messages( ... );
More about returning from a function in the manual.
Data type
proto_location is a character type. If you actually want to return an array of text (like your title states) you can aggregate like this:
SELECT array_agg(DISTINCT tm.proto_location) ...
And adapt the return type of the function to SETOF text[].
For a sorted array:
SELECT array_agg(DISTINCT tm.proto_location ORDER BY proto_location) ...
Or, to get a sorted list (text) instead of an array (text[]):
SELECT string_agg(DISTINCT tm.proto_location ORDER BY proto_location, ', ') ...
See:
How to use array_agg() for varchar[]
Alternatives to array_agg()?
I have been trying to create a View where one of the column pending_amount gets its value as a result of a stored procedure execution.
The stored procedure is pending_stock(int,int) and returns an integer. The view is created successfully but when i try to perform any query like select on the this view it takes ever to return a value.
CREATE OR REPLACE VIEW view_production_parts AS
SELECT p.part_id, p.min_amount, gp.part_num, gp.description
, p.quantity_available
, p.quantity_total - p.quantity_available AS quantity_alloc
, p.quantity_total
, (SELECT pending_stock(p.part_id, 0) AS pending_stock) AS pending_amount
, p.production_run
, CASE
WHEN ppur.purchased_part_id IS NOT NULL THEN true
ELSE false
END AS is_purchased_part, ppur.purchased_part_id, p.store_move_type_id
, gp.part_status_id, p.default_location
, COALESCE(pwoh.part_work_order_hold_id, 0) AS part_work_order_hold_id
FROM general_part gp
JOIN part p ON gp.part_id = p.part_id
LEFT JOIN purchased_part ppur ON ppur.part_id = p.part_id
LEFT JOIN part_work_order_hold pwoh ON pwoh.part_id = p.part_id
ORDER BY gp.part_num;
Can a stored procedure be used in a view? If used, Is my declaration correct?
Find the result from of this query at explain.depesz.com:
EXPLAIN ANALYZE SELECT count(*) FROM view_production_parts
I am using Postgres 8.4.
Function definition for pending_stock(int,int):
CREATE OR REPLACE FUNCTION pending_stock(var_part_id integer
, var_pattern_id integer)
RETURNS integer AS
$BODY$
declare
r record;
var_qty_expected integer;
var_qty_moved_to_stock integer;
var_total_stock_moved_out integer;
var_actual_qty integer;
begin
var_total_stock_moved_out := 0;
var_qty_expected := 0;
for r in
select work_order_id,quantity_expected
from view_work_orders
where part_id = var_part_id and open = 'TRUE'
and quantity_allocated is null and quantity_expected >= quantity_actual
loop
var_qty_expected = var_qty_expected + r.quantity_expected;
select sum(quantity) from view_work_order_move_parts_details
where source_work_order_id = r.work_order_id
and part_id = var_part_id into var_qty_moved_to_stock;
if var_qty_moved_to_stock is null then
var_qty_moved_to_stock = 0;
end if;
var_total_stock_moved_out = var_total_stock_moved_out
+ var_qty_moved_to_stock;
end loop;
var_actual_qty := var_qty_expected - var_total_stock_moved_out;
if var_actual_qty > 0 then
return var_actual_qty;
else
return 0;
end if;
end;
$BODY$
LANGUAGE 'plpgsql' VOLATILE STRICT
COST 100;
ALTER FUNCTION pending_stock(integer, integer) OWNER TO postgres;
View
You don't need a subquery for the function call. And you can simplify some other minor details:
CREATE OR REPLACE VIEW view_production_parts AS
SELECT p.part_id, p.min_amount
, gp.part_num, gp.description, p.quantity_available
, p.quantity_total - p.quantity_available AS quantity_alloc
, p.quantity_total
, pending_stock(gp.part_id, 0) AS pending_amount
, p.production_run
,(ppur.purchased_part_id IS NOT NULL) AS is_purchased_part
, ppur.purchased_part_id, p.store_move_type_id, gp.part_status_id
, p.default_location
, COALESCE(pwoh.part_work_order_hold_id, 0) AS part_work_order_hold_id
FROM general_part gp
JOIN part p USING (part_id)
LEFT JOIN purchased_part ppur USING (part_id)
LEFT JOIN part_work_order_hold pwoh USING (part_id)
ORDER BY gp.part_num;
Other than that the VIEW definition looks fine.
Function
Can be largely simplified:
CREATE OR REPLACE FUNCTION pending_stock(var_part_id integer
, var_pattern_id integer)
RETURNS integer AS
$func$
DECLARE
r record;
var_qty_expected integer := 0;
var_total_stock_moved_out integer := 0;
BEGIN
FOR r IN
SELECT work_order_id, quantity_expected
FROM view_work_orders
WHERE part_id = var_part_id
AND open = 'TRUE' -- A string instead of a boolean?
AND quantity_allocated IS NULL
AND quantity_expected >= quantity_actual
LOOP
var_qty_expected := var_qty_expected + r.quantity_expected;
SELECT var_total_stock_moved_out + COALESCE(sum(quantity), 0)
FROM view_work_order_move_parts_details
WHERE source_work_order_id = r.work_order_id
AND part_id = var_part_id
INTO var_total_stock_moved_out;
END LOOP;
RETURN GREATEST(var_qty_expected - var_total_stock_moved_out, 0);
END
$func$ LANGUAGE plpgsql
Major points
Generally, assignments are comparatively expensive in plpgsql. Every assignment is executed with a (very simple and fast) SELECT statement internally. Try to use fewer of them.
You can init variables at declaration time. No need for another statement.
The assignment operator in plpgsql is :=. = works, but is undocumented.
Use COALESCE() to catch NULL values.
The function parameter var_pattern_id is never used. This is probably not the full function definition.
The whole final part can be replaced with a single statement using GREATEST
Superior query
Now, this cleaned up function will be a bit faster, but not much. Your whole design of looping repeatedly is extremely inefficient. It results in correlated subqueries that loop through correlated subqueries yet again. Performance nightmare.
Recast the problem as set-based operation to make it faster. Well, a lot faster.
SELECT e.part_id
,GREATEST(COALESCE(sum(e.quantity_expected), 0)
- COALESCE(sum(m.total_stock_moved_out), 0), 0)
FROM view_work_orders e
LEFT JOIN (
SELECT source_work_order_id AS work_order_id
,COALESCE(sum(quantity), 0) AS total_stock_moved_out
FROM view_work_order_move_parts_details
WHERE part_id = var_part_id
GROUP BY 1
) m USING (work_order_id)
WHERE e.part_id = var_part_id
AND e.open = 'TRUE'
AND e.quantity_allocated IS NULL
AND e.quantity_expected >= e.quantity_actual
GROUP BY 1;
Superior view
Integrate this into the original query / view:
CREATE OR REPLACE VIEW view_production_parts AS
SELECT p.part_id, p.min_amount
,gp.part_num, gp.description, p.quantity_available
,p.quantity_total - p.quantity_available AS quantity_alloc
,p.quantity_total
,x.pending_amount
,p.production_run
,(ppur.purchased_part_id IS NOT NULL) AS is_purchased_part
,ppur.purchased_part_id, p.store_move_type_id, gp.part_status_id
,p.default_location
,COALESCE(pwoh.part_work_order_hold_id, 0) AS part_work_order_hold_id
FROM general_part gp
JOIN part p USING (part_id)
LEFT JOIN purchased_part ppur USING (part_id)
LEFT JOIN part_work_order_hold pwoh USING (part_id)
LEFT JOIN (
SELECT e.part_id
,GREATEST(COALESCE(sum(e.quantity_expected), 0)
- COALESCE(sum(m.total_stock_moved_out), 0)
, 0) AS pending_amount
FROM view_work_orders e
LEFT JOIN (
SELECT source_work_order_id AS work_order_id
,sum(quantity) AS total_stock_moved_out
FROM view_work_order_move_parts_details
WHERE part_id = var_part_id
GROUP BY 1
) m USING (work_order_id)
WHERE e.part_id = var_part_id
AND e.open = 'TRUE'
AND e.quantity_allocated IS NULL
AND e.quantity_expected >= e.quantity_actual
GROUP BY 1
) x USING (part_id)
ORDER BY gp.part_num;
Untested, obviously.
I just started working with the EVE static dump, which is just a lot of tables with data about the game, such as a list of what solar systems connect, which is what I'm dealing with.
I want to make a webpage that lets you filter out systems, and the first step is getting a list of systems nearby, with the distance to them.
I found a script that does it for MSSQL
--By Joanna Davaham http://forum.eveuniversity.org/viewtopic.php?t=44601&p=396107#p424943
--set values
DECLARE #jumpsAway INT =10
DECLARE #MiddleSystemName VARCHAR(50) = 'Aldrat'
DECLARE #Level INT =1
IF OBJECT_ID('tempdb..#map') IS NOT NULL
DROP TABLE #map
CREATE TABLE #map
(fromSolarSystemID INT, toSolarSystemID INT, Level INT)
INSERT INTO #map
SELECT -1, mSS.solarSystemID, 0 FROM mapSolarSystems mSS
WHERE mSS.solarSystemName= #MiddleSystemName
WHILE #Level <= #jumpsAway
BEGIN
INSERT INTO #map
SELECT mSSJ.fromSolarSystemID, mSSJ.toSolarSystemID, #Level FROM mapSolarSystemJumps mSSJ
WHERE mSSJ.fromSolarSystemID IN (SELECT toSolarSystemID FROM #map WHERE Level = #Level-1)
AND mSSJ.fromSolarSystemID NOT IN (SELECT fromSolarSystemID FROM #map)
SET #Level=#Level+1
END
SELECT m.*, mSS.solarSystemName, mSS.security FROM #map m
JOIN mapSolarSystems mSS ON m.toSolarSystemID=mSS.solarSystemID
--WHERE mSS.security<0.45 --uncomment to check all nearby lowsec system
I know that I could probably just use the MSSQL version of the dump, but I also want to be learning more about how to use PostgreSQL better.
I understand what it's doing and everything, but I just don't understand PL/pgSQL well enough to make it work.
My attempt is
CREATE FUNCTION near(VARCHAR, INTEGER) RETURNS TABLE(fromID INT,toID INT,jumps INT,name VARCHAR,security VARCHAR) AS $$
DECLARE --Declaration from here http://www.postgresql.org/docs/9.1/static/plpgsql-declarations.html
MiddleSystemName ALIAS FOR $1;
jumpsAway ALIAS FOR $2;
jumps INTEGER :=1;
BEGIN
--http://stackoverflow.com/questions/11979154/select-into-to-create-a-table-in-pl-pgsql
CREATE TEMP TABLE map AS
SELECT -1, mSS.solarSystemID, 0
FROM mapSolarSystems mSS
WHERE mSS.solarSystemName= MiddleSystemName;
LOOP
--http://www.postgresql.org/docs/9.1/static/plpgsql-statements.html#PLPGSQL-STATEMENTS-EXECUTING-DYN
--If you don't do it with execute, you can only do one row, I guess?
EXECUTE 'SELECT
|| mSSJ.fromSolarSystemID,
|| mSSJ.toSolarSystemID,
|| $1
|| FROM
|| mapSolarSystemJumps mSSJ
|| WHERE
|| mSSJ.fromSolarSystemID EXISTS (SELECT toSolarSystemID FROM map WHERE jumps = $1 - 1)
|| AND mSSJ.fromSolarSystemID NOT EXISTS (SELECT fromSolarSystemID FROM map)'
INTO map
USING jumps;
jumps := jumps + 1
EXIT WHEN jumps > jumpsAway;
END LOOP;
RETURN QUERY SELECT m.*,mSS.solarSystemName, mSS.security FROM JOIN mapSolarSystems mSS ON m.toSolarSystemID = mSS.solarSystemID;
END;
$$ LANGUAGE plpgsql;
And the error that produces is
Error is
ERROR: "map" is not a known variable
LINE 27: INTO map
^
Thanks for all the help.
PL/pgSQL
This should be a valid translation to plpgsql:
CREATE OR REPLACE FUNCTION f_near(_middlesystemname text, _jumpsaway int)
RETURNS TABLE(fromid int, toid int, jumps int, name text, security text) AS
$func$
DECLARE
_jumps integer;
BEGIN
CREATE TEMP TABLE map AS
SELECT -1 AS "fromSolarSystemID"
,m."solarSystemID" AS "toSolarSystemID"
,0 AS level
FROM "mapSolarSystems" m
WHERE "solarSystemName" = _middlesystemname;
-- potentially add indexes on the temp table and ANALYZE if it gets big
FOR _jumps IN 1 .. _jumpsaway LOOP
INSERT INTO map ("fromSolarSystemID", "toSolarSystemID", level)
SELECT sj."fromSolarSystemID", sj."toSolarSystemID", _jumps AS level
FROM "mapSolarSystemJumps" sj
JOIN map m ON m."toSolarSystemID" = sj."fromSolarSystemID"
AND m."level" = _jumps - 1
LEFT JOIN map mx ON mx."fromSolarSystemID" = sj."fromSolarSystemID"
WHERE mx."fromSolarSystemID" IS NULL;
END LOOP;
RETURN QUERY
SELECT m.*, s."solarSystemName", s."security"
FROM map m
JOIN "mapSolarSystems" s ON m."toSolarSystemID" = s."solarSystemID";
END
$func$ LANGUAGE plpgsql;
RECURSIVE CTE - doesn't seem to work
This short SQL query with a recursive CTE should have done it:
WITH RECURSIVE map AS (
SELECT -1 AS fromsolarsystemid, m.solarsystemid, 0 AS level
FROM mapsolarsystems m
WHERE m.solarsystemname = from_id
UNION ALL
SELECT sj.fromsolarsystemid, sj.tosolarsystemid, level + 1
FROM mapsolarsystemjumps sj
JOIN map m USING (level)
LEFT JOIN map mx USING (fromsolarsystemid)
WHERE sj.fromsolarsystemid = m.tosolarsystemid
AND mx.fromsolarsystemid IS NULL
AND m.level < 10 -- jumpsAway
)
SELECT m.*, s.solarsystemname, s.security
FROM map m
JOIN mapsolarsystems s ON m.tosolarsystemid = s.solarsystemid
-- WHERE s.security < 0.45 -- uncomment to check all nearby lowsec system
However:
ERROR: recursive reference to query "map" must not appear within an outer join
LINE 9: LEFT JOIN map mx USING (fromsolarsystemid)