SQL RIGHT function not working as expected - tsql

I'm trying to extract the month number from a date as a left padded string with 0's.
So, for example, from '2018-01-31' I want the string '01'.
Currently I have this:
SELECT RIGHT('0' + CAST(MONTH('2018-01-31') AS CHAR(2)), 2)
Which is returning '1' but I would have expected it to return '01' because I've provided the second argument to RIGHT as 2.
Could someone explain why this isn't working as I think it should?

You need to change CHAR to VARCHAR:
SELECT RIGHT('0' + CAST(MONTH('2018-01-31') AS VARCHAR(2)), 2)
db<>fiddle demo
CHAR(2) is blank padded so you get RIGHT('01 ',2) which is '1 '.

You could use FORMAT instead, when you first cast the string to a DATE type.
SELECT FORMAT(CAST('2018-01-31' as DATE),'MM')
As for why that SQL with the right didn't work?
Try this SQL and notice the difference (the extra space):
SELECT quotename('0' + CAST(1 AS CHAR(2))), quotename('0' + CAST(1 AS VARCHAR(2)))

Related

TSQL query to extract a value between to char where a specific set of characters is there

I have a problem I can't seem to figure out. I am trying to extract capacity from a product description. It is always between two values, "," and "oz." however there could be other commas included in the description that are not part of what I'm trying to extract. Example value is , 15 oz., or , 2 oz.,
I'm trying to find values that have the oz in them and are between two commas and I have been completely unsuccessfully. I've tried many things, but here is the latest that I have tried today and I'm just getting an error.
SELECT SUBSTRING(
FullDescription,
CHARINDEX(',', FullDescription),
CHARINDEX('oz.',FullDescription)
- CHARINDEX(',', FullDescription)
+ Len('oz.')
)
from CatalogManagement.Product
Since the backwards pattern ,.zo is more recognisable, I'd go with the REVERSE function
Sample values:
"something, something more, 18oz., complete"
"shorter, 12oz., remainder"
"there is no capacity, in this, value"
"a bit more, 14oz, and some followups, maybe"
SELECT REVERSE(
SUBSTRING (
REVERSE(FullDescription),
CHARINDEX(',.zo', REVERSE(FullDescription)) + 1,
CHARINDEX(',', REVERSE(FullDescription), CHARINDEX(',.zo', REVERSE(FullDescription)) + 1) - CHARINDEX(',.zo', REVERSE(FullDescription)) - 1
)
)
FROM CatalogManagement.Product
WHERE FullDescription LIKE '%oz.,%'
You might use XML-splitting together with a XQuery predicate:
DECLARE #tbl TABLE(ID INT IDENTITY, YourString VARCHAR(MAX));
INSERT INTO #tbl VALUES('Here is one with an amount, 1 oz., some more text')
,('Here is one with no amount, some more text')
,('a, 10 oz.')
,('b, 20oz., no blank between oz and the number')
,('30oz., starts with the pattern, no leading comma');
SELECT t.*
,A.oz.value('.','nvarchar(max)') oz
FROM #tbl t
CROSS APPLY(SELECT CAST('<x>' + REPLACE((SELECT t.YourString AS [*] FOR XML PATH('')),',','</x><x>') + '</x>' AS XML)
.query('/x[contains(text()[1],"oz.")]')) A(oz);
The idea in short:
We use some string methods to replace commas with XML tags and to cast your string to XML. each fragment is placed within a decent <x> element.
We use a predicate to return just the fragments containing "oz.".
You can filter easily with
WHERE LEN(A.oz.value('.','nvarchar(max)'))>0

How to find/replace weird whitespace in string

