Cannot find ID , but really exists in Postgres - sql

I am trying to pull data from a database to create a progress board fro some this query keeps returning for output."Data", even for values of projectId that actually exist in DB, project_id is an id that is inputted into the function.
-- FUNCTION: "ProjectProgress"."GetStepStatusbyProject"("Common"."InputDBMessage")
-- DROP FUNCTION "ProjectProgress"."GetStepStatusbyProject"("Common"."InputDBMessage");
CREATE OR REPLACE FUNCTION "ProjectProgress"."GetStepStatusbyProject"(
userinput "Common"."InputDBMessage")
RETURNS "Common"."OutputDBMessage"
LANGUAGE "plpgsql"
COST 100
VOLATILE
AS $BODY$
-- History:
-- 04/24/2020 Nick: Created Function
DECLARE
status_info jsonb;
input_params jsonb;
project_id bigint;
project_exists bool = true;
output "Common"."OutputDBMessage";
BEGIN
input_params := userinput."Parameters";
project_id := input_params ->> 'Id';
PERFORM "Log"."LogBeginRoutine"(userinput."UserName", userinput."ApplicationName", 'StepStatuses', 'GetStepStatusbyProject', userinput."ClientId", userinput."SessionId");
SELECT INTO project_exists (EXISTS(SELECT * FROM "LIMS"."Projects" WHERE "Id" = project_id));
IF NOT project_exists THEN
output."ErrorCode" = 1;
output."UserMessage" = 'project_id id not found';
ELSE
SELECT
array_to_json(array_agg(( ss.*, st."Color",st."Description",st."Icon"))) INTO status_info
FROM "LIMS"."Projects" as pr INNER JOIN "ProjectProgress"."Steps" as s
ON s."ProjectId" = pr."Id"
INNER JOIN (
SELECT
stepstatus.*
FROM
"ProjectProgress"."StepStatuses" AS stepstatus INNER JOIN
(
SELECT
MAX("Id") m_id, "StepId"
FROM
"ProjectProgress"."StepStatuses"
GROUP BY "StepId"
) AS mss ON mss.m_id = stepstatus."Id"
) as ss
ON ss."StepId" = s."Id"
INNER JOIN "ProjectProgress"."StatusTemplates" as st
ON ss."StatusTemplateId" = st."Id"
WHERE
st."Description" != 'Not Started' AND pr."Id" = project_id;
output."Data" := status_info;
output."ErrorCode" := 0;
output."UserMessage" := 'status successfully retrieved.';
PERFORM "Log"."LogEndSuccessRoutine"(userinput."UserName", userinput."ApplicationName", 'StepStatuses', 'GetStepStatusbyProject', userinput."ClientId", userinput."SessionId");
return output;
END
$BODY$;

Related

Generate a WITH clause/UNIONs from a SELECT

