Escaping formula for Grails derived properties - postgresql

Grails offers derived properties to generate a field from a SQL expression using the formula mapping parameter:
static mapping = {
myfield formula: "field1 + field2"
}
I'm trying to use the formula parameter with a PostgreSQL database to make a concatenated field. The syntax is a little strange since PostgreSQL 8.4 doesn't yet support concat_ws:
static mapping = {
myfield formula: "array_to_string(array[field1, field2],' ')"
}
The produced SQL shown with loggingSql = true in the DataSource config has the table prefix inserted into some strange places:
select table0_.field1 as field1_19_0_,
table0_.field2 as field2_19_0_,=
array_to_string(table0_.array[field1, table0_.field2], ' ') as formula0_0_
from test_table table0_ where table0_.id=?
The table prefix errantly appears before array but not before field1 in the derived formula. Is there a way to escape the prefix or correct this behavior more explicitly?

This is just an issue with parsing the formula syntax. GORM tries to insert the table prefix for unquoted expressions not followed by parens, so the ARRAY[] notation trips it up.
My solution was to define the concat_ws function:
CREATE OR REPLACE FUNCTION concat_ws(separator text, variadic str text[])
RETURNS text as $$
SELECT array_to_string($2, $1);
$$ LANGUAGE sql;
The GORM formula parameter can now avoid the ARRAY[] syntax, and works as expected.
myfield formula: "concat_ws(' ', field1, field2)"

I had a very similar problem and solved it by adding single-quotes around the things that GORM was trying to prefix:
static mapping =
{
dayOfYear formula: " EXTRACT('DOY' FROM observed) "
}
GORM then produced this, which worked:
select
EXTRACT('DOY' FROM observed) as y1_
This may not work in all cases, but I hope it helps somebody.

Related

PostgreSQL execute SELECT * FROM (t as a); return ERROR: syntax error at or near ")"

Why these SQLs can't work in PostgreSQL?
SELECT * FROM (t as a);
or
SELECT * FROM (t);
ERROR: syntax error at or near ")"
Those SQLs work well in MySQL.
Well, it's invalid SQL.
If you want to give the table an alias you need to use from t as a.
The form from (....) requires a valid query inside the parentheses and t as a (or just t) is not a valid query. Additionally: a derived table (which is what (...) defines) requires an alias. So at least it has to be from (...) as bla
To get all rows and all columns from a table the SQL standard provides the shortcut table some_table which is supported by Postgres.
So the following would be valid:
select * from (table t) as a
Typically, table alias' are applied where there is a join. For example:
SELECT alias1.login_name, alias2.name
FROM tablename1 AS alias1
JOIN tablename2 AS alias2
ON alias1.id = alias2.role_id;
To apply an alias to a single table:
SELECT * FROM tablename AS alias;
..will do it for you, no need for parentheses.
You can confirm/test by example below if value is an integer:
SELECT * FROM tablename AS alias
WHERE alias.colum = value;
..or if value is a string:
SELECT * FROM tablename AS alias
WHERE alias.colum = 'value';
postgreSQL is strongly-typed, so the first example above will work for Int and Bool values, however if you have Date or other values, you may need to apply type casting. See this link for helpful info*: www.postgresqltutorial.com/postgresql-tutorial/postgresql-cast.
**Remember: For psql strings, always use single quotes for values, use double quotes for table names and column names.

How to select a column to appear with two single quote in the field

