ORDER BY in SQL where column is synthetic string with embedded integer - sql

I have the following table :
CREATE TABLE TEST
(
name VARCHAR(10),
date_of_entry DATE,
flag1 INT,
flag2 INT,
salary FLOAT,
flag3 INT,
id INT
);
with the following rows :
name date_of_entry flag1 flag2 salary flag3 id
--------------------------------------------------------------
AGMA 2018-11-08 0 1 265466940 1 1
AGMA 2018-11-08 0 1 220737125 1 2
AGMA 2018-11-08 0 1 181270493 0 3
AGMA 2018-11-08 0 1 8584205 0 4
I would like to execute the following SQL to order the rows in a specific manner :
SELECT
name
+ '.' + CONVERT(varchar(8), date_of_entry, 112)
+ '.' + CONVERT(varchar(1), flag1)
+ '.' + CONVERT(varchar(1), flag2)
+ '.' + CONVERT(varchar(2555), salary)
+ '.' + CONVERT(varchar(1), flag3)
+ '.' + CONVERT(varchar(1), id) AS SYNTHETIC_ORDER
FROM
TEST
ORDER BY
SYNTHETIC_ORDER DESC
However, the salary column gets sorted incorrectly within the string. So my end result is (when executed within Microsoft SQL Server) :
SYNTHETIC_ORDER
-----------------------------------
AGMA.20181108.0.1.8.58421e+006.0.4
AGMA.20181108.0.1.2.65467e+008.1.1
AGMA.20181108.0.1.2.20737e+008.1.2
AGMA.20181108.0.1.1.8127e+008.0.3
As can be noted, the result is that id 4 comes first, when I want id 1 to come first.
Expected result :
SYNTHETIC_ORDER
-----------------------------------
AGMA.20181108.0.1.2.65467e+008.1.1
AGMA.20181108.0.1.2.20737e+008.1.2
AGMA.20181108.0.1.1.8127e+008.0.3
AGMA.20181108.0.1.8.58421e+006.0.4
Is there a way to ensure that the salary is correctly ordered in this SQL?

Why can't you just order it by the individual columns?
SELECT
date_of_entry, flag1, flag2, salary, flag3
, name
+ '.' + CONVERT(varchar(8), date_of_entry, 112)
+ '.' + CONVERT(varchar(1), flag1)
+ '.' + CONVERT(varchar(1), flag2)
+ '.' + CONVERT(varchar(2555), salary)
+ '.' + CONVERT(varchar(1), flag3)
+ '.' + CONVERT(varchar(1), id) AS SYNTHETIC_ORDER
FROM TEST
ORDER BY date_of_entry DESC, flag1 DESC, flag2 DESC, salary DESC, flag3 DESC
This will get you the MAX.
SELECT SYNTHETIC_ORDER
FROM (
SELECT
ROW_NUMBER() OVER(ORDER BY date_of_entry DESC, flag1 DESC, flag2 DESC, salary DESC, flag3 DESC) AS RowNum
, name
+ '.' + CONVERT(varchar(8), date_of_entry, 112)
+ '.' + CONVERT(varchar(1), flag1)
+ '.' + CONVERT(varchar(1), flag2)
+ '.' + CONVERT(varchar(2555), salary)
+ '.' + CONVERT(varchar(1), flag3)
+ '.' + CONVERT(varchar(1), id) AS SYNTHETIC_ORDER
FROM TEST
) a
WHERE RowNum = 1

