Dynamic SQL pivot returning only NULLs - sql-server

I am very new to dynamic SQL and am trying to construct a query to report on our workers, what certificates they have and what the expiry date is on their last certificate where one exists. My temp table holds the correct data and my dynamic column string seems to be correct. When everything is run, the column headings show as expected and the personnel names are grouped correctly but none of the dates are showing, the values are all NULL. I haven't put the specific code for the first 2 sections as the selection criteria is a bit long winded.
For the dynamic column variable, SELECT STUFF(#columns, 1, 1, '') returns the single value below.
[Air Supervisor - AODC], [Air Supervisor - IMCA], [ALST - Certificate of Achievement], [ALST - IMCA], [Competence - A1/A2 Competent Assessor], [Competence - Air Diver-Surface Supplied], etc...
The data itself is held in a temp table, SELECT * FROM #results gives the below (example) output. This is every cert matched to the relevant personnel ID with the most recent expiry date.
id cert date
3484 [ALST - Banksman and Slinging] 28/07/2029
3648 [ALST - Banksman and Slinging] 05/11/2099
3701 [ALST - Banksman and Slinging] 27/05/2029
3740 [ALST - Banksman and Slinging] 20/01/2055
1181 [ALST - Crane Operators] 31/12/2029
1137 [ALST - Crane Operators] 31/12/2029
1072 [ALST - Crane Operators] 31/12/2029
The below is the actual pivot query. I need the [cert] field from above to become the column headers, dates (where they exist) to become the values and the personnel ID matched to the correct name.
SET #dynamicpivot =
N'SELECT pd.name, ' + STUFF(#columns, 1, 1, '') + '
FROM #results
PIVOT (MAX([date]) FOR [cert] IN (' + STUFF(#columns, 1, 1, '') + ')) as ce
JOIN dbo.personnel as pd
ON pd.person_id = ce.id
ORDER BY pd.name'
EXEC sp_executesql #dynamicpivot
The query runs without error, the column names show correctly and the personnel names show in order and grouped but none of the dates show, it's all NULLs.
I've tried to keep this fairly succinct, let me know if you need more info.

You need to initialise #columns. The default value is NULL, which you cannot use in string concatenation. This is because NULL cannot be combined with any other value. For example NULL + 1 returns NULL and NULL + 'Some Text' also returns NULL. You can test this with the below query.
NULL Concatenation Example
-- NULL cannot be concatenated.
SELECT
NULL + 'ABC'
;
This returns NULL. In effect your query is doing the same thing.
In this second example I'm using an initialised variable. The lack of NULL allows me to concatenate values to the string.
Initialised Variable Example
DECLARE #columns VARCHAR(255) = '';
-- Will return: 'ABC'.
SELECT
#columns + 'ABC'
;
Alternativly you could achieve the same result using ISNULL. This time the variable starts life as NULL. This is replaced with a blank string during concatenation.
ISNULL Example
DECLARE #columns VARCHAR(255) = NULL;
-- Using ISNULL returns: 'ABC'.
SELECT
ISNULL(#columns, '') + 'ABC'
;

Related

The report parameter has a DefaultValue or a ValidValue that depends on the report parameter . Forward dependencies are not valid

ALTER PROCEDURE [dbo].[R$20_covid_application]
#Guarantors int,
#CustNo int,
#Months tinyint = null
WITH EXECUTE AS SELF
AS
BEGIN
SET NOCOUNT ON;
SELECT
L.CUSTNO
,CUST_NAME1
,CUST_NAME2
,CUST_NAME3
,CUST_NAME4
,L.BRANCH
,REFER_NO
,RIGHT('000000' + CONVERT(VARCHAR, L.CUSTNO), 6) + ' - ' + CAST(LOAN_PRODUCT AS CHAR(4)) + ' - ' + RIGHT('00' + CONVERT(VARCHAR, LOAN_ACC_SEQ_NO), 2) ACC_STR
,[CHECK_PRIN]
,[L_PRIN]
,[L_INT]
,[L_INTLQ]
,[L_EXP]
,[L_ACCR]
,[INV_BILL_CHARG_AMT]
,[P_PRIN]
,[P_INT]
,[P_OTH]
,[P_EXP]
,[P_PRV_S_INT]
,[P_ACCRUED]
,[P_OFF_ACCR]
,[MATURITY_DATE]
,[AFM]
,[TEL_1]
,[TEL_2]
,[CUST_ADDRESS]
,[SIGN]
, CITY = CASE L.BRANCH
WHEN 101 THEN '212'
WHEN 102 THEN '12321'
WHEN 103 THEN '123123Α'
WHEN 104 THEN 'ΚΑΛ12312ΚΑ'
WHEN 105 THEN '313Σ'
WHEN 106 THEN '132'
WHEN 107 THEN '12312'
WHEN 108 THEN '23123123'
WHEN 109 THEN '23123'
WHEN 110 THEN '123213123'
ELSE 'Unknown city mate'
END
FROM
LB.LM.LOAN AS L
JOIN
LB.LM.CUSTOMER_MF AS C ON L.CUSTNO = C.CUSTNO
LEFT JOIN
[asdf6].[fsdfsdf].[MASTER_Guarantors] AS G ON L.CUSTNO = G.CUST
WHERE
ToClose = 0
AND L.CUSTNO = #CustNo
AND G.[SIGN] = #Guarantors
END
So this is the SQL of my stored procedure that works fine so far. The problem is that I have a 3 parameter report that does work because of dependencies on itself. What I want the report to do is to have a multi value parameter(drop down) that will have all the guarantors for the specific loan customer number. In the picture I posted you can see the error and the output I want in my report. (eg customer 1 has 2, 3, 4 as guarantors).
Some names have been changed
If you are calling R420_covid_application to populate the available values of your Guarantors parameter then that's the problem. You can't reference the parameter value in the dataset query that produces the parameter values in the first place.
It's difficult to tell from your description and you don't explain your dependencies but it sounds like you would need..
A Customer parameter (e.g. #CustomerID) which would be populated from a dataset query something like SELECT * FROM myCustomers
A Guarantor parameter (e.g. #Guarantor) which would be populated from a dataset query someting like SELECT * from myGuarantors JOIN blah blah.... WHERE CustomerID = #CustomerID
This way the customers dataset does not depend on anything and when the users chooses a value the value of #CustomerID is set. Then, the 2nd dataset will be populated and filtered based on the value of #CustomerID.
If this does not help, you will need to edit your question and describe what you expect the user to see in each parameter and the dependencies from one parameter to the next. Give some sample data and expected results.

SQL Server stored procedure works in SSMS but not as Access pass-through

I am attempting to troubleshoot an issue reported to me by a user. The application in question is an Access front end with a SQL Server back end. In a nutshell, the procedure in question attaches new medical claims to an existing subrogation case. (For those as unfamiliar with the term as I was, it's where my employer (an insurance company) is disputing with another company which of us is the primary payer and how much each side is to pay, such as when you have both health and auto insurance and get hurt in a car accident.)
Sounds straightforward enough, right? Here's the catch: the stored procedure works perfectly fine when run directly through SSMS. It only fails when this is executed via a pass-through query. Additionally, it even when run through the front end, it works correctly for 99% of our cases.
Here is the stored procedure involved:
CREATE PROCEDURE [AppSchema].[USP_StoredProcWithError]
#ContractNum VARCHAR(9),
#PatientBirthDate DATE,
#PatientFstName VARCHAR(15),
#GrpID VARCHAR(9),
#CvgStartDate DATE,
#CvgEndDate DATE,
#FileNumber VARCHAR(15),
#SearchStartDate DATE,
#SearchEndDate DATE,
#AltName NVARCHAR(15) = NULL
AS
IF OBJECT_ID('tempdb..#ClaimStagingMedical') IS NOT NULL
DROP TABLE #ClaimStagingMedical
/* CREATE TEMPORARY TABLE */
SELECT ClaimID
INTO #ClaimStagingMedical
FROM DBName.DBSchema.ClaimsTableName CTN WITH (NOLOCK)
WHERE YR_MTH >= CAST(YEAR(#CvgStartDate) AS VARCHAR) + RIGHT('0' + CAST(MONTH(#CvgStartDate) AS VARCHAR), 2)
AND PYMT_DATE BETWEEN #SearchStartDate AND #SearchEndDate
AND FST_SERV_DATE BETWEEN #CvgStartDate AND #CvgEndDate
AND AUTOGEN_NUM = #ContractNum
AND PAT_DOB_DATE = #PatientBirthDate
AND GRP_BASE1_NUM+GRP_BASE2_NUM = #GrpID
AND (LEFT(LEFT(PATIENT_NAME, (LEN(PATIENT_NAME) - (LEN(PATIENT_NAME) - CHARINDEX(' ', PATIENT_NAME) + 1))), 3) = LEFT(#PatientFstName, 3)
OR
LEFT(LEFT(PATIENT_NAME, (LEN(PATIENT_NAME) - (LEN(PATIENT_NAME) - CHARINDEX(' ', PATIENT_NAME) + 1))), 3) = LEFT(#AltName, 3)
)
AND LatestICNInd IS NULL
UNION
SELECT ClaimID
FROM DBName.DBSchema.ClaimsTableName CTN WITH (NOLOCK)
WHERE YR_MTH >= CAST(YEAR(#CvgStartDate) AS VARCHAR) + RIGHT('0' + CAST(MONTH(#CvgStartDate) AS VARCHAR), 2)
AND PYMT_DATE BETWEEN #SearchStartDate AND #SearchEndDate
AND FST_SERV_DATE BETWEEN #CvgStartDate AND #CvgEndDate
AND SUB_CERT_BASE_NUM = #ContractNum
AND PAT_DOB_DATE = #PatientBirthDate
AND GRP_BASE1_NUM + GRP_BASE2_NUM = #GrpID
AND (LEFT(LEFT(PATIENT_NAME, (LEN(PATIENT_NAME) - (LEN(PATIENT_NAME) - CHARINDEX(' ', PATIENT_NAME) + 1))), 3) = LEFT(#PatientFstName, 3)
OR
LEFT(LEFT(PATIENT_NAME, (LEN(PATIENT_NAME) - (LEN(PATIENT_NAME) - CHARINDEX(' ', PATIENT_NAME) + 1))), 3) = LEFT(#AltName, 3)
)
AND LatestICNInd IS NULL
/* INSERT CLAIM LINES */
INSERT INTO AppSchema.AppClaimsTable
(FileNumber, ClaimID, YR_MTH, ConnectToCase, NotConnected,
SendForAdjustment, SendWithCheck, Credit,
CLM_ACVN_PLAN_NUM, ICN13, LINE_SEQUENCE_NUM,
LastModifiedDate, LoadType)
SELECT DISTINCT
#FileNumber AS FileNumber, CTN.ClaimID, CTN.YR_MTH,
1 AS ConnectToCase, 0 AS NotConnected,
0 AS SendForAdjustment, 0 AS SendWithCheck, 0 AS Credit,
CTN.CLM_ACVN_PLAN_NUM,
LEFT(CTN.ICN_NUM,13) AS ICN13,
CTN.LINE_SEQUENCE_NUM,
CAST(CAST(GETDATE() AS DATE) AS DATETIME) AS LastModifiedDate,
'M' As LoadType
FROM
DBName.DBSchema.ClaimsTableName CTN WITH (NOLOCK)
INNER JOIN
#ClaimStagingMedical CSM ON CTN.ClaimID = CSM.ClaimID
LEFT JOIN
(SELECT
FileNumber,
ClaimID
FROM
AppSchema.AppClaimsTable WITH (NOLOCK)
WHERE
FileNumber = #FileNumber) FNLoaded ON CTN.ClaimID = FNLoaded.ClaimID
LEFT JOIN
(SELECT
FileNumber, AdjClaimID
FROM
AppSchema.AppClaimsTable WITH (NOLOCK)
WHERE
FileNumber = #FileNumber) FNLoadedAdj ON CTN.ClaimID = FNLoadedAdj.AdjClaimID
WHERE
FNLoaded.FileNumber IS NULL
AND FNLoadedAdj.FileNumber IS NULL
SELECT ##ROWCOUNT AS RowsAdded;
I am fully aware that the LEFT part of the WHERE clause is entirely too complicated and can be replaced just fine with LEFT(PATIENT_NAME, 3), as PATIENT_NAME is saved as 'FirstName MI LastName'. I already plan on doing just that.
Anyway, the actual pass-through is much simpler:
EXEC AppSchema.USP_StoredProcWithError #ContractNum = '123456789', #PatientBirthDate = '1/1/1950', #PatientFstName = 'BOB', #GrpID = '123456789', #CvgStartDate = '1/1/2000', #CvgEndDate = '12/31/9999', #FileNumber = '1234567', #SearchStartDate = '1/1/2000', #SearchEndDate = '12/31/9999'
The error being returned by SQL Server is this:
Error Number: 537
Description: Invalid length parameter passed to the LEFT or SUBSTRING function.
When I execute the exact same statement directly in SSMS, the procedure runs as intended and does what it is supposed to. When run in Access as a pass-through, however, I get the above error.
Note that the parameter in question is determined by the USP rather than being passed to the function. I have checked the data involved, and PATIENT_NAME is always 14 characters in length, and the first name (which the overly complicated LEFT function checks, is at least 3 characters in every instance where this happens. The case I'm using as my test case has a standard name that the LEFT function parses out exactly as expected.
There are no non-printing characters hidden in the data - I checked. The field is an nvarchar(31) field, but all the data is 14 characters long, regardless of whether it had to be padded or truncated to reach that length.
I have also tried replacing
(
LEFT(LEFT(PATIENT_NAME,(LEN(PATIENT_NAME)-(LEN(PATIENT_NAME)-CHARINDEX(' ',PATIENT_NAME)+1))),3)=LEFT(#PatientFstName,3)
OR
LEFT(LEFT(PATIENT_NAME,(LEN(PATIENT_NAME)-(LEN(PATIENT_NAME)-CHARINDEX(' ',PATIENT_NAME)+1))),3)=LEFT(#AltName,3)
)
with
(
LEFT(PATIENT_NAME,3)=LEFT(#PatientFstName,3)
OR
LEFT(PATIENT_NAME,3)=LEFT(#AltName,3)
)
to no avail. No error is generated by SQL Server, but Access returns 'Pass-through query with ReturnsRecords property set to True did not return any records', which shouldn't happen because of the final SELECT in the USP. Additionally, there are 265 records that match the criteria in the USP itself with the values I am using as a test case. Additionally, as with the original, the same EXEC statement that failed in Access runs the procedure without error when executed in SSMS.
I have spent several hours working on this, both now and a year and a half ago, and I have not found an answer. My previous issue with this is documented HERE.
I would dearly love if someone can point me to why this keeps happening, and why it only happens occasionally.
ADDENDUM 2019-08-26:
When I started work this morning, this procedure was now working correctly. It has GOT to be a data issue someplace, even though I looked for one and didn't find anything. I do thank everyone for their help, and will be taking into account the suggestions made here when this gets rewritten next year as part of our redesign of this application.

Execution time for a SQL Query seems excessive

I have a data set of about 33 million rows and 20 columns. One of the columns is a raw data tab I'm using to extract relevant data from, inlcuding ID's and account numbers.
I extracted a column for User ID's into a temporary table to trim the User ID's of spaces. I'm now trying to add the trimmed User ID column back into the original data set using this code:
SELECT *
FROM [dbo].[DATA] AS A
INNER JOIN #TempTable AS B ON A. [RawColumn] = B. [RawColumn]
Extracting the User ID's and trimming the spaces took about a minute for each query. However, running this last query I'm at the 2 hour mark and I'm only 2% of the way through the dataset.
Is there a better way to run the query?
I'm running the query in SQL Server 2014 Management Studio
Thanks
Update:
I continued to let it run through the night. When I got back into work, only 6 million rows had been completed of the 33 million rows. I cancelled the execution and I'm trying to add a smaller primary key (The only other key I could see on the table was the [RawColumn], which was a very long string of text) using:
ALTER TABLE [dbo].[DATA]
ADD ID INT IDENTITY(1,1)
Right now I'm an hour into the execution.
Next, I'm planning to make it the primary key using
ALTER TABLE dbo.[DATA]
ADD CONSTRAINT PK_[DATA] PRIMARY KEY(ID)
I'm not familiar with using Indexes.. I've tried looking up on Stack Overflow how to create one, but from what I'm reading it sounds like it would take just as long to create an index as it would to run this query. Am I wrong about that?
For context on the RawColumn data, it looks something like this:
FirstName: John LastName: Smith UserID: JohnS Account#: 000-000-0000
Update #2:
I'm now learning that using "ALTER TABLE" is a bad idea. I should have done a little bit more research into how to add a primary key to a table.
Update #3
Here's the code I used to extract the "UserID" code out of the "RawColumn" data.
DROP #TEMPTABLE1
GO
SELECT [RAWColumn],
SUBSTRING([RAWColumn], CHARINDEX('USERID:', [RAWColumn])+LEN('USERID:'), CHARINDEX('Account#:', [RAWColumn])-Charindex('Username:', [RAWColumn]) - LEN('Account#:') - LEN('USERID:')) AS 'USERID_NEW'
INTO #TempTable1
FROM [dbo].[DATA]
Next I trimmed the data from the temporary tables
DROP #TEMPTABLE2
GO
SELECT [RawColumn],
LTRIM([USERID_NEW]) AS 'USERID_NEW'
INTO #TempTable2
FROM #TempTable1
So now I'm trying to get the data from #TEMPTABLE2 back into my original [DATA] table. Hopefully this is more clear now.
So I think your parsing code is a little bit wrong. Here's an approach that doesn't assume that the values appear in any particular order. It does assume that the header/tag name has a space after the colon character and it assumes that the value end at the subsequent space character. Here's a snippet that manipulates a single value.
declare #dat varchar(128) = 'FirstName: John LastName: Smith UserID: JohnS Account#: 000-000-0000';
declare #tag varchar(16) = 'UserID: ';
/* datalength() counts the trailing space character unlike len() */
declare #idx int = charindex(#tag, #dat) + datalength(#tag);
select substring(#dat, #idx, charindex(' ', #dat + ' ', #idx + 1) - #idx) as UserID
To use it in a single query without the temporary variable, the most straightforward approach is to just replace each instance of "#idx" with the original expression:
declare #tag varchar(16) = 'UserID: ';
select RawColumn,
substring(
RawColumn,
charindex(#tag, RawColumn) + datalength(#tag),
charindex(
' ', RawColumn + ' ',
charindex(#tag, RawColumn) + datalength(#tag) + 1
) - charindex(#tag, RawColumn) + datalength(#tag)
) as UserID
from dbo.DATA;
As an update it looks something like this:
declare #tag varchar(16) = 'UserID: ';
update dbo.DATA
set UserID =
substring(
RawColumn,
charindex(#tag, RawColumn) + datalength(#tag),
charindex(
' ', RawColumn + ' ',
charindex(#tag, RawColumn) + datalength(#tag) + 1
) - charindex(#tag, RawColumn) + datalength(#tag)
) as UserID;
You also appear to be ignoring upper/lower case in your string matches. It's not clear to me whether you need to consider that more carefully.

SQL to split Comma Separated values and compare it with a multi list value in SSRS

I have a field in my table which has multiple reason codes concatenated in 1 column.
e.g. 2 records
Reason_Codes
Record1: 001,002,004,009,010
Record2: 001,003,005,006
In my SSRS report the user will be searching for data using one of the above reason codes. e.g.
001 will retrieve both records.
005 will retrieve the second record
and so on.
Kindly advise how this can be achieved using SQL or Stored Procedure.
Many thanks.
If you are just passing in a single Reason Code to search on, you don't even need to bother with splitting the comma-separated list: you can just use a LIKE clause as follows:
SELECT tb.field1, tb.field2
FROM SchemaName.TableName tb
WHERE ',' + tb.Reason_Codes + ',' LIKE '%,' + #ReasonCode + ',%';
Try the following to see:
DECLARE #Bob TABLE (ID INT IDENTITY(1, 1) NOT NULL, ReasonCodes VARCHAR(50));
INSERT INTO #Bob (ReasonCodes) VALUES ('001,002,004,009,010');
INSERT INTO #Bob (ReasonCodes) VALUES ('001,003,005,006');
DECLARE #ReasonCode VARCHAR(5);
SET #ReasonCode = '001';
SELECT tb.ID, tb.ReasonCodes
FROM #Bob tb
WHERE ',' + tb.ReasonCodes + ',' LIKE '%,' + #ReasonCode + ',%';
-- returns both rows
SET #ReasonCode = '005';
SELECT tb.ID, tb.ReasonCodes
FROM #Bob tb
WHERE ',' + tb.ReasonCodes + ',' LIKE '%,' + #ReasonCode + ',%';
-- returns only row #2
I have blogged about something like this a long time ago. May be this will help: http://dotnetinternal.blogspot.com/2013/10/comma-separated-to-temp-table.html
The core solution would be to convert the comma separated values into a temporary table and then do a simple query on the temporary table to get your desired result.

SQL Server Code Pages and Collations

Is there any way in SQL Server of determining what a character in a code page would represent without actually creating a test database of that collation?
Example. If I create a test database with collation SQL_Ukrainian_CP1251_CS_AS and then do CHAR(255) it returns я.
If I try the following on a database with SQL_Latin1_General_CP1_CS_AS collation however
SELECT CHAR(255) COLLATE SQL_Ukrainian_CP1251_CS_AS
It returns y
SELECT CHAR(255)
Returns ÿ so it is obviously going first via the database's default collation then trying to find the closest equivalent to that in the explicit collation. Can this be avoided?
Actually I have found an answer to my question now. A bit clunky but does the job unless there's a better way out there?
SET NOCOUNT ON;
CREATE TABLE #Collations
(
code TINYINT PRIMARY KEY
);
WITH E00(N) AS (SELECT 1 UNION ALL SELECT 1), --2
E02(N) AS (SELECT 1 FROM E00 a, E00 b), --4
E04(N) AS (SELECT 1 FROM E02 a, E02 b), --16
E08(N) AS (SELECT 1 FROM E04 a, E04 b) --256
INSERT INTO #Collations
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 0)) - 1
FROM E08
DECLARE #AlterScript NVARCHAR(MAX) = ''
SELECT #AlterScript = #AlterScript + '
RAISERROR(''Processing' + name + ''',0,1) WITH NOWAIT;
ALTER TABLE #Collations ADD ' + name + ' CHAR(1) COLLATE ' + name + ';
EXEC(''UPDATE #Collations SET ' + name + '=CAST(code AS BINARY(1))'');
EXEC(''UPDATE #Collations SET ' + name + '=NULL WHERE ASCII(' + name + ') <> code'');
'
FROM sys.fn_helpcollations()
WHERE name LIKE '%CS_AS'
AND name NOT IN /*Unicode Only Collations*/
( 'Assamese_100_CS_AS', 'Bengali_100_CS_AS',
'Divehi_90_CS_AS', 'Divehi_100_CS_AS' ,
'Indic_General_90_CS_AS', 'Indic_General_100_CS_AS',
'Khmer_100_CS_AS', 'Lao_100_CS_AS',
'Maltese_100_CS_AS', 'Maori_100_CS_AS',
'Nepali_100_CS_AS', 'Pashto_100_CS_AS',
'Syriac_90_CS_AS', 'Syriac_100_CS_AS',
'Tibetan_100_CS_AS' )
EXEC (#AlterScript)
SELECT * FROM #Collations
DROP TABLE #Collations
While MS SQL supports both code pages and Unicode unhelpfully it doesn't provide any functions to convert between the two so figuring out what character is represented by a value in a different code page is a pig.
There are two potential methods I've seen to handle conversions, one is detailed here
http://www.codeguru.com/cpp/data/data-misc/values/article.php/c4571
and involves bolting a custom conversion program onto the database and using that for conversions.
The other is to construct a db table consisting of
[CodePage], [ANSI Value], [UnicodeValue]
with the unicode value stored as either the int representing the unicode character to be converted using nchar()or the nchar itself
Your using the collation SQL_Ukrainian_CP1251_CS_AS which is code page 1251 (CP1251 from the centre of the string). You can grab its translation table here http://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP1251.TXT
Its a TSV so after trimming the top off the raw data should import fairly cleanly.
Personally I'd lean more towards the latter than the former especially for a production server as the former may introduce instability.

Resources