SQL Summarizing data in final row - sql

I have a query that generates rows of detail information about checks that are issued. In the final output, the data is all concatenated and put into one column (long story short, it needs to be run from Infomaker and exported to text with no delimiting).
Anyway, at the end of these rows I need a summary row that contains two values that are sums of the detail used in the previous rows, along with other values that are hard-coded. This final row also needs to be concatenated to one column, and appear after all the detail rows.
Example of how the summary row should appear with the computed values in brackets:
00123456789999999999940[CHECK AMOUNT SUMMARY][TOTAL NUMBER OF CHECKS (ROWS)]000
Again, no spaces, tabs, or any other delimiters allowed.
I'm stumped on how to achieve this. I have had suggestions of using UNION but I'm not sure exactly how to make that work for this situation.
Current query:
declare #checkDate date = '08/30/13'
select
record = (
-- Checking account number (Record positions 1-9)
cast(cna.BANK_ACCT_NUM as varchar(9)) +
-- Check number (Record positions 10-19) -- must always be nine characters
(case
when LEN(cr.CHECK_NUM_NUMERIC) = 1
then '00000000'
when LEN(cr.CHECK_NUM_NUMERIC) = 2
then '0000000'
when LEN(cr.CHECK_NUM_NUMERIC) = 3
then '000000'
when LEN(cr.CHECK_NUM_NUMERIC) = 4
then '00000'
when LEN(cr.CHECK_NUM_NUMERIC) = 5
then '0000'
when LEN(cr.CHECK_NUM_NUMERIC) = 6
then '000'
when LEN(cr.CHECK_NUM_NUMERIC) = 7
then '00'
when LEN(cr.CHECK_NUM_NUMERIC) = 8
then '0'
else ''
end + cast(cr.CHECK_NUM_NUMERIC as varchar(9))) +
-- Record positions 20-21 - as determined by the bank
'20' +
-- Check amount (Record positions 22-31) -- must always be 10 characters
(case
when LEN(cr.CHECK_AMT) = 1
then '000000000'
when LEN(cr.CHECK_AMT) = 2
then '00000000'
when LEN(cr.CHECK_AMT) = 3
then '0000000'
when LEN(cr.CHECK_AMT) = 4
then '000000'
when LEN(cr.CHECK_AMT) = 5
then '00000'
when LEN(cr.CHECK_AMT) = 6
then '0000'
when LEN(cr.CHECK_AMT) = 7
then '000'
when LEN(cr.CHECK_AMT) = 8
then '00'
when LEN(cr.CHECK_AMT) = 9
then '0'
else ''
end + cast(REPLACE(cr.CHECK_AMT,'.','') as varchar(10))) +
-- Date issued (MMDDYY)(Record positions 32-37)
cast(REPLACE(convert(char(10),cr.CHECK_DTE,101), '/', '') as varchar(10)) +
-- Record positions 38-40 - as determined by the bank
'000' +
-- Payee information line 1 (Record positions 41-90)
cr.CHECK_NAME)
from chk_num_alpha_ctl cna,
chk_reconciliation cr
where ( cr.check_num_alpha = cna.check_num_alpha ) and
( ( cr.check_rtn_void_dte is null ) AND
( cr.check_dte = #checkDate ) ) AND
( cna.bank_acct_num = 'xxxx-xxxx' )
order by cr.check_dte ASC

-- First, you can simplify your query using this type of 'right-justify-zero-fill' statement (adjust if more or less than 9-characters):
select right('000000000' + cast(cr.CHECK_NUM_NUMERIC as varchar(9)),9)
-- Then try something like this (I'm not able to test it, so there may be some adjustments):
UNION
select '00123456789999999999940'
+ right('000000000' + cast(sum(cr.CHECK_AMT) as varchar(9)),9)
+ right('000000000' + cast(count(cr.CHECK_AMT) as varchar(9)),9)
+ '000'
from chk_num_alpha_ctl cna,
chk_reconciliation cr
where ( cr.check_num_alpha = cna.check_num_alpha ) and
( ( cr.check_rtn_void_dte is null ) AND
( cr.check_dte = #checkDate ) ) AND
( cna.bank_acct_num = 'xxxx-xxxx' )
GROUP BY cr.check_dte
order by cr.check_dte ASC

Related

When I set a specific date in the where close it works but when I change the year it doesn't work. Why?

When I filter to 1/1/2020 in the where clause the query works. When I change the year to 2019 I get this error:
Conversion failed when converting date and/or time from character
string
Why does it work for 1/1/2020 and not 1/1/2019? The matteropendate column is a date column -- I checked.
WITH aa (MatterNumber, claim_cat_NJS, ClaimCategoriesMulti) AS
(
SELECT
MatterNumber
,LEFT(ic.ClaimCategories, CHARINDEX(CHAR(10), ic.ClaimCategories + CHAR(10) ) - 1)
,STUFF(ic.ClaimCategories, 1, CHARINDEX( CHAR(10) , ic.ClaimCategories + CHAR(10) ), '')
FROM cases AS ic
UNION ALL
SELECT
aa.MatterNumber
,LEFT(aa.ClaimCategoriesMulti,CHARINDEX(CHAR(10),aa.ClaimCategoriesMulti+CHAR(10)
,STUFF(aa.ClaimCategoriesMulti, 1, CHARINDEX(CHAR(10), aa.ClaimCategoriesMulti + CHAR(10)), '')
FROM aa
WHERE 1 = 1
AND aa.ClaimCategoriesMulti > ''
)
Select *
FROM cases AS c
LEFT JOIN aa ON c.matterNumber = aa.matterNumber
WHERE 1 = 1
AND c.MatterOpenDate > '1/1/2019' /*this line of code results in error message whenever changed to a different year*/
You need to use the format
WHERE DateField >= '2020-01-01'
Bear in mind the time component if its a DateTime

Order string alpha numerically A1-1-1, A1-2-1, A1-10-1, A1-2-2, A1-2-3 etc

I have a column with different length strings which has dashes (-) that separates alphanumeric strings.
The string could look like "A1-2-3".
I need to order by first "A1" then "2" then "3"
I want to achieve the following order for the column:
A1
A1-1-1
A1-1-2
A1-1-3
A1-2-1
A1-2-2
A1-2-3
A1-7
A2-1-1
A2-1-2
A2-1-3
A2-2-1
A2-2-2
A2-2-3
A2-10-1
A2-10-2
A2-10-3
A10-1-1
A10-1-2
A10-1-3
A10-2-1
A10-2-2
A10-2-3
I can separate the string with the following code:
declare #string varchar(max) = 'A1-2-3'
declare #first varchar(max) = SUBSTRING(#string,1,charindex('-',#string)-1)
declare #second varchar(max) = substring(#string, charindex('-',#string) + 1, charindex('-',reverse(#string))-1)
declare #third varchar(max) = right(#string,charindex('-',reverse(#string))-1)
select #first, #second, #third
With the above logic I thought that I could use the following:
Note this only regards strings with 2 dashes
select barcode from tabelWithBarcodes
order by
case when len(barcode) - len(replace(barcode,'-','')) = 2 then
len(SUBSTRING(barcode,1,charindex('-',barcode)-1))
end
, case when len(barcode) - len(replace(barcode,'-','')) = 2 then
SUBSTRING(barcode,1,(charindex('-',barcode)-1))
end
, case when len(barcode) - len(replace(barcode,'-','')) = 2 then
len(substring(barcode, charindex('-',barcode) + 1, charindex('-',reverse(barcode))-1))
end
, case when len(barcode) - len(replace(barcode,'-','')) = 2 then
substring(barcode, charindex('-',barcode) + 1, charindex('-',reverse(barcode))-1)
end
, case when len(barcode) - len(replace(barcode,'-','')) = 2 then
len(right(barcode,charindex('-',reverse(barcode))-1))
end
, case when len(barcode) - len(replace(barcode,'-','')) = 2 then
right(barcode,charindex('-',reverse(barcode))-1)
end
But the sorting is not working for the second and third section of the string.
(I haven't added the code for checking if the string has only 1 or no dash in it for simplicity)
Not sure if I'm on the right path here.
Is anybody able to solve this?
This is not pretty, however...
USE Sandbox;
GO
WITH VTE AS(
SELECT V.SomeString
--Randomised order
FROM (VALUES ('A1-1-1'),
('A10-1-3'),
('A10-2-2'),
('A1-1-3'),
('A10-2-1'),
('A2-2-2'),
('A1-2-1'),
('A1-2-2'),
('A2-1-1'),
('A10-1-2'),
('B2-1-2'),
('A1'),
('A2-2-1'),
('A2-10-3'),
('A10-2-3'),
('A2-1-2'),
('B1-4'),
('A2-10-2'),
('A2-2-3'),
('A10-1-1'),
('A1-A1-3'),
('A1-7'),
('A2-10-1'),
('A2-1-3'),
('A1-1-2'),
('A1-2-3')) V(SomeString)),
Splits AS(
SELECT V.SomeString,
DS.Item,
DS.ItemNumber,
CONVERT(int,STUFF((SELECT '' + NG.token
FROM dbo.NGrams8k(DS.item,1) NG
WHERE TRY_CONVERT(int, NG.Token) IS NOT NULL
ORDER BY NG.position
FOR XML PATH('')),1,0,'')) AS NumericPortion
FROM VTE V
CROSS APPLY dbo.DelimitedSplit8K(V.SomeString,'-') DS),
Pivoted AS(
SELECT S.SomeString,
MIN(CASE V.P1 WHEN S.Itemnumber THEN REPLACE(S.Item, S.NumericPortion,'') END) AS P1Alpha,
MIN(CASE V.P1 WHEN S.Itemnumber THEN S.NumericPortion END) AS P1Numeric,
MIN(CASE V.P2 WHEN S.Itemnumber THEN REPLACE(S.Item, S.NumericPortion,'') END) AS P2Alpha,
MIN(CASE V.P2 WHEN S.Itemnumber THEN S.NumericPortion END) AS P2Numeric,
MIN(CASE V.P3 WHEN S.Itemnumber THEN REPLACE(S.Item, S.NumericPortion,'') END) AS P3Alpha,
MIN(CASE V.P3 WHEN S.Itemnumber THEN S.NumericPortion END) AS P3Numeric
FROM Splits S
CROSS APPLY (VALUES(1,2,3)) AS V(P1,P2,P3)
GROUP BY S.SomeString)
SELECT P.SomeString
FROM Pivoted P
ORDER BY P.P1Alpha,
P.P1Numeric,
P.P2Alpha,
P.P2Numeric,
P.P3Alpha,
P.P3Numeric;
This outputs:
A1
A1-1-1
A1-1-2
A1-1-3
A1-2-1
A1-2-2
A1-2-3
A1-7
A1-A1-3
A2-1-1
A2-1-2
A2-1-3
A2-2-1
A2-2-2
A2-2-3
A2-10-1
A2-10-2
A2-10-3
A10-1-1
A10-1-2
A10-1-3
A10-2-1
A10-2-2
A10-2-3
B1-4
B2-1-2
This makes use of 2 user defined functions. Firstly or DelimitedSplit8k_Lead (I used DelimitedSplit8k as I don't have the other on my sandbox at the moment). Then you also have NGrams8k.
I really should explain how this works, but yuck... (edit coming).
OK... (/sigh) What it does. Firstly, we split the data into its relevant parts using delimitedsplit8k(_lead). Then, within the SELECT we use FOR XML PATH to get (only) the nuemrical part of that string (For example, for 'A10' we get '10') and we convert it to a numerical value (an int).
Then we pivot that data out into respective parts. The alphanumerical part, and the numerical part. So, for the value 'A10-A1-12' we end up with the row:
'A', 10, 'A', 1, 12
Then, now that we've pivoted the data, we sort it by each column individually. And voila.
This will fall over if you have a value like 'A1A' or '1B1', and honestly, I'm not changing it to catter for that. This was messy, and really isn't what the RDBMS should be doing.
Up to 3 dashes can be covered by fiddling with replace & parsename & patindex:
declare #TabelWithBarcodes table (id int primary key identity(1,1), barcode varchar(20) not null, unique (barcode));
insert into #TabelWithBarcodes (barcode) values
('2-2-3'),('A2-2-2'),('A2-2-1'),('A2-10-3'),('A2-10-2'),('A2-10-1'),('A2-1-3'),('A2-1-2'),('A2-1-1'),
('A10-2-3'),('A10-2-2'),('A10-2-10'),('A10-1-3'),('AA10-A111-2'),('A10-1-1'),
('A1-7'),('A1-2-3'),('A1-2-12'),('A1-2-1'),('A1-1-3'),('B1-1-2'),('A1-1-1'),('A1'),('A10-10-1'),('A12-10-1'), ('AB1-2-E1') ;
with cte as
(
select barcode,
replace(BarCode, '-', '.')
+ replicate('.0', 3 - (len(BarCode)-len(replace(BarCode, '-', '')))) as x
from #TabelWithBarcodes
)
select *
, substring(parsename(x,4), 1, patindex('%[0-9]%',parsename(x,4))-1)
,cast(substring(parsename(x,4), patindex('%[0-9]%',parsename(x,4)), 10) as int)
,substring(parsename(x,3), 1, patindex('%[0-9]%',parsename(x,3))-1)
,cast(substring(parsename(x,3), patindex('%[0-9]%',parsename(x,3)), 10) as int)
,substring(parsename(x,2), 1, patindex('%[0-9]%',parsename(x,2))-1)
,cast(substring(parsename(x,2), patindex('%[0-9]%',parsename(x,2)), 10) as int)
,substring(parsename(x,1), 1, patindex('%[0-9]%',parsename(x,1))-1)
,cast(substring(parsename(x,1), patindex('%[0-9]%',parsename(x,1)), 10) as int)
from cte
order by
substring(parsename(x,4), 1, patindex('%[0-9]%',parsename(x,4))-1)
,cast(substring(parsename(x,4), patindex('%[0-9]%',parsename(x,4)), 10) as int)
,substring(parsename(x,3), 1, patindex('%[0-9]%',parsename(x,3))-1)
,cast(substring(parsename(x,3), patindex('%[0-9]%',parsename(x,3)), 10) as int)
,substring(parsename(x,2), 1, patindex('%[0-9]%',parsename(x,2))-1)
,cast(substring(parsename(x,2), patindex('%[0-9]%',parsename(x,2)), 10) as int)
,substring(parsename(x,1), 1, patindex('%[0-9]%',parsename(x,1))-1)
,cast(substring(parsename(x,1), patindex('%[0-9]%',parsename(x,1)), 10) as int)
extend each barcode to 4 groups by adding trailing .0 if missing
split each barcode in 4 groups
split each group in leading characters and trailing digits
sort by the leading character first
then by casting the digits as numeric
See db<>fiddle
An alterative approach would be to use your technique to split the string into its 3 component parts, then left pad those strings with leading zeros (or characters of your choice). That avoids any issues where the string may contain alphanumerics rather than just numerics. However, it does mean that strings containing different length alphabetic characters may not be sorted as you may expect... Here's the code to play with (using the definitions from #dnoeth's excellent answer):
;with cte as
(
select barcode
, case
when barcode like '%-%' then
substring(barcode,1,charindex('-',barcode)-1)
else
barcode
end part1
, case
when barcode like '%-%' then
substring(barcode, charindex('-',barcode) + 1, case
when barcode like '%-%-%' then
(charindex('-',barcode,charindex('-',barcode) + 1)) - 1
else
len(barcode)
end
- charindex('-',barcode))
else
''
end part2
, case
when barcode like '%-%-%' then
right(barcode,charindex('-',reverse(barcode))-1) --note: assumes you don't have %-%-%-%
else
''
end part3
from #TabelWithBarcodes
)
select barcode
, part1, part2, part3
, right('0000000000' + coalesce(part1,''), 10) lpad1
, right('0000000000' + coalesce(part2,''), 10) lpad2
, right('0000000000' + coalesce(part3,''), 10) lpad3
from cte
order by lpad1, lpad2, lpad3
DBFiddle Example

Updating database column with string built based on value of another column

I have a table with a column called Days. The Days column stores a comma delimited string representing days of the week. For example the value 1,2 would represent Sunday, Monday. Instead of storing this information as a comma delimited string, I want to convert it to JSON and store it in a column called Frequency in the same table. For example, a record with the Days value of 1,2 should be updated to store the following in it's Frequency column:
'{"weekly":"interval":1,"Sunday":true,"Monday":true,"Tuesday":false,"Wednesday":false,"Thursday":false,"Friday":false,"Saturday":false}}'
I found a way to do this using a case statement assuming that there is only one digit in the Days column like so:
UPDATE SCH_ITM
SET
FREQUENCY =
CASE
WHEN SCH_ITM.DAYS = 1 THEN '{"weekly":{"interval":1,"Sunday":true,"Monday":false,"Tuesday":false,"Wednesday":false,"Thursday":false,"Friday":false,"Saturday":false}}'
WHEN SCH_ITM.DAYS = 2 THEN '{"weekly":{"interval":1,"Sunday":false,"Monday":true,"Tuesday":false,"Wednesday":false,"Thursday":false,"Friday":false,"Saturday":false}}'
WHEN SCH_ITM.DAYS = 3 THEN '{"weekly":{"interval":1,"Sunday":false,"Monday":false,"Tuesday":true,"Wednesday":false,"Thursday":false,"Friday":false,"Saturday":false}}'
WHEN SCH_ITM.DAYS = 4 THEN '{"weekly":{"interval":1,"Sunday":false,"Monday":false,"Tuesday":false,"Wednesday":true,"Thursday":false,"Friday":false,"Saturday":false}}'
WHEN SCH_ITM.DAYS = 5 THEN '{"weekly":{"interval":1,"Sunday":false,"Monday":false,"Tuesday":false,"Wednesday":false,"Thursday":true,"Friday":false,"Saturday":false}}'
WHEN SCH_ITM.DAYS = 6 THEN '{"weekly":{"interval":1,"Sunday":false,"Monday":false,"Tuesday":false,"Wednesday":false,"Thursday":false,"Friday":true,"Saturday":false}}'
WHEN SCH_ITM.DAYS = 7 THEN '{"weekly":{"interval":1,"Sunday":false,"Monday":false,"Tuesday":false,"Wednesday":false,"Thursday":false,"Friday":false,"Saturday":true}}'
END
WHERE SCH_TYPE = 'W';
However I cannot seem to figure out an effecient way to handle converting a value such as 1,5 into the correct JSON representation. Obviously I could write out every possible permutation, but surely is a better way?
Okay this will give you what you have asked for
create table test (days varchar(20), frequency varchar(500))
insert into test(days) values('1'),('2'),('3'),('4'),('5'),('6'),('7'),('1,5')
update test set frequency = '{"weekly":{"interval":1,'
+ '"Sunday": ' + case when days like '%1%' then 'true' else 'false' end + ','
+ '"Monday": ' + case when days like '%2%' then 'true' else 'false' end + ','
+ '"Tuesday": ' + case when days like '%3%' then 'true' else 'false' end + ','
+ '"Wednesday": ' + case when days like '%4%' then 'true' else 'false' end + ','
+ '"Thursday": ' + case when days like '%5%' then 'true' else 'false' end + ','
+ '"Friday": ' + case when days like '%6%' then 'true' else 'false' end + ','
+ '"Saturday": ' + case when days like '%7%' then 'true' else 'false' end + '}}'
select * from test
Though of course e.g. Days = '1234' will produce the same as '1,2,3,4' - as will 'Bl4arg3le12' for that matter. If Days is a string, you can put '8' which is meaningless?
Really it sounds like you need an extra table or two:
If "MyTable" is the table with the Days column, add a Days table with the days of the week, then a MyTableDays table to link MyTable entries to days - for the 1,5 example, there would be two rows in MyTableDays
With the help of a parse function and an cross apply
;with cteDays As (Select ID,Name From (Values(1,'Sunday'),(2,'Monday'),(3,'Tuesday'),(4,'Wednesday'),(5,'Thursday'),(6,'Friday'),(7,'Saturday')) D(ID,Name))
Update YourTable Set Frequency = '{"weekly":"interval":1,'+String+'}}'
From YourTable A
Cross Apply (
Select String = Stuff((Select ','+String
From (
Select String='"'+Name+'":'+case when RetVal is null then 'false' else 'true' end
From [dbo].[udf-Str-Parse](A.Days,',') A
Right Join cteDays B on RetVal=ID) N
For XML Path ('')),1,1,'')
) B
Select * from YourTable
Updated Table
Days Frequency
1,2 {"weekly":"interval":1,"Sunday":true,"Monday":true,"Tuesday":false,"Wednesday":false,"Thursday":false,"Friday":false,"Saturday":false}}
1,2,3 {"weekly":"interval":1,"Sunday":true,"Monday":true,"Tuesday":true,"Wednesday":false,"Thursday":false,"Friday":false,"Saturday":false}}
The UDF if needed
CREATE FUNCTION [dbo].[udf-Str-Parse] (#String varchar(max),#Delimiter varchar(10))
Returns Table
As
Return (
Select RetSeq = Row_Number() over (Order By (Select null))
,RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>'+ Replace(#String,#Delimiter,'</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
);
--Select * from [dbo].[udf-Str-Parse]('Dog,Cat,House,Car',',')
--Select * from [dbo].[udf-Str-Parse]('John Cappelletti was here',' ')

How to create ordinal numbers (i.e. "1st" "2nd", etc.) in SQL Server

I recently responded to this question in the SSRS-2008 tag that required changing the day number in a date to the ordinal number (i.e. "1st", "2nd" instead of "1", "2"). The solution involved a VB.Net function. I'm curious how one would go about performing this task in SQL (t-sql and SQL Server in particular), or if there is some built in support.
So here is a scenario: say you have organized a footrace for 1000 runners and have the results in a table with the columns Name and Place (in normal numbers). You want to create a query that will display a user's name and their place in ordinal numbers.
Here's a scalable solution that should work for any number. I thought other's used % 100 for 11,12,13 but I was mistaken.
WITH CTE_Numbers
AS
(
SELECT 1 num
UNION ALL
SELECT num + 1
FROM CTE_Numbers
WHERE num < 1000
)
SELECT CAST(num AS VARCHAR(10))
+
CASE
WHEN num % 100 IN (11,12,13) THEN 'th' --first checks for exception
WHEN num % 10 = 1 THEN 'st'
WHEN num % 10 = 2 THEN 'nd'
WHEN num % 10 = 3 THEN 'rd'
ELSE 'th' --works for num % 10 IN (4,5,6,7,8,9,0)
END
FROM CTE_Numbers
OPTION (MAXRECURSION 0)
You can do that just as easily in SQL as in the app layer:
DECLARE #myDate DATETIME = '2015-05-21';
DECLARE #day INT;
SELECT #day = DAY(#myDate);
SELECT CASE WHEN #day IN ( 11, 12, 13 ) THEN CAST(#day AS VARCHAR(10)) + 'th'
WHEN #day % 10 = 1 THEN CAST(#day AS VARCHAR(10)) + 'st'
WHEN #day % 10 = 2 THEN CAST(#day AS VARCHAR(10)) + 'nd'
WHEN #day % 10 = 3 THEN CAST(#day AS VARCHAR(10)) + 'rd'
ELSE CAST(#day AS VARCHAR(10)) + 'th'
END
You could also put this in a scalar function if necessary.
EDIT
For your example, it would be:
SELECT Name ,
CASE WHEN Place IN ( 11, 12, 13 )
THEN CAST(Place AS VARCHAR(10)) + 'th'
WHEN Place % 10 = 1 THEN CAST(Place AS VARCHAR(10)) + 'st'
WHEN Place % 10 = 2 THEN CAST(Place AS VARCHAR(10)) + 'nd'
WHEN Place % 10 = 3 THEN CAST(Place AS VARCHAR(10)) + 'rd'
ELSE CAST(Place AS VARCHAR(10)) + 'th'
END AS Place
FROM FootRaceResults;
Be very afraid:
with
ArabicRomanConversions as (
select *
from ( values
( 0, '', '', '', '' ), ( 1, 'I', 'X', 'C', 'M' ), ( 2, 'II', 'XX', 'CC', 'MM' ), ( 3, 'III', 'XXX', 'CCC', 'MMM' ), ( 4, 'IV', 'XL', 'CD', '?' ),
( 5, 'V', 'L', 'D', '?' ), ( 6, 'VI', 'LX', 'DC', '?' ), ( 7, 'VII', 'LXX', 'DCC', '?' ), ( 8, 'VIII', 'LXXX', 'DCCC', '?' ), ( 9, 'IX', 'XC', 'CM', '?' )
) as Placeholder ( Arabic, Ones, Tens, Hundreds, Thousands )
),
OrdinalConversions as (
select *
from ( values
( 1, 'st' ), ( 2, 'nd' ), ( 3, 'rd' ), ( 11, 'th' ), ( 12, 'th' ), ( 13, 'th' )
) as Placeholder2 ( Number, Suffix )
),
Numbers as (
select 1 as Number
union all
select Number + 1
from Numbers
where Number < 3999 )
select Number as Arabic,
( select Thousands from ArabicRomanConversions where Arabic = Number / 1000 ) +
( select Hundreds from ArabicRomanConversions where Arabic = Number / 100 % 10 ) +
( select Tens from ArabicRomanConversions where Arabic = Number / 10 % 10 ) +
( select Ones from ArabicRomanConversions where Arabic = Number % 10 ) as Roman,
Cast( Number as VarChar(4) ) + Coalesce( (
select top 1 Suffix from OrdinalConversions where Number = Numbers.Number % 100 or Number = Numbers.Number % 10 order by Number desc ), 'th' ) as Ordinal
from Numbers option ( MaxRecursion 3998 );
You could use a case statement, I.e.,
UPDATE: Taking into account the teens, as mentioned by TPhe and refactored slightly.
SELECT
Name,
CASE
WHEN Place in(11, 12, 13) then CAST(Place as VARCHAR(20)) + 'th'
WHEN RIGHT(CAST(Place as VARCHAR(20)), 1) = '1' then CAST(Place as VARCHAR(20)) + 'st'
WHEN RIGHT(CAST(Place as VARCHAR(20)), 1) = '2' then CAST(Place as VARCHAR(20)) + 'nd'
WHEN RIGHT(CAST(Place as VARCHAR(20)), 1) = '3' then CAST(Place as VARCHAR(20)) + 'rd'
ELSE CAST(Place as VARCHAR(20)) + 'th'
END as Place
FROM
RunnerTable
DECLARE #Number int = 94
SELECT
CONVERT(VARCHAR(10),#NUMBER) + CASE WHEN #Number % 100 IN (11, 12, 13) THEN 'th'
ELSE
CASE #Number % 10
WHEN 1 THEN 'st'
WHEN 2 THEN 'nd'
WHEN 3 THEN 'rd'
ELSE 'th'
END
END
This Would be much better for any number
create Function dbo.fn_Numbers_Ordinal (#N as bigint) returns varchar(50)
as Begin
Declare #a as varchar(50)= CAST(#N AS VARCHAR(50))
return(
SELECT CAST(#N AS VARCHAR(50))
+
CASE
WHEN Right(#a,2)='11' or Right(#a,2)='12' or Right(#a,2)='13' Then 'th'
WHEN #N % 10 = 1 THEN 'st'
WHEN #N % 10 = 2 THEN 'nd'
WHEN #N % 10 = 3 THEN 'rd'
ELSE 'th' --for #N % 10 IN (4,5,6,7,8,9,0)
END
)
end
Just figured I would add onto the various options. Here's a one-liner. I left this as a comment about a year ago. But someone suggested I submit it as an answer. So here ya go:
SELECT OrdinalRank = CONCAT(num, IIF(num % 100 IN (11,12,13),'th',COALESCE(CHOOSE(num % 10,'st','nd','rd'),'th')))
FROM (
VALUES (1),(2),(3),(4),(5),(10),(11),(20),(21),(22),(23),(24),(101),(102),(103)
) x(num)
--Result:
--1st
--2nd
--3rd
--4th
--5th
--10th
--11th
--20th
--21st
--22nd
--23rd
--24th
--101st
--102nd
--103rd
This takes advantage of the IIF and CHOOSE functions, which are only available in SQL 2012+.
DECLARE #Number int = 113,
#Superscript int
IF #Number IS NOT NULL
BEGIN
IF LEN(#Number) >= 2
SELECT #Superscript = RIGHT(#Number, 2)
ELSE
SELECT #Superscript = RIGHT(#Number, 1)
SELECT #Number as Number,
CASE WHEN #Superscript in (11,12,13) THEN 'th'
ELSE CASE WHEN #Superscript = 1 THEN 'st'
WHEN #Superscript = 2 THEN 'nd'
WHEN #Superscript = 3 THEN 'rd'
ELSE 'th'
END
END as Superscript
END ELSE
SELECT 0 as Number, 'th' as Superscript
Another option to solve this with the FORMAT function (also you can display month names in other languages):
;WITH cte AS (
SELECT 1 AS dayordinal ,'st' AS suffix
UNION
SELECT 2 AS dayordinal ,'nd' AS suffix
UNION
SELECT 3 AS dayordinal ,'rd' AS suffix
)
, YourTable AS --this is just for example
(SELECT CAST('1/1/2022' AS DATE) DateColumn
UNION
SELECT CAST('1/14/2022' AS DATE) DateColumn
UNION
SELECT CAST('4/4/2022' AS DATE) DateColumn
UNION
SELECT CAST('2/2/2022' AS DATE) DateColumn
UNION
SELECT CAST('3/13/2022' AS DATE) DateColumn
)
SELECT CAST(DATEPART(DAY, DateColumn) AS NVARCHAR(2))+ISNULL(c.suffix, 'th')+' '+ FORMAT(DateColumn, 'MMMM yyyy', 'fr-FR')
FROM YourTable t
LEFT JOIN cte c ON c.dayordinal=RIGHT(DATEPART(DAY, DateColumn),1)
Use the SSRS expression below:
= DAY(Globals!ExecutionTime) &
SWITCH(
DAY(Globals!ExecutionTime)= 1 OR DAY(Globals!ExecutionTime) = 21 OR DAY(Globals!ExecutionTime)=31, "st",
DAY(Globals!ExecutionTime)= 2 OR DAY(Globals!ExecutionTime) = 22 , "nd",
DAY(Globals!ExecutionTime)= 3 OR DAY(Globals!ExecutionTime) = 23 , "rd",
true, "th"
)
This is easy to implement and works well.
Public Function OrdinalNumberSuffix(ByVal InNumber As Integer) As String
Dim StrNumber As String, _
Digit As Byte, _
Suffix As String
StrNumber = Trim(Str(InNumber))
If Val(StrNumber) > 3 And Val(StrNumber) < 14 Then
Digit = Val(Right(StrNumber, 2))
Else
Digit = Val(Right(StrNumber, 1))
End If
Select Case Digit
Case 1: Suffix = "st"
Case 2: Suffix = "nd"
Case 3: Suffix = "rd"
Case Else: Suffix = "th"
End Select
OrdinalNumberSuffix = " " & StrNumber & Suffix & " "
End Function

Split string in column and add value in column

I have a table with several rows of data like this :
16 W:\2-Work\ALBO\00_Proposal\ALxO_Amendement #1_20091022_signed.pdf
17 W:\2-Work\ALBO\00_Proposal\Level1\ALBO_Amendment #1_20110418.docx
18 W:\2-Work\ALBO\00_Proposal\A\BR\T\X_#1_20110418_final.docx
19 W:\2-Work\ALBO\MyOptionl\AO_Amendment_2 August 2013.docx
I have created columns from Col1 to Col10
I would like to separate each value with the delimiter '\'
The idea is to have on each column :
Col1 | Col2 | Col3 | Col4 | Col5 |etc...
W: 2-Work ALBO 00_Proposal ALxO_Amendement #1_20091022_signed.pdf
I know how to use charindex and substring but the number of '\' are different on each line (8500 rows).
Could you help me?
I'm using Microsoft SQL Server 2012.
Thank you very much
Edit 2014/06/24
My goal is to generate an XML of the full path and split path.
Actually, here is my idea :
1 - Identify all the ID in a temporary table to do loop
--On déclare une table tempo
declare #IdTable Table (
id int,
src nvarchar(max))
--On injecte tous les id existant de la table
insert into #IdTable (id, src)
select id, src from albo
--on déclare l'id de début en commencant par le plus petit
declare #id int = (select min(id) from ALBO)
--Tnat qu'il reste des ID on continue la boucle
while #id is not null
begin
print #id
select #id = min(id) from #IdTable where ID > #id
end
--Fin de la boucle des ID
2 - Split each row and update column (Colx => The Clolumns have been created before)
This code should be placed into my previous loop.
Declare #products varchar(max) = 'W:\2-Work\ALBO\13_WP Reporting\13_07_Monthly reports\13_07_01 Archives\2012\201211\Draft\ALBO-MR-201211\gp_scripts\v1\Top10_duree_final.txt'
Declare #individual varchar(max) = null
WHILE LEN(#products) > 0
BEGIN
IF PATINDEX('%\%',#products) > 0
BEGIN
SET #individual = SUBSTRING(#products, 0, PATINDEX('%\%',#products))
select #individual --i have to make and update with the ID
SET #products = SUBSTRING(#products, LEN(#individual + '\') + 1,
LEN(#products))
END
ELSE
BEGIN
SET #individual = #products
SET #products = NULL
print #individual
END
END
As others have said, this probably isn't the best way to do things, if you explain what you'll be doing with the results it might help us provide a better option
[Also, for some reason the colours of the code below are showing up odd, so copy and paste it into your Sql server to see it better]
drop table #Path
create table #Path (item bigint,location varchar(1000))
insert into #Path
select 16 ,'W:\2-Work\ALBO\00_Proposal\ALxO_Amendement #1_20091022_signed.pdf' union
select 17 ,'W:\2-Work\ALBO\00_Proposal\Level1\ALBO_Amendment #1_20110418.docx' union
select 18 ,'W:\2-Work\ALBO\00_Proposal\A\BR\T\X_#1_20110418_final.docx' union
select 19 ,'W:\2-Work\ALBO\MyOptionl\AO_Amendment_2 August 2013.docx'
select * from #Path;
with Path_Expanded(item,subitem,location, start, ending, split)
as(
select item
, 1 --subitem begins at 1
, location -- full location path
, 0 --start searching the file from the 0 position
, charindex('\',location) -- find the 1st '\' charactor
, substring(location,0,charindex('\',location)) --return the string from the start position, 0, to the 1st '\' charactor
from #Path
union all
select item
, subitem+1 --add 1 to subitem
, location -- full location path
, ending+1 -- start searching the file from the position after the last '\' charactor
, charindex('\',location,ending+1)-- find the 1st '\' charactor that occurs after the last '\' charactor found
, case when charindex('\',location,ending+1) = 0 then substring(location,ending+1,1000) --if you cant find anymore '\', return everything else after the last '\'
else substring(location,ending+1, case when charindex('\',location,ending+1)-(ending+1) <= 0 then 0
else charindex('\',location,ending+1)-(ending+1) end )--returns the string between the last '\' charactor and the next '\' charactor
end
from Path_Expanded
where ending > 0 --stop once you can't find anymore '\' charactors
)
--pivots the results
select item
, max(case when subitem = 1 then split else '' end) as col1
, max(case when subitem = 2 then split else '' end) as col2
, max(case when subitem = 3 then split else '' end) as col3
, max(case when subitem = 4 then split else '' end) as col4
, max(case when subitem = 5 then split else '' end) as col5
, max(case when subitem = 6 then split else '' end) as col6
, max(case when subitem = 7 then split else '' end) as col7
, max(case when subitem = 8 then split else '' end) as col8
, max(case when subitem = 9 then split else '' end) as col9
, max(case when subitem = 10 then split else '' end) as col10
from Path_Expanded
group by item
you might prefer to have each folder on its own row, if so replace the pivot part above with the below query instead
select item
, subitem
, location
, split from Path_Expanded where item = 16
The following query will get what you are looking for; as others have noted, it's not a particularly good design. For example, what happens when you're looking for the file name and it's in a different column each time?
Regardless, this will do what you asked for (and maybe even what you want):
-- Test Data
CREATE TABLE #FilePath (FileNumber INT IDENTITY(1,1), FilePath VARCHAR(1000))
INSERT INTO #FilePath (FilePath)
SELECT 'W:\2-Work\ALBO\00_Proposal\ALxO_Amendement #1_20091022_signed.pdf' UNION
SELECT 'W:\2-Work\ALBO\00_Proposal\Level1\ALBO_Amendment #1_20110418.docx' UNION
SELECT 'W:\2-Work\ALBO\00_Proposal\A\BR\T\X_#1_20110418_final.docx' UNION
SELECT 'W:\2-Work\ALBO\MyOptionl\AO_Amendment_2 August 2013.docx'
GO
-- Numbers CTE
WITH Numbers AS
(
SELECT n = 1
UNION ALL
SELECT n + 1
FROM Numbers
WHERE n+1 <= 1000 -- set this to the maximum length of your file path
)
SELECT
FilePath,
[1] AS Col1,
[2] AS Col2,
[3] AS Col3,
[4] AS Col4,
[5] AS Col5,
[6] AS Col6,
[7] AS Col7,
[8] AS Col8,
[9] AS Col9,
[10] AS Col10
FROM
(
SELECT
FilePath,
ROW_NUMBER() OVER (PARTITION BY FilePath ORDER BY n) RowNum,
CAST(LTRIM(RTRIM(NULLIF(SUBSTRING('\' + FilePath + '\' , n , CHARINDEX('\' , '\' + FilePath + '\' , n) - n) , ''))) AS VARCHAR(1000)) FolderName
FROM Numbers, #FilePath
WHERE
n <= Len('\' + FilePath + '\') AND SubString('\' + FilePath + '\' , n - 1, 1) = '\' AND
CharIndex('\' , '\' + FilePath+ '\' , n) - n > 0
)P
PIVOT
(MAX(FolderName) FOR RowNum IN
([1],[2],[3],[4],[5],[6],[7],[8],[9],[10])
) UP
OPTION (MAXRECURSION 1000)-- set this to the maximum length of your file path
-- Clean up
DROP TABLE #FilePath
One way (de-dupes):
;with T(ordinal, path, starts, pos) as (
select 1, path, 1, charindex('\', path) from #tbl
union all
select ordinal + 1, path, pos + 1, charindex('\', path, pos + 1)
from t where pos > 0
)
select [1],[2],[3],[4],[5],[6],[7],[8],[9],[10] from (
select
ordinal, path, substring(path, starts, case when pos > 0 then pos - starts else len(path) end) token
from T
) T2
pivot (max(token) for ordinal in ([1],[2],[3],[4],[5],[6],[7],[8],[9],[10])) T3