Postgres: cast, money and NULL values - postgresql

In my select I am using this in order to convert an integer to a money form.
CAST(mytable.discount AS money) AS Discount
But I cannot figure out how to avoid the 'NULL' output if the join fails (for good cause) to bring the optional value.
I've done this to avoid NULLS in the past:
COALESCE(mytable.voucher,'----') AS Voucher
But I cannot figure out how to combined CAST and COALESCE for the same field. I just want my discount NULL fields to be '----'

That's tricky, as the "mytable.discount AS money" converts NULLS to $0
It's actually not what happens, but an implicit cast which happens after that.
An expression must have a particular type. In this case it's money. So you see $0.00 as a result of my proposed expression because it's ---- that is converted to money, not NULL.
As a solution you may explicitly convert the inner expression to text like:
SELECT COALESCE(CAST('1' as money)::text, '--');
or
SELECT COALESCE(CAST(null as money)::text, '--');
SQLFiddle demo: http://sqlfiddle.com/#!12/d41d8/2866

Related

How to specify on error behavior for postgresql conversion to UUID

I need to write a query to join 2 tables based on UUID field.
Table 1 contains user_uuid of type uuid.
Table 2 has this user_uuid in the end of url, after the last slash.
The problem is that sometimes this url contains other value, not castable to uuid.
My workaround like this works pretty good.
LEFT JOIN table2 on table1.user_uuid::text = regexp_replace(table2.url, '.*[/](.*)$', '\1')
However i have a feeling that better solution would be to try to cast to uuid before joining.
And here i have a problem. Such query:
LEFT JOIN table2 on table1.user_uuid = cast (regexp_replace(table2.url, '.*[/](.*)$', '\1') as uuid)
gives ERROR: invalid input syntax for type uuid: "rfa-hl-21-014.html" SQL state: 22P02
Is there any elegant way to specify the behavior on cast error? I mean without tons of regexp checks and case-when-then-end...
Appreciate any help and ideas.
There are additional considerations when converting a uuid to text. Postgres will yield a converted value in standard form (lower case and hyphened). However there are other formats for the same uuid value that could occur in you input. For example upper case and not hyphened. As text these would not compare equal but as uuid they would. See demo here.
select *
from table1 t1
join table2 t2
on replace(t_uuid::text, '-','') = replace(lower(t2.t_stg),'-','') ;
Since your data clearly contains non-uuid values, you cannot assume standard uuid format either. There are also additional formats (although not apparently often used) for a valid UUID. You may want to review UUID Type documentation
You could cast the uuid from table 1 to text and join that with the suffix from table 2. That will never give you a type conversion error.
This might require an extra index on the expression in the join condition if you need fast nested loop joins.

How to concate the Currency symbol for negative integers?

I am trying to show the currency symbol with the numbers. I am using the CONCAT method to do this.
select concat('$', "amount") from payments;
This method working good when the amount is positive but when the amount is negative it is concat the currency symbol before minus.
eg:
$-243.44
What is the proper way to do this?
You can use select case
select case when amount < 0 then concat('-$', abs("amount")) else concat('$', "amount") end from payments;
I suggest that you take advantage of Postgres' in-built currency type, e.g.
SELECT '-243.44'::float8::numeric::money;
This printed -£243.44 on the demo tool I am using, which appears to be located in the UK. The actual currency symbol you see would depend on your Postgres locale settings.
If you really need to do this concatenation yourself, you could use REGEXP_REPLACE:
WITH cte AS (
SELECT '-123.456'::text AS val UNION ALL
SELECT '123.456'::text
)
SELECT
val,
REGEXP_REPLACE(val, '^(-?)', '\1$') AS val_out
FROM cte;

How to add a leading zero when the length of the column is unknown?

