Combine Multiple records per Foreign Key - sql

I have a somewhat tricky table structure that was inherited from way legacy.
I have a table with about 4 columns that matter.
DayNight Cust_Code Name Phone Counter
D ABC0111 Marty aaaaa 1
D ABC0111 John bbbbb 2
D ABC0111 Beth ccccc 3
N ABC0111 Sue ddddd 1
N ABC0111 Mary eeeee 2
I need to combine these 5 records into one row with the following stucture.
CustCode, Day1, Day2, Day3, Night1, Night2, Night3
ABC0111, Marty aaaaa, John bbbbb, Beth ccccc, Sue ddddd , Mary eeeee, null or ''
What I have tried
SELECT DISTINCT
x.NAME,
x.DAYNIGHT,
x.PHONE,
x.COUNTER,
cp.NAME,
cp.DAYNIGHT,
cp.COUNTER,
cp.PHONE,
cp.POSITION
FROM (
SELECT *
from table1 where
table1.DAYNIGHT LIKE 'N'
) x
join table1 t1 on t1.CUST_CODE = x.CUST_CODE
where cp.DAYNIGHT LIKE 'D'

I would be inclined to do this using conditional aggregation:
select CustCode,
max(case when DayNight = 'D' and Counter = 1 then Name + ' ' + Phone end) as Day1,
max(case when DayNight = 'D' and Counter = 2 then Name + ' ' + Phone end) as Day2,
max(case when DayNight = 'D' and Counter = 3 then Name + ' ' + Phone end) as Day3,
max(case when DayNight = 'N' and Counter = 1 then Name + ' ' + Phone end) as Night1,
max(case when DayNight = 'N' and Counter = 2 then Name + ' ' + Phone end) as Night2,
max(case when DayNight = 'N' and Counter = 3 then Name + ' ' + Phone end) as Night3
from table1
group by CustCode;

you can also use Pivot
SELECT *
FROM (SELECT cust_code,
NAME + ' ' + phone AS pp,
CASE WHEN daynight ='D' THEN 'Day' ELSE 'Night' END + CONVERT(VARCHAR(30), counter) AS rr
FROM tablename)a
PIVOT (Max(pp)
FOR rr IN([Day1],
[Day2],
[Day3],
[Night1],
[Night2],
[Night3])) pv

Related

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

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;

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.

X Number Of Rows Into One Row Based On One Column