Fixed width rep and it uses only functions available in both H2 (not tagged) and SQLS (tagged):
SELECT
CONCAT(
CAST(name as CHAR(10)), --right pad to 10,
YEAR(date_of_entry),
RIGHT(CONCAT('0',MONTH(date_of_entry)),2),
RIGHT(CONCAT('0',DAY(date_of_entry)),2), --yyyymmdd
CAST(flag1 as CHAR(1)), --rpad to 1, doesn't need cast if never null/0 length
CAST(flag2 as CHAR(1)), --maybe doesn't need cast, see above
RIGHT(CONCAT('0000000000', CAST(salary AS INT)),10), --lpad with 0 to 10 wide
CAST(flag3 as CHAR(1)), --maybe doesn't need cast, see above
RIGHT(CONCAT('0000000000', id), 10) --lpad with 0 to 10 wide
) AS SYNTHETIC_ORDER
FROM
TEST
ORDER BY
SYNTHETIC_ORDER DESC
Points of note:
Your CREATE TABLE statement doesn't mention ID, but your query does; included ID
Your query doesn't mention NAME but your example data output does; included NAME
You might not need to pad the ID or salary so much
Come of the casts to chars (e.g. on flag columns) can be dropped (if the flag column is 100% guaranteed to always be 1 char long)
If salary max value in table is larger than an int can hold, consider a cast to something else
By padding the salary with leading zeroes, the sort will work out. Normalising it to between 0 and 1 could also work, if all the values were padded out to the same width but you possibly then get the problem that loss of precision (dividing a 10 digit salary down to eg 0.123456) will cause two different salaries to merge because there aren't enough digits to fully represent. With any division-that-quantizes-to-lower precision you then risk the original values sorting wrongly (e.g. If salaries of 1000000000 and 1000000001 with id of 2 and 1 respectively both normalise to 0.123456 they would end up sorted wrongly. To guard against this you probably need as many digits for the division answer as the salary had in the first place, padded to a fixed width, but if you've gone that far you might as well just pad all the salaries out either to the width of the widest or to some width that will contain them all. Here utilising a cast to an int might be handy, if the int will overflow. You can make a decision to pad to one digit wider than an int will hold and then if someone inserts a large value in future and your query starts failing because of overflow it at least won't silently deliver wrong results because the pad is chopping digits off the left hand edge. In addressing the cast to bit you can choose whether to add some logic that pads out to the LENGTH() of the string form of the SELECT MAX salary
CONCAT is nice cos you can pass most types to it without first casting to varchar, and it doesn't null the whole thing if you concat a null on, unlike regular string concat ops with + or ||

This will get you what you want but maybe not in a way you like fun to the sub-query
SELECT
name
+ '.' + CONVERT(varchar(8), date_of_entry, 112)
+ '.' + CONVERT(varchar(1), flag1)
+ '.' + CONVERT(varchar(1), flag2)
+ '.' + CONVERT(varchar(2555), salary / (SELECT MIN(salary) AS min_sal FROM TEST))
+ '.' + CONVERT(varchar(1), flag3)
+ '.' + CONVERT(varchar(1), id) AS SYNTHETIC_ORDER
FROM TEST
ORDER BY SYNTHETIC_ORDER DESC

Can you try:
SELECT name
+ '.' + CONVERT(varchar(8), date_of_entry, 112)
+ '.' + CONVERT(varchar(1), flag1)
+ '.' + CONVERT(varchar(1), flag2)
+ '.' + CHAR(DIGITS(salary))
+ '.' + CONVERT(varchar(1), flag3)
+ '.' + CONVERT(varchar(1), id) AS SYNTHETIC_ORDER
FROM TEST
ORDER BY SYNTHETIC_ORDER DESC

If you need the "max" synthetic order, simply do:
select top (1) name, date_of_entry, falg1, flag2, salary, flag3, id
from test
order by name desc, date_of_entry desc, flag1 desc, flag2 desc, salary desc, flag3 desc, id desc;
I don't see a reason to stuff these values into a string.
If you want a separate row for each name, then:
select top (1) with ties name, date_of_entry, falg1, flag2, salary, flag3, id
from test
order by row_number() over (partition by name desc order by date_of_entry desc, flag1 desc, flag2 desc, salary desc, flag3 desc, id desc);

Sir,
As soon as you convert it to a huge string(varchar), it follows alphabetical order instead of order of magnitude like you are expecting.
Can't you just use a row_number as your "Synthetic order". In other words, instead of this:
SELECT
name
+ '.' + CONVERT(varchar(8), date_of_entry, 112)
+ '.' + CONVERT(varchar(1), flag1)
+ '.' + CONVERT(varchar(1), flag2)
+ '.' + CONVERT(varchar(2555), salary)
+ '.' + CONVERT(varchar(1), flag3)
+ '.' + CONVERT(varchar(1), id) AS SYNTHETIC_ORDER
FROM
TEST
ORDER BY
SYNTHETIC_ORDER DESC
this:
SELECT
id,
row_number() over (order by date_of_entry,flag1,flag2,salary,flag3,id) as SYNTHETIC_ORDER
FROM
TEST
ORDER BY
SYNTHETIC_ORDER DESC
Good luck!