I find in my sql database string whit weird whitespace which cannot be replace like REPLACE(string, ' ', '') RTRIM and cant it even find with string = '% %'. This space is even transfered to new table when using SELECT string INTO
If i select this string in managment studio and copy that is seems is normal space and when everything is works but cant do nothing directly from database. What else can i do? Its some kind of error or can i try some special character for this?
First, you must identify the character.
You can do that by using a tally table (or a cte) and the Unicode function:
The following script will return a table with two columns: one contains a char and the other it's unicode value:
DECLARE #Str nvarchar(100) = N'This is a string containing 1 number and some words.';
with Tally(n) as
(
SELECT TOP(LEN(#str)) ROW_NUMBER() OVER(ORDER BY ##SPID)
FROM sys.objects a
--CROSS JOIN sys.objects b -- (unremark if there are not enough rows in the tally cte)
)
SELECT SUBSTRING(#str, n, 1) As TheChar,
UNICODE(SUBSTRING(#str, n, 1)) As TheCode
FROM Tally
WHERE n <= LEN(#str)
You can also add a condition to the where clause to only include "special" chars:
AND SUBSTRING(#str, n, 1) NOT LIKE '[a-zA-Z0-9]'
Then you can replace it using it's unicode value using nchar (I've used 32 in this example since it's unicode "regular" space:
SELECT REPLACE(#str, NCHAR(32), '|')
Result:
This|is|a|string|containing|1|number|and|some|words.

concatenate arrays in aggregate query

i have a starting table where there are some meteo data stored every 15 minutes, one field stores leaf wet at 1 minute sampling in a numeric array form, thus i have a 15 values array each row.
Now i want to create a 1 hour aggregation of this table, crating an array of 60 values for this field.
I tried array_cat at first place, but says
array_cat(numeric[]) not existing
the function obviuously exists, so i tought the format was not the one expected, i tried first unnesting and then aggregating, not working again.
Finally i was able to aggregate trough string conversion, but it's not what i wanted (i might in the future apply some numeric elaboration oh that 60-values array)
I paste the query for further investigations
SELECT dati1_v.id_stazione,
to_char(dati1_v.data_ora, 'YYYY-MM-DD HH24:00:00'::text) AS date_hour,
round(avg(dati1_v.temp1_media), 2) AS t_avg,
round(avg(dati1_v.ur1_media), 2) AS hum_avg,
sum(dati1_v.pioggia) AS rain_tot,
max(dati1_v.pioggia) AS rain_max,
round((avg((SELECT avg(lw.lw) AS avg FROM unnest(dati1_v.lw_top_array) lw(lw))) - lws.top_min) /
(lws.top_max - lws.top_min) * 100::numeric, 2) AS lw_top_avg,
array_agg((SELECT round((avg(lw.lw) - lws.top_min) / (lws.top_max - lws.top_min) * 100::numeric, 2) AS round
FROM unnest(dati1_v.lw_top_array) lw(lw))) AS lw_top_array,
array_cat(dati1_v.lw_top_array) AS lw_top_array_tot,
-- array_agg((select lw_top_array from unnest(dati1_v.lw_top_array))) AS lw_top_array_tot,
-- array_agg(array_to_string(dati1_v.lw_top_array, ',')) AS lw_top_array_tot,
round((avg((SELECT avg(lw.lw) AS avg FROM unnest(dati1_v.lw_bottom_array) lw(lw))) - lws.bottom_min) /
(lws.bottom_max - lws.bottom_min) * 100::numeric, 2) AS lw_bottom_avg,
array_agg((SELECT round((avg(lw.lw) - lws.bottom_min) / (lws.bottom_max - lws.bottom_min) * 100::numeric,
2) AS round
FROM unnest(dati1_v.lw_bottom_array) lw(lw))) AS lw_bottom_array
FROM dati1_v,
lw_settings lws
WHERE lws.id = 1
GROUP BY dati1_v.id_stazione, to_char(dati1_v.data_ora, 'YYYY-MM-DD HH24:00:00'::text), lws.top_min, lws.top_max,
lws.bottom_min, lws.bottom_max
ORDER BY dati1_v.id_stazione, to_char(dati1_v.data_ora, 'YYYY-MM-DD HH24:00:00'::text)
in particular, my tries were related to this specific block:
array_cat(dati1_v.lw_top_array) AS lw_top_array_tot,
-- array_agg((select lw_top_array from unnest(dati1_v.lw_top_array))) AS lw_top_array_tot,
-- array_agg(array_to_string(dati1_v.lw_top_array, ',')) AS lw_top_array_tot
Thanks
For me in similar case helped UNNEST in subquery and ARRAY_AGG of unnnested
SELECT
ARRAY_AGG(
DISTINCT lw_top
) as lw_top_array
FROM (
SELECT
UNNEST(lw_top_array) AS lw_top
FROM
dati1_v
) as tmp;
for me helped next query
SELECT
my_table.key,
array_agg(_unnested.item) as array_coll
from my_table
left join LATERAL (SELECT unnest(my_table.array_coll) as item) _unnested ON TRUE
GROUP by my_table.key
In PostgreSQL, the Group_concat function is not available but you can get similar result as string_agg and array_to_string.
string_agg(array_to_string(file_ids, ','), ',') filter ( where file_ids notnull ) AS file_ids_str
array_to_string and array_to_string works in next way
array_to_string([1, 2, 456], ',') => '1,2,456'
string_agg(['a', 'ab'], ',') => 'a,ab'
the only problem is that result is string with ',' as separator

Dynamic pivot - how to obtain column titles parametrically?

I wish to write a Query for SAP B1 (t-sql) that will list all Income and Expenses Items by total and month by month.
I have successfully written a Query using PIVOT, but I do not want the column headings to be hardcoded like: Jan-11, Feb-11, Mar-11 ... Dec-11.
Rather I want the column headings to be parametrically generated, so that if I input:
--------------------------------------
Query - Selection Criteria
--------------------------------------
Posting Date greater or equal 01.09.10
Posting Date smaller or equal 31.08.11
[OK] [Cancel]
the Query will generate the following columns:
Sep-10, Oct-10, Nov-10, ..... Aug-11
I guess DYNAMIC PIVOT can do the trick.
So, I modified one SQL obtained from another forum to suit my purpose, but it does not work. The error message I get is Incorrect Syntax near 20100901.
Could anybody help me locate my error?
Note: In SAP B1, '[%1]' is an input variable
Here's my query:
/*Section 1*/
DECLARE #listCol VARCHAR(2000)
DECLARE #query VARCHAR(4000)
-------------------------------------
/*Section 2*/
SELECT #listCol =
STUFF(
( SELECT DISTINCT '],[' + CONVERT(VARCHAR, MONTH(T0.RefDate), 102)
FROM JDT1
FOR XML PATH(''))
, 1, 2, '') + ']'
------------------------------------
/*Section 3*/
SET #query = '
SELECT * FROM
(
SELECT
T0.Account,
T1.GroupMask,
T1.AcctName,
MONTH(T0.RefDate) as [Month],
(T0.Debit - T0.Credit) as [Amount]
FROM dbo.JDT1 T0
JOIN dbo.OACT T1 ON T0.Account = T1.AcctCode
WHERE
T1.GroupMask IN (4,5,6,7) AND
T0.[Refdate] >= '[%1]' AND
T0.[Refdate] <= '[%2]'
) S
PIVOT
(
Sum(Amount)
FOR [Month] IN ('+#listCol+')
) AS pvt
'
--------------------------------------------
/*Section 4*/
EXECUTE (#query)
I don't know SAP, but a couple of things spring to mind:
It looks like you want #listCol to contain a collection of numbers within square brackets, for example [07],[08],[09].... However, your code appears not to put a [ at the start of this string.
Try replacing the lines
T0.[Refdate] >= '[%1]' AND
T0.[Refdate] <= '[%2]'
with
T0.[Refdate] >= ''[%1]'' AND
T0.[Refdate] <= ''[%2]''
(I also added a space before the AND in the first of these two lines while I was editing your question.)

TSQL -- Inserting Dates Into Dynamic SQL

Consider the following TSQL:
SET #WhereClause1 = 'where a.Date > ' + #InvoiceDate
I get a date/string conversion error. #InvoiceDate is a datetime variable. What is the right syntax?
This might work.
SET #WhereClause1 = 'where a.Date > ''' + convert(varchar, #InvoiceDate) + ''''
although an error will be raised if the value is null.
This will work:
SET #WhereClause1 = 'where a.Date > ''' + cast(#InvoiceDate as varchar(100)) + ''''
Since your composing query as a string first, then I think you need to convert #InvoiceDate to a string with something like this. http://www.databasejournal.com/features/mssql/article.php/10894_2197931_1/Working-with-SQL-Server-DateTime-Variables-Part-Two---Displaying-Dates-and-Times-in-Different-Formats.htm
... and you will probably need to enclose date strings in quotes.
It would probably actually be better to construct the date string in the calling routine because you should be checking there for null values and maybe other validations.
EXEC sp_executesql N'SELECT * FROM Orders WHERE a.Date > #date',
N'#date datetime',
#date = #InvoiceDate