I have been asked to display X number of rows, into a row that is going across with results. So for example :-
The Data
Tency Number| ClientNo | Name | DOB | Prim Client | Rent
10001 20 Joe 01/10/1900 Y 100
10001 21 bob 01/10/1901 N 100
10001 26 jim 01/10/1902 n 100
The format the user would like is
Tency Number | ClientNo | Name | DOB | Prim Client | Rent | Client2 | DOB 2|
10001 20 Joe 01/10/1900 Y 100 | Bob 01/10/1901 | Jim | 01/10/1902
We haven't got a pre defined number of client linked to Tenacy Number. I'm lost at what to use to achieve this. There is nothing there to pivot the table, and I used STUFF to link all the client into one column on the row (But they didn't want that).
Is this possible? And if so how would I achieve it?
Thank you!
The SQL you would need would be something like:
SELECT TencyNumber,
ClientNo = MIN(CASE WHEN RowNum = 1 THEN ClientNo END),
Client1 = MAX(CASE WHEN RowNum = 1 THEN Name END),
DOB1 = MAX(CASE WHEN RowNum = 1 THEN DOB END),
Client2 = MAX(CASE WHEN RowNum = 2 THEN Name END),
DOB2 = MAX(CASE WHEN RowNum = 2 THEN DOB END),
Client3 = MAX(CASE WHEN RowNum = 3 THEN Name END),
DOB3 = MAX(CASE WHEN RowNum = 3 THEN DOB END)
FROM ( SELECT TencyNumber,
ClientNo,
Name,
DOB,
PrimClient,
Rent,
RowNum = ROW_NUMBER() OVER(PARTITION BY TencyNumber ORDER BY Name)
FROM T
) c
GROUP BY TencyNumber;
But since you have an unknown number of clients, you would need to build the SQL dynamically:
DECLARE #SQL NVARCHAR(MAX) = '';
SELECT #SQL = #SQL +
',Client' + RowNum + ' = MAX(CASE WHEN RowNum = ' + RowNum + ' THEN Name END)
,DOB' + RowNum + ' = MAX(CASE WHEN RowNum = ' + RowNum + ' THEN DOB END)'
FROM ( SELECT RowNum = CAST(ROW_NUMBER() OVER(PARTITION BY TencyNumber ORDER BY Name) AS VARCHAR(10))
FROM T
) c;
SET #SQL = 'SELECT TencyNumber, ClientNo = MIN(CASE WHEN RowNum = 1 THEN ClientNo END) ' + #SQL + '
FROM ( SELECT TencyNumber,
ClientNo,
Name,
DOB,
PrimClient,
Rent,
RowNum = ROW_NUMBER() OVER(PARTITION BY TencyNumber ORDER BY Name)
FROM T
) c
GROUP BY TencyNumber;';
EXECUTE SP_EXECUTESQL #SQL;
Example on SQL Fiddle

sql query different column based on input

I am using MS-SQL 2008. I have a table with different columns based on locations in it that will have a 'Y' or Null value. The table also has other data other than location from survey results. I have set up a temptable #TempLocation to hold the location based on the one or all. I need to select rows from the table based on 'Y' from one or more location rows within a date range.
TableID Northwest Northeast Southwest Southeast Batchno first_choice date_completed
1 Y Y Y 1 A 2012-11-10
2 Y Y 1 SA 2012-19-10
3 Y Y 1 N 2012-07-10
4 Y Y Y 2 A 2012-10-10
5 Y 2 A 2012-16-10
6 Y Y 2 D 2012-21-10
7 Y NULL A 2012-19-10
8 Y Y Y Y 3 SA 2012-11-10
9 Y 3 A 2012-10-10
10 Y Y 3 A 2012-07-10
I have created a Dynamic SQL statement to pull one location successfully but is it possible to pull all of them?
select ''' + (SELECT * FROM #TempLocation) + ''',
count(batchno),
count(case when first_choice is not null then batchno end),
count(case when t.First_choice =''SD'' then 1 end) ,
count(case when t.First_choice=''D'' then 1 end) ,
count(case when t.First_choice=''N'' then 1 end) ,
count(case when t.First_choice=''A'' then 1 end) ,
count(case when t.First_choice=''SA'' then 1 end)
from customer_satisfaction_survey t
where t.date_completed>= ''' + CAST(#beg_date AS VARCHAR) + '''
and t.date_completed < ''' + CAST(dateadd(day,1,#end_date) AS Varchar) + '''
and t.' + (SELECT * FROM #TempLocation) + ' = ''Y'''
An All result would look like this.
Number Location Total Total2 SA A N D SD
1 Northwest 6 6 1 3 1 1 0
2 Northeast 5 4 2 2 1 0 0
3 Southwest 4 4 1 3 0 0 0
4 Southeast 6 6 2 3 0 1 0
I have to think that you are approaching this in the wrong way, because your data is not normalized. The first thing you should do is to normalize the data using UNPIVOT. I'm assuming that you are using SQL Server, since your syntax suggests that. It is a good idea to tag all questions with the database, though.
You can unpivot your data with a statement such as:
select BatchNo, FirstChoice, DateCompleted, Location
from d
unpivot (val for location in (Northwest, Northeast, Southwest, Southeast)) as unpvt
Next, set up your temporary table to have a separate row for each location. Then, you can do the join with no dynamic SQL. Something like:
with dnorm as (
THE NORMALIZATION QUERY HERE
)
select dnorm.location, count(*) as total,
sum(case when dnorm.first_choice is not null then 1 else 0 end) as total2,
sum(case when dnorm.first_choice = 'SA' then 1 else 0 end) as SA,
. . .
from dnorm join
#TempLocation tl
on dnorm.location = tl.location
where ALL YOUR WHERE CONDITIONS HERE
The final query looks something like:
with dnorm as (
select BatchNo, FirstChoice, DateCompleted, Location
from d
unpivot (val for location in (Northwest, Northeast, Southwest, Southeast)) as unpvt
)
select dnorm.location, count(*) as total,
sum(case when dnorm.first_choice is not null then 1 else 0 end) as total2,
sum(case when dnorm.first_choice = 'SA' then 1 else 0 end) as SA,
. . .
from dnorm join
#TempLocation tl
on dnorm.location = tl.location
where ALL YOUR WHERE CONDITIONS HERE
The dynamic SQL approach is quite clever, but I don't think it is the simplest way to approach this.

How to find the day of the dates

Using SQL Server 2000
Table1
ID Date Value1 Value2
001 01/01/2012 100 0
001 02/01/2012 200 200
...
...
001 31/01/2012 250 0
002 01/01/2012 050 100
002 02/01/2012 100 0
...
002 31/01/2012 075
....
I want to display the value (value1 column) by date wise (date column) group by id
Output like
ID 01/01/2012 02/01/2012 ... 31/01/2012
001 100 200 .... 250
002 050 100 .... 075
.....
Query
DECLARE #loop int,#date Date,#sql nvarchar(4000)
DECLARE #TempTable TABLE
(
[Date] DATE
)
INSERT INTO #TempTable SELECT DISTINCT [Date] FROM table1 ORDER BY [Date]
SET #sql = ''
SET #loop = 1
WHILE (#loop<=31)
BEGIN
IF EXISTS(SELECT * FROM #TempTable WHERE DAY([Date])=#loop)
BEGIN
SET #date = (SELECT [Date] FROM #TempTable WHERE DAY([Date])=#loop)
SET #sql = #sql+ ',MAX(CASE CONVERT(nvarchar(10),[Date],103) WHEN '''+CONVERT(nvarchar(10),#date,103)+''' THEN [Value1] END) AS [DATE'+CONVERT(nvarchar(2),#loop)+']'
END
ELSE
SET #sql = #sql+ ', NULL AS [DATE'+CONVERT(nvarchar(2),#loop)+']'
SET #loop = #loop+1
END
EXEC('SELECT ID'+#sql+' FROM table1 GROUP BY ID')
The above query is working, from the above query i want to find the sunday, if it is sunday then date column should display a value from value2 column otherwise value1 column
should display
How to do this.
Need Query help
DATEPART function gives you integer day of the week. Sunday - Saturday become 1 - 7.
try change the line by this one
SET #sql = #sql+ ',MAX(CASE CONVERT(nvarchar(10),[Date],103) WHEN '''+CONVERT(nvarchar(10),#date,103)+''' THEN CASE DATEPART(weekday,[Date])WHEN 1 THEN [Value1] ELSE [Value2] END END) AS [DATE'+CONVERT(nvarchar(2),#loop)+']'
For total calculation per id
SELECT ID,
SUM(CASE WHEN DATEPART(weekday,[Date]) <> 1 THEN [Value1] END) AS Value1,
SUM(CASE DATEPART(weekday,[Date]) WHEN 1 THEN [Value2] END) AS Value2,
FROM table1 GROUP BY ID
It may turn out you don't need iteration, nor dynamic scripting. Consider the following:
SELECT
ID,
Day1 = MAX(CASE D WHEN 1 THEN V END),
Day2 = MAX(CASE D WHEN 2 THEN V END),
Day3 = MAX(CASE D WHEN 3 THEN V END),
Day4 = MAX(CASE D WHEN 4 THEN V END),
Day5 = MAX(CASE D WHEN 5 THEN V END),
Day6 = MAX(CASE D WHEN 6 THEN V END),
Day7 = MAX(CASE D WHEN 7 THEN V END),
Day8 = MAX(CASE D WHEN 8 THEN V END),
Day9 = MAX(CASE D WHEN 9 THEN V END),
Day10 = MAX(CASE D WHEN 10 THEN V END),
Day11 = MAX(CASE D WHEN 11 THEN V END),
Day12 = MAX(CASE D WHEN 12 THEN V END),
Day13 = MAX(CASE D WHEN 13 THEN V END),
Day14 = MAX(CASE D WHEN 14 THEN V END),
Day15 = MAX(CASE D WHEN 15 THEN V END),
Day16 = MAX(CASE D WHEN 16 THEN V END),
Day17 = MAX(CASE D WHEN 17 THEN V END),
Day18 = MAX(CASE D WHEN 18 THEN V END),
Day19 = MAX(CASE D WHEN 19 THEN V END),
Day20 = MAX(CASE D WHEN 20 THEN V END),
Day21 = MAX(CASE D WHEN 21 THEN V END),
Day22 = MAX(CASE D WHEN 22 THEN V END),
Day23 = MAX(CASE D WHEN 23 THEN V END),
Day24 = MAX(CASE D WHEN 24 THEN V END),
Day25 = MAX(CASE D WHEN 25 THEN V END),
Day26 = MAX(CASE D WHEN 26 THEN V END),
Day27 = MAX(CASE D WHEN 27 THEN V END),
Day28 = MAX(CASE D WHEN 28 THEN V END),
Day29 = MAX(CASE D WHEN 29 THEN V END),
Day30 = MAX(CASE D WHEN 30 THEN V END),
Day31 = MAX(CASE D WHEN 31 THEN V END)
FROM (
SELECT
ID,
V = CASE DATENAME(WEEKDAY, Date) WHEN 'Sunday' THEN Value2 ELSE Value1 END,
D = DAY(Date)
FROM Table1
WHERE … /* perhaps, a condition to retrieve a specific month is due here */
) s
GROUP BY ID
;