I want to generate a WITH clause/UNIONs — for the purpose of easily sharing small samples of data (10-20 rows).
I want to do that without creating tables or inserting rows.
Example:
Take a table or query like this:
...and generate this:
with cte as(
select 10 as asset_id, 1 as vertex_num, 118.56 as x, 3.8 as y from dual
union all
select 10 as asset_id, 2 as vertex_num, 118.62 as x, 1.03 as y from dual
union all
select 10 as asset_id, 3 as vertex_num, 121.93 as x, 1.03 as y from dual)
--There are lots more rows. But it's too much work to write them all out.
select * from cte
Using SQL, how can I automatically generate a WITH clause/UNIONs from the resultset?
I believe there is OOTB export functionality in Toad that can do that. But I don't think there are any tools in SQL Developer that can do it, which is what I'm using.
When attempting this with SQL, I think the main challenge is to loop through n columns. I'm not sure how to do that.
It would be easier to use xmltable or (json_table for Oracle 12+) for such purposes.
Example with xmltable:
Just aggregate all the required data into xmltype:
you can use xmltype(cursor(select...from...)):
select xmltype(cursor(select * from test)) xml from dual;
or dbms_xmlgen.getxmltype(query_string):
select dbms_xmlgen.getxmltype('select * from test') xml from dual;
then you can use the returned XML with
xmltable('/ROWSET/ROW' passing xmltype(your_xml) columns ...)
Example:
select *
from xmltable(
'/ROWSET/ROW'
passing xmltype(q'[<?xml version="1.0"?>
<ROWSET>
<ROW>
<ASSET_ID>10</ASSET_ID>
<VERTEX_NUM>1</VERTEX_NUM>
<X>118.56</X>
<Y>3.8</Y>
</ROW>
<ROW>
<ASSET_ID>10</ASSET_ID>
<VERTEX_NUM>2</VERTEX_NUM>
<X>118.62</X>
<Y>1.03</Y>
</ROW>
</ROWSET>
]')
columns
asset_id,vertex_num,x,y
) test
Full example on DBFiddle: https://dbfiddle.uk/?rdbms=oracle_11.2&fiddle=036b718f2b18df898c3e3de722c97378
You could use dbms_sql to execute a query against your real table, interrogate the data types, and use that information to generate the CTE and its inner queries.
As a first stab:
create or replace procedure print_cte (p_statement varchar2) as
-- dbms_sql variables
l_c pls_integer;
l_col_cnt pls_integer;
l_rows pls_integer;
l_desc_t dbms_sql.desc_tab;
l_first_row boolean := true;
l_varchar2 varchar2(4000);
l_number number;
l_date date;
-- etc.
begin
-- ideally add some checks for p_statement being a sinple query
l_c := dbms_sql.open_cursor;
dbms_sql.parse(c => l_c, statement => p_statement, language_flag => dbms_sql.native);
l_rows := dbms_sql.execute(c => l_c);
dbms_sql.describe_columns(c => l_c, col_cnt => l_col_cnt, desc_t => l_desc_t);
-- define columns, and output CTE columns at the same time
dbms_output.put('with cte (');
for i in 1..l_col_cnt loop
case l_desc_t(i).col_type
when 1 then
dbms_sql.define_column(c => l_c, position=> i, column => l_varchar2, column_size => 4000);
when 2 then
dbms_sql.define_column(c => l_c, position=> i, column => l_number);
when 12 then
dbms_sql.define_column(c => l_c, position=> i, column => l_date);
-- etc. plus else to skip or throw error for anything not handled
end case;
if i > 1 then
dbms_output.put(', ');
end if;
dbms_output.put('"' || l_desc_t(i).col_name || '"');
end loop;
dbms_output.put(') as (');
while dbms_sql.fetch_rows(c => l_c) > 0 loop
if (l_first_row) then
l_first_row := false;
else
dbms_output.put(' union all');
end if;
dbms_output.new_line;
for i in 1..l_col_cnt loop
if i = 1 then
dbms_output.put(' select ');
else
dbms_output.put(', ');
end if;
case l_desc_t(i).col_type
when 1 then
dbms_sql.column_value(c => l_c, position => i, value => l_varchar2);
dbms_output.put(q'[']' || l_varchar2 || q'[']');
when 2 then
dbms_sql.column_value(c => l_c, position => i, value => l_number);
dbms_output.put(l_number);
when 12 then
dbms_sql.column_value(c => l_c, position => i, value => l_date);
dbms_output.put(q'[to_date(']'
|| to_char(l_date, 'SYYYY-MM-DD-HH24:MI:SS')
|| q'[', 'SYYYY-MM-DD HH24:MI:SS')]');
-- etc. plus else to skip or throw error for anything not handled
end case;
end loop;
dbms_output.put(' from dual');
dbms_output.new_line;
end loop;
dbms_output.put_line(')');
dbms_output.put_line('select * from cte;');
dbms_sql.close_cursor(c => l_c);
end print_cte;
/
and then you can do:
begin
print_cte('select * from your_table');
end;
/
which produces:
with cte ("ASSET_ID", "VERTEX_NUM", "X", "Y") as (
select 10, 1, 118.56, 3.8 from dual
union all
select 10, 2, 118.62, 1.03 from dual
union all
select 10, 3, 121.93, 1.03 from dual
)
select * from cte;
Your client has to be configured to handle dbms_output, of course.
As noted in the inline comments you should check the passed-in statement isn't going to do something nasty; and you need to add handling for other data types. This is just a starting point.
db<>fiddle
I know my answer is not pure SQL or PLSQL.
But I suggest you to use Javascript to generate CTE query because data is small.
Javascript is very easy and maintainable than SQL or PLSQL in your case.
You can use this small script in here whenever you want. (No additional editor needed)
https://jsfiddle.net/pLvgr8oh/
Or you can run script with Chrome browser if you worried about security.
https://developer.chrome.com/docs/devtools/javascript/snippets/
function convertToSelect(tsv, firstRowIsColumn, columnsComma, typesComma) {
function getCol(column, value, type) {
// In case type is 'date', after colon is date format
const [ typeNew, dateFormat ] = type.split(':')
switch (typeNew) {
case 'string': return `'${value}' as ${column}`
case 'number': return `${value} as ${column}`
case 'date': return `to_date('${value}', '${dateFormat}') as ${column}`
}
}
const columns = columnsComma ? columnsComma.split(',') : []
const types = typesComma ? typesComma.split(',') : []
// Split row by '\r\n' or '\n'
const list = tsv.split(/\r*\n/)
const colCount = list[0].split(/\t/).length
let columnsNew = []
let typesNew = types
// If first row is column name
if (firstRowIsColumn) {
columnsNew = list[0].split(/\t/);
list.shift(0)
}
// If column name is specified then override column names in first row
if (columns.length > 0) {
columnsNew = columns
}
// If type is not specified set all type to 'string'
if (typesNew.length === 0) {
typesNew = [...Array(colCount)].map(t => 'string')
}
const rows = list.map(item => {
// [ '2 as F_INT', '2.223 as F_LONG'... ]
const cols = item
.split(/\t/)
.map((value, index) => {
return getCol(columnsNew[index], value, typesNew[index])
})
.join(', ')
// select 2 as F_INT, 2.223 as F_LONG... from dual
return ` select ${cols} from dual`
})
const selectUnion = rows.join(`
union all
`)
return `with cte as
(
${selectUnion}
)
select * from cte;
`
}
const tsv = `F_INT F_LONG F_VARCHAR F_DATE
1 1.123 a 2022-12-01
2 2.223 b 2022-12-02
3 3.323 c 2022-12-03`
const firstRowIsColumn = true
const columnsComma = 'v_num,v_lng,v_str,v_date'
//const columnsComma = ''
const typesComma = 'number,number,string,date:YYYY-MM-DD'
//const typesComma = ''
const ret = convertToSelect(tsv, firstRowIsColumn, columnsComma, typesComma)
console.log(ret)
which generates (if the snippet doesn't mangle the tab characters):
with cte as
(
select 1 as v_num, 1.123 as v_lng, 'a' as v_str, to_date('2022-12-01', 'YYYY-MM-DD') as v_date from dual
union all
select 2 as v_num, 2.223 as v_lng, 'b' as v_str, to_date('2022-12-02', 'YYYY-MM-DD') as v_date from dual
union all
select 3 as v_num, 3.323 as v_lng, 'c' as v_str, to_date('2022-12-03', 'YYYY-MM-DD') as v_date from dual
)
select * from cte;

How to merge two jsonb objects in Postgres?

I have function where I select common fields for task, I have task_type and I need to select different fields by task_type and then return Json. I don't know SQL and Postgres tricks. Please give me hint! thanks
This is my Idea:
CREATE OR REPLACE FUNCTION crm.task_select(_task_uid bigint)
RETURNS jsonb
LANGUAGE plpgsql
AS $function$
DECLARE
_data jsonb;
_task jsonb;
begin
SELECT
task.task_uid AS "id",
task.action_date,
task.descrtiption,
sub.subject_data,
dtask.task_type,
dtask.name AS "task_name",
dtask.label AS "task_label",
lbadmin.user_name_reverse(crea) AS "created_by",
lbadmin.user_name_reverse(upda) AS "updated_by",
task.created,
task.updated,
case when dtask.task_type == 'email'
_task = select crm.selectEmail(task_uid);
end;
FROM crm.tasks "task"
LEFT JOIN abook2.subjects "sub" ON (task.subject_uid = sub.subject_uid)
LEFT JOIN crm.def_tasks "dtask" ON (task.def_task_uid = dtask.def_task_uid)
LEFT JOIN lbadmin.users_user "crea" ON (task.insert_user_uid = crea.role_uid)
LEFT JOIN lbadmin.users_user "upda" ON (task.update_user_uid = upda.role_uid)
LEFT JOIN crm.emails "email" ON (task.email_uid = email.email_uid)
WHERE
(task.task_uid = _task_uid)
AND (task.org_uid IS NULL)
AND (task.card_uid IS NULL) into _data;
_data = _task + _data;
return _data;
END;
$function$
Solution that helped me to get array of json objects:
select to_json(array_agg(row_to_json(t)))
from (select typ,prop from bgb where id=b.id) t )```

PL/SQL Numeric or value error: character to number conversion error

Procedure Code:
CREATE OR REPLACE PROCEDURE GetOrder(
p_Order IN NUMBER,
p_OrderDate OUT DATE,
p_CusNbr OUT NUMBER,
p_OrderOut OUT NUMBER,
p_Name OUT VARCHAR2,
p_Address OUT VARCHAR2,
p_City OUT VARCHAR2,
p_State OUT CHAR,
p_Zip OUT NUMBER,
p_Itm OUT NUMBER,
p_Desc OUT VARCHAR2,
p_Qty OUT NUMBER,
p_Price OUT NUMBER)
AS
v_OrderDate ord.ord_date%type;
v_CusNbr ord.Cus_nbr%type;
v_Order ord.Ord_nbr%type;
v_Name cus.cus_nme%type;
v_Address cus.str_adr%type;
v_City zip.city%type;
v_state zip.st%type;
v_Zip zip.zip%type;
v_Itm itm.itm_nbr%type;
v_Desc itm.ITM_DSC%TYPE;
v_Qty ord_itm.ord_Qty%type;
v_Price itm.ord_itm_price%type;
BEGIN
SELECT O.ORD_NBR, C.CUS_NBR, O.ORD_DATE, C.CUS_NME, C.STR_ADR, Z.CITY, Z.ST, I.ITM_NBR, I.ITM_DSC, OI.ORD_QTY, I.ORD_ITM_PRICE
INTO
v_Order, v_CusNbr, v_OrderDate, v_Name, v_Address, v_Zip, v_state, v_Itm, v_Desc, v_Qty, v_Price
FROM ITM I JOIN ORD_ITM OI ON (I.ITM_NBR = OI.ITM_NBR)
JOIN ORD O ON (OI.ORD_NBR = O.ORD_NBR)
JOIN CUS C ON (O.CUS_NBR = C.CUS_NBR)
JOIN ZIP Z ON (C.ZIP_ADR = Z.ZIP)
WHERE O.ORD_NBR = p_Order;
DBMS_OUTPUT.PUT_LINE('ILLINOIS COMPUTERS');
DBMS_OUTPUT.PUT_LINE('CUSTOMER ORDER FORM');
DBMS_OUTPUT.PUT_LINE(v_Order || v_OrderDate || v_CusNbr);
DBMS_OUTPUT.PUT_LINE('Name: ' || v_Name);
DBMS_OUTPUT.PUT_LINE('Address: ' || v_Address);
DBMS_OUTPUT.PUT_LINE('City: ' || v_City || ' State : ' || v_state || ' Zip: ' || v_Zip);
DBMS_OUTPUT.PUT_LINE('Item No.' || 'Description' || 'Quantity' || 'Price');
DBMS_OUTPUT.PUT_LINE(v_Itm || v_Desc || v_Qty || v_Price);
END GetOrder;
/
And when I call the procedure:
DECLARE
p_OrderNbr NUMBER := &ordernumber;
p_OrderDateOutput DATE;
p_CusNbrOutput NUMBER;
p_OrderNbrOutput NUMBER;
p_NameOutput cus.cus_nme%type;
p_AddressOutput cus.str_adr%type;
p_CityOutput zip.city%type;
p_StateOutput CHAR;
p_ZipOutput NUMBER;
p_ItmOutput NUMBER;
p_DescOutput itm.itm_dsc%type;
p_QtyOutput NUMBER;
p_PriceOutput NUMBER;
begin
GetOrder(p_OrderNbr,
p_OrderDateOutput,
p_CusNbrOutput,
p_OrderNbrOutput,
p_NameOutput,
p_AddressOutput,
p_CityOutput,
p_StateOutput,
p_ZipOutput,
p_ItmOutput,
p_DescOutput,
p_QtyOutput,
p_PriceOutput);
END;
/
I'm getting
ORA-06502 error: numeric or value error: character to number
conversion error.
I believe all my parameters are in order so I'm not getting conflicting variable assignments.
I ran this query seperately to make sure I'm getting the right output and it works fine.
SELECT O.ORD_NBR, C.CUS_NBR, O.ORD_DATE, C.CUS_NME, C.STR_ADR, Z.CITY, Z.ST, I.ITM_NBR, I.ITM_DSC, OI.ORD_QTY, I.ORD_ITM_PRICE
--INTO
--v_Order, v_CusNbr, v_OrderDate, v_Name, v_Address, v_Zip, v_state, v_Itm, v_Desc, v_Qty, v_Price
FROM ITM I JOIN ORD_ITM OI ON (I.ITM_NBR = OI.ITM_NBR)
JOIN ORD O ON (OI.ORD_NBR = O.ORD_NBR)
JOIN CUS C ON (O.CUS_NBR = C.CUS_NBR)
JOIN ZIP Z ON (C.ZIP_ADR = Z.ZIP)
WHERE O.ORD_NBR = 100;
Select list says
... C.STR_ADR, Z.CITY, Z.ST, ...
Into list says
... v_Address, v_Zip, v_state ...
Seems you got the wrong field in Z table.
Unless Z.CITY is the numeric zip code?

how to convert the stored procedure to Linq

this is my Stored Procedure how to write in Linq
please help me
ALTER PROCEDURE sample
(#LID var char(20)=null
)
As
SET NO COUNT ON;
begin
declare #err1 int
begin transaction
BEGIN
Select * from sample_table where Aid is not null and Sid is not null and #LID IS NULL OR Lid = #LID
order by SrNo1, SrNo2, SrNo3
END
you need this:
var query = dbContext.DraftLOB
.Where(u => u.AssemblyId.HasValue &&
u.SessionId.HasValue &&
#LOBID == null ? true : u.LOBId == #LOBID)
.OrderBy(u => u.SrNo1)
.ThenBy(u => u.SrNo2)
.ThenBy(u => u.SrNo3)
.ToList();

Flatten association table to multi-value column?

I have a table with just product ID's and category ID's (products can be in more than one category). How can I flatten the category ID's into a product column so I end us with this:
id | name | desc | categories
1 | test1 | lorem | 1,3,4,23
2 | test2 | ipsom | 4,6,24
It is like I need to loop into a separate table for the categories column. How can I do this or is there a better way?
I created an CLR aggregate function that takes a varchar column and returns all its values separated by commas. In other words, it joins several strings into a comma-separated list. I am sure its performance is way better than any T-Sql trick.
As any aggregate function, it can be used in combination with group by. For example:
SELECT id, name, desc, JoinStrings(CONVERT(VARCHAR(20), category_id))
FROM product p
INNER JOIN category_products c ON p.category_id = c.category_id
GROUP BY id, name, desc
Here's the C# code to create the CLR assembly into Sql Server 2008:
using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
[Serializable]
[Microsoft.SqlServer.Server.SqlUserDefinedAggregate(Format.UserDefined, IsInvariantToDuplicates=false, IsInvariantToOrder=false, IsInvariantToNulls=true, MaxByteSize=-1)]
public struct JoinStrings : IBinarySerialize
{
private char[] sb;
private int pos;
public void Init()
{
sb = new char[512000];
pos = 0;
}
public void Accumulate(SqlString Value)
{
if (Value.IsNull) return;
char[] src = Value.ToString().ToCharArray();
Array.Copy(src, 0, sb, pos, src.Length);
pos += src.Length;
sb[pos] = ',';
pos++;
}
public void Merge(JoinStrings Group)
{
Accumulate(Group.Terminate());
}
public SqlString Terminate()
{
if (pos <= 0)
return new SqlString();
else
return new SqlString(new String(sb, 0, pos-1));
}
public void Read(System.IO.BinaryReader r)
{
this.Init();
pos = r.ReadInt32();
r.Read(sb, 0, pos);
}
public void Write(System.IO.BinaryWriter w)
{
w.Write(pos);
w.Write(sb, 0, pos);
}
}
Here's the code to create the function (although deploying from Visual Studio should do it automatically):
CREATE AGGREGATE [dbo].[JoinStrings]
(#s [nvarchar](4000))
RETURNS[nvarchar](max)
EXTERNAL NAME [YouAssemblyName].[JoinStrings]
There's no in-built way to do it in MSSQL.
Simulating group_concat MySQL function in Microsoft SQL Server 2005? has a good description of how to go about implementing a workaround.
I would suggest using a Recursive CTE. I believe that it would be something like this:
select productid, categoryid,
row_number() over (partition by id order by categoryid) as rownum
into #tabletorecurse
from TABLENAME
with finaloutput as
(
select productid as id, name, desc, categoryid as categories, rownum
from #tabletorecurse
join PRODUCTTABLE
on PRODUCTTABLE.id = #tabletorecurse.productid
where rownum = 1
union all
select tr.id, tr.name, tr.desc,
finaloutput.categories + ', ' + tr.categoryid, tr.rownum
from #tabletorecurse as tr
join finaloutput
on finaloutput.rownum + 1 = tr.rownum
and finaloutput.id = tr.productid
)
select id, name, desc, categories
from finaloutput
join
(
select max(rownum) as maxrow, id
from finaloutput
group by id
) as maxvalues
on maxvalues.id = finaloutput.id
and maxvalues.maxrow = finaloutput.rownum
Use a function.
This does a lookup to text so you will need to adapt.
The COALESCE is just to put a ,.
This is from a large scale production application - it works and it fast.
Function was questioned by JustinPony as function is slow
I am hitting some tables of million of records but only returning 100 rows.
The function is only applied to the hundred rows.
usage:
select top 5 sID, ( select [dbo].[JoinMVEnum](docSVsys.sID, '140') ) as [Flag Issue]
from docSVsys
function
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION [dbo].[JoinMVText]
(
#sID int,
#fieldID tinyint
)
RETURNS VARCHAR(MAX)
AS
BEGIN
DECLARE #MVtextList varchar(max)
SELECT #MVtextList = COALESCE(#MVtextList + '; ', '') + docMVtext.value
FROM docMVtext with (nolock)
WHERE docMVtext.sID = #sID and fieldID = #fieldID
RETURN #MVtextList
END
GO