Select all rows in table separated by commas with condition - sql-server

A customer enters a ticket and selects a department, or in some cases, multiple departments from the list. My application returns the departments separated by comma's for each Ticket ID. It works great. However, the user may have selected "ALL" departments from the selection (the DEPARTMENT_ID for the selection ALL is '1'). I would then need to return every department in the Departments table separated by commas.
Ticket Table
TICKET_ID | ISSUE
-------------------------
100 | Power Outage
101 | Internet is not working
Deartments Table:
DEPARTMENT_ID | DEPARTMENT
--------------------------
1 | ALL
2 | Accounting
3 | Human Resources
4 | Receiving
DepartmentTickets table
DEPARTMENT_TICKETS_ID | TICKET_ID | DEPARTMENT_ID
----------------------------------------------------------
1 | 100 | 2
2 | 100 | 3
3 | 101 | 1
Using my query, ticket 100 shows the following results:
Power Outage: Accounting, Human Resources
How can I make ticket 101 show the following:
Internet is not working: All, Accounting, Human Resources, Receiving
Select ISSUE,
stuff((
SELECT ', ' + cast(Departments.DEPARTMENT as varchar(max))
FROM Departments
left join DepartmentTickets
ON DepartmentTicket.TICKET_ID = TICKETS.TICKET_ID
WHERE DepartmentTickets.TICKET_ID = TICKETS.TICKET_ID and DepartmentTickets.DEPARTMENT_ID = Departments.DEPARTMENT_ID
FOR XML PATH('')
), 1, 2, '') AS DEPARTMENTS
FROM TICKETS
WHERE TICKET_ID = '100'
ORDER BY ISSUE

First make your inner join in disguise a proper inner join. In the ON clause then add with an OR a check for the department ID being 1.
And by the way, if the IDs are integers not strings, which they appear to be, you shouldn't enclose the literals in single quotes.
SELECT t.issue,
stuff((SELECT ', ' + cast(d.department AS varchar(max))
FROM departments d
INNER JOIN departmenttickets dt
ON dt.department_id = d.department_id
OR dt.department_id = 1
WHERE dt.ticket_id = t.ticket_id
FOR XML PATH('')),
1,
2,
'') departments
FROM tickets t
WHERE t.ticket_id = 101
ORDER BY t.issue;
db<>fiddle

Related

Sql Server - Get SUM() of values for only Active Users