Here is my postgresql query
select 'insert into employee(ID_NUMBER,NAME,OFFICE) values ('''||ID_NUMBER||''','''||NAME||''','''||replace(DESIGNATION,'&','and')||''','''||replace(DEPT_NAME,'&','and')||''')' as col
from icare_employee_view
where id_number='201403241'
order by name;
output
insert into employee(ID_NUMBER,NAME,OFFICE) values ('201403241','ABINUMAN, JOSEPHINE CALLO','Assistant AGrS Principal for Curriculum and Instruction','AGrS Principal's Office')
but I need 'AGrS Principal's Office' to be 'AGrS Principal''s Office'
but I need 'AGrS Principal's Office' to be 'AGrS Principal''s Office'
any suggestions or sol'n is highly appreciated on how to fix my PostgreSQL query
Hi check this from pgDocs:
quote_literal ( text ) → text
Returns the given string suitably quoted to be used as a string
literal in an SQL statement string. Embedded single-quotes and
backslashes are properly doubled. Note that quote_literal returns null
on null input; if the argument might be null, quote_nullable is often
more suitable. See also Example 43.1.
quote_literal(E'O'Reilly') → 'O''Reilly'

How to properly parameterize my postgresql query

I'm trying to parameterize my postgresql query in order to prevent SQL injection in my ruby on rails application. The SQL query will sum a different value in my table depending on the input.
Here is a simplified version of my function:
def self.calculate_value(value)
calculated_value = ""
if value == "quantity"
calculated_value = "COALESCE(sum(amount), 0)"
elsif value == "retail"
calculated_value = "COALESCE(sum(amount * price), 0)"
elsif value == "wholesale"
calculated_value = "COALESCE(sum(amount * cost), 0)"
end
query = <<-SQL
select CAST(? AS DOUBLE PRECISION) as ? from table1
SQL
return Table1.find_by_sql([query, calculated_value, value])
end
If I call calculate_value("retail"), it will execute the query like this:
select location, CAST('COALESCE(sum(amount * price), 0)' AS DOUBLE PRECISION) as 'retail' from table1 group by location
This results in an error. I want it to execute without the quotes like this:
select location, CAST(COALESCE(sum(amount * price), 0) AS DOUBLE PRECISION) as retail from table1 group by location
I understand that the addition of quotations is what prevents the sql injection but how would I prevent it in this case? What is the best way to handle this scenario?
NOTE: This is a simplified version of the queries I'll be writing and I'll want to use find_by_sql.
Prepared statement can not change query structure: table or column names, order by clause, function names and so on. Only literals can be changed this way.
Where is SQL injection? You are not going to put a user-defined value in the query text. Instead, you check the given value against the allowed list and use only your own written parts of SQL. In this case, there is no danger of SQL injection.
I also want to link to this article. It is safe to create a query text dynamically if you control all parts of that query. And it's much better for RDBMS than some smart logic in query.

Trying to manipulate string such as if '26169;#c785643', then the result should be like 'c785643'

I am trying to manipulate string data in a column such as if the given string is '20591;#e123456;#17507;#c567890;#15518;#e135791' or '26169;#c785643', then the
result should be like 'e123456;c567890;e135791' or 'c785643'. The number of digits in between can be of any length.
Some of the things I have tried so far are:
select replace('20591;#e123456;#17507;#c567890;#15518;#e135791','#','');
This leaves me with '20591;e123456;17507;c567890;15518;e135791', which still includes the digits without 'e' or 'c' prefixed to them. i want to get rid of 20591, 17507 and 15518.
Create function that will keep a pattern of '%[#][ec][0-9][;]%' and will get rid of the rest.
The most important advise is: Do not store any data in a delimited string. This is violating the most basic principle of relational database concepts (1.NF).
The second hint is SO-related: Please always add / tag your questions with the appropriate tool. The tag [tsql] points to SQL-Server, but this might be wrong (which would invalidate both answers). Please tag the full product with its version (e.g. [sql-server-2012]). Especially with string splitting there are very important product related changes from version to version.
Now to your question.
Working with (almost) any version of SQL-Server
My suggestion uses a trick with XML:
(credits to Alan Burstein for the mockup)
DECLARE #table TABLE (someid INT IDENTITY, somestring VARCHAR(50));
INSERT #table VALUES ('20591;#e123456;#17507;#c567890;#15518;#e135791'),('26169;#c785643')
--the query
SELECT t.someid,t.somestring,A.CastedToXml
,STUFF(REPLACE(A.CastedToXml.query('/x[contains(text()[1],"#") and empty(substring(text()[1],2,100) cast as xs:int?)]')
.value('.','nvarchar(max)'),'#',';'),1,1,'') TheNewList
FROM #table t
CROSS APPLY(SELECT CAST('<x>' + REPLACE(t.somestring,';','</x><x>') + '</x>' AS XML)) A(CastedToXml);
The idea in short:
By replacing the ; with XML tags </x><x> we can transform your delimited list to XML. I included the intermediate XML into the result set. Just click it to see how this works.
In the next query I use a XQuery predicate first to find entries, which contain a # and second, which do NOT cast to an integer without the #.
The thrid step is specific to XML again. The XPath . in .value() will return all content as one string.
Finally we have to replace the # with ; and cut away the leading ; using STUFF().
UPDATE The same idea, but a bit shorter:
You can try this as well
SELECT t.someid,t.somestring,A.CastedToXml
,REPLACE(A.CastedToXml.query('data(/x[empty(. cast as xs:int?)])')
.value('.','nvarchar(max)'),' ',';') TheNewList
FROM #table t
CROSS APPLY(SELECT CAST('<x>' + REPLACE(t.somestring,';#','</x><x>') + '</x>' AS XML)) A(CastedToXml);
Here I use ;# to split your string and data() to implicitly concatenate your values (blank-separated).
UPDATE 2 for v2017
If you have v2017+ I'd suggest a combination of a JSON splitter and STRING_AGG():
SELECT t.someid,STRING_AGG(A.[value],';') AS TheNewList
FROM #table t
CROSS APPLY OPENJSON(CONCAT('["',REPLACE(t.somestring,';#','","'),'"]')) A
WHERE TRY_CAST(A.[value] AS INT) IS NULL
GROUP BY t.someid;
You did not include the version of SQL Server you are on. If you are using 2016+ you can use SPLIT_STRING, otherwise a good T-SQL splitter will do.
Against a single variable:
DECLARE #somestring VARCHAR(1000) = '20591;#e123456;#17507;#c567890;#15518;#e135791';
SELECT NewString = STUFF((
SELECT ','+split.item
FROM STRING_SPLIT(#somestring,';') AS s
CROSS APPLY (VALUES(REPLACE(s.[value],'#',''))) AS split(item)
WHERE split.item LIKE '[a-z][0-9]%'
FOR XML PATH('')),1,1,'');
Against a table:
NewString
----------------------
e123456,c567890,e135791
-- Against a table
DECLARE #table TABLE (someid INT IDENTITY, somestring VARCHAR(50));
INSERT #table VALUES ('20591;#e123456;#17507;#c567890;#15518;#e135791'),('26169;#c785643')
SELECT t.*, fn.NewString
FROM #table AS t
CROSS APPLY
(
SELECT NewString = STUFF((
SELECT ','+split.item
FROM STRING_SPLIT(t.somestring,';') AS s
CROSS APPLY (VALUES(REPLACE(s.[value],'#',''))) AS split(item)
WHERE split.item LIKE '[a-z][0-9]%'
FOR XML PATH('')),1,1,'')
) AS fn;
Returns:
someid somestring NewString
----------- -------------------------------------------------- -----------------------------
1 20591;#e123456;#17507;#c567890;#15518;#e135791 e123456,c567890,e135791
2 26169;#c785643 c785643

Postgresql if null in field,the whole sql is null

I use this sql to execute sql:
v_sql4 :='
INSERT INTO public.rebatesys(head,contract_no,history_no,f_sin,line_no,s_line_no,departmentcd,catagorycd,jan,seriescd,f_exclude, f_del,ins_date,ins_time,ins_user_id,ins_func_id,ins_ope_id,upd_date,upd_time,upd_user_id,upd_func_id,upd_ope_id)
VALUES (0, '''||v_contract_no||''', '||v_history_no||',1, '||v_line_no||', '||v_down_s_line_no||', '||coalesce(v_deptCD,null)||', '||0||', '''||v_singleJan||''','''||0||''','||v_fExclude||',
0, current_date, current_time, '||v_ins_user_id||', 0, 0,
current_date,current_time,'||v_upd_user_id||',0, 0);';
RAISE NOTICE 'v_sql4 IS : %', v_sql4;
EXECUTE v_sql4;
But when field "v_deptCD" is null,the whole sql is null,even I use coalesce,I still can't do id, the out put is :
NOTICE: v_sql4 IS : <NULL>
How to fix it?
When v_deptCD is null, you want to replace it by the string 'null', not the keyword.
', '||coalesce(v_deptCD,'null')||', '
You can use this
case when v_deptCD notnull then v_deptCD else null end
or use this for string concatination inside sql
concat(field1, ', ', field2)
Alternative approach to JGH solution is to use function format(your_string, list, of, values), it can ignore NULL values, but has the option to display them as NULL if you use %L in your format string. It will however single quote numbers if you use that format specifier, requiring casting in some cases.
Format arguments according to a format string. This function is similar to the C function sprintf. See Section 9.4.1.
But in my opinion best solution is to use USING clause and pass values in there. It looks kinda like prepared statement, protects you from SQL Injection, but does not cache plans like prepared statements. There are simple examples on how to do this in documentation for executing dynamic commands.
EXECUTE 'SELECT count(*) FROM mytable WHERE inserted_by = $1 AND inserted <= $2'
INTO c
USING checked_user, checked_date;