How to concatenate and sum random comma separated values with case statement? - sql

I have a view which is a simple case statement.
SELECT CAST(
CASE
WHEN [value] = 'Canadian' and fieldid = 78
THEN 5
WHEN [value] = 'US' and fieldid = 78
Then 3
WHEN [value] = 'UK' and fieldid = 78
Then 1
When [value] = 'Australia' and fieldid = 78
Then 1
When [value] = 'Israel' and fieldid = 78
Then 1
When [value] = 'Others' and fieldid = 78
Then 1
Everything is ok so far when a column has the matching value it gives it the point but sometimes the column can hold random multiple values separated by comma. How can i calculate that?
This is what happens now.
+----+------------------+--------+
| ID | Value | Points |
+----+------------------+--------+
| 1 | Canadian | 5 |
| 2 | UK | 1 |
| 3 | Canadian,UK | 0 |
+----+----------+----------------+
Expected result
+----+------------------+--------+
| ID | Value | Points |
+----+------------------+--------+
| 1 | Canadian | 5 |
| 2 | UK | 1 |
| 3 | Canadian,UK | 6 |
+----+----------+----------------+

You can do this just with CASE and LIKE:
SELECT ((CASE WHEN ',' + [value] + ',' LIKE '%,Canadian,%' and fieldid = 78
THEN 5 ELSE 0
END) +
(CASE WHEN ',' + [value] + ',' LIKE '%,US,%' and fieldid = 78
THEN 3 ELSE 0
END) +
(CASE WHEN ',' + [value] + ',' LIKE '%,UK,%' and fieldid = 78
THEN 1 ELSE 0
END) +
(CASE WHEN ',' + [value] + ',' LIKE '%,Australia,%' and fieldid = 78
THEN 1 ELSE 0
END) +
(CASE WHEN ',' + [value] + ',' LIKE '%,Israel,%' and fieldid = 78
THEN 1 ELSE 0
END) +
(CASE WHEN ',' + [value] + ',' LIKE '%,Others,%' and fieldid = 78
THEN 1 ELSE 0
END)
) as val
That said, I strongly, strongly, strongly discourage you from storing multiple values in a single column. SQL is designed to store a single value in columns (at least for the scalar column types and a string is a scalar type).
You should have a separate table that is a junction/association table with one row per entity and country.
EDIT: If you were using SQL Server 2017 or had a split string function, I would advise you to do it this way:
SELECT t.ID, t.[value], COALESCE(s.points, 0) as points
FROM tab t OUTER APPLY
(SELECT SUM(CASE WHEN s.[value] = 'Canadian' THEN 5
WHEN s.[value] = 'US' THEN 3
WHEN s.[value] IN ('UK', 'Australia' , 'Israel', 'Others') THEN 1
END) AS Points
FROM STRING_SPLIT(t.[value], ',') s
WHERE t.fieldid = 78
) s
ORDER BY t.id;

Starting from SQL Server 2017 you could use:
SELECT DISTINCT t.ID, t.[value],
SUM(CASE
WHEN s.[value] = 'Canadian' and fieldid = 78
THEN 5
WHEN s.[value] = 'US' and fieldid = 78
Then 3
WHEN s.[value] = 'UK' and fieldid = 78
Then 1
When s.[value] = 'Australia' and fieldid = 78
Then 1
When s.[value] = 'Israel' and fieldid = 78
Then 1
When s.[value] = 'Others' and fieldid = 78
Then 1
END) OVER(PARTITION BY ID) AS Points
FROM tab t
CROSS APPLY STRING_SPLIT(t.[value], ',') s
ORDER BY t.id;
DBFiddle Demo
Storing multiple values in single column violates 1st Normal Form and it should be avoided.
It could be simplified to:
SELECT DISTINCT t.ID, t.[value],
SUM(CASE
WHEN s.[value] = 'Canadian' and fieldid = 78
THEN 5
WHEN s.[value] = 'US' and fieldid = 78
Then 3
WHEN s.[value] IN ('UK', 'Australia' , 'Israel', 'Others')
and fieldid = 78
Then 1
END) OVER(PARTITION BY ID) AS Points
FROM tab t
CROSS APPLY STRING_SPLIT(t.[value], ',') s
ORDER BY t.id;

Related

How to combine multiple SQL rows into columns dynamically

I have a table with
+-------+-------+-----------------+
| P1_ID | P2_ID | Relationship_ID |
+-------+-------+-----------------+
| 1 | 21 | 3 |
| 1 | 32 | 3 |
| 2 | 45 | 2 |
| 2 | 65 | 1 |
| 3 | 98 | 3 |
| 3 | 94 | 4 |
+-------+-------+-----------------+
I want the final table to look like:
+-------+--------+--------+------+------+
| P1_ID | P2_ID1 | P2_ID2 | RID1 | RID2 |
+-------+--------+--------+------+------+
| 1 | 21 | 32 | 3 | 3 |
| 2 | 45 | 65 | 2 | 1 |
| 3 | 98 | 94 | 3 | 4 |
+-------+--------+--------+------+------+
I am not sure which direction to go with this. I am trying to use a pivot but I can not seem to make it work. Maybe I am doing it wrong.
You can use conditional aggregation for this. This isn't exactly what you stated for output because the ordering of your data is a little funky. But this should point you in the right direction.
declare #Something table
(
P1_ID int
, P2_ID int
, Realationship_ID int
)
insert #Something values
(1,21,3)
, (1,32,3)
, (2,45,2)
, (2,65,1)
, (3,98,3)
, (3,94,4)
select P1_ID
, P2_ID1 = MAX(Case when RowNum = 1 then P2_ID end)
, P2_ID2 = max(case when RowNum = 2 then P2_ID end)
, RID1 = MAX(Case when RowNum = 1 then Realationship_ID end)
, RID2 = max(case when RowNum = 2 then Realationship_ID end)
from
(
select *
, RowNum = ROW_NUMBER() over(partition by s.P1_ID order by Realationship_ID)
from #Something s
) x
group by x.P1_ID
--EDIT--
Here is a fully dynamic solution for this. I switched to using a temp table because a table variable would be out of scope for dynamic sql. Obviously in your situation you would be using a persistent table. This will order the output by P1_ID and the columns within each row by Realationship_ID.
if OBJECT_ID('tempdb..#Something') is not null
drop table #Something
create table #Something
(
P1_ID int
, P2_ID int
, Realationship_ID int
)
insert #Something values
(1,21,3)
, (1,32,3)
, (2,45,2)
, (2,65,1)
, (3,98,3)
, (3,94,4)
;
declare #DynamicPortion nvarchar(max) = '';
declare #FinalStaticPortion nvarchar(2000) = ' from OrderedResults Group by P1_ID order by P1_ID';
declare #StaticPortion nvarchar(2000) =
'with OrderedResults as
(
select *
, RowNum = ROW_NUMBER() over(partition by s.P1_ID order by Realationship_ID)
from #Something s
)
select P1_ID';
with E1(N) AS (select 1 from (values (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))dt(n)),
E2(N) AS (SELECT 1 FROM E1 a cross join E1 b), --10E+2 or 100 rows
cteTally(N) AS
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E2
)
select #DynamicPortion = #DynamicPortion +
', MAX(Case when RowNum = ' + CAST(N as varchar(6)) + ' then P2_ID end) as P2_ID' + CAST(N as varchar(6)) + CHAR(10) +
', MAX(Case when RowNum = ' + CAST(N as varchar(6)) + ' then Realationship_ID end) as RID' + CAST(N as varchar(6)) + CHAR(10)
from cteTally t
where t.N <=
(
select top 1 Count(*)
from #Something
group by P1_ID
order by COUNT(*) desc
)
declare #SqlToExecute nvarchar(max) = #StaticPortion + #DynamicPortion + #FinalStaticPortion;
exec sp_executesql #SqlToExecute

