I want to replace a value, in a SELECT statement, if the value matches a value in a lookup table. This is to handle a mapping from a child to a parent.
DECLARE #Mappings TABLE
(
IdKey INT IDENTITY PRIMARY KEY ,
ParentModule NVARCHAR(255) ,
ChildModule NVARCHAR(255)
)
This is populated with child modules and their parent module, there will be about 200 such mappings.
Then in my SELECT statement I want to use the ParentModule instead of the Child but if the child is not matched then use whatever value would have been selected.
SELECT DISTINCT
RTRIM(StudentId) ,
ISNULL(( RTRIM(AOSCode) + '_' + RTRIM(AOSPeriod) ), '') AS Module
FROM Curriculum
The value I want to compare to ChildModule is (RTRIM(AOSCode) + '_' + RTRIM(AOSPeriod)). So if that matches I want the select to return the #Mappings ParentModule, otherwise the value returned by the concatenation of AOSCode_AOSPeriod
The SELECT is used in an INSERT INTO statement...
Try this:
SELECT DISTINCT
RTRIM(StudentId) ,
ISNULL(Map.ParentModule,ISNULL((RTRIM(AOSCode) + '_' + RTRIM(AOSPeriod)), '')) AS Module
FROM
Curriculum AS Cr
LEFT JOIN #Mappings AS Map ON
((RTRIM(Cr.AOSCode) + '_' + RTRIM(Cr.AOSPeriod)) = Map.ChildModule;
You will join Expression RTRIM(AOSCode) + '_' + RTRIM(AOSPeriod) with ChildModule. If there is a match you will show ParentModule. Otherwise you will show ISNULL(( RTRIM(AOSCode) + '_' + RTRIM(AOSPeriod) ), '').
Please take into account that I can not decide from your data if ISNULL have to be used in the expression in the join.
Related
I have a json document with an internal array of attributes. On one of these attributes, the key name changes dynamically/randomly. I can easily extract all the data points except for this last pesky attribute. All the methods I have found or used in the past with OPENJSON relied on the key name being known.
Inside the the "inner" array, the first attribute will have a key name that changes. I want to extract the value associated with that dynamic key, without knowing exactly what that key will be. Hopefully the code below will describe the problem better than I can with words.
Here is what the JSON document looks like formatted for readability ...
{
"outer1": {
"inner1": {
"dynamicKey123": "attribute1",
"staticKey1": "attribute2",
"staticKey2": "attribute3",
"staticKey3": "attribute4"
}
},
"outer2": {
"inner2": {
"dynamicKeyABC": "attribute1",
"staticKey1": "attribute2",
"staticKey2": "attribute3",
"staticKey3": "attribute4"
}
}
}
Some code to test with ...
CREATE TABLE openjson_test (json_col VARCHAR(MAX));
INSERT INTO openjson_test (json_col)
VALUES ('{"outer1":{"inner1":{"dynamicKey123":"attribute1","staticKey1":"attribute2","staticKey2":"attribute3","staticKey3":"attribute4"}},"outer2":{"inner2":{"dynamicKeyABC":"attribute1","staticKey1":"attribute2","staticKey2":"attribute3","staticKey3":"attribute4"}}}');
The query I have developed so far with troublesome parts commented out ...
SELECT
json_col,
so.[key] AS soKey,
si.[key] AS siKey,
si.[value] AS siValue,
--ar.dynamicKey,
ar.staticKey1,
ar.staticKey2,
ar.staticKey3
FROM openjson_test
CROSS APPLY OPENJSON(json_col) so
CROSS APPLY OPENJSON(json_col, '$.' + so.[key]) si
CROSS APPLY OPENJSON(json_col, '$.' + so.[key] + '.' + si.[key])
WITH (
--dynamicKey VARCHAR(256) '$.dynamicKey???', How do I extract this value without knowing the key
staticKey1 VARCHAR(256) '$.staticKey1',
staticKey2 VARCHAR(256) '$.staticKey2',
staticKey3 VARCHAR(256) '$.staticKey3'
) ar
I'd suggest an approach with conditional aggregation
SELECT
so.[key] AS soKey,
si.[key] AS siKey,
MAX(CASE WHEN attr.[key] NOT IN('staticKey1','staticKey2','staticKey3') THEN attr.[value] END) AS DynamicAttr,
MAX(CASE WHEN attr.[key]='staticKey1' THEN attr.[value] END) AS attrKey1,
MAX(CASE WHEN attr.[key]='staticKey2' THEN attr.[value] END) AS attrKey2,
MAX(CASE WHEN attr.[key]='staticKey3' THEN attr.[value] END) AS attrKey3
FROM openjson_test
CROSS APPLY OPENJSON(json_col) so
CROSS APPLY OPENJSON(json_col, '$.' + so.[key]) si
CROSS APPLY OPENJSON(json_col, '$.' + so.[key] + '.' + si.[key]) attr
GROUP BY so.[key],si.[key];
This technique is used in PIVOT scenarios but allows for a more generic logic.
You can use OPENJSON without the WITH clause and filter out the column with known names:
SELECT json_col,
so.[key] AS soKey,
si.[key] AS siKey,
si.[value] AS siValue,
ar2.Value AS dynamicKey,
ar.staticKey1,
ar.staticKey2,
ar.staticKey3
FROM dbo.openjson_test t
CROSS APPLY OPENJSON(t.json_col) so
CROSS APPLY OPENJSON(json_col, '$.' + so.[key]) si
CROSS APPLY OPENJSON(json_col, '$.' + so.[key] + '.' + si.[key])
WITH (
staticKey1 VARCHAR(256) '$.staticKey1',
staticKey2 VARCHAR(256) '$.staticKey2',
staticKey3 VARCHAR(256) '$.staticKey3'
) ar
CROSS APPLY (
SELECT *
FROM OPENJSON(json_col, '$.' + so.[key] + '.' + si.[key])
WHERE [Key] NOT IN ('staticKey1','staticKey2','staticKey3')
) ar2
I am trying to normalize my tables to make the db more efficient.
To do this I have removed several columns from a table that I was updating several columns on.
Here is the original query when all the columns were in the table:
UPDATE myActDataBaselDataTable
set [Correct Arrears 2]=(case when [Maturity Date]='' then 0 else datediff(d,convert(datetime,#DataDate, 102),convert(datetime,[Maturity Date],102)) end)
from myActDataBaselDataTable
Now I have removed [Maturity Date] from the table myActDataBaselDataTable and it's necessary to retrieve that column from the base reference table ACTData, where it is called Mat.
In my table myActDataBaselDataTable the Account number field is a concatenation of 3 fields in ACTData, thus
myActDataBaselDataTable.[Account No]=ac.[Unit] + ' ' + ac.[ACNo] + ' ' + ac.[Suffix]
(where ac is the alias for ACTData)
So, having looked at the answers given elsewhere on SO (such as 1604091: update-a-table-using-join-in-sql-server), I tried to modify this particular update statement as below, but I cannot get it right:
UPDATE myActDataBaselDataTable
set dt.[Correct Arrears 2]=(
case when ac.[Mat]=''
then 0
else datediff(d,convert(datetime,'2014-04-30', 102),convert(datetime,ac.[Mat],102))
end)
from ACTData ac
inner join myActDataBaselDataTable dt
ON dt.[Account No]=ac.[Unit] + ' ' + ac.[ACNo] + ' ' + ac.[Suffix]
I either get an Incorrect syntax near 'From' error, or The multi-part identifier "dt.Correct Arrears 2" could not be bound.
I'd be grateful for any guidance on how to get this right, or suugestiopns about how to do it better.
thanks
EDIT:
BTW, when I run the below as a SELECT it returns data with no errors:
select case when [ac].[Mat]=''
then 0
else datediff(d,convert(datetime,'2014-04-30', 102),convert(datetime,[ac].[Mat],102))
end
from ACTData ac
inner join myActDataBaselDataTable dt
ON dt.[Account No]=ac.[Unit] + ' ' + ac.[ACNo] + ' ' + ac.[Suffix]
In a join update, update the alias
update dt
What is confusing is that in later versions of SQL you don't need to use the alias in the update line
I am working on migrating data from one database to another for a hospital. In the old database, the doctor's specialty IDs are all in one column (swvar_specialties), each separated by commas. In the new database, each specialty ID will have it's own column (example: Specialty1_PrimaryID, Specialty2_PrimaryID, Specialty3_PrimaryID, etc). I am trying to export the data out of the old database and separate these into these separate columns. I know I can use indexof and substring to do this - I just need help with the syntax.
So this query:
Select swvar_specialties as Specialty1_PrimaryID
From PhysDirectory
might return results similar to 39,52,16. I need this query to display Specialty1_PrimaryID = 39, Specialty2_PrimaryID = 52, and Specialty3_PrimaryID = 16 in the results. Below is my query so far. I will eventually have a join to pull the specialty names from the specialties table. I just need to get this worked out first.
Select pd.ref as PrimaryID, pd.swvar_name_first as FirstName, pd.swvar_name_middle as MiddleName,
pd.swvar_name_last as LastName, pd.swvar_name_suffix + ' ' + pd.swvar_name_degree as NameSuffix,
pd.swvar_birthdate as DateOfBirth,pd.swvar_notes as AdditionalInformation, 'images/' + '' + pd.swvar_photo as ImageURL,
pd.swvar_philosophy as PhilosophyOfCare, pd.swvar_gender as Gender, pd.swvar_specialties as Specialty1_PrimaryID, pd.swvar_languages as Language1_Name
From PhysDirectory as pd
The article Split function equivalent in T-SQL? provides some details on how to use a split function to split a comma-delimited string.
By modifying the table-valued function in presented in this article to provide an identity column we can target a specific row such as Specialty1_PrimaryID:
/*
Splits string into parts delimitered with specified character.
*/
CREATE FUNCTION [dbo].[SDF_SplitString]
(
#sString nvarchar(2048),
#cDelimiter nchar(1)
)
RETURNS #tParts TABLE (id bigint IDENTITY, part nvarchar(2048) )
AS
BEGIN
if #sString is null return
declare #iStart int,
#iPos int
if substring( #sString, 1, 1 ) = #cDelimiter
begin
set #iStart = 2
insert into #tParts
values( null )
end
else
set #iStart = 1
while 1=1
begin
set #iPos = charindex( #cDelimiter, #sString, #iStart )
if #iPos = 0
set #iPos = len( #sString )+1
if #iPos - #iStart > 0
insert into #tParts
values ( substring( #sString, #iStart, #iPos-#iStart ))
else
insert into #tParts
values( null )
set #iStart = #iPos+1
if #iStart > len( #sString )
break
end
RETURN
END
Your query can the utilise this split function as follows:
Select
pd.ref as PrimaryID,
pd.swvar_name_first as FirstName,
pd.swvar_name_middle as MiddleName,
pd.swvar_name_last as LastName,
pd.swvar_name_suffix + ' ' + pd.swvar_name_degree as LastName,
pd.swvar_birthdate as DateOfBirth,pd.swvar_notes as AdditionalInformation,
'images/' + '' + pd.swvar_photo as ImageURL,
pd.swvar_philosophy as PhilosophyOfCare, pd.swvar_gender as Gender,
(Select part from SDF_SplitString(pd.swvar_specialties, ',') where id=1) as Specialty1_PrimaryID,
(Select part from SDF_SplitString(pd.swvar_specialties, ',') where id=2) as Specialty2_PrimaryID,
pd.swvar_languages as Language1_Name
From PhysDirectory as pd
I already have query to concatenate
DECLARE #ids VARCHAR(8000)
SELECT #ids = COALESCE(#ids + ', ', '') + concatenatedid
FROM #HH
but if I have to do it inline how can I do that? Any help please.
SELECT sum(quantity), COALESCE(#ids + ', ', '') + concatenatedid from #HH
Thanks.
Use the XML PATH trick. You may need a CAST
SELECT
SUBSTRING(
(
SELECT
',' + concatenatedid
FROM
#HH
FOR XML PATH ('')
)
, 2, 7999)
Also:
Join characters using SET BASED APPROACH (Sql Server 2005)
Subquery returned more than 1 value
I have the following SP
CREATE PROCEDURE GetAllHouses
set #webRegionID = 2
set #sortBy = 'case_no'
set #sortDirection = 'ASC'
AS
BEGIN
Select
tbl_houses.*
from tbl_houses
where
postal in (select zipcode from crm_zipcodes where web_region_id = #webRegionID)
ORDER BY
CASE UPPER(#sortBy)
when 'CASE_NO' then case_no
when 'AREA' then area
when 'FURNISHED' then furnished
when 'TYPE' then [type]
when 'SQUAREFEETS' then squarefeets
when 'BEDROOMS' then bedrooms
when 'LIVINGROOMS' then livingrooms
when 'BATHROOMS' then bathrooms
when 'LEASE_FROM' then lease_from
when 'RENT' then rent
else case_no
END
END
GO
Now everything in that SP works but I want to be able to choose whether I want to sort ASCENDING or DESCENDING.
I really can't fint no solution for that using SQL and can't find anything in google.
As you can see I have the parameter sortDirection and I have tried using it in multiple ways but always with errors... Tried Case Statements, IF statements and so on but it is complicated by the fact that I want to insert a keyword.
Help will be very much appriciated, I have tried must of the things that comes into mind but haven't been able to get it right.
You could use two order by fields:
CASE #sortDir WHEN 'ASC' THEN
CASE UPPER(#sortBy)
...
END
END ASC,
CASE #sortDir WHEN 'DESC' THEN
CASE UPPER(#sortBy)
...
END
END DESC
A CASE will evaluate as NULL if none of the WHEN clauses match, so that causes one of the two fields to evaluate to NULL for every row (not affecting the sort order) and the other has the appropriate direction.
One drawback, though, is that you'd need to duplicate your #sortBy CASE statement. You could achieve the same thing using dynamic SQL with sp_executesql and writing a 'ASC' or 'DESC' literal depending on the parameter.
That code is going to get very unmanageable very quickly as you'll need to double nest your CASE WHEN's... one set for the Column to order by, and nested set for whethers it's ASC or DESC
Might be better to consider using Dynamic SQL here...
DECLARE #sql nvarchar(max)
SET #sql = '
Select
tbl_houses.*
from tbl_houses
where
postal in (select zipcode from crm_zipcodes where web_region_id = ' + #webRegionID + ') ORDER BY '
SET #sql = #sql + ' ' + #sortBy + ' ' + #sortDirection
EXEC (#sql)
You could do it with some dynamic SQL and calling it with an EXEC. Beware SQL injection though if the user has any control over the parameters.
CREATE PROCEDURE GetAllHouses
set #webRegionID = 2
set #sortBy = 'case_no'
set #sortDirection = 'ASC'
AS
BEGIN
DECLARE #dynamicSQL NVARCHAR(MAX)
SET #dynamicSQL =
'
SELECT
tbl_houses.*
FROM
tbl_houses
WHERE
postal
IN
(
SELECT
zipcode
FROM
crm_zipcodes
WHERE
web_region_id = ' + CONVERT(nvarchar(10), #webRegionID) + '
)
ORDER BY
' + #sortBy + ' ' + #sortDirection
EXEC(#dynamicSQL)
END
GO