How to use function in subquery - sql

I have one table named MemberCheque where the fields are:
MemberName, Amount
I want to to show the name and the respective amount in numbers and as well as in words after separating the integer amount from the decimal. So my query is like:
SELECT MemName, Amount, (SELECT (Amount)%1*100 AS lefAmn, dbo.fnNumberToWords(lefAmn)+
'Hundred ', (Amount) - (Amount)%1 AS righAmnt, dbo.fnNumberToWords (righAmnt)+' Cents'
from MemberCheque) AS AmountInWords FROM MemberCheque
but my store procedure can take only integer value to change into words. So, I am doing separating the Amount into two parts before and after decimal but when I am trying to run this query it gives me error that lefAmn and righAmnt is not recognised. Because I am trying to send the parameter from the same query.

The first problem is that you have a subquery that is returning more than one value, and that is not allowed for a subquery in the select clause.
That answer to your specific question is to use cast() (or convert()) to make the numbers integers:
select leftAmt, rightAmt,
(dbo.fnNumberToWords(cast(leftAmt as int))+'Hundred ' +
dbo.fnNumberToWords(cast(rightAmt as int))+' Cents'
) as AmountInWords
from (SELECT (Amount%1)*100 AS leftAmt,
(Amount) - (Amount)%1 AS rightAmt
from MemberCheque
) mc

If you can't alter your function, then CAST the left/right values as INT:
CAST((Amount)%1*100 AS INT) AS lefAmn
CAST((Amount) - (Amount)%1 AS INT) AS righAmnt
You can't pass the alias created in the same statement as your function parameter, you need:
dbo.fnNumberToWords (CAST((Amount)%1*100 AS INT))
dbo.fnNumberToWords (CAST((Amount) - (Amount)%1 AS INT))

Related

SQL Query to compare the first X characters of 2 fields in a table

Say I have a table named 'Parts'. I am looking to create a SQL query that compares the first X characters of two of the fields, let's call them 'PartNum1' and 'PartNum2'. For example, I would like to return all records from 'Parts' where the first 6 characters of 'PartNum1' equals the first 6 characters of 'PartNum2'.
Parts
PartNum1
PartNum2
12345678
12345600
12388888
12345000
12000000
14500000
the query would only return row 1 since the first 6 characters match. MS SQL Server 2017 in case that makes a difference.
If they are strings, use left():
left(partnum1, 6) = left(partnum2, 6)
This would be appropriate in a where, on, or case expression. Note that using left() would generally prevent the use of indexes. If this is for a join and you care about performance, you might want to include a computed column with the first six characters.
you can try something like this. I am assuming datatype as integer. You can set size of varchar based on length of fields.
select *
from Parts
WHERE SUBSTRING(CAST(PartNum1 AS VARCHAR(max)), 1,6) = SUBSTRING(CAST(PartNum2 AS VARCHAR(max)), 1,6)
You can go for simple division to see if the numerator matches for those partnumbers.
DECLARE #table table(partnum int, partnum2 int)
insert into #table values
(12345678, 12345600)
,(12388888, 12345000)
,(12000000, 14500000);
select * from #table where partnum/100 = partnum2/100
partnum
partnum2
12345678
12345600

SQL Query to select a value between two known strings