You can change your query to
SELECT
name
+ '.' + CONVERT(varchar(8), date_of_entry, 112)
+ '.' + CONVERT(varchar(1), flag1)
+ '.' + CONVERT(varchar(1), flag2)
+ '.' + CONVERT(varchar(2555), salary)
+ '.' + CONVERT(varchar(1), flag3)
+ '.' + CONVERT(varchar(1), id) AS SYNTHETIC_ORDER
FROM
TEST
ORDER BY
salary DESC

Try using the following routine.
In your code, you write CONVERT(varchar(2555), salary). This doesn't work because when you convert a float to a string using convert, the sort order of the result is not the same as the sort order of the float. e.g. 3 < 20, but '20' < '3'.
The routine FloatToSortable solves that problem. If you pass a bunch of floats through the routine, and sort on the results, you'll get the same order as if you sorted the floats. e.g. FloatToSortable(3) < FloatToSortable(20).
And so in your code, where you write
CONVERT(varchar(2555), salary)
replace it with
dbo.FloatToSortable(salary).
You say that you can't add a function to your database. That's unfortunate. I've just used functions here to avoid repetition. You can certainly use the same premise to create a single expression that will give the same result, although that expression will be much longer and harder to understand.
-- FloatToSortable takes a FLOAT parameter and returns a string
-- such that the sort order of FLOATs X and Y will match the
-- sort order of strings F(X) and F(Y).
--
-- The internal format of FLOAT is an 8-byte double-precision
-- float, starting with the SIGN where 0=positive and 1=negative,
-- followed by the EXPONENT and then the MANTISSA.
-- If it weren't for the SIGN we could just sort by the binary
-- value. Because of the sign we need to XOR the binary
-- before we can sort on it.
--
-- If the parameter is positive, XOR with 8000000000000000
-- If the parameter is negative, XOR with FFFFFFFFFFFFFFFF
--
-- Then we convert each byte to a Sortable string. We could
-- use hexidecimal, but it's simpler just use letters A..O
--
-- This function is working with salaries, so we don't have
-- to worry about NANs and Infinities, but it should work
-- with all values.
-- NybbleToSortable
-- Given an integer in range 0..15 return a character
-- We just map the number to a letter, 0 -> 'A', 15 -> 'O'
create function NybbleToSortable ( #a tinyint )
returns varchar(16)
as
begin
return char(#a + ascii('A'))
end
go
-- XorToSortable
-- Take the nth byte of #a, XOR it with the nth byte of #b,
-- and convert that byte to a Sortable string.
create function dbo.XorToSortable ( #a varbinary(8),
#b varbinary(8),
#n int )
returns varchar(16)
as
begin
declare #aa tinyint, #bb tinyint, #x tinyint
set #aa = cast ( substring ( #a, #n, 1 ) as tinyint )
set #bb = cast ( substring ( #b, #n, 1 ) as tinyint )
set #x = #aa ^ #bb
return dbo.NybbleToSortable ( #x / 16 )
+ dbo.NybbleToSortable ( #x % 16 )
end
go
create function dbo.FloatToSortable ( #x float )
returns varchar(16)
as
begin
declare #m varbinary(8), #b varbinary(8)
set #b = cast(#x as varbinary(8))
if #x < 0
set #m = 0xFFFFFFFFFFFFFFFF
else
set #m = 0x8000000000000000
return dbo.XorToSortable ( #b, #m, 1 )
+ dbo.XorToSortable ( #b, #m, 2 )
+ dbo.XorToSortable ( #b, #m, 3 )
+ dbo.XorToSortable ( #b, #m, 4 )
+ dbo.XorToSortable ( #b, #m, 5 )
+ dbo.XorToSortable ( #b, #m, 6 )
+ dbo.XorToSortable ( #b, #m, 7 )
+ dbo.XorToSortable ( #b, #m, 8 )
end
go
-- Create some test data
create table dbo.sal ( salary float, salbin as dbo.FloatToSortable(salary)) ;
go
declare #x float
set #x = pi()/9876543
while abs(#x) < 170
begin
insert into sal ( salary ) values ( #x )
set #x=#x * -2.014159265
end
select * from sal order by salbin
-- result is:
-- salary salbin
-- ---------------------- ----------------
-- -51.6508818660658 DPLGCMKPOHCLNIAP
-- -12.7318092715982 DPNGIJFAELIPCGOM
-- -3.1383581745746 DPPGOEKEHICIIKOI
-- -0.773597202236665 EABHDOLBBEIDLJLO
-- -0.190689716730473 EADHJHHKLHHKMEDG
-- -0.0470045237516562 EAFHOPAFOHHBPGCJ
-- -0.0115864939704268 EAHIEFFHBKJCNPMF
-- -0.00285604090440349 EAJIJKHCLHAGILBG
-- -0.000704006722693307 EALIOOFNBDCOOAMG
-- -0.000173535842863177 EANJEBBKHFNPDPAD
-- -4.27761380502506E-05 EAPJJCKPBGJEEFHA
-- -1.0544207791913E-05 EBBJODBPBNKKNIPE
-- -2.59912004745334E-06 EBDKDCGOKEJGGCDL
-- -6.4067639356036E-07 EBFKIAKBLGBGEJKE
-- 3.180862629353E-07 LOJFFIKOCNMOIIKB
-- 1.29042429395639E-06 LOLFKGFEIEBGJGMI
-- 5.23504172442538E-06 LONFPFBFEPNNJAIF
-- 2.12377138161667E-05 LOPGEEPEJEJMLAHP
-- 8.61579547748313E-05 LPBGJFPGGEGGLLMK
-- 0.000349528825712453 LPDGOIBOOABNBNJK
-- 0.00141798166313501 LPFHDLHCDHKFMEBP
-- 0.00575252124882327 LPHHIPPEKKCBMBFH
-- 0.0233370441794017 LPJHOFKKIGCELCJB
-- 0.094674597011311 LPLIDMJICJOMPBIA
-- 0.384079459692908 LPNIJEMCADJMJBKO
-- 1.55814797226306 LPPIOOCMJBHDCNED
-- 6.32115319420792 MABJEINMGCAIIEAO
-- 25.6438916046025 MADJKENGBEIHOPME
-- 104.033102255957 MAFKACBOFIOMLAIO

Related

Adding a count variable as a suffix in sql

I want to create a new view in SQL Server 2008 R2.
Given is a col called "ADRESS", based on which I want to create a col called "CompanyID".
I want to add a suffix, which counts +1 for each row in a group of adresses, ideally starting from ".002".
The output should look like this:
ADRESS
CompanyID
100000
100000.002
100000
100000.003
100000
100000.004
200000
100000.002
200000
100000.003
300000
100000.002
My idea was to declare a count variable:
DECLARE #count AS
SET #count = '002'
And then use a while loop:
WHILE ()
BEGIN
SELECT ADRESS + '.' + #count AS CompanyID
SET #count = #count +1
END
Problem is, I don't have a idea what to loop through and also, which data type allows 3 digits without removing the first two zeros. I'm new to SQL so i would appreciate a short explanation.
Here is how you can do it:
Use ROW_NUMBER() to get your ordered numbers
+1 to start from 2
Cast to varchar
Add 000 in front
Cut to last 3 characters - RIGHT()
Add Address and '.' in front
SELECT ADRESS
, CAST(ADRESS AS VARCHAR(10))
+ '.'
+ RIGHT('000' + CAST(1 + ROW_NUMBER() OVER (PARTITION BY ADRESS ORDER BY ADRESS) AS VARCHAR(3)),3) AS CompanyId
FROM Table1
db<>fiddle
Use the below query
SELECT Address, CAST(Address AS VARCHAR) + '.' + RIGHT('000' + CAST(ROW_NUMBER() OVER(PARTITION BY Address ORDER BY Address) + 1 AS VARCHAR),3) CompanyID
FROM [dbo].[TblTestNum]
Change the CAST(Address AS VARCHAR) to 100000 if you want all rows in 100000.

SQL Server Pivot With Grand Total For Row And Column Sorting Issue In Left Side Data

Using the below query to get the Grand Total for Row & Column in SQL server pivot which works as expected.
But the problem is the Question No is not sorting in asc to desc or desc to asc order.
Changing this
ORDER BY sQtnNo asc
to ascending/descending don't have any effect.
/* COLUMNS HEADERS */
DECLARE #columnHeaders NVARCHAR (MAX)
SELECT #columnHeaders = COALESCE (#columnHeaders
+ ',[' + sAnsText + ']', '[' + sAnsText + ']')
FROM tblEntries
GROUP BY sAnsText
ORDER BY sAnsText
/* GRAND TOTAL COLUMN */
DECLARE #GrandTotalCol NVARCHAR (MAX)
SELECT #GrandTotalCol = COALESCE (#GrandTotalCol + 'ISNULL ([' +
CAST (sAnsText AS VARCHAR) +'],0) + ', 'ISNULL([' + CAST(sAnsText AS VARCHAR)+ '],0) + ')
FROM tblEntries
GROUP BY sAnsText
ORDER BY sAnsText
SET #GrandTotalCol = LEFT (#GrandTotalCol, LEN (#GrandTotalCol)-1)
/* GRAND TOTAL ROW */
DECLARE #GrandTotalRow NVARCHAR(MAX)
SELECT #GrandTotalRow = COALESCE(#GrandTotalRow + ',ISNULL(SUM([' +
CAST(sAnsText AS VARCHAR)+']),0)', 'ISNULL(SUM([' + CAST(sAnsText AS VARCHAR)+']),0)')
FROM tblEntries
GROUP BY sAnsText
ORDER BY sAnsText
----------------------------------------------
-- DROP TABLE temp_MatchesTotal
/* MAIN QUERY */
DECLARE #FinalQuery NVARCHAR (MAX)
SET #FinalQuery = 'SELECT *, (' + #GrandTotalCol + ')
AS [Grand Total] INTO #temp_MatchesTotal
FROM
(SELECT sQtnNo as ''Sort'',sQtnNo ,sAnsText,1 as ''Qty''
FROM tblEntries
) A
PIVOT
(
sum (Qty)
FOR sAnsText
IN (' +#columnHeaders + ')
) B
ORDER BY sQtnNo asc
SELECT * FROM #temp_MatchesTotal
UNION ALL
SELECT ''Grand Total'','''','+#GrandTotalRow +',
ISNULL (SUM([Grand Total]),0) FROM #temp_MatchesTotal
DROP TABLE #temp_MatchesTotal'
-- PRINT 'Pivot Query '+#FinalQuery
-- SELECT #FinalQuery
EXECUTE(#FinalQuery)
Result is given below
Any suggestion will be highly helpful.
Thanks in advance.
When Using Try_Convert() Below Error Occurs
Like Is aid in the comments, your column is a varchar, and strings don't order in the same order as a numerical value. '14' has a lower value than '2' and '99' has a greater value than '843657365476547362'.
If you want your columns to order like a numerical value, you need to treat them as a numerical value:
ORDER BY CASE Sort WHEN 'Grand Total' THEN 1 ELSE 0 END ASC, --Put Grand Total at the bottom
TRY_CONVERT(int,Sort) ASC;

How to Get ##ROWCOUNT from a Single Query in a UNION - Create Trailer record with RowCount

I need to create a HEADER and a TRAILER record. The TRAILER needs to include the RowCount of the 'main' query. How Can I get the ##Rowcount of the query and save it to variable , to be included in the TRAILER.
See this.
--- Var to save Count
declare #cnt int
-- HEADER RECORD
Select Cast('H' as Char(2)) +
Cast('MyFile' as Char(30))
+ CONVERT(Char(8),GetDate(),112)
union all
-- MAIN Query that I need the Count of
Select top 10 lastname from CUSTOMERS
set #cnt = ##ROWCOUNT
union all <--ERROR obviously
-- TRAILER record
Select Cast('T' as Char(2)) +
CONVERT(Char(9),GetDate(),112) +
Right(Replicate('0',9) + Cast(#cnt as VarChar(9)),9)
thx in advance
You can use a CTE to reuse the definition of the main query:
WITH Query AS (
SELECT top 10 lastname from CUSTOMERS
)
SELECT X.Result
FROM (
SELECT Cast('H' as Char(2)) + Cast('MyFile' as Char(30))
+ CONVERT(Char(8),GetDate(),112) AS Result, 1 AS Position
UNION ALL
SELECT *, 2 AS Position FROM Query
UNION ALL
Select Cast('T' as Char(2)) + CONVERT(Char(9),GetDate(),112) + Right(Replicate('0',9)
+ Cast((SELECT COUNT(*) FROM Query) as VarChar(9)),9) AS Result, 3 AS Position
) X ORDER BY X.Position
However, the CTE will be evaluated twice; if your query is complex and time-consuming, you might want to use a temporary table:
SELECT TOP 10 lastname INTO #Temp FROM CUSTOMERS
SELECT X.Result
FROM (
SELECT Cast('H' as Char(2)) + Cast('MyFile' as Char(30))
+ CONVERT(Char(8),GetDate(),112) AS Result, 1 AS Position
UNION ALL
SELECT *, 2 AS Position FROM #Temp
UNION ALL
Select Cast('T' as Char(2)) + CONVERT(Char(9),GetDate(),112) + Right(Replicate('0',9)
+ Cast((SELECT COUNT(*) FROM #Temp) as VarChar(9)),9) AS Result, 3 AS Position
) X ORDER BY X.Position
Try this following script-
-- HEADER RECORD
SELECT CAST('H' AS CHAR(2)) + CAST('MyFile' AS CHAR(30)) + CONVERT(CHAR(8), GETDATE(), 112)
UNION ALL
-- MAIN Query that I need the Count of
SELECT lastname FROM CUSTOMERS
UNION ALL
-- TRAILER record
SELECT CAST('T' AS CHAR(2)) + CONVERT(CHAR(9), GETDATE(), 112) + RIGHT(REPLICATE('0', 9) + CAST(
(
SELECT COUNT(lastname) FROM CUSTOMERS
) AS VARCHAR(9)), 9);

SQL Server formatting negative values from selected data

I am new to stackoverflow but I do search it often.
I am creating a report from data in which I have to format negative numbers like,
-00000010 (9 characters max)
I am getting this,
000000-10
This is what I am attempting now but I'm having issues. Any help would be greatly appreciated.
SELECT 'H'
+ DG.BLOCK
+ LEFT(DG.ZIP,5)
+ RIGHT('000000000'
+ CAST(CAST(SUM(DG.WP)AS INT) AS VARCHAR(9)),9)
+ RIGHT('000000000' + CAST(CAST(SUM(DG.WE)AS INT) AS VARCHAR(9)),9)
+ RIGHT('000000000' + CAST(CAST(SUM(DG.EP)AS INT) AS VARCHAR(9)),9)
+ RIGHT('000000000' + CAST(CAST(SUM(DG.EE)AS INT) AS VARCHAR(9)),9)
+ RIGHT('000000000' + CAST(CAST(COUNT(DGG.CLAIMCONTROL)AS INT) AS VARCHAR(9)),9)
+ RIGHT('000000000' + CAST(CAST(SUM(DGG.INC) AS INT) AS VARCHAR(9)),9)
+ RIGHT('000000000' + CAST(CAST(SUM(DGG.PAID)AS INT) AS VARCHAR(9)),9)
+ RIGHT('000000000' + CAST(CAST(SUM(DGG.ALAE) AS INT) AS VARCHAR(9)),9)
AS [H Record]
FROM TABLE
If 2012+, you have the option of Format().
Example
Select replace(format(-1,' 000000000'),'- ','-')
Returns
-000000001 If number was negative
000000001 If number was positive
Just a word of caution. Format() has some great functionality, but is not known to be a high performer.
The following code demonstrates formatting the data as either 9 digits plus an optional sign or as a fixed 9 characters including the sign.
-- Sample data.
declare #Samples as Table ( Sample Int );
insert into #Samples ( Sample ) values ( 0 ), ( 1 ), ( -10 ), ( 100 ), ( -1000 );
-- Format the data.
select Sample,
case when Sign( Sample ) = -1 then '-' else '' end +
Right( Replicate( '0', 8 ) + Cast( Abs( Sample ) as VarChar(9) ), 9 ) as FormattedSample9PlusSign,
case when Sign( Sample ) = -1 then
'-' + Right( Replicate( '0', 7 ) + Cast( -Sample as VarChar(8) ), 8 ) else
Right( Replicate( '0', 8 ) + Cast( Sample as VarChar(9) ), 9 ) end as FormattedSample9
from #Samples;
Tip: In SSMS use query results to text (Ctrl-T) for more convenient display.
You can try this, if you do not have v2012+:
DECLARE #mockup TABLE(SomeNumber INT);
DECLARE #padWidth INT=3;
INSERT INTO #mockup VALUES(-1000),(-500),(-1),(0),(1),(500),(1000);
SELECT CASE WHEN m.SomeNumber < 0 THEN '-' ELSE ' ' END
+ REPLACE(STR(ABS(m.SomeNumber),#padWidth),' ','0')
FROM #mockup AS m;
Numbers, which are to big, will be returned as ***. This is better than other approaches cutting the string with RIGHT or LEFT. They might return a bad result...
This is returned
-***
-500
-001
000
001
500
***
In DB2 this works to get a number of 15 digits including the sign:
CASE WHEN MYNUMBER < 0 THEN '-' || LPAD(MYNUMBER , 14, 0)
ELSE LPAD(MYNUMBER , 15, 0)
END

2005 SSRS/SQL Server PIVOT results need reversing

Preamble: I've read through the three questions/answers here,here, and here, with big ups to #cade-roux. This all stemmed from trying to use the following data in a 2005 SSRS matrix that, I believe, doesn't work because I want to show a member having to take a test multiple times, and SSRS seems to require the aggregate where I want to show all dates.
I get the following results in my table, which seems to be showing all the data correctly:
How do I change the code below to show a) the "tests" at the top of each column with b) if it's called for, the multiple dates that test was taken?
Here's the code I have to produce the table, above. Much of it is commented out as I was just trying to get the pivot to work, but you may notice I am also trying to specify which test column comes first.
CREATE TABLE #tmp ( ---THIS WORKS BUT TESTS ARE VERTICAL
[TEST] [varchar](30) NOT NULL,
[ED] [datetime] NOT NULL
)
--WHERE THE TEST AND ED COME FROM
INSERT #TMP
SELECT DISTINCT
-- N.FULL_NAME
-- , CONVERT(VARCHAR(30), AM.CREATEDATE, 101) AS ACCOUNT_CLAIMED
-- , N.EMAIL
-- , NULL AS 'BAD EMAIL'
-- , CONVERT(VARCHAR(30), AC.EFFECTIVE_DATE, 101) AS EFFECTIVE_DATE
AC.PRODUCT_CODE AS TEST
, CONVERT(VARCHAR(30), AC.EFFECTIVE_DATE, 101) AS ED
-- , CASE
-- WHEN AC.PRODUCT_CODE = 'NewMem_Test' THEN '9'
-- WHEN AC.PRODUCT_CODE = 'NM_Course1' THEN '1'
-- WHEN AC.PRODUCT_CODE = 'NMEP_Course1' THEN '2'
-- WHEN AC.PRODUCT_CODE = 'NMEP_Course2' THEN '3'
-- WHEN AC.PRODUCT_CODE = 'NMEP_Course3' THEN '4'
-- WHEN AC.PRODUCT_CODE = 'NMEP_Course4' THEN '5'
-- WHEN AC.PRODUCT_CODE = 'NMEP_Course5' THEN '6'
-- WHEN AC.PRODUCT_CODE = 'NMEP_Course6' THEN '7'
-- WHEN AC.PRODUCT_CODE = 'NMEP_Course7' THEN '8'
-- END AS 'COLUMN_ORDER'
FROM NAME N
JOIN USERMAIN UM
ON N.ID = UM.CONTACTMASTER
JOIN formTransLog TL
ON UM.USERID = TL.USERNAME
JOIN anet_Users AU
ON UM.USERID = AU.USERNAME
JOIN anet_Membership AM
ON AU.USERID = AM.USERID
JOIN ACTIVITY AC
ON N.ID = AC.ID
AND AC.ACTIVITY_TYPE = 'COURSE'
AND AC.PRODUCT_CODE LIKE 'N%'
--ORDER BY 1, 7
DECLARE #sql AS varchar(max)
DECLARE #pivot_list AS varchar(max) -- Leave NULL for COALESCE technique
DECLARE #select_list AS varchar(max) -- Leave NULL for COALESCE technique
SELECT #pivot_list = COALESCE(#pivot_list + ', ', '') + '[' + CONVERT(varchar, PIVOT_CODE) + ']'
,#select_list = COALESCE(#select_list + ', ', '') + '[' + CONVERT(varchar, PIVOT_CODE) + '] AS [col_' + CONVERT(varchar, PIVOT_CODE) + ']'
FROM (
SELECT DISTINCT PIVOT_CODE
FROM (
SELECT TEST, ED, ROW_NUMBER() OVER (PARTITION BY TEST ORDER BY ED) AS PIVOT_CODE
FROM #tmp
) AS rows
) AS PIVOT_CODES
SET #sql = '
;WITH p AS (
SELECT TEST, ED, ROW_NUMBER() OVER (PARTITION BY TEST ORDER BY ED) AS PIVOT_CODE
FROM #tmp
)
SELECT TEST, ' + #select_list + '
FROM p
PIVOT (
MIN(ED)
FOR PIVOT_CODE IN (
' + #pivot_list + '
)
) AS pvt
'
PRINT #sql
EXEC (#sql)
EDIT:
The goal is to have the report in SSRS look like this:
I was able to produce the results you were looking for by adding in a number (RowNum) to the query underneath the PIVOT operator. It doesn't have to be in the final query (though you might want it for client-side sorting), but by having it in the underlying layer the PIVOT operation treats that number like a member of a GROUP BY clause.
Please look through my sample SQL below and let me know if this matches your criteria.
CREATE TABLE #TMP
(
Name VARCHAR(10),
Test VARCHAR(20),
EffectiveDate DATETIME
)
INSERT INTO #TMP (Name, Test, EffectiveDate)
SELECT 'Jane', 'NM_Course1', '01/17/2014' UNION
SELECT 'Jane', 'NMEP_Course1', '12/19/2013' UNION
SELECT 'Jane', 'NMEP_Course1', '12/20/2013' UNION
SELECT 'Jane', 'NMEP_Course2', '12/19/2013' UNION
SELECT 'Jane', 'NMEP_Course2', '12/22/2013' UNION
SELECT 'Jane', 'NMEP_Course2', '01/05/2014' UNION
SELECT 'John', 'NM_Course1', '01/17/2014' UNION
SELECT 'John', 'NMEP_Course1', '01/11/2014'
DECLARE #sql AS varchar(max)
DECLARE #pivot_list AS varchar(max) -- Leave NULL for COALESCE technique
DECLARE #select_list AS varchar(max) -- Leave NULL for COALESCE technique
SELECT #pivot_list = COALESCE(#pivot_list + ', ', '') + '[' + CONVERT(varchar, PIVOT_CODE) + ']'
,#select_list = COALESCE(#select_list + ', ', '') + '[' + CONVERT(varchar, PIVOT_CODE) + '] AS [col_' + CONVERT(varchar, PIVOT_CODE) + ']'
FROM (
SELECT DISTINCT PIVOT_CODE
FROM (
SELECT TEST AS PIVOT_CODE
FROM #tmp
) AS rows
) AS PIVOT_CODES
SET #sql = '
SELECT Name, ' + #select_list + '
FROM
(
SELECT b.Name, RowNum, b.EffectiveDate, b.TEST AS PIVOT_CODE
FROM
(
SELECT Name, Test, EffectiveDate, ROW_NUMBER() OVER (PARTITION BY NAME, TEST ORDER BY EffectiveDate) RowNum
FROM #Tmp
) b
) p
PIVOT (
MIN(EffectiveDate)
FOR PIVOT_CODE IN (
' + #pivot_list + '
)
) AS pvt
ORDER BY Name, RowNum
'
PRINT #sql
EXEC (#sql)
DROP TABLE #TMP