Calculation between row_value at sqlite3

I have the following table.
| col_num | row_num | value |
|---------|----------|-----------|
| 1 | 3 | 5(value1) |
| 2 | 4 | 7(value2) |
| 5 | 1 | 8(value3) |
Now I want to conduct (value2) / (sqrt(value1)*sqrt(value2)) by querying in sqlite3.
Do you have any idea for this?
Try something like this.
select col_num, row_num,
value/((select sqrt(value) from tbl where row_num = 3) * sqrt(value)) as newValue
from tbl where row_num = 4;
It is unclear how you know what is value1, value2, and value3. You can use conditional aggregation. If you were to use row_num, then it would look like:
select (max(case when row_number = 4 then value end) /
sqrt(max(case when row_nunmber = 3 then value end) *
max(case when row_nunmber = 1 then value end)
)
)
from t;
EDIT:
If the valueX is actually part of the value itself:
select max(case when value like '%(value2)' then substring_index(value, '(', 1) + 0 end) /
sqrt(max(case when '%(value1)' then substring_index(value, '(', 1) + 0 end) *
max(case when '%(value2)' then substring_index(value, '(', 1) + 0 end)
)
)
from t;

How do I compute the difference between consecutive rows for a histogram?

I'm trying to create a histogram from some data. SQL Server Developer 2014
Data structure:
+-------------Simulations------------+
+ ID | Cycle | Xa | nextCycleShort +
+ 0 | 0 | 5.63 | True +
+ 0 | 1 | 11.45 | False +
+ 0 | 2 | 12.3 | True +
+-Parameters-+
+ ID | CR +
+ 0 | 1 +
+ 1 | 2 +
In array notation, I want a table with something like:
(Xa[i + 1] - Xa[i])*(CASE nextCycleShort[i] WHEN 0 THEN 1.0 ELSE 2.0) AS DIFF
From this table, I want to select the COUNT(CAST(DIFF as int)). And I want to group that by CAST(DIFF as INT),Parameters.CR.
So for each CR, I'll be able to make a histogram of the DIFFs. What does this look like? Here's my attempt at it:
SELECT
p.ControlRange as ControlRange,
CAST(DIFF as int) as XaMinusXb,
Count(DIFF) as total_diffs,
Select q.Xnew FROM
(SELECT Top 1 Xa AS Xnew
FROM Simulations t
WHERE t.ExperimentID = s.ExperimentID AND t.CycleCount > s.CycleCount
ORDER BY CycleCount DESC) q,
(q.Xnew - s.Xa)*(CASE WHEN s.nextCycleShort = 0 then 1.0 ELSE 2.0) AS DIFF
FROM Simulations s join Parameters p
GROUP BY CAST(DIFF as int), p.ControlRange
ORDER by p.controlRange ASC, DIFF ASC
on s.ExperimentID = p.ExperimentID
Just a thought to do it like this. every row looks back to the previous Xa. You can see how we can get simple diff and also the case based multiplier DIFF:
select
p.CR, s.Xa,
lag(s.Xa) over (partition by p.CR order by cycle asc) prev_Xa,
s.Xa - lag(s.Xa) over (partition by p.CR order by cycle asc) diff,
case when nextCycleShort = 'False'
then 1.0
else 2.0
end nextCyleShort_int,
(s.Xa - lag(s.Xa) over (partition by p.CR order by cycle asc)) * (case when nextCycleShort = 'False' then 1.0 else 2.0 end) myDIFF
from
(
select 0 ID, 0 Cycle, 5.63 Xa , 'True' nextCycleShort union
select 0 ID, 1 Cycle, 11.45 Xa , 'False' nextCycleShort union
select 0 ID, 2 Cycle, 12.3 Xa , 'True' nextCycleShort
) s
join
(
select 0 ID, 1 CR union
select 1 ID, 2 CR
) p
on s.ID = p.ID