How can I add a leading zero to a varchar column in the table and I don't know the length of the column. If the column is not null, then I should add a leading zero.
Examples:
345 - output should be 0345
4567 - output should be 04567
I tried:
SELECT lpad(column1,WHAT TO SPECIFY HERE?, '0')
from table_name;
I will run an update query after I get this.
You may be overthinking this. Use plain concatenation:
SELECT '0' || column1 AS padded_col1 FROM table_name;
If the column is NULL, nothing happens: concatenating anything to NULL returns NULL.
In particular, don't use concat(). You would get '0' for NULL columns, which you do not want.
If you also have empty strings (''), you may need to do more, depending on what you want.
And since you mentioned your plan to updated the table: Consider not doing this, you are adding noise, that could be added for display with the simple expression. A VIEW might come in handy for this.
If all your varchar values are in fact valid numbers, use an appropriate numeric data type instead and format for display with the same expression as above. The concatenation automatically produces a text result.
If circumstances should force your hand and you need to update anyway, consider this:
UPDATE table_name
SET column1 = '0' || column1
WHERE column1 IS DISTINCT FROM '0' || column1;
The added WHERE clause to avoid empty updates. Compare:
How do I (or can I) SELECT DISTINCT on multiple columns?
try concat instead?..
SELECT concat(0::text,column1) from table_name;

PostgreSQL use function result in ORDER BY

Is there a way to use the results of a function call in the order by clause?
My current attempt (I've also tried some slight variations).
SELECT it.item_type_id, it.asset_tag, split_part(it.asset_tag, 'ASSET', 2)::INT as tag_num
FROM serials.item_types it
WHERE it.asset_tag LIKE 'ASSET%'
ORDER BY split_part(it.asset_tag, 'ASSET', 2)::INT;
While my general assumption is that this can't be done, I wanted to know if there was a way to accomplish this that I wasn't thinking of.
EDIT: The query above gives the following error [22P02] ERROR: invalid input syntax for integer: "******"
Your query is generally OK, the problem is that for some row the result of split_part(it.asset_tag, 'ASSET', 2) is the string ******. And that string cannot be cast to an integer.
You may want to remove the order by and the cast in the select list and add a where split_part(it.asset_tag, 'ASSET', 2) = '******', for instance, to narrow down that data issue.
Once that is resolved, having such a function in the order by list is perfectly fine. The quoted section of the documentation in the comments on the question is referring to applying an order by clause to the results of UNION, INTERSECTION, etc. queries. In other words, the order by found in this query:
(select column1 as result_column1 from table1
union
select column2 from table 2)
order by result_column1
can only refer to the accumulated result columns, not to expressions on individual rows.

error in convert datetime from a text field

I have a subquery which converts a text coloumn into datetime. Since it is in text format there are coloumns wich contains bad data. I know the first answer would be to correct the data, I strongly agree that. I do not have the privileges to do that, unfortunately i have to deal with it.
below is my query
INNER JOIN TABLE XYZ
ON XYZ.COLOUMN1=YZX.COLOUMN2
LEFT JOIN ( SELECT ABC.stu_id
ABC.stu_name
CONVERT(DATETIME,LMN.startDate,111) STARTDATE
CONVERT(DATETIME,LMN.endDate,111) ENDDATE
FROM STUDENT ABC
INNER JOIN AN_STUDENT_TABLE LMN
ON ABC.stu_id=LMN.stu_id
WHERE ISDATE(startDate)=1
AND ISDATE(endDate)=1
GROUP BY ABC.stu_id,ABC.stu_name,STARTDATE,ENDDATE) DIN ON DIN.stu_id=LMNOP.stu_id
WHERE e.date BETWEEN DIN.STARTDATE AND DIN.ENDDATE
when i compare e.date with the startdate and enddate it fails giving me an well know error
"The conversion of a varchar data type to a datetime data type resulted in an out-of-range value."
what can be done to atleast skip those bad data records which cannot be converted?
I tried my best to figure this out but failed. Any help/advice appretiated!
Your ISDATE in the where clause does not necessarily filter out the bad dates before they are used in the conversion.
I think you should do this in two steps. First create a temp table or table variable that holds the rows from STUDENT where startDate and endDate is correct (use ISDATE for this) and then use that table in your actual query.