I have a requirement where i need to get Total Active Employees and Total Sales by RegionId
My query result should be like below.
RegionId | TotalEmployees | TotalSales | Average
1 10 100 10
2 3 15 5
My front end application will pass all the RegionIds as a single string separated by a comma, my query parameter is of type VARCHAR() and the Input paramter will look like '1,2,3,4,7,14,26' and there can be upto 20 Region Ids in a single string separated by a comma.
SELECT E.[RegionId] as RegionId
,COUNT(E.[EmployeeId) AS TotalEmployees
,(SELECT SUM([Sale])
FROM dbo.[Sales]
WHERE RegionId = R.[RegionId]
) AS TotalSales
,TotalSales/TotalEmployees AS Average
FROM dbo.[Employee]
JOIN [dbo].[ufn_StringSplit](#RegionIdCollection, ',') RegionId
ON E.RegionId = CAST(RegionId.[Data] AS Varchar(5000))
WHERE E.[Active] = 1
GROUP BY E.[RegionId]
My Employee table structures look alike below
EmployeeId | Name | RegionId | Active
100 Tom 2 1
101 Jim 4 0
103 Ben 2 1
Sales Table
SaleId | EmployeeId| RegionId | Sale
1 100 2 3500
2 101 4 2000
3 100 2 1500
Now my issue is when i am getting TotalSales the below query gets all the sales by RegionId, but i need to get All the sales done by only current Active employees in the Employee table
(SELECT SUM([Sale])
FROM dbo.[Sales]
WHERE RegionId = R.[RegionId]
) AS TotalSales
There is no reason to use a sub-select to find the sum of sales here, that will result in running that query for each and every row. You want to aproach this in a set based way which means you need to join and group appropriately:
with s as
(
select e.RegionId
,e.EmployeeId
,sum(s.Sale) as EmployeeSales
from dbo.ufn_StringSplit(#RegionIdCollection, ',') as r
join dbo.Employee as e
on r.RegionId = CAST(r.[Data] AS varchar(20)) -- Do you really need 5000 characters here?
left join dbo.Sales as s
on r.RegionId = s.RegionId
and e.EmployeeId = s.EmployeeId
where e.Active = 1
group by e.RegionId
,e.EmployeeId
)
select s.RegionId
,count(s.EmployeeId) as TotalEmployees
,sum(s.EmployeeSales) as TotalSales
,sum(s.EmployeeSales)/count(s.EmployeeId) as Average
from s
group by s.RegionId

Using STRING_SPLIT for 2 columns in a single table

I've started from a table like this
ID | City | Sales
1 | London,New York,Paris,Berlin,Madrid| 20,30,,50
2 | Istanbul,Tokyo,Brussels | 4,5,6
There can be an unlimited amount of cities and/or sales.
I need to get each city and their salesamount their own record. So my result should look something like this:
ID | City | Sales
1 | London | 20
1 | New York | 30
1 | Paris |
1 | Berlin | 50
1 | Madrid |
2 | Istanbul | 4
2 | Tokyo | 5
2 | Brussels | 6
What I got so far is
SELECT ID, splitC.Value, splitS.Value
FROM Table
CROSS APLLY STRING_SPLIT(Table.City,',') splitC
CROSS APLLY STRING_SPLIT(Table.Sales,',') splitS
With one cross apply, this works perfectly. But when executing the query with a second one, it starts to multiply the number of records a lot (which makes sense I think, because it's trying to split the sales for each city again).
What would be an option to solve this issue? STRING_SPLIT is not neccesary, it's just how I started on it.
STRING_SPLIT() is not an option, because (as is mentioned in the documantation) the output rows might be in any order and the order is not guaranteed to match the order of the substrings in the input string.
But you may try with a JSON-based approach, using OPENJSON() and string transformation (comma-separated values are transformed into a valid JSON array - London,New York,Paris,Berlin,Madrid into ["London","New York","Paris","Berlin","Madrid"]). The result from the OPENJSON() with default schema is a table with columns key, value and type and the key column is the 0-based index of each item in this array:
Table:
CREATE TABLE Data (
ID int,
City varchar(1000),
Sales varchar(1000)
)
INSERT INTO Data
(ID, City, Sales)
VALUES
(1, 'London,New York,Paris,Berlin,Madrid', '20,30,,50'),
(2, 'Istanbul,Tokyo,Brussels', '4,5,6')
Statement:
SELECT d.ID, a.City, a.Sales
FROM Data d
CROSS APPLY (
SELECT c.[value] AS City, s.[value] AS Sales
FROM OPENJSON(CONCAT('["', REPLACE(d.City, ',', '","'), '"]')) c
LEFT OUTER JOIN OPENJSON(CONCAT('["', REPLACE(d.Sales, ',', '","'), '"]')) s
ON c.[key] = s.[key]
) a
Result:
ID City Sales
1 London 20
1 New York 30
1 Paris
1 Berlin 50
1 Madrid NULL
2 Istanbul 4
2 Tokyo 5
2 Brussels 6
STRING_SPLIT has no context of what oridinal positions are. In fact, the documentation specifically states that it doesn't care about it:
The order of the output may vary as the order is not guaranteed to match the order of the substrings in the input string.
As a result, you need to use something that is aware of such basic things, such as DelimitedSplit8k_LEAD.
Then you can do something like this:
WITH Cities AS(
SELECT ID,
DSc.Item,
DSc.ItemNumber
FROM dbo.YourTable YT
CROSS APPLY dbo.DelimitedSplit8k_LEAD(YT.City,',') DSc)
Sales AS(
SELECT ID,
DSs.Item,
DSs.ItemNumber
FROM dbo.YourTable YT
CROSS APPLY dbo.DelimitedSplit8k_LEAD(YT.Sales,',') DSs)
SELECT ISNULL(C.ID,S.ID) AS ID,
C.Item AS City,
S.Item AS Sale
FROM Cities C
FULL OUTER JOIN Sales S ON C.ItemNumber = S.ItemNumber;
Of course, however, the real solution is fix your design. This type of design is going to only cause you 100's of problems in the future. Fix it now, not later; you'll reap so many rewards sooner the earlier you do it.

Joining two tables and need to have MAX aggregate function in ON clause

This is my code! I want to give a part id and purchase order id to my report and it brings all the related information with those specification. The important thing is that, if we have same purchase order id and part id we need the code to return the result with the highest transaction id. The following code is not providing what I expected. Could you please help me?
SELECT MAX(INVENTORY_TRANS.TRANSACTION_ID), INVENTORY_TRANS.PART_ID
, INVENTORY_TRANS.PURC_ORDER_ID, TRACE_INV_TRANS.QTY, TRACE_INV_TRANS.CREATE_DATE, TRACE_INV_TRANS.TRACE_ID
FROM INVENTORY_TRANS
JOIN TRACE_INV_TRANS ON INVENTORY_TRANS.TRANSACTION_ID = TRACE_INV_TRANS.TRANSACTION_ID
WHERE INVENTORY_TRANS.PART_ID = #PartID
AND INVENTORY_TRANS.PURC_ORDER_ID = #PurchaseOrderID
GROUP BY TRACE_INV_TRANS.QTY, TRACE_INV_TRANS.CREATE_DATE, TRACE_INV_TRANS.TRACE_ID, INVENTORY_TRANS.PART_ID
, INVENTORY_TRANS.PURC_ORDER_ID
The sample of trace_inventory_trans table is :
part_id trace_id transaction id qty create_date
x 1 10
x 2 11
x 3 12
the sample of inventory_trans table is :
transaction_id part_id purc_order_id
11 x p20
12 x p20
I wanted to have the result of biggest transaction which is transaction 12 but it shows me transaction 11
I would use a sub-query to find the MAX value, then join that result to the other table.
The ORDER BY + TOP (1) returns the MAX value for transaction_id.
SELECT
inv.transaction_id
,inv.part_id
,inv.purc_order_id
,tr.qty
,tr.create_date
,tr.trace_id
FROM
(
SELECT TOP (1)
transaction_id,
part_id,
purc_order_id
FROM
INVENTORY_TRANS
WHERE
part_id = #PartID
AND
purc_order_id = #PurchaseOrderID
ORDER BY
transaction_id DESC
) AS inv
JOIN
TRACE_INV_TRANS AS tr
ON inv.transaction_id = tr.transaction_id;
Results:
+----------------+---------+---------------+------+-------------+----------+
| transaction_id | part_id | purc_order_id | qty | create_date | trace_id |
+----------------+---------+---------------+------+-------------+----------+
| 12 | x | p20 | NULL | NULL | 3 |
+----------------+---------+---------------+------+-------------+----------+
Rextester Demo

MSSQL: T-SQL to find only the IDs that are found in all 4 columns regardless of row

I have a table that lists all of the classes all of our students are taking. Along with the Students ID, there are 4 columns in the table that contain the IDs of our teachers assigned to a students class, the column names showing what position this teacher has for that student.
An example of the [Courses] table structure is:
|StudentsID|CourseChair|Member1|Member2|Member3|
------------------------------------------------
| 1234 | 12 | 44 | 38 | 99 |
| 2345 | 44 | NULL | NULL | NULL |
| 4566 | 38 | 88 | 72 | 31 |
| 4368 | 11 | 93 | 44 | NULL |
| 9812 | 12 | 38 | 99 | 44 |
We have tens of thousands of students with hundreds of teachers, but I want to see a list of Teacher IDs only if that ID is present in all 4 columns, regardless of the row/record. So in the above table, I would want to return only Teacher ID 44 since that ID is a CourseChair, and a Member1, Member2, and a Member3; what student or what class is unimportant in this situation.
I've tried this with CTE, multiple nested SELECT in the SELECT and/or in the WHERE, as well as 4 self-joins, none of which are very efficient with as many records I'm dealing with. Can someone help me find the best way to write this query? I'm using SQL Server 2008 R2.
You can also use UNPIVOT
SELECT StaffMember
FROM StudentClass
UNPIVOT (StaffMember FOR Position IN ([CourseChair], [Member1], [Member2], [Member3])) U
GROUP BY StaffMember
HAVING COUNT(DISTINCT Position) = 4
You can try using intersect:
select CourseChair from #yourstudents where CourseChair is not null
intersect
select member1 from #yourstudents where Member1 is not null
intersect
select member2 from #yourstudents where member2 is not null
intersect
select member3 from #yourstudents where member3 is not null
This should be pretty straightforward. Just JOIN (INNER) on each column.
SELECT DISTINCT C0.CourseChair
FROM Courses C0
INNER JOIN Courses C1
ON C0.CourseChair = C1.Member1
INNER JOIN Courses C2
ON C1.Member1 = C2.Member2
INNER JOIN Coureses C3
ON C2.Member2 = C3.Member3
Here's another way:
SELECT DISTINCT CourseChair
FROM Courses
WHERE (SELECT COUNT(*)
FROM Courses C1
WHERE c1.Member1 = Courses.CourseChair) > 0
AND (SELECT COUNT(*)
FROM Courses C2
WHERE c2.Member2 = Courses.CourseChair) > 0
AND (SELECT COUNT(*)
FROM Courses C3
WHERE c3.Member2 = Courses.CourseChair) > 0
If, however, the issue is efficiency, you probably need to look at indexing. If you have Database Engine Tuning Adviser, that can help.
You would probably need an index on each column. If you don't, then you're doing table searches for each of them, and that will, in fact, take forever.
** UPDATE **
Here's another strategy that may improve the efficiency/speed:
SELECT ID, COUNT(*)
FROM (
SELECT DISTINCT (CourseChair) AS ID FROM Courses
UNION ALL
SELECT DISTINCT (Member1) AS ID FROM Courses
UNION ALL
SELECT DISTINCT (Member2) AS ID FROM Courses
UNION ALL
SELECT DISTINCT (Member3) AS ID FROM Courses
) T1
GROUP BY ID
HAVING COUNT(*) = 4
** And one final thought***
SELECT DISTINCT (CourseChair)
FROM Courses
WHERE CourseChair IN
(
SELECT DISTINCT (Member1)
FROM Courses
WHERE Member1 IN
(
SELECT DISTINCT (Member2)
FROM Courses
WHERE Member2 IN
(
SELECT DISTINCT (Member3)
FROM Courses
)
)
)
Can be done using EXISTS as well. Demo - http://rextester.com/SRSIH80664
SELECT T1.CourseChair
FROM Table1 as T1
WHERE EXISTS (SELECT 1 FROM Table1 as T2 WHERE T1.CourseChair = T2.Member1 AND T2.Member1 IS NOT NULL)
AND EXISTS (SELECT 1 FROM Table1 as T3 WHERE T1.CourseChair = T3.Member2 AND T3.Member2 IS NOT NULL)
AND EXISTS (SELECT 1 FROM Table1 as T4 WHERE T1.CourseChair = T4.Member3 AND T4.Member3 IS NOT NULL);

SQL Server problems when using ODER BY and DISTINCT together

I got two problems, the first problem is my two COUNTS that I start with. GroupID is a string that keep products together (Name_Year together), same product but different size.
If I have three reviews in tblReview and they all have the same GroupID I want to return 3. My problem is that if I have three Products with different ProductID but same GroupID and I add three Review to that GroupID I got 9 returns (3*3). If I only have one Product With the same GroupID and three Reviews it works (1*3=3 returns)
The Second problem is that if I have the ORDER BY CASE Price I have to add GROUP BY Price as well and then I don't get the DISTINCT effect that I want. And that is to just show products that have unique GroupID.
Here's the query, hope somebody can help me with this.
ALTER PROCEDURE GetFilterdProducts
#CategoryID INT, #ColumnName varchar(100)
AS
SELECT COUNT(tblReview.GroupID) AS ReviewCount,
COUNT(tblComment.GroupID) AS CommentCount,
Product.ProductID,
Product.Name,
Product.Year,
Product.Price,
Product.BrandID,
Product.GroupID,
AVG(tblReview.Grade) AS Grade
FROM Product LEFT JOIN
tblComment ON Product.GroupID = tblComment.GroupID LEFT JOIN
tblReview ON Product.GroupID = tblReview.GroupID
WHERE (Product.CategoryID = #CategoryID)
GROUP BY Product.ProductID, Product.BrandID, Product.GroupID, Product.Name, Product.Year, Product.Price
HAVING COUNT(distinct Product.GroupID) = 1
ORDER BY
CASE
WHEN #ColumnName='Name' THEN Name
WHEN #ColumnName='Year' THEN Year
WHEN #ColumnName='Price' THEN Price
END
My tabels:
Product:
ProductID, Name, Year, Price, BrandID, GroupID
tblReview:
ReviewID, Description, Grade, ProductID, GroupID
tblComment:
CommentID, Description, ProductID, GroupID
I think that my problem is that if I have three GroupID with the same name, ex Nike_2010 in Product and I have three Reviews in tblReview that counts the first row in Products that contain Nike_2010 counts how many reviews in tblReview with the same GroupID, Nike_2010 and then the second row in Product that contains Nike_2010 and then do the same count again and again, that results to 9 rows. How do I avoid that?
For starters, because you're joining on multiple tables, you're going to end up with the cross product of all of them as a result. Your counts will then return the total count of rows containing data in that column. Consider the following example:
- PRODUCTS - -- COMMENTS -- --- REVIEWS ---
Key | Name Key | Comment Key | Review
1 | A 1 | Foo 1 | Great
2 | B 1 | Bar 1 | Wonderful
The query
SELECT PRODUCTS.Key, PRODUCTS.Name, COMMENTS.Comment, REVIEWS.Review
FROM PRODUCTS
LEFT OUTER JOIN COMMENTS ON PRODUCTS.KEY = COMMENTS.KEY
LEFT OUTER JOIN REVIEWS ON PRODUCTS.KEY = REVIEWS.KEY
will result in the following data:
Key | Name | Comment | Review
1 | A | Foo | Great
1 | A | Foo | Wonderful
1 | A | Bar | Great
1 | A | Bar | Wonderful
2 | B | NULL | NULL
Thus, counting in this format
SELECT PRODUCTS.Key, PRODUCTS.Name, COUNT(COMMENTS.Comment), COUNT(REVIEWS.Review)
FROM PRODUCTS
LEFT OUTER JOIN COMMENTS ON PRODUCTS.KEY = COMMENTS.KEY
LEFT OUTER JOIN REVIEWS ON PRODUCTS.KEY = REVIEWS.KEY
GROUP BY PRODUCTS.Key, PRODUCTS.Name
will give you
Key | Name | Count1 | Count2
1 | A | 4 | 4
2 | B | 0 | 0
because it's counting each row in the table produced by the join!
Instead, you want to count each table separately in a subquery before joining it back like the following:
SELECT PRODUCTS.Key, PRODUCTS.Name, ISNULL(CommentCount.NumComments, 0),
ISNULL(ReviewCount.NumReviews, 0)
FROM PRODUCTS
LEFT OUTER JOIN (SELECT Key, COUNT(*) as NumComments
FROM COMMENTS
GROUP BY Key) CommentCount on PRODUCTS.Key = CommentCount.Key
LEFT OUTER JOIN (SELECT Key, COUNT(*) as NumReviews
FROM REVIEWS
GROUP BY Key) ReviewCount on PRODUCTS.Key = ReviewCount.Key
which will produce the following
Key | Name | NumComments | NumReviews
1 | A | 2 | 2
2 | B | 0 | 0
As for the "DISTINCT effect" you refer to, I'm not exactly sure I follow. Could you elaborate a bit?
About second problem - cannot you group by same CASE statement? You shouldn't have Price field in results list then though.

Resources