how to split a string column into 4 string columns? - sql-server-2008

I have definition column of a medication. I want the definition to be split into [product name(PN)], [doseform(DF)], [total dose(TD)] and [units].
Eg:
BENADRYL: MYLANTA 1:1 SOLUTION(Benadryl mylanta(PN),Sol(DF),1:1(TD),NULL(units))
MASK AND SPACER (Mark and Spacer(PN),NUll,NUll,NUll)
BL VITAMIN B-6 50 MG TABS(BL Vitamin(PN),Tabs(DF),50(TD),MG(Units))

I've made a few assumptions about your data. 1 is that the first opening bracket begins the definition ie. there are no brackets in the name before the definition. I am also assuming the the product definition structure will not change.
So, to get to your answer, I have used a modified split string function. I used this one as a base: T-SQL split string
The difference here is that the function will return a 2 column table so we can identify the definition parts
CREATE FUNCTION dbo.splitstring_custom
(
#stringToSplit VARCHAR(MAX)
)
RETURNS #returnList TABLE
(
[Type] [nvarchar](20)
,[Name] [nvarchar](500)
)
AS
BEGIN
DECLARE #name NVARCHAR(255)
DECLARE #pos INT
DECLARE #colidx INT = 1
WHILE CHARINDEX(',', #stringToSplit) > 0
BEGIN
SELECT #pos = CHARINDEX(',', #stringToSplit)
SELECT #name = SUBSTRING(#stringToSplit, 1, #pos - 1)
INSERT INTO #returnList
SELECT CASE WHEN #colidx = 1 THEN 'Product Name'
WHEN #colidx = 2 THEN 'Dose Form'
WHEN #colidx = 3 THEN 'Total Dose'
WHEN #colidx = 4 THEN 'Units'
END
,#name
SELECT #stringToSplit = SUBSTRING(#stringToSplit, #pos + 1, LEN(#stringToSplit) - #pos)
SET #colidx += 1
END
INSERT INTO #returnList
SELECT CASE WHEN #colidx = 1 THEN 'Product Name'
WHEN #colidx = 2 THEN 'Dose Form'
WHEN #colidx = 3 THEN 'Total Dose'
WHEN #colidx = 4 THEN 'Units'
END
,#stringToSplit
RETURN
END
Then we get the relevant part of the definition using some string manipulation:
SUBSTRING(medication, CHARINDEX('(', medication) + 1, LEN(medication) - (CHARINDEX('(', medication) + 1))
Then PIVOT the results to flatten it out:
DECLARE #t TABLE (id varchar(20), medication varchar(100))
INSERT INTO #t VALUES ('BENADRYL', 'BENADRYL: MYLANTA 1:1 SOLUTION(Benadryl mylanta(PN),Sol(DF),1:1(TD),NULL(units))')
INSERT INTO #t VALUES ('MASK', 'MASK AND SPACER (Mark and Spacer(PN),NULL,NULL,NULL)')
INSERT INTO #t VALUES ('BL VITAMIN', 'BL VITAMIN B-6 50 MG TABS(BL Vitamin(PN),Tabs(DF),50(TD),MG(Units))')
SELECT id, [Product Name], [Dose Form], [Total Dose], [Units]
FROM (SELECT id, Type, Name
FROM #t t
CROSS APPLY dbo.splitstring_custom(SUBSTRING(medication, CHARINDEX('(', medication) + 1, LEN(medication) - (CHARINDEX('(', medication) + 1)))
) X PIVOT ( MAX(Name) FOR TYPE IN ([Product Name], [Dose Form], [Total Dose], [Units]) ) pvt

Related

Convert result into doubly nested JSON format

I am trying to convert SQL Server results into a doubly nested JSON format.
Source SQL Server table:
ID
Name
Program
Type
Section
Director
Project
Sr Manager
PCM
Contractor
Cost Client
123
abc
qew
tyu
dd
ghghjg
hkhjk
fghfgf
gnhghj
gghgh
gghhg
456
yui
gdffgf
ghgf
jkjlkll
uiop
rtyuui
rfv
ujmk
rfvtg
efgg
Convert into doubly JSON as shown here:
[
[
{"key":"ID","value":"123"},
{"key":"Name","value":"abc"},
{"key":"Program","value":"qew"},
{"key":"Type","value":"tyu"},
{"key":"Section","value":"dd"},
{"key":"Director","value":"ghghjg"},
{"key":"Project","value":"hkhjk"},
{"key":"Sr Manager","value":"fghfgf"},
{"key":"PCM","value":"gnhghj"},
{"key":"Contractor","value":"gghgh"},
{"key":"Cost Client","value":"gghhg"}
],
[
{"key":"ID","value":"456"},
{"key":"Name","value":"yui"},
{"key":"Program","value":"gdffgf"},
{"key":"Type","value":"ghgfjhjhj"},
{"key":"Section","value":"jkjlkll"},
{"key":"Director","value":"uiop"},
{"key":"Project","value":"rtyuui"},
{"key":"Sr Manager","value":"rfv"},
{"key":"PCM","value":"ujmk"},
{"key":"Contractor","value":"rfvtg"},
{"key":"Cost Client","value":"efgg"}
]
]
Any help would be greatly appreciated.
Edit:
I started with this by rewriting the "FOR JSON AUTO" so that I can add "Key" "Value" text somehow.
But because my table has space in the column name, FOR XML PATH('') giving invalid XML identifier as required by FOR XML error.
that is when I thought of taking community help.
Create PROCEDURE [dbo].[GetSQLtoJSON] #TableName VARCHAR(255)
AS
BEGIN
IF OBJECT_ID(#TableName) IS NULL
BEGIN
SELECT Json = '';
RETURN
END;
DECLARE #SQL NVARCHAR(MAX) = N'SELECT * INTO ##T ' +
'FROM ' + #TableName;
EXECUTE SP_EXECUTESQL #SQL;
DECLARE #X NVARCHAR(MAX) = '[' + (SELECT * FROM ##T FOR XML PATH('')) + ']';
SELECT #X = REPLACE(#X, '<' + Name + '>',
CASE WHEN ROW_NUMBER() OVER(ORDER BY Column_ID) = 1 THEN '{'
ELSE '' END + Name + ':'),
#X = REPLACE(#X, '</' + Name + '>', ','),
#X = REPLACE(#X, ',{', '}, {'),
#X = REPLACE(#X, ',]', '}]')
FROM sys.columns
WHERE [Object_ID] = OBJECT_ID(#TableName)
ORDER BY Column_ID;
DROP TABLE ##T;
SELECT Json = #X;
END
Sample data:
CREATE TABLE [dbo].[Test1](
[ID] [int] IDENTITY(1,1) NOT NULL,
[Col1] [int] NOT NULL,
[Col 2] varchar(50)
) ON [PRIMARY]
GO
SET IDENTITY_INSERT [dbo].[Test1] ON
GO
INSERT [dbo].[Test1] ([ID], [Col1], [Col 2]) VALUES (1, 0,'ABCD')
GO
INSERT [dbo].[Test1] ([ID], [Col1] ,[Col 2]) VALUES (2, 1, 'POIU')
GO
SET IDENTITY_INSERT [dbo].[Test1] OFF
GO
You can use the following code:
Inside an APPLY, unpivot the columns as key/value pairs...
... and aggregate using FOR JSON PATH
Use STRING_AGG to do another aggregation.
SELECT '[' + STRING_AGG(CAST(v.json AS nvarchar(max)), ',') + ']'
FROM T
CROSS APPLY (
SELECT *
FROM (VALUES
('ID', CAST(ID AS nvarchar(100))),
('Name', Name),
('Program', Program),
('Type', [Type]),
('Section', Section),
('Director', Director),
('Project', Project),
('Sr Manager', [Sr Manager]),
('PCM', PCM),
('Contractor', Contractor),
('Cost Client', [Cost Client])
) v([key], value)
FOR JSON PATH
) v(json)
db<>fiddle
You cannot use FOR JSON again, because then you will get ["json": [{"key" : ...
first of all check this link you can find what you want
format-query-results-as-json-with-for-json-sql-server
but in your case you can try this
SELECT
ID,Name,Program,Type,Section,
Director,Project,Sr,Manager,PCM,Contractor,Cost,Client
FROM table
FOR JSON AUTO;
check the link there is more sample so it can help you

Postgres Returning HTML table tags grouped by column for resultset

I'm trying to construct a function that generates the row and cell HTML tags which will be used in another application to build a basic HTML table. The row and cells need to be grouped/concatenated by the region id. This is the part I'm currently struggling with.
I've tried to put the following function together, but not really sure where to go to ensure that the output is grouped correctly by the region_id.
This is not something I would generally do SQL, but I'm working with some limited technologies.
create table reporting
(
id integer,
region_id integer,
category text,
item text,
status text
);
insert into reporting values
(1, 1, 'audio', 'speakers', 'delivered'),
(2, 1, 'display', 'monitors', 'pending'),
(3, 2, 'cables', 'hdmi', 'pre-order'),
(4, 3, 'storage', 'sdd', 'cancelled'),
(5, 3, 'software', 'business', 'delivered'),
(6, 3, 'other', 'support', 'delivered');
create function html_out (query text)
returns TABLE(region_id text, result text) language plpgsql as $$
declare
rec record;
header boolean := true;
begin
for rec in
execute format($q$
select row_to_json(q) json_row
from (%s) q
$q$, query)
loop
return query select region_id,
format ('<tr><td>%s</td></tr>', string_agg(value, '</td><td>'))
from json_each_text(rec.json_row);
end loop;
end $$;
select html_out('select region_id, category, item, status from reporting');
You can use string aggregation. I think the logic you want is:
select
region_id,
'<tr><td>'
|| string_agg(concat_ws('</td><td>', category, item, status), '</td></tr><tr><td>')
|| '</td></tr>' html
from reporting
group by region_id
order by 1
Demo on DB Fiddle
region_id | html
--------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1 | <tr><td>audio</td><td>speakers</td><td>delivered</td></tr><tr><td>display</td><td>monitors</td><td>pending</td></tr>
2 | <tr><td>cables</td><td>hdmi</td><td>pre-order</td></tr>
3 | <tr><td>storage</td><td>sdd</td><td>cancelled</td></tr><tr><td>software</td><td>business</td><td>delivered</td></tr><tr><td>other</td><td>support</td><td>delivered</td></tr>
If this is about consolidating repeating region_id with rowspan=, then you can get that in one go like this:
with consolidate as (
select region_id, concat('<td rowspan="', count(*), '">') as rowspan
from reporting
group by region_id
), trows as (
select row_number() over (order by r.region_id, r.id) as rnum,
concat(
'<tr>',
case
when lag(c.region_id) over w = c.region_id then ''
else concat(c.rowspan, c.region_id, '</td>')
end,
'<td>',
array_to_string(array[r.category, r.item, r.status]::text[], '</td><td>', '</td>'),
'</tr>'
) as html
from consolidate c
join reporting r on r.region_id = c.region_id
window w as (partition by r.region_id order by r.id)
)
select array_to_string(array_agg(html order by rnum), '
')
from trows;
-[ RECORD 1 ]---+-----------------------------------------------------------------------------
array_to_string | <tr><td rowspan="2">1</td><td>audio</td><td>speakers</td><td>delivered</tr> +
| <tr><td>display</td><td>monitors</td><td>pending</tr> +
| <tr><td rowspan="1">2</td><td>cables</td><td>hdmi</td><td>pre-order</tr> +
| <tr><td rowspan="3">3</td><td>storage</td><td>sdd</td><td>cancelled</tr> +
| <tr><td>software</td><td>business</td><td>delivered</tr> +
| <tr><td>other</td><td>support</td><td>delivered</tr>
I put a newline in as the delimiter for that last array_to_string() just to get it to look nice. A ' ' or '' would probably be what you want.

SQL remove the next letter/character after a character ^

How to remove a character after the character ^ from a selected rows in table?
e.g.
TABLE Things
Boat
Do^2gs
Cat^fs
^KBear
Mi^&ce
D^Rice
RESULTS:
Boat
Dogs
Cats
Bear
Mice
Dice
select case when charindex('^', col) <> 0
then stuff(col, charindex('^', col), 2, '')
else col
end
-- to handle multiple ^ up to max of 4
select t.col,
r4.col
from Things t
cross apply
(
select col = case when charindex('^', col) <> 0
then stuff(col, charindex('^', col), 2, '')
else col
end
) r1
cross apply
(
select col = case when charindex('^', r1.col) <> 0
then stuff(r1.col, charindex('^', r1.col), 2, '')
else r1.col
end
) r2
cross apply
(
select col = case when charindex('^', r2.col) <> 0
then stuff(r2.col, charindex('^', r2.col), 2, '')
else r2.col
end
) r3
cross apply
(
select col = case when charindex('^', r3.col) <> 0
then stuff(r3.col, charindex('^', r3.col), 2, '')
else r3.col
end
) r4
-- UDF to remove the ^
create function remove_chr
(
#str varchar(100)
)
returns varchar(100)
as
begin
while charindex('^', #str) <> 0
begin
select #str = case
when charindex('^', #str) <> 0
then stuff(#str, charindex('^', #str), 2, '')
else #str
end
end
return #str
end
If you are using MySQL you could use:
SELECT col,
IF(INSTR(col,'^') > 0,CONCAT(LEFT(col,INSTR(col, '^')-1),
RIGHT(col,LENGTH(col) - INSTR(col, '^')-1)), col) AS result
FROM Things;
SqlFiddleDemo
And SQL Server equivalent:
SELECT col,
IIF(CHARINDEX('^',col) > 0,CONCAT(LEFT(col,CHARINDEX('^',col)-1),
RIGHT(col,LEN(col) - CHARINDEX('^',col)-1)), col) AS result
FROM Things
LiveDemo
SQL Server 2008:
SELECT col,
CASE WHEN CHARINDEX('^',col) > 0
THEN LEFT(col,CHARINDEX('^',col)-1) + RIGHT(col,LEN(col) - CHARINDEX('^',col)-1)
ELSE col
END AS result
FROM Things;
Keep in mind that it will work only if there is none or one occurence of ^.
Here is the solution which removes any number of occurrence of '^' .
I have created a function SQL server which is not used any loop or cursor.
CREATE FUNCTION [dbo].[FnReplaceChar](#pOriginalText VARCHAR(2000))
RETURNS VARCHAR(1000)
AS
BEGIN
DECLARE #vText VARCHAR(1000)
,#vXML XML
--Convert text as XML format
SELECT #vXML = '<Root><dtl><f>' + REPLACE(#pOriginalText,'^','</f></dtl><dtl><f>^')+'</f></dtl></Root>'
--Splits words started with '^' and combines after removing character starts with '^'
SET #vText = (
SELECT '' + ACT_TEXT
FROM
(
SELECT CASE WHEN CHARINDEX('^',DOC.COL.value('f[1]','VARCHAR(100)') ,0) > 0
THEN STUFF(DOC.COL.value('f[1]','VARCHAR(100)'),CHARINDEX('^',DOC.COL.value('f[1]','VARCHAR(100)') ,0),2,'')
ELSE DOC.COL.value('f[1]','VARCHAR(100)')
END AS ACT_TEXT
FROM #vXML.nodes('/Root/dtl') DOC(COL)
)T
FOR XML PATH('')
)
RETURN #vText
END
You can use this function in your select query
SELECT dbo.[FnReplaceChar](col_Name)
FROM [Things]

SQL CASE in SELECT clause

I need to add a concatenated field to my SELECT clause based on the results of my WHERE clause. Here is my current query:
Declare
#Low numeric(13,0) = 10000,
#High numeric(13,0) = 100000000000,
#Name varchar(100) = '%',
#Stname varchar(100) = '%georgia',
#Sumlev varchar(3) = 1,
#County varchar(30) = 123456,
#Place varchar (5) = 8,
#Gid2 varchar(5),
#Gid2_1 varchar (7);
if object_id('tempdb..#GeoID2') is not null drop table #GeoID2
SELECT
SUMLEV, State, County, Place, Name, Stname, ESTIMATESBASE2010, CONCAT(REPLICATE('0', 2 - LEN(STATE)) + STATE, REPLICATE('0', 3 - LEN(COUNTY)) + COUNTY) AS Gid2
INTO
#GeoID2
FROM
[CensusData].[dbo].[SUB-EST2014_ALL]
WHERE
(NOT (SUMLEV = #Sumlev)) AND (NOT (County = #County)) AND (NOT (PLACE = #Place)) AND
(ESTIMATESBASE2010 > #Low) AND (ESTIMATESBASE2010 < #High) AND (Name LIKE #Name) and (Stname LIKE #Stname)
SELECT
[#GeoID2].SUMLEV, [#GeoID2].State, [#GeoID2].County, [#GeoID2].Place, [#GeoID2].Stname, [#GeoID2].Name, [#GeoID2].ESTIMATESBASE2010, [ACS_14_5YR_S1901_with_ann].[HC01_EST_VC13] as Avg_Salary, [#GeoID2].Gid2, [#GeoID2].Gid21
FROM
[CensusData].[dbo].[#GeoID2] INNER JOIN
[CensusData].[dbo].[ACS_14_5YR_S1901_with_ann] ON [CensusData].[dbo].[#GeoID2].Gid2 = [CensusData].[dbo].[ACS_14_5YR_S1901_with_ann].[GEO id2]
ORDER BY
[#GeoID2].NAME
I need to add a CASE statement to my first SELECT clause to assign a value to Gid2. The current value is assigned as:
CONCAT(REPLICATE('0', 2 - LEN(STATE)) + STATE, REPLICATE('0', 3 - LEN(COUNTY)) + COUNTY) AS Gid2
What I need to do is check the value of [County] before I assign this value. Based on that result I will use one of two formulas. Here is what I tried (that does not work):
Case
When (County = 0)
Then 'CONCAT(REPLICATE('0', 2 - LEN(STATE)) + STATE, REPLICATE('0', 5 - LEN(PLACE)) + PLACE) AS Gid2'
Else 'CONCAT(REPLICATE('0', 2 - LEN(STATE)) + STATE, REPLICATE('0', 3 - LEN(COUNTY)) + COUNTY) AS Gid2'
End
I am not sure that using CASE is the right way to go, but I can't figure out any other way to approach this. Can someone help?
Ok I figured it out. I was using ' in front of my Concat function when I should have been using (. Here is a working query:
Declare
#Low numeric(13,0) = 10000,
#High numeric(13,0) = 100000000000,
#Name varchar(100) = '%',
#Stname varchar(100) = '%georgia',
#Sumlev varchar(3) = 1,
#County varchar(30) = 123456,
#Place varchar (5) = 8,
#Gid2 varchar(5),
#Gid2_1 varchar (7);
if object_id('tempdb..#GeoID2') is not null drop table #GeoID2
SELECT
SUMLEV, State, County, Place, Name, Stname, ESTIMATESBASE2010,
Case
When (County = 0)
Then (CONCAT(REPLICATE('0', 2 - LEN(STATE)) + STATE, REPLICATE('0', 5 - LEN(PLACE)) + PLACE))
Else (CONCAT(REPLICATE('0', 2 - LEN(STATE)) + STATE, REPLICATE('0', 3 - LEN(COUNTY)) + COUNTY))
End AS Gid2
INTO
#GeoID2
FROM
[CensusData].[dbo].[SUB-EST2014_ALL]
WHERE
(NOT (SUMLEV = #Sumlev)) AND (NOT (County = #County)) AND (NOT (PLACE = #Place)) AND
(ESTIMATESBASE2010 > #Low) AND (ESTIMATESBASE2010 < #High) AND (Name LIKE #Name) and (Stname LIKE #Stname)
SELECT
[#GeoID2].SUMLEV, [#GeoID2].State, [#GeoID2].County, [#GeoID2].Place, [#GeoID2].Stname, [#GeoID2].Name, [#GeoID2].ESTIMATESBASE2010, [ACS_14_5YR_S1901_with_ann].[HC01_EST_VC13] as Avg_Salary, [#GeoID2].Gid2
FROM
[CensusData].[dbo].[#GeoID2] INNER JOIN
[CensusData].[dbo].[ACS_14_5YR_S1901_with_ann] ON [CensusData].[dbo].[#GeoID2].Gid2 = [CensusData].[dbo].[ACS_14_5YR_S1901_with_ann].[GEO id2]
ORDER BY
[#GeoID2].NAME

How to wirte a query for updating two tables at a time?

HI i have two tables in my database named...Requests and Balance tracker which has no relation....but i want to select data from two tables and binf it two grid...
Requests
EmpID |EmpRqsts|EmpDescription|ApproverID|ApprovedAmount|RequestPriority
1 |asdfsb |sadbfsbdf |1 |
2 |asbfd |sjkfbsd |1 |
Balance Tracker
EmpId|BalanceAmnt|LastUpdated|lastApprovedAmount
| 1 |5000 |sdfbk |
| 2 |3000 |sjbfsh |
now i want to update based on the EmpID two tables at a time...when ever amount is approved it should be updates in request table column [ApprovedAmount] and with priority...
when [ApprovedAmount] is Updated [BalanceAmnt] Balance Tracker of also should be Updated by adding the amount approved,[LastUpdated],[lastApprovedAmount] should be updated with date and time
can any one help me with the query please....
#Anil, here is an example of SQL Server 2008 code which would help you to get your goal acomplished:
DECLARE #Requests TABLE
(
EmpId int
, EmpRqsts nvarchar(50)
, EmpDescription nvarchar(250)
, ApproverID int
, ApprovedAmount money
, RequestPriority int
)
DECLARE #BalanceTracker TABLE
(
EmpId int
, BalanceAmnt money
, LastUpdated datetime
, lastApprovedAmount money
)
-- Insert data for testing
INSERT INTO #Requests VALUES
(
1
, 'Something here'
, 'Some descriptio here'
, 1
, 100
, 1
)
INSERT INTO #Requests VALUES
(
2
, 'Something here 2 '
, 'Some descriptio here 3'
, 1
, 215
, 2
)
INSERT INTO #BalanceTracker VALUES
(
1
, 5000
, GETDATE() - 3
, 310
)
INSERT INTO #BalanceTracker VALUES
(
2
, 3000
, (GETDATE() - 1)
, 98
)
-- Declare local variables
DECLARE
#NewAmount money
, #NewPriority int
, #SelectedEmpId int
-- Assing values for example
SELECT #NewAmount = 1000
, #SelectedEmpId = 1
, #NewPriority = 5
-- Get the tables values pre - updates
SELECT *
FROM #Requests
SELECT *
FROM #BalanceTracker
BEGIN TRY
-- Update the record with new ApprovedAmount and Request Priority
UPDATE #Requests
SET ApprovedAmount = #NewAmount
, RequestPriority = #NewPriority
WHERE EmpId = #SelectedEmpId
-- If no error found then update BalanceAmnt trable
IF (##ERROR = 0)
BEGIN TRY
UPDATE #BalanceTracker
SET BalanceAmnt = (BalanceAmnt + #NewAmount)
, LastUpdated = GETDATE()
, lastApprovedAmount = #NewAmount
WHERE EmpId = #SelectedEmpId
END TRY
BEGIN CATCH
PRINT N'Error found updating #BalanceTracker table: ' + ISNULL(LTRIM(STR(ERROR_NUMBER())) , N'Unknown Error' )
+ N', Message: ' + ISNULL ( ERROR_MESSAGE() , N'No Message' )
END CATCH
END TRY
BEGIN CATCH
PRINT N'Error found updating #Requests table: ' + ISNULL(LTRIM(STR(ERROR_NUMBER())) , N'Unknown Error' )
+ N', Message: ' + ISNULL ( ERROR_MESSAGE() , N'No Message' )
END CATCH
-- Get the tables values post - updates
SELECT *
FROM #Requests
SELECT *
FROM #BalanceTracker
Note 1: #Table are Variable Tables handlded by SQL Server 2008. If you're using previous version you should be able to create Temporary Table (#Table).
Note 2: data data-types may vary depending upon the SQL version you're using.
You could do this type of thing with a trigger. This way whenever you do the first update, it will automatically do the other update you specify.