Get related tables not contains result - sql

I have a DesignGroup table as:
+--------------------------------------+----------+
| DesignGroupId | Name |
+--------------------------------------+----------+
| 3A81C1FF-442F-4291-B8E2-7079D80920CF | Design 1 |
| 3238F4C6-7BA7-4B3F-9383-17702B0D1CC3 | Design 2 |
+--------------------------------------+----------+
Each DesignGroup can have multiple customers, so I have a table DesignGroupCustomers as:
+--------------------------------------+--------------------------------------+-------------+
| DesignGroupCustomerId | DesignGroupId (FK) | CustomerKey |
+--------------------------------------+--------------------------------------+-------------+
| D0828677-F295-46F7-BB85-65888D5A48B7 | 3A81C1FF-442F-4291-B8E2-7079D80920CF | 10 |
| 10C01BB9-1DDB-4DB4-BEC4-9539E030BF68 | 3A81C1FF-442F-4291-B8E2-7079D80920CF | 20 |
| F88C9F66-C0D9-EB11-8481-5CF9DDF6DC87 | 3238F4C6-7BA7-4B3F-9383-17702B0D1CC3 | 10 |
+--------------------------------------+--------------------------------------+-------------+
Each customer have a CustomerType as, customerTable:
+-------------+-------------+
| CustomerKey | CustTypeKey |
+-------------+-------------+
| 10 | 2 |
| 20 | 1 |
+-------------+-------------+
That I want to achieve is to get only this statement:
return only the DesignGroup who not have a customer with custTypeKey = 1
In this case it should return Design 2 because it does not have customer with custTypeKey = 1
I was thinking about CTE usage but I just have not idea how to get the desire result:
;WITH CTE
AS (SELECT
[DG].[DesignGroupId]
, ROW_NUMBER() OVER(PARTITION BY [DesignGroupCustomer]) AS [RN]
FROM [DesignGroup] AS [DG]
INNER JOIN [DesignGroupCustomer] AS [DGC] ON [DG].[DesignGroupId] = [DGC].[DesignGroupId]
INNER JOIN [Customer] AS [C] ON [DGC].[CustomerKey] = [C].[CustomerKey]
INNER JOIN [CustomerType] AS [CT] ON [C].[CustTypeKey] = [CT].[CustTypeKey])
SELECT
[DesignGroupId]
FROM [CTE] -- WHERE CustomerType NOT CONTAINS (1)

WITH temp AS (
SELECT DISTINCT
dgc.DesignGroupId AS DesignGroupId
FROM DesignGroupCustomers dgc
INNER JOIN customerTable ct
ON dgc.CustomerKey = ct.CustomerKey
WHERE ct.CustTypeKey = 1
)
SELECT
DesignGroupId
FROM DesignGroup
WHERE DesignGroupId NOT IN (
SELECT
DesignGroupId
FROM temp
)
Firstly, you can get all designgroups having CustTypeKey =1 and then get all other designgroups using NOT IN. Please let me know if you face any issues