Aggregative sum of objects belonging to objects residing inside hierarchy structure

My problem is similar in a way to this one, yet different enough in my understanding.
I have three tables:
Units ([UnitID] int, [UnitParentID] int)
Students ([StudentID] int, [UnitID] int)
Events ([EventID] int, [EventTypeID] int, [StudentID] int)
Students belong to units, units are stacked in a hierarchy (tree form - one parent per child), and each student can have events of different types.
I need to sum up the number of events of each type per user, then aggregate for all users in a unit, then aggregate through hierarchy until I reach the mother of all units.
The result should be something like this:
My tools are SQL Server 2008 and Report Builder 3.
I put up a SQL fiddle with sample data for fun.
Use this query:
;WITH CTE(Id, ParentId, cLevel, Title, ord) AS (
SELECT
u.UnitID, u.UnitParentID, 1,
CAST('Unit ' + CAST(ROW_NUMBER() OVER (ORDER BY u.UnitID) AS varchar(3)) AS varchar(max)),
CAST(RIGHT('000' + CAST(ROW_NUMBER() OVER (ORDER BY u.UnitID) AS varchar(3)), 3) AS varchar(max))
FROM
dbo.Units u
WHERE
u.UnitParentID IS NULL
UNION ALL
SELECT
u.UnitID, u.UnitParentID, c.cLevel + 1,
c.Title + '.' + CAST(ROW_NUMBER() OVER (PARTITION BY c.cLevel ORDER BY c.Id) AS varchar(3)),
c.ord + RIGHT('000' + CAST(ROW_NUMBER() OVER (ORDER BY u.UnitID) AS varchar(3)), 3)
FROM
dbo.Units u
JOIN
CTE c ON c.Id = u.UnitParentID
WHERE
u.UnitParentID IS NOT NULL
), Units AS (
SELECT
u.Id, u.ParentId, u.cLevel, u.Title, u.ord,
SUM(CASE WHEN e.EventTypeId = 1 THEN 1 ELSE 0 END) AS EventA,
SUM(CASE WHEN e.EventTypeId = 2 THEN 1 ELSE 0 END) AS EventB,
SUM(CASE WHEN e.EventTypeId = 3 THEN 1 ELSE 0 END) AS EventC,
SUM(CASE WHEN e.EventTypeId = 4 THEN 1 ELSE 0 END) AS EventD
FROM
CTE u
LEFT JOIN
dbo.Students s ON u.Id = s.UnitId
LEFT JOIN
dbo.[Events] e ON s.StudentId = e.StudentId
GROUP BY
u.Id, u.ParentId, u.cLevel, u.Title, u.ord
), addStudents AS (
SELECT *
FROM Units
UNION ALL
SELECT
s.StudentId, u.Id, u.cLevel + 1,
'Student ' + CAST(s.StudentId AS varchar(3)),
u.ord + RIGHT('000' + CAST(s.StudentId AS varchar(3)), 0),
SUM(CASE WHEN e.EventTypeId = 1 THEN 1 ELSE 0 END),
SUM(CASE WHEN e.EventTypeId = 2 THEN 1 ELSE 0 END),
SUM(CASE WHEN e.EventTypeId = 3 THEN 1 ELSE 0 END),
SUM(CASE WHEN e.EventTypeId = 4 THEN 1 ELSE 0 END)
FROM Units u
JOIN
dbo.Students s ON u.Id = s.UnitId
LEFT JOIN
dbo.[Events] e ON s.StudentId = e.StudentId
GROUP BY
s.StudentID, u.ID, u.cLevel, u.ord
)
SELECT --TOP(10)
REPLICATE(' ', cLevel) + Title As Title,
EventA, EventB, EventC, EventD
FROM
addStudents
ORDER BY
ord
For this:
Title | EventA | EventB | EventC | EventD
-----------------+--------+---------+--------+--------
Unit 1 | 0 | 1 | 0 | 0
Student 6 | 0 | 1 | 0 | 0
Unit 1.1 | 0 | 0 | 0 | 1
Student 21 | 0 | 0 | 0 | 1
Student 33 | 0 | 0 | 0 | 0
Unit 1.1.1 | 0 | 0 | 0 | 0
Student 23 | 0 | 0 | 0 | 0
Unit 1.1.1.1 | 3 | 2 | 3 | 0
Student 10 | 0 | 0 | 0 | 0
Student 17 | 1 | 0 | 0 | 0
...
SQL Fiddle Demo
Do you need also the hierarchy to be sorted / visualized? At least this will calculate the sums, but the order of the data is pretty random :)
;with CTE as (
select S.StudentId as UnitID, S.UnitId as UnitParentID,
S.StudentID, 'Student' as Type
from Students S
union all
select U.UnitId, U.UnitParentId,
CTE.StudentId as StudentID, 'Unit ' as Type
from
Units U
join CTE
on U.UnitId = CTE.UnitParentId
)
select C.Type + ' ' + convert(varchar, C.UnitId),
sum(case when EventTypeId = 1 then 1 else 0 end) as E1,
sum(case when EventTypeId = 2 then 1 else 0 end) as E2,
sum(case when EventTypeId = 3 then 1 else 0 end) as E3,
sum(case when EventTypeId = 4 then 1 else 0 end) as E4
from
CTE C
left outer join events E on C.StudentId = E.StudentId
group by
C.Type, C.UnitId
SQL Fiddle
If you need also the hierarchy to be in order, you'll probably have add few extra CTEs to get the numbering from top down with something like #shA.t did. This gathers the hierarchy separately for each student, so it's not really possible to add the level numbers in a simple way.

