SQL - Sum content of cell - sql

I have a database with a table that very simplified looks like this
Column 1 Column 2 (varchar)
Vector 1 23^34^45^65
Vector 2 0^54^10^31
Now I want to sum the numbers in the cells of column 2 together. That is, I want it to look like this:
Column 1 Column 2 (varchar)
Vector 1 167
Vector 2 95
How do I do this in SQL?

Perhaps you want something like this:
SELECT a.col1, sum_of_values = SUM(d.val)
FROM (VALUES ('Vector 1', '23^34^45^65'), ('Vector 2', '0^54^10^31')) a (col1, col2)
CROSS APPLY (SELECT CONVERT(xml, '<a>' + REPLACE(a.col2, '^', '</a><a>')+'</a>')) AS b(doc)
CROSS APPLY b.doc.nodes('a') c (item)
CROSS APPLY (SELECT c.item.value('.', 'int')) d (val)
GROUP BY a.col1
Output:
col1 sum_of_values
-------- -------------
Vector 1 167
Vector 2 95
Explanation:
The VALUES clause is a placeholder for your data.
By REPLACE'ing caret (^) with XML tags we can use methods on the xml datatype to efficiently split values.
CROSS APPLY with the nodes() method of the xml datatype returns a new row per item, and an xml column containing the item value.
value('.', 'int') converts the inner text of an <a> element to an int.
The GROUP BY and SUM aggregate these results, and reduce the number of rows back to the original two.
Edit: You could move the split into its own function. Like this:
CREATE FUNCTION dbo.SplitIntOnCaret (#list varchar(max)) RETURNS TABLE AS RETURN
SELECT Position = ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
, Value = item.value('.', 'int')
FROM (SELECT CONVERT(xml, '<a>' + REPLACE(#list, '^', '</a><a>')+'</a>')) AS a(doc)
CROSS APPLY doc.nodes('a') c (item)
So your final query could look like this:
SELECT l.list_id, sum_of_values = SUM(s.value)
FROM dbo.MyLists l
CROSS APPLY dbo.SplitIntOnCaret(l.list) AS s
GROUP BY l.list_id

If you cannot change the way your data is stored, you will have to
Split the String (see Turning a Comma Separated string into individual rows)
Compute the SUM using GROUP BY.

Related

Extract two values between square brackets an get the results in two different columns?

I have in a column some Names and then square brackets with some numbers and letters inside.
How can I extract two values between square brackets and get the results in two different columns?
I start from the Column 'NAME' with the value 'XCDRT [20.9 kd]'
--NAME--
XCDRT [20.9 kd]
qwer [12.234 r.t.]
and I would like to get 3 columns with the values in different columns
-- NAME--- NAME 1--- NAME 2---
--XCDRT---- 20.9-------- kd----
--qwer----- 12.234-------- r.t.-----
Is there a function for such a problem?
I tried to split the value but I don't get the results that I need.
As an alternative solution, if you are on a bleeding edge version of the SQL Server data engine, then you make use of STRING_SPLIT and it's (new) ability to return the ordinal position of a value. Then, with some conditional aggregation, you can unpivot the results:
SELECT TRIM(MAX(CASE N.ordinal WHEN 1 THEN N.[value] END)) AS [Name],
TRIM(MAX(CASE N.ordinal WHEN 2 THEN LEFT(N.[value], CHARINDEX(' ',N.[value] + ' ')) END)) AS [Name1],
TRIM(MAX(CASE N.ordinal WHEN 2 THEN NULLIF(STUFF(N.[value], 1, CHARINDEX(' ',N.[value] + ' '),''),'') END)) AS [Name2]
FROM (VALUES('XCDRT [20.9 kd] qwer [12.234 r.t.]'))V([NAME])
CROSS APPLY STRING_SPLIT(V.[NAME],']',1) R
CROSS APPLY STRING_SPLIT(R.[value],'[',1) N
WHERE R.[value] != ''
GROUP BY V.[NAME],
R.ordinal;
The TRIMs and NULLIF are there to "tidy" the values, as you'd have leading whitespace and incase you don't have a value for Name2.
With a bit of JSON and a CROSS APPLY (or two)
Cross Apply B will split/parse the string
Cross Apply C will create JSON to be consumed.
This will also support N groups of 3
Example
Declare #YourTable Table ([Name] varchar(50)) Insert Into #YourTable Values
('XCDRT [20.9 kd] qwer [12.234 r.t.]')
Select [Name] = JSON_VALUE(JS,'$[0]')
,[Name1] = JSON_VALUE(JS,'$[1]')
,[Name2] = JSON_VALUE(JS,'$[2]')
From #YourTable A
Cross Apply string_split([Name],']') B
Cross Apply ( values ('["'+replace(string_escape(trim(replace(B.Value,'[','')),'json'),' ','","')+'"]') ) C(JS)
Where B.value<>''
Results
Name Name1 Name2
XCDRT 20.9 kd
qwer 12.234 r.t.

Is there a method to simply transpose a table in SQL. This table contains Numeric and Varchar values

I would like to know how to transpose very simply a table in SQL. There is no sum or calculations to do.
This table contains Numeric and Varchar values.
Meaning, I have a table of 2 rows x 195 columns. I would like to have the same table with 195 rows x 2 columns (maybe 3 columns)
time_index
legal_entity_code
cohort
...
...
0
AAA
50
...
...
1
BBB
55
...
...
TO
Element
time_index_0
time_index_1
legal_entity_code
AAA
BBB
cohort
50
55
...
...
...
...
...
...
I have created this piece of code for testing
SELECT time_index, ValueT, FieldName
FROM (select legal_entity_code, cohort, time_index from ifrs17.output_bba where id in (1349392,1349034)) as T
UNPIVOT
(
ValueT
FOR FieldName in ([legal_entity_code],[cohort])
) as P
but I receive this error message :
The type of column "cohort" conflicts with the type of other columns specified in the UNPIVOT list.
I would recommend using apply for this. I don't fully follow the specified results because the query and the sample data are inconsistent in their naming.
I'm pretty sure you want:
select o.time_index, v.*
from ifrs17.output_bba o cross apply
(values ('Name1', o.name1),
('Value1', convert(varchar(max), o.value1)),
('Name2', o.name2)
) v(name, value)
where o.id in (1349392,1349034);
Gordon's approach is correct and certainly more performant. +1
However, if you want to dynamically unpivot 195 columns without having to list them all, consider the following:
Note: if not 2016+ ... there is a similar XML approach.
Example or dbFiddle
Select Element = [Key]
,Time_Index_0 = max(case when time_index=0 then value end)
,Time_Index_1 = max(case when time_index=1 then value end)
From (
Select [time_index]
,B.*
From YourTable A
Cross Apply (
Select [Key]
,Value
From OpenJson( (Select A.* For JSON Path,Without_Array_Wrapper ) )
Where [Key] not in ('time_index')
) B
) A
Group By [Key]
Returns
Element Time_Index_0 Time_Index_1
cohort 50 55
legal_entity_code AAA BBB

Select from a comma separated list in a colum

I have a table, Table 1,
and I want to select all regions that are neighbours of region 3. What would be my query for this? The NEIGHBOUR column is a CHAR column.
I know the table should not be set up this way, but that is what I have to work with, as I don't have the rights to the database.
Fix your data model! There are numerous reasons why this is broken.
But if you ware stuck with it, you can use:
select t.*
from t
where ',' + neighbor + ',' like '%,3,%';
You can also unnest the value using string_split():
select t.*
from t cross apply
string_split(t.neighbor, ',') s
where s.value = '3';
You can use STRING_SPLIT() as
SELECT *
FROM Data
WHERE Region IN
(
SELECT Value
FROM STRING_SPLIT((SELECT Neighbor FROM Data WHERE Region = 3), ',')
);
The query 'll returns 0 rows because there is no region in the table marked as a neighbor for region 3.
If you change (3, 'Name3', '5,8,12'), to (3, 'Name3', '1,2'),, then it'll returns the regions 1 and 2 because they 're neighbors of region 3.
Here is a db<>fiddle.
Another way without using a string splitter
SELECT *
FROM Data D
JOIN (VALUES((SELECT Neighbor FROM Data WHERE Region = 3))) T(V)
ON CONCAT(',', T.V, ',') LIKE CONCAT('%,', D.Region,',%');

How can run second query based on first query?

Im using two query's, 1st separated one column to two columns and inserted one table and second query (PIVOT) fetching based on inserted table,
1st Query,
SELECT A.MDDID, A.DeviceNumber,
Split.a.value('.', 'VARCHAR(100)') AS MetReading
FROM (
SELECT MDDID,DeviceNumber,
CAST ('<M>' + REPLACE(Httpstring, ':', '</M><M>') + '</M>' AS XML) AS MetReading
FROM [IOTDBV1].[dbo].[MDASDatas] E
Where E.MDDID = 49101
) AS A CROSS APPLY MetReading.nodes ('/M') AS Split(a);
2nd Query
SELECT * FROM
(
Select ID,MDDID,DeviceNumber,ReceivedDate
, ROW_NUMBER() OVER(PARTITION BY ID ORDER BY (SELECT 1)) AS ID2
, SPLT.MR.value('.','VARCHAR(MAX)') AS LIST FROM (
Select ID,MDDID,DeviceNumber,ReceivedDate
, CAST( '<M>'+REPLACE(MeterReading,',','</M><M>')+'</M>' AS XML) AS XML_MR
From [dbo].[PARSEMDASDatas] E
Where E.MeterReading is Not Null
)E
CROSS APPLY E.XML_MR.nodes('/M') AS SPLT(MR)
)A
PIVOT
(
MAX(LIST) FOR ID2 IN ([1],[2],[3],[4],[5],[6],[7],[8])
)PV
I want 2nd query run based on first query no need to require table.
any help would be appreciated.
Your question is not very clear... And it is a very good example, why you always should add a MCVE, including DDL, sample data, own attempts, wrong output and expected output. This time I do this for you, please try to prepare such a MCVE the next time yourself...
If I get this correctly, your source table includes a CSV column with up to 8 (max?) values. This might be solved much easier, no need to break this up in two queries, no need for an intermediate table and not even for PIVOT.
--create a mockup-table to simulate your situation (slightly shortened for brevity)
DECLARE #YourTable TABLE(ID INT,MDDID INT, DeviceNumber VARCHAR(100),MetReading VARCHAR(2000));
INSERT INTO #YourTable VALUES
(2,49101,'NKLDEVELOPMENT02','DCPL,981115,247484,9409') --the character code and some numbers
,(3,49101,'NKLDEVELOPMENT02','SPPL,,,,,,,,') --eigth empty commas
,(4,49101,'NKLDEVELOPMENT02','BLAH,,,999,,'); --A value somewhere in the middle
--The cte will return the table as is. The only difference is a cast to XML (as you did it too)
WITH Splitted AS
(
SELECT ID
,MDDID
,DeviceNumber
,CAST('<x>' + REPLACE(MetReading,',','</x><x>') + '</x>' AS XML) AS Casted
FROM #YourTable t
)
SELECT s.ID
,s.MDDID
,s.DeviceNumber
,s.Casted.value('/x[1]','varchar(100)') AS [1]
,s.Casted.value('/x[2]','varchar(100)') AS [2]
,s.Casted.value('/x[3]','varchar(100)') AS [3]
,s.Casted.value('/x[4]','varchar(100)') AS [4]
,s.Casted.value('/x[5]','varchar(100)') AS [5]
,s.Casted.value('/x[6]','varchar(100)') AS [6]
,s.Casted.value('/x[7]','varchar(100)') AS [7]
,s.Casted.value('/x[8]','varchar(100)') AS [8]
FROM Splitted s;
the result
ID MDDID DeviceNumber 1 2 3 4 5 6 7 8
2 49101 NKLDEVELOPMENT02 DCPL 981115 247484 9409 NULL NULL NULL NULL
3 49101 NKLDEVELOPMENT02 SPPL
4 49101 NKLDEVELOPMENT02 BLAH 999 NULL NULL
The idea in short:
Each CSV is tranformed to a XML similar to this:
<x>DCPL</x>
<x>981115</x>
<x>247484</x>
<x>9409</x>
Using a position predicate in the XPath, we can call the first, the second, the third <x> easily.
CTE: WITH common_table_expression is answer
you can prepare some data in first query and user in second
WITH cte_table AS
(
SELECT *
FROM sys.objects
)
SELECT *
FROM cte_table
where name like 'PK%'

Pull the rightmost string in parentheses

My data looks like this
ABCD.(123).(456)
789.(DEF) JKL.MNO
(PQ).(RS).(TUV)||(WXYz)
I am looking to pull the string from the RIGHTMOST parentheses brackets. Results would look like this
(456)
(DEF)
(WXYz)
The entire strings and strings within the parentheses can vary. Its some combination of substring and charindex, but i cannot figure it out.
Such a question suggests a problem with the data structure -- that the string actually consists of multiple items. However, sometimes such string processing is necessary.
The following approach should work, assuming that a parenthesized component always exists:
select t.*, colparen
from t cross apply
(values (right(col, charindex('(', reverse(col)) - 1)) v(colr) cross apply
(values (charindex(colr, charindex(')', col) + 1)) v(colparen)
Since you are 2016, you can use String_Split() in concert with a Cross Apply.
Note: Use Outer Apply if there are no observations and you want to display a null value.
Example
Declare #YourTable table (SomeCol varchar(100))
Insert Into #YourTable values
('ABCD.(123).(456)'),
('789.(DEF).JKL.MNO'),
('(PQ).(RS).(TUV).(WXYz)')
Select B.Value
From #YourTable A
Cross Apply (
Select Top 1 *
,RN=Row_Number() over (Order By (Select Null))
From String_Split(A.SomeCol,'.')
Where Value Like '(%)'
Order by 2 Desc
) B
Returns
Value
(456)
(DEF)
(WXYz)
dbFiddle
select REVERSE(substring(reverse('ABCD.(123).(456)'),CHARINDEX(')',reverse('ABCD.(123).(456)')),CHARINDEX('(',reverse('ABCD.(123).(456)'))))
This should get you what you want