I need a SQL query to get the value between two known strings in a text column.
The column name is d_info and the table name is Details.
The text is an XML fragment, but stored as a text value.
What I need is to get the value between the bookends <nettoeinkommen> and </nettoeinkommen> which is 718 in this example.
I also need the output to be saved in new column named income with data type float(8).
land>DE</land></wohnanschrift><taetigkeit>rentner</taetigkeit><dkbkundenstatus><bestandskunde>false</bestandskunde></dkbkundenstatus><haushaltsangaben><einnahmen><einkommen><nettoeinkommen>718</nettoeinkommen></einkommen><kindergeld>0</kindergeld><vermietungverpachtungnetto>0</vermietungverpachtungnetto><elterngeld>0</elterngeld><rentenunbefristet>0</rentenunbefristet><unselbststaendigetaetigkeit>740</unselbststaendigetaetigkeit><geringfuegigebeschaeftigung>0</geringfuegigebeschaeftigung></einnahmen><ausgaben><warmmiete>550</warmmiete><ratenimmobilienfinanzierung>0</ratenimmobilienfinanzierung>
I tried this code:
SELECT cast(SUBSTRING(d_info, CHARINDEX('<nettoeinkommen>', d_info)
, CHARINDEX('</nettoeinkommen>', d_info) - CHARINDEX('<nettoeinkommen>', d_info)) as float(8)) as income
from dbo.Details
But it's returning an Error converting data type varchar to real.
When I remove the cast function, the script works but it returns <nettoeinkommen>718 instead of only 718.
Thanks.
It is starting at the start of the tag not the end of it.
SELECT cast(
SUBSTRING(
d_info,
CHARINDEX('<nettoeinkommen>', d_info) + len('<nettoeinkommen>'),
CHARINDEX('</nettoeinkommen>', d_info) - (CHARINDEX('<nettoeinkommen>', d_info) + len('<nettoeinkommen>'))
) as float(8)) as income
from dbo.Details
you might even have these defined in variables:
SELECT cast(
SUBSTRING(
d_info,
CHARINDEX(#startTag, d_info) + len(#startTag),
CHARINDEX(#endTag, d_info) - (CHARINDEX(#startTag,d_info)+ len(#startTag))
) as float(8)) as income
from dbo.Details
I think the code is much easier to understand with the variables.
You need to add the length of your opening tag from the start index and subtract from the length of your substring statement:
SUBSTRING(d_info, CHARINDEX('<nettoeinkommen>', d_info)+16,
CHARINDEX('</nettoeinkommen>', d_info) - CHARINDEX('<nettoeinkommen>', d_info)-16)
As it seems, you are querieing plain xml data, for such purpose sql-server provides xquery functionality:
SELECT CAST(r.d_info AS XML).value('(/haushaltsangaben/einnahmen/einkommen/nettoeinkommen)[1]', 'decimal(19,2)')
FROM
(
SELECT '<taetigkeit>rentner</taetigkeit>
<dkbkundenstatus>
<bestandskunde>false</bestandskunde>
</dkbkundenstatus>
<haushaltsangaben>
<einnahmen>
<einkommen>
<nettoeinkommen>718</nettoeinkommen>
</einkommen>
</einnahmen>
</haushaltsangaben>' AS d_info
) AS r
If you intend to query more info from your source, you will end up with a bunch of stacked substring, patindex functions or even your own defined functions. This should be more readable and mantainable.
Using XQuery: https://learn.microsoft.com/en-us/sql/t-sql/xml/query-method-xml-data-type
As for your initial issue The SUBSTRING function in SQL returns the subset from a string starting from a given index for a specific length. For example SELECT SUBSTRING('whatever',5,4) returns 'ever'.
In case of CHARINDEX it gives the index for the first found match of a given pattern within a string. Example SELECT CHARINDEX('ever','whatever') should return 5, as 'ever' starts at the fifth position in 'whatever').
Now in your case you need to add the length of '<nettoeinkommen>' to the starting charindex and substract the length of '</nettoeinkommen>' from the length of the substring:
Also consider using decimal or numeric type instead of float, if you need to precise calculations: https://technet.microsoft.com/en-us/library/ms187912(v=sql.105).aspx

SQL Server: How to select rows which contain value comprising of only one digit

I am trying to write a SQL query that only returns rows where a specific column (let's say 'amount' column) contains numbers comprising of only one digit, e.g. only '1's (1111111...) or only '2's (2222222...), etc.
In addition, 'amount' column contains numbers with decimal points as well and these kind of values should also be returned, e.g. 1111.11, 2222.22, etc
If you want to make the query generic that you don't have to specify each possible digit you could change the where to the following:
WHERE LEN(REPLACE(REPLACE(amount,LEFT(amount,1),''),'.','') = 0
This will always use the first digit as comparison for the rest of the string
If you are using SQL Server, then you can try this script:
SELECT *
FROM (
SELECT CAST(amount AS VARCHAR(30)) AS amount
FROM TableName
)t
WHERE LEN(REPLACE(REPLACE(amount,'1',''),'.','') = 0 OR
LEN(REPLACE(REPLACE(amount,'2',''),'.','') = 0
I tried like this in place of 1111111 replace with column name:
Select replace(Str(1111111, 12, 2),0,left(11111,1))

How to remove currency symbol from price column?

I have a price column which is string and has price for the product from all over the world , now When I try to perform any operation like sum I am getting error.
So my question is how can I remove currency symbol from price column for all the countries?
Here is my sample input:-
locale price
cs_CZ 2462475,38 K
da_DK kr 591.872,50
de_AT 267,70
de_CH CHF 1'998.99
de_DE 1.798,09
en_AE AED7,236.20
en_AU $1,699.00
en_BD Tk999,999.00
en_HK HK$6,188.00
en_HU Ft344,524,655.48
tr_TR 2.344.697,66 TL
Postgres offloads most locale handling to the operating system. So the Postgres currency conversion routines will only work for you if the OS understands the locale names, and your price strings match its expected format.
For example, Windows won't accept da_DK as a locale, and even if it did, it will not accept the string kr 591.872,50, as it expects the Danish currency symbol to be kr. instead of kr.
That said, I think this should work reasonably well on a Linux-based server:
CREATE FUNCTION convert_currency(amount TEXT, locale TEXT) RETURNS NUMERIC AS
$$
BEGIN
PERFORM set_config('lc_monetary', locale || '.UTF-8', True);
RETURN amount::MONEY::NUMERIC;
END
$$
LANGUAGE plpgsql
SET lc_monetary TO DEFAULT;
You seem to have both decimal point and decimal comma but always two decimals (hopefully in the rest of the data too).
You can start by putting those values in a value list for testing (adding extra single quotes where needed).
Then you have to trim out spaces and letters with regular expressions. In the inner SELECT you get the substring with single quotes and commas for thousand separators still in it.
In the outer SELECT you replace the decimal commas in the decimal side and strip out thousand separators on the integer side. The result is cast to type numeric with which you can count sums etc.
SELECT (
regexp_replace(left(substring, length(substring) -3),'[.,'']','','g')
|| replace(right(substring, 3),',','.'))::numeric,
*
FROM (
SELECT substring(column1 from '(([0-9]+[,.''])*[0-9]+[.,][0-9]{2})[^0-9]*$'),
column1
FROM (
VALUES ('2462475,38 K'),
('kr 591.872,50'),
('267,70'),
('CHF 1''998.99'),
('1.798,09'),
('AED7,236.20'),
('$1,699.00'),
('Tk999,999.00'),
('HK$6,188.00'),
('Ft344,524,655.48'),
('2.344.697,66 TL')
) currencies
) sq1;
The following is the whole answer compatible with 9.0 version of PostgreSQL (no left() or right() functions used). Also values list is replaced with a SELECT query that you can replace with your own table and column. Finally it's all been enclosed in a SELECT query that demonstrates the use of the sum-function.
SELECT sum(numeric) FROM (
SELECT (
regexp_replace(substr(substring, 0, length(substring) -3),'[.,'']','','g')
|| replace(substr(substring, length(substring) -3, length(substring)),',','.'))::numeric,
*
FROM (
SELECT substring(column1 from '(([0-9]+[,.''])*[0-9]+[.,][0-9]{2})[^0-9]*$'),
column1
FROM (
SELECT column1 FROM your_table
) currencies
) sq1
) sq2

PostgreSQL: IN A SINGLE SQL SYNTAX order by numeric value computed from a text column

A column has a string values like "1/200", "3.5" or "6". How can I convert this String to numeric value in single SQL query?
My actual SQL is more complicated, here is a simple example:
SELECT number_value_in_string FROM table
number_value_in_string's format will be one of:
##
#.##
#/###
I need to sort by the numeric value of this column. But of course postgres doesn't agree with me that 1/200 is a proper number.
Seeing your name I cannot but post a simplification of your answer:
SELECT id, number_value_in_string FROM table
ORDER BY CASE WHEN substr(number_value_in_string,1,2) = '1/'
THEN 1/substr(number_value_in_string,3)::numeric
ELSE number_value_in_string::numeric END, id;
Ignoring possible divide by zero.
I would define a stored function to convert the string to a numeric value, more or less like this:
CREATE OR REPLACE FUNCTION fraction_to_number(s CHARACTER VARYING)
RETURN DOUBLE PRECISION AS
BEGIN
RETURN
CASE WHEN s LIKE '%/%' THEN
CAST(split_part(s, '/', 1) AS double_precision)
/ CAST(split_part(s, '/', 2) AS double_precision)
ELSE
CAST(s AS DOUBLE PRECISION)
END CASE
END
Then you can ORDER BY fraction_to_number(weird_column)
If possible, I would revisit the data design. Is it all this complexity really necessary?
This postgres SQL does the trick:
select (parts[1] :: decimal) / (parts[2] :: decimal) as quotient
FROM (select regexp_split_to_array(number_value_in_string, '/') as parts from table) x
Here's a test of this code:
select (parts[1] :: decimal) / (parts[2] :: decimal) as quotient
FROM (select regexp_split_to_array('1/200', '/') as parts) x
Output:
0.005
Note that you would need to wrap this in a case statement to protect against divide-by-zero errors and/or array out of bounds issues etc if the column did not contain a forward slash
Note also that you could do it without the inner select, but you would have to use regexp_split_to_array twice (once for each part) and you would probably incur a performance hit. Nevertheless, it may be easier to code in-line and just accept the small performance loss.
I managed to solve my problem. Thanks all.
It goes something like this, in a single SQL. (I'm using POSTGRESQL)
It will sort a string coming in as either "#", "#.#" or "1/#"
SELECT id, number_value_in_string FROM table ORDER BY CASE WHEN position('1/' in number_value_in_string) = 1
THEN 1/substring(number_value_in_string from (position('1/' in number_value_in_string) + 2) )::numeric
ELSE number_value_in_string::numeric
END ASC, id
Hope this will help someone outhere in the future.