You can use a subquery to return the design groups which have this customer type key of 1 and then LEFT JOIN the subquery on the design table and filter down to results that have a DesignGroupId of null (any design group that isn't included in the dataset of the subquery)
SELECT d.[DesignGroupId]
FROM [DesignGroup] AS d
LEFT JOIN
(
SELECT dgc.[DesignGroupId]
FROM [DesignGroupCustomer] AS dgc
ON dgc.[DesignGroupId] = d.[DesignGroupId]
INNER JOIN [Customer] AS c
ON c.[CustomerKey] = dgc.[CustomerKey]
WHERE c.[CustTypeKey] = 1
GROUP BY dgc.[DesignGroupId]
) x
ON x.[DesignGroupId] = d.[DesignGroupId]
WHERE x.[DesignGroupId] IS NULL

Related

Multiple select from CTE with different number of rows in a StoredProcedure

How to do two select with joins from the cte's which returns total number of columns in the two selects?
I tried doing union but that appends to the same list and there is no way to differentiate for further use.
WITH campus AS
(SELECT DISTINCT CampusName, DistrictName
FROM dbo.file
),creditAcceptance AS
(SELECT CampusName, EligibilityStatusFinal, CollegeCreditAcceptedFinal, COUNT(id) AS N
FROM dbo.file
WHERE (EligibilityStatusFinal LIKE 'Eligible%') AND (CollegeCreditEarnedFinal = 'Yes') AND (CollegeCreditAcceptedFinal = 'Yes')
GROUP BY CampusName, EligibilityStatusFinal, CollegeCreditAcceptedFinal
),eligibility AS
(SELECT CampusName, EligibilityStatusFinal, COUNT(id) AS N, CollegeCreditAcceptedFinal
FROM dbo.file
WHERE (EligibilityStatusFinal LIKE 'Eligible%')
GROUP BY CampusName, EligibilityStatusFinal, CollegeCreditAcceptedFinal
)
SELECT a.CampusName, c.[EligibilityStatusFinal], SUM(c.N) AS creditacceptCount
FROM campus as a FULL OUTER JOIN creditAcceptance as c ON a.CampusName=c.CampusName
WHERE (a.DistrictName = 'xy')
group by a.CampusName ,c.EligibilityStatusFinal
Union ALL
SELECT a.CampusName , b.[EligibilityStatusFinal], SUM(b.N) AS eligible
From Campus as a FULL OUTER JOIN eligibility as b ON a.CampusName = b.CampusName
WHERE (a.DistrictName = 'xy')
group by a.CampusName,b.EligibilityStatusFinal
Expected output:
+------------+------------------------+--------------------+
| CampusName | EligibilityStatusFinal | creditacceptCount |
+------------+------------------------+--------------------+
| M | G | 1 |
| E | NULL | NULL |
| A | G | 4 |
| B | G | 8 |
+------------+------------------------+--------------------+
+------------+------------------------+----------+
| CampusName | EligibilityStatusFinal | eligible |
+------------+------------------------+----------+
| A | G | 8 |
| C | G | 9 |
| A | T | 9 |
+------------+------------------------+----------+
As you can see here CTEs can be used in a single statement only, so you can't get the expected output with CTEs.
Here is an excerpt from Microsoft docs:
A CTE must be followed by a single SELECT, INSERT, UPDATE, or DELETE
statement that references some or all the CTE columns. A CTE can also
be specified in a CREATE VIEW statement as part of the defining SELECT
statement of the view.
You can use table variables (declare #campus table(...)) or temp tables (create table #campus (...)) instead.

Select from multiple table, eliminating duplicates values

I have these tables and values:
Person Account
------------------ -----------------------
ID | CREATED_BY ID | TYPE | DATA
------------------ -----------------------
1 | 1 | T1 | USEFUL DATA
2 | 2 | T2 |
3 | 3 | T3 |
4 | 4 | T2 |
Person_account_link
--------------------------
ID | PERSON_ID | ACCOUNT_ID
--------------------------
1 | 1 | 1
2 | 1 | 2
3 | 2 | 3
4 | 3 | 4
I want to select all persons with T1 account type and get the data column, for the others persons they should be in the result without any account information.
(I note that person 1 has two accounts : account_id_1 and account_id_2 but only one row must be displayed (priority for T1 type if exist otherwise null)
The result should be :
Table1
-----------------------------------------------------
PERSON_ID | ACCOUNT_ID | ACCOUNT_TYPE | ACCOUNT_DATA
-----------------------------------------------------
1 | 1 | T1 | USEFUL DATA
2 | NULL | NULL | NULL
3 | NULL | NULL | NULL
4 | NULL | NULL | NULL
You can do conditional aggregation :
SELECT p.id,
MAX(CASE WHEN a.type = 'T1' THEN a.id END) AS ACCOUNT_ID,
MAX(CASE WHEN a.type = 'T1' THEN 'T1' END) AS ACCOUNT_TYPE,
MAX(CASE WHEN a.type = 'T1' THEN a.data END) AS ACCOUNT_DATA
FROM person p LEFT JOIN
Person_account_link pl
ON p.id = pl.person_id LEFT JOIN
account a
ON pl.account_id = a.id
GROUP BY p.id;
You would need an outer join, starting with Person and then to the other two tables. I would also aggregate with group by and min to tackle the situation where a person would have two or more T1 accounts. In that case one of the data is taken (the min of them):
select p.id person_id,
min(a.id) account_id,
min(a.type) account_type,
min(a.data) account_data
from Person p
left join Person_account_link pa on p.id = pa.person_id
left join Account a on pa.account_id = a.id and a.type = 'T1'
group by p.id
In Postgres, I like to use the FILTER keyword. In addition, the Person table is not needed if you only want persons with an account. If you want all persons:
SELECT p.id,
MAX(a.id) FILTER (a.type = 'T1') as account_id,
MAX(a.type) FILTER (a.type = 'T1') as account_type,
MAX(a.data) FILTER (a.type = 'T1') as account_data
FROM Person p LEFT JOIN
Person_account_link pl
ON pl.person_id = p.id LEFT JOIN
account a
ON pl.account_id = a.id
GROUP BY p.id;

SQL Query, I need 1 line per person with lowest Id of two columns

SELECT
Mem.MemberID, Mem.LastName + ', ' + Mem.FirstName AS Name,
MD.DiagnosisID,
Diag.DiagnosisDescription,
DC.DiagnosisCategoryID, DC.CategoryDescription, DC.CategoryScore
FROM
Member AS Mem
LEFT OUTER JOIN
MemberDiagnosis AS MD ON Mem.MemberID = MD.MemberID
LEFT OUTER JOIN
Diagnosis AS Diag ON MD.DiagnosisID = Diag.DiagnosisID
LEFT OUTER JOIN
DiagnosisCategoryMap AS Map ON Map.DiagnosisID = Diag.DiagnosisID
LEFT OUTER JOIN
DiagnosisCategory AS DC ON DC.DiagnosisCategoryID = Map.DiagnosisCategoryID
ORDER BY
Name ASC
I have this query, which returns the following results:
ID | Name | D.ID | D.Desc | C.ID | C. Desc | C.Score
----+---------------+------+----------------+------+-------------+----------
2 | Smith, Jack | NULL | NULL | NULL | NULL | NULL
1 | Smith, John | 2 | Test Diagnosis | 2 | Category B | 20
1 | Smith, John | 4 | Test Diagnosis | 3 | Category C | 30
3 | Smyth, Will | 3 | Test Diagnosis | 3 | Category C | 30
3 | Smyth, Will | 4 | Test Diagnosis | 3 | Category C | 30
With in that query and result set, how can I go about returning 1 line per person with;
The lowest Diagnosis ID and it's desc.
The lowest Category ID with desc and score.
In stead of null category, return Category ID 1 and its desc and score.
Try the following query:
SELECT T1.*,Diag2.DiagnosisDescription, DC2.CategoryDescription, DC2.CategoryScore
FROM (SELECT DISTINCT
Mem.MemberID, Mem.LastName + ', ' + Mem.FirstName AS Name,
MIN(MD.DiagnosisID) OVER(PARTITION BY Mem.MemberID) AS DiagnosisID,
MIN(ISNULL(DC.DiagnosisCategoryID,1)) OVER(PARTITION BY Mem.MemberID) AS DiagnosisCategoryID
FROM
Member AS Mem
LEFT OUTER JOIN
MemberDiagnosis AS MD ON Mem.MemberID = MD.MemberID
LEFT OUTER JOIN
Diagnosis AS Diag ON MD.DiagnosisID = Diag.DiagnosisID
LEFT OUTER JOIN
DiagnosisCategoryMap AS Map ON Map.DiagnosisID = Diag.DiagnosisID
LEFT OUTER JOIN
DiagnosisCategory AS DC ON ISNULL(DC.DiagnosisCategoryID,1) = ISNULL(Map.DiagnosisCategoryID,1) ) AS T1
LEFT JOIN Diagnosis AS Diag2 ON T1.DiagnosisID = Diag2.DiagnosisID
LEFT JOIN DiagnosisCategory AS DC2 ON T1.DiagnosisCategoryID = DC2.DiagnosisCategoryID
ORDER BY T1.NAME

How to Inner Join Three Tables and Return Maximum of Value in Third

I have three tables as follows:
First table:
ordenes
id_orden | date | total | id_usuario
1 |15-may|50 | 1
2 |20-may|60 | 2
Second table:
usuario
id_usuario | name | phone
1 | abc | 999
2 | def | 888
Third table:
estado
id_orden | edo
1 | c
1 | b
1 | a
2 | b
2 | a
And this is the desired result:
Results:
id_orden | date | total | id_usuario | name | phone | maxedo
1 |15-may|50 | 1 | abc | 999 | c
2 |20-may|60 | 2 | def | 888 | b
maxedo needs to be the maximum record from the edo in the third table after aggregating based on order.
How do I do this?
The below code sample gives you the result.
CREATE TABLE #ordenes(id_orden int, datevalue date, total int, id_usuario int)
INSERT INTO #ordenes
VALUES
(1,'20160515',50,1),
(2,'20160520',60,2)
CREATE TABLE #usuario(id_usuario int, name varchar(10), phone int)
INSERT INTO #usuario
VALUES
(1,'abc',999),
(2,'def',888)
CREATE TABLE #estado(id_orden int, edo char(1))
INSERT INTO #estado
VALUES
(1,'c'),
(1,'b'),
(1,'a'),
(2,'b'),
(2,'a')
SELECT id_orden,datevalue,total,id_usuario,name,phone,edo as maxedo
FROM
(SELECT o.id_orden,o.datevalue,o.total,o.id_usuario,u.name,u.phone,e.edo,ROW_NUMBER() OVER(PARTITION BY o.id_orden ORDER BY e.edo DESC) as rnk
FROM #ordenes o
JOIN #usuario u
on o.id_usuario = u.id_usuario
join #estado e
on o.id_orden = e.id_orden) as t
where rnk = 1
The following should do the job (assuming edo is actually a numeric amount). I've included aliases using the AS command so you even get the column titles you want.
SELECT
oe.id_orden AS id_orden,
oe.date AS date,
oe.total AS total,
u.id_usario AS id_usuario,
u.name AS name,
u.phone AS phone,
oe.maxedo AS maxedo
FROM usuario u
INNER JOIN
(SELECT
o.id_orden AS id_orden,
o.date AS date,
o.total AS total,
o.id_usuario AS id_usuario,
e.maxestedo AS maxestedo
FROM ordenes o
INNER JOIN
(SELECT
id_orden AS id_orden,
MAX(edo) AS maxedo
FROM estado
GROUP BY id_orden) e
ON e.id_orden=o.id_orden) oe
ON u.id_usuario=oe.id_usuario
In order of processing (which is not how SQL works but is useful way of breaking it down into steps) it goes:
Create table of Maximum edos (NB: MAX also works on alphabetical order);
Links this to ordenes using the id_ordene;
Joins this to usuario data using the id_usuario; and
Publishes this as a table in the required format.
The problem can be split into the following three steps:
Step 1: Calculate maximum edo for each id_orden in the table estado:
Select id_orden, max(edo) maxedo
From estado
Group By id_orden;
Result:
| id_orden | edo |
| 1 | c |
| 2 | b |
Step 2: Join the two tables ordenes and usuario on the key "id_usuario":
Select o.id_orden, o.date, o.total, o.id_usuario, u.name, u.phone
From ordenes o Join usuario u
On o.id_usuario = u.id_usuario;
Result:
id_orden | date | total | id_usuario | name | phone
1 |15-may|50 | 1 | abc | 999
2 |20-may|60 | 2 | def | 888
Step 3: Join the table form the step1 and step2 on the key id_orden:
Select a.id_orden, a.date, a.total, a.id_usuario, a.name, a.phone, b.maxestado
From (Select o.id_orden, o.date, o.total, o.id_usuario, u.name, u.phone
From ordenes o Inner Join usuario u
On o.id_usuario = u.id_usuario ) a
Join (Select id_orden, max(edo) maxestado
From estado
Group By id_orden) b
On a.id_orden = b.id_orden;
Result:
id_orden | date | total | id_usuario | name | phone | maxedo
1 |15-may|50 | 1 | abc | 999 | c
2 |20-may|60 | 2 | def | 888 | b
SQLFiddle example: http://sqlfiddle.com/#!5/a79a1/2
:)
I think you need to get the max(edo) from the third table and group by id_orden, yes? try this.
select temp.*, max(edo) as maxedo
from estado
inner join(
select ordenes.*,usuario.name,usuario.phone
from ordenes,usuario
where ordenes.id_usuario = usuario.id_usuario
) as temp
on temp.id_orden = estado.id_orden
group by estado.id_orden

Using GROUP BY to only show the row with latest update for each user

I've been having trouble figuring out the syntax to do a GROUP BY to only show the row that has the latest ups.db_LastUpdate for each User (by db_UserId).
SELECT up.db_FirstName, up.db_LastName, up.db_UserId, ups.db_Initials, ups.db_LastUpdate
FROM tblUserProfile up
JOIN tblUserSel ups
ON ups.db_Code = up.db_UserId
WHERE ups.db_UserTech = 'U'
Output (There will be several hundred users, but you get the point):
Jeff | Ledger | 1-34 | JL | 2015-08-11
Jeff | Ledger | 1-34 | DBC | 2015-06-06
Jeff | Ledger | 1-34 | YX | 2015-08-01
John | Barker | 1-26 | JR | 2015-04-04
John | Barker | 1-26 | YY | 2015-02-18
John | Barker | 1-26 | FF | 2015-11-14
Maybe something like GROUP BY ups.dbUserId, MAX(db_LastUpdate)
Thanks for your help
Use ROW_NUMBER:
;WITH CTE AS
(
SELECT up.db_FirstName,
up.db_LastName,
up.db_UserId,
ups.db_Initials,
ups.db_LastUpdate,
RN = ROW_NUMBER() OVER(PARTITION BY up.db_UserId ORDER BY ups.db_LastUpdate DESC)
FROM tblUserProfile up
INNER JOIN tblUserSel ups
ON ups.db_Code = up.db_UserId
WHERE ups.db_UserTech = 'U'
)
SELECT *
FROM CTE
WHERE RN = 1;
As pointed in the comments, you can use MAX and then join with your table:
;WITH CTE AS
(
SELECT up.db_UserId,
MAX(ups.db_LastUpdate) MaxLastUpdate
FROM tblUserProfile up
INNER JOIN tblUserSel ups
ON ups.db_Code = up.db_UserId
WHERE ups.db_UserTech = 'U'
GROUP BY up.db_UserId
)
SELECT B.*
FROM CTE A
INNER JOIN tblUserSel B
ON A.db_UserId = B.db_Code
AND A.MaxLastUpdate = B.db_LastUpdate;
But you need to know that if there exists a row with the same date for the same user you'll get those 2 rows as a result.
If your tables have a unique ID column, I usually handle that situation something like this:
WITH LastEdit AS (
SELECT ups.db_Code, ups.db_Initials, ups.db_LastUpdate
FROM tblUserSel ups
WHERE ups.db_UserTech = 'U' AND ups.ID = (
SELECT TOP 1 ID
FROM tblUserSel upsn
WHERE ups.db_Initials = upsn.db_Initials
ORDER BY upsn.db_LastUpdate DESC
)
)
SELECT up.db_FirstName, up.db_LastName, up.db_UserId, le.db_Initials, le.db_LastUpdate
FROM tblUserProfile up
INNER JOIN LastEdit le
ON le.db_Code = up.db_UserId