SQL Multiple count on same row with dynamic column

I need to alter view that show user count(ScheduleID) by period on same row. Now the Period table content can grow and contain more than 3 periods.
The actual SQL is:
SELECT r.Code,
SUM(CASE WHEN s.PeriodID=1 THEN 1 ELSE 0 END) AS PeriodID1,
SUM(CASE WHEN s.PeriodID=2 THEN 1 ELSE 0 END) AS PeriodID2,
SUM(CASE WHEN s.PeriodID=3 THEN 1 ELSE 0 END) AS PeriodID3,
SUM(CASE WHEN s.PeriodID IN (1,2,3) THEN 1 ELSE 0 END) AS Total
FROM Schedules s
JOIN Periods p ON p.PeriodID = s.PeriodID
JOIN Resources r ON r.ResourceID = s.ResourceID
GROUP BY r.Code;
Example data:
Table Schedules
ScheduleID(int) ResourceID(int) ResourceCode(varchar 4) PeriodID(int)
1 1 AA 1
2 1 AA 3
3 1 AA 3
4 2 BB 1
5 3 CC 1
6 1 AA 1
7 3 CC 2
8 3 CC 3
9 2 BB 1
10 2 BB 2
11 2 BB 3
12 1 AA 3
Table Periods
PeriodID(int) Code (varchar 4)
1 P1
2 P2
3 P3
4 P4
5 P5
6 P6
7 P7
8 P8
The result I need is:
ResourceCode PeriodID1 PeriodID2 PeriodID3 ... PeriodID8 TOTAL
AA 2 0 3 0 5
BB 2 1 1 0 4
CC 1 1 1 0 3
The Periods table content is now dynamic.
The database version is an Microsoft SQL 2008
I like to know if is possible to do that without create stored procedure...and doing this in one query like this:
SELECT *
FROM (
SELECT R.Code, P.PeriodID, COUNT(S.ScheduleID) AS RPCount
FROM Schedules S INNER JOIN Periods P ON S.PeriodID = P.PeriodID
JOIN Resources R ON S.ResourceID = R.ResourceID
WHERE S.ResourceID is not null
GROUP BY R.Code, P.PeriodID
) as data
PIVOT
(
SUM(RPCount)
--FOR PeriodID IN ([1],[2],[3])
FOR PeriodID IN (SELECT PeriodID From Periods)
)AS pvt
ORDER BY Code
Since you are using SQL Server then you can implement the PIVOT function and if you have an unknown number of period values, then you will need to use dynamic SQL:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME('PeriodId'+cast(periodid as varchar(10)))
from Periods
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT resourcecode, ' + #cols + ' , Total
from
(
select s.resourcecode,
''PeriodId''+cast(p.periodid as varchar(10)) period,
count(*) over(partition by s.resourcecode) Total
from periods p
left join schedules s
on p.periodid = s.periodid
) x
pivot
(
count(period)
for period in (' + #cols + ')
) p
where resourcecode is not null
order by resourcecode'
execute(#query)
See SQL Fiddle with Demo. This gives a result:
| RESOURCECODE | PERIODID1 | PERIODID2 | PERIODID3 | PERIODID4 | PERIODID5 | PERIODID6 | PERIODID7 | PERIODID8 | TOTAL |
------------------------------------------------------------------------------------------------------------------------
| AA | 2 | 0 | 3 | 0 | 0 | 0 | 0 | 0 | 5 |
| BB | 2 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 4 |
| CC | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 3 |
Based on your previous question that was tagged with MySQL, I am assuming you are using MySQL as the database. If so, then you do not have a PIVOT function so you will have to use an aggregate function with a CASE expression to transform the rows of data into columns.
If your column values are known, then you can hard-code the query:
select resourcecode,
sum(case when period = 'PeriodId1' then 1 else 0 end) PeriodId1,
sum(case when period = 'PeriodId2' then 1 else 0 end) PeriodId2,
sum(case when period = 'PeriodId3' then 1 else 0 end) PeriodId3,
sum(case when period = 'PeriodId4' then 1 else 0 end) PeriodId4,
sum(case when period = 'PeriodId5' then 1 else 0 end) PeriodId5,
sum(case when period = 'PeriodId6' then 1 else 0 end) PeriodId6,
sum(case when period = 'PeriodId7' then 1 else 0 end) PeriodId7,
sum(case when period = 'PeriodId8' then 1 else 0 end) PeriodId8,
count(*) Total
from
(
select concat('PeriodId', p.periodid) Period,
s.resourcecode
from periods p
left join schedules s
on p.periodid = s.periodid
) d
where resourcecode is not null
group by resourcecode;
See SQL Fiddle with Demo. But if the values will be unknown or dynamic then you will need to use a prepared statement to generate a sql string to execute:
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'sum(CASE WHEN period = ''',
concat('PeriodId', periodid),
''' THEN 1 else 0 END) AS `',
concat('PeriodId', periodid), '`'
)
) INTO #sql
FROM periods;
SET #sql
= CONCAT('SELECT resourcecode, ', #sql, ' , count(*) Total
from
(
select concat(''PeriodId'', p.periodid) Period,
s.resourcecode
from periods p
left join schedules s
on p.periodid = s.periodid
) d
where resourcecode is not null
group by resourcecode');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
See SQL Fiddle with Demo.
Use PIVOT
try this
SELECT *
FROM (
SELECT
S.ResourceCode ,
P.PeriodID AS period,
COUNT(*) AS PCount
FROM Schedules S INNER JOIN Periods P ON S.PeriodID =P.PeriodID
GROUP BY S.ResourceCode ,P.PeriodID
) as s
PIVOT
(
PCount,
FOR [period] IN (SELECT DISTINCT PeriodID From Periods)
)AS pivot
Please try below code for MS Sql server:
DECLARE #column VARCHAR(MAX), #SumQuery VARCHAR(MAX)
SELECT
#column = COALESCE(#column + '], [', '')+ CAST(PeriodID as nvarchar(10)),
#SumQuery = COALESCE(#SumQuery + ']+[', '')+ CAST(PeriodID as nvarchar(10))
FROM
Periods
GROUP BY PeriodID
EXEC ('select *, ['+#SumQuery+'] as [Total] From
(
select * From Schedules
)up
pivot (count(ScheduleID) for PeriodID in (['+#column+'])) as pvt')