Find combination which does not have 'AC' status - sql-server

Consider the data below which is stored in temp table. How to get all those ID / State / Group combination which does not have Status record having 'AC' value ?
Data Image
The result should yield
ID State Group
2 FL LI
3 FL VA
5 FL LI

There are several query patterns that will achieve that result.
An example of one of those patterns, using an GROUP BY operation, with aggregation of a condition (to exclude the groupings where there's a row in the grouping that has a status value of 'AC')
SELECT t.id
, t.state
, t.group
FROM mytable t
GROUP
BY t.id
, t.state
, t.group
HAVING MAX(CASE WHEN t.status='AC' THEN 1 ELSE 0 END) = 0
An example of another pattern, using an anti-join:
SELECT t.id
, t.state
, t.group
FROM mytable t
LEFT
JOIN mytable s
ON s.id = t.id
AND s.state = t.state
AND s.group = t.group
AND s.status = 'AC'
WHERE s.id IS NULL
GROUP
BY t.id
, t.state
, t.group

Select *
FROM TableName t
WHERE NOT EXISTS (SELECT 1
FROM TableName
WHERE [State] = t.[State]
AND [Group] = t.[Group]
AND [Status] = 'AC')

Related

BigQuery LEFT JOIN a table and filter its array elements based on conditions

I want to join a table to another table containing arrays and in the joined result I want to have only the array elements which pass a condition. In this case a date condition.
The code snippet below illustrates my problem. I want the output to contain only ids with record_dates less than '2019-10-15'
WITH platform AS (
SELECT 'u1' AS id, 'm1' AS platform_id, '2019-10-12' as record_date
UNION ALL
SELECT 'u2' AS id, 'm1' AS platform_id, '2019-10-13' as record_date
UNION ALL
SELECT 'u21' AS id, 'm1' AS platform_id, '2019-10-16' as record_date
),
platform_agg AS (
SELECT platform_id
, ARRAY_AGG(id) as ids
, ARRAY_AGG(record_date) as record_dates
FROM platform
GROUP BY platform_id
),
orders AS(
SELECT 'u2' AS id, 'c1' AS order_id, '2019-10-15' as order_date
),
orders_plus_platform AS (
SELECT order_id
, orders.id
, orders.order_date
, platform.platform_id
, CASE WHEN platform.platform_id IS NOT NULL THEN platform_agg.ids ELSE [orders.id] END AS ids
, CASE WHEN platform.platform_id IS NOT NULL THEN platform_agg.record_dates ELSE NULL END AS record_dates
FROM orders
LEFT JOIN platform
ON orders.id = platform.id and platform.record_date <= orders.order_date
LEFT JOIN platform_agg
ON platform.platform_id = platform_agg.platform_id
)
SELECT * FROM orders_plus_platform
Below is the current query output, however, in the desired output the u21 element should be filtered out as the record_date is after '2019-10-15'.
Thank you,
The below solution worked for me. Basically you join twice to the platform table to get all the ids associated with a platform, instead of joining to a pre-aggregated versions of it. This way you can more easily apply filters.
orders_plus_platform AS (
SELECT order_id
, orders.id
, orders.order_date
, platform.platform_id
, ARRAY_AGG(CASE WHEN platform.platform_id IS NOT NULL THEN platform2.id ELSE orders.id END) AS ids
, ARRAY_AGG(CASE WHEN platform.platform_id IS NOT NULL THEN platform2.record_date ELSE NULL END) AS record_dates
FROM orders
LEFT JOIN platform
ON orders.id = platform.id and platform.record_date <= orders.order_date
LEFT JOIN platform platform2
ON platform.platform_id = platform2.platform_id AND platform2.record_date <= orders.order_date
GROUP BY
order_id
, orders.id
, orders.order_date
, platform.platform_id
)
You can use subqueries in your WHERE clause. Subqueries can run on unnested arrays and return a boolean value - e.g. count of dates < something should be more than zero:
SELECT c_id
, c.id
, c.c_date
, cxd.record_id
, CASE WHEN cxd.record_id IS NOT NULL THEN rd_agg.ids ELSE [c.id] END AS ids
, CASE WHEN cxd.record_id IS NOT NULL THEN rd_agg.record_dates ELSE NULL END AS record_dates
FROM c
LEFT JOIN record_ids cxd
ON c.id = cxd.id and cxd.record_date <= c.c_date
LEFT JOIN record_ids_agg rd_agg
ON cxd.record_id = rd_agg.record_id
WHERE (SELECT COUNT(1)>0 FROM UNNEST(record_dates) AS r WHERE r < '2019-10-15')

Create a stored procedure to aggregate rows

Having a transaction table with the following rows:
Id UserId PlatformId TransactionTypeId
-------------------------------------------------
0 1 3 1
1 1 1 2
2 2 3 2
3 3 2 1
4 2 3 1
How do I write a stored procedure that can aggregate the rows into a new table with the following format?
Id UserId Platforms TransactionTypeId
-------------------------------------------------
0 1 {"p3":1,"p1":1} {"t1":1,"t2":1}
1 2 {"p3":2} {"t2":1,"t1":1}
3 3 {"p2":1} {"t1":1}
So the rows are gouped by User, count each platform/transactionType and store as key/value json string.
Ref: My previous related question
You could use GROUP BY and FOR JSON:
SELECT MIN(ID) AS ID, UserId, MIN(sub.x) AS Platforms, MIN(sub2.x) AS Transactions
FROM tab t
OUTER APPLY (SELECT CONCAT('p', platformId) AS platform, cnt
FROM (SELECT PlatformId, COUNT(*) AS cnt
FROM tab t2 WHERE t2.UserId = t.UserId
GROUP BY PlatformId) s
FOR JSON AUTO) sub(x)
OUTER APPLY (SELECT CONCAT('t', TransactiontypeId) AS Transactions, cnt
FROM (SELECT TransactiontypeId, COUNT(*) AS cnt
FROM tab t2 WHERE t2.UserId = t.UserId
GROUP BY TransactiontypeId) s
FOR JSON AUTO) sub2(x)
GROUP BY UserId;
DBFiddle Demo
Result is a bit different(array of key-value) but please treat it as starting point.
Your sample JSON is not really a json, but since you want it that way:
SELECT u.UserId, plt.pValue, ttyp.ttValue
FROM Users AS [u]
CROSS APPLY (
SELECT '{'+STUFF( (SELECT ',"'+pn.pName+'":'+LTRIM(STR(pn.pCount))
FROM (SELECT p.Name AS pName, COUNT(*) AS pCount
FROM transactions t
left JOIN Platforms p ON p.PlatformID = t.PlatformId
WHERE t.UserId = u.UserId
GROUP BY p.PlatformId, p.Name
) pn
FOR XML PATH('')),1,1,'')+'}'
) plt(pValue)
CROSS APPLY (
SELECT '{'+STUFF( (SELECT ',"'+tty.ttName+'":'+LTRIM(STR(tty.ttCount))
FROM (SELECT tt.Name AS ttName, COUNT(*) AS ttCount
FROM transactions t
left JOIN dbo.TransactionType tt ON tt.TransactionTypeId = t.TransactionTypeID
WHERE t.UserId = u.UserId
GROUP BY tt.TransactionTypeId, tt.Name
) tty
FOR XML PATH('')),1,1,'')+'}'
) ttyp(ttValue)
WHERE EXISTS (SELECT * FROM transactions t WHERE u.UserId = t.UserId)
ORDER BY UserId;
DBFiddle Sample

FOLLOW UP to SQL query to retrieve the latest status of a process

The original question and schema are shown at the following link:
SQL query to retrieve the latest status of a process
The solution provided by #mendosi was perfect. However, now that the deadline for submission is past, management wants more information. I've been able to give them the information they want using the following query (incorporating the aforementioned solution into the "EXISTS" clause):
SELECT
proposalPackage.proposalPackageID, refProposalType.name, proposalPackage.title,
[user].lastName, [user].firstName, [user].email, [user].phone,
proposalReviewAction.approvalTypeID
FROM
proposalReviewAction, proposalPackage
INNER JOIN
refProposalType ON proposalPackage.proposalTypeID = refProposalType.proposalTypeID
INNER JOIN
proposalManagerAssignment ON proposalPackage.proposalPackageID = proposalManagerAssignment.proposalPackageID
INNER JOIN
[user] ON proposalManagerAssignment.userID = [user].userID
WHERE
EXISTS (SELECT ls.*
FROM
(SELECT
r.proposalPackageID, r.approvalTypeID,
RowNr = ROW_NUMBER() OVER (PARTITION BY r.proposalPackageID ORDER BY r.reviewedDate DESC)
FROM
proposalReviewAction AS r
JOIN
proposalPackage AS pp ON pp.proposalPackageID = r.proposalPackageID
WHERE
pp.proposalCallID = 7) AS ls
WHERE
ls.RowNr = 1
AND (ls.approvalTypeID = 50))
GROUP BY
proposalPackage.proposalTypeID, [user].lastName, [user].firstName,
[user].email, [user].phone, proposalPackage.title,
refProposalType.name, proposalManagerAssignment.isPrimary,
proposalPackage.proposalCallID, approvalTypeID,
proposalPackage.proposalPackageID, proposalReviewAction.approvalTypeID
HAVING
(proposalManagerAssignment.isPrimary = 1)
AND (proposalPackage.proposalCallID = 7)
AND (approvalTypeID = 50)
ORDER BY
proposalPackage.proposalPackageID
My problem seems to be that the subquery in the Exists clause returns 95 rows (as it should) limiting the results to those with a status of 50.
As I understand the EXISTS clause, the results should be limited to those records that "exist" in the subquery that follows... right? So, in this case, if a record does not exist in the subquery, it will not exist in the final result...??
The problem is, I'm getting 112 records when there are only 95 records to choose from (or join on) in the results list of the subquery.
So, I try to limit is by adding some additional qualifiers and joins to the subquery:
SELECT
proposalPackage.proposalPackageID, refProposalType.name,
proposalPackage.title,
[user].lastName, [user].firstName, [user].email, [user].phone,
proposalReviewAction.approvalTypeID
FROM
proposalReviewAction, proposalPackage
INNER JOIN
refProposalType ON proposalPackage.proposalTypeID = refProposalType.proposalTypeID
INNER JOIN
proposalManagerAssignment ON proposalPackage.proposalPackageID = proposalManagerAssignment.proposalPackageID
INNER JOIN
[user] ON proposalManagerAssignment.userID = [user].userID
WHERE
EXISTS (SELECT ls.*
FROM
(SELECT
r.proposalPackageID,
r.approvalTypeID,
RowNr = ROW_NUMBER() OVER (PARTITION BY r.proposalPackageID ORDER BY r.reviewedDate DESC)
FROM
proposalReviewAction AS r
JOIN
proposalPackage AS pp ON pp.proposalPackageID = r.proposalPackageID
WHERE
pp.proposalCallID = 7) AS ls
WHERE
ls.RowNr = 1
AND (ls.approvalTypeID = 50)) AS distinctified
INNER JOIN
proposalPackage ON distinctified.proposalPackageID = proposalPackage.proposalPackageID
INNER JOIN
refProposalApprovalType ON distinctified.approvalTypeID = refProposalApprovalType.approvalTypeID
GROUP BY
proposalPackage.proposalTypeID, [user].lastName, [user].firstName,
[user].email, [user].phone, proposalPackage.title, refProposalType.name,
proposalManagerAssignment.isPrimary, proposalPackage.proposalCallID,
approvalTypeID, proposalPackage.proposalPackageID, proposalReviewAction.approvalTypeID
HAVING
(proposalManagerAssignment.isPrimary = 1)
AND (proposalPackage.proposalCallID = 7)
AND (distinctified.approvalTypeID = 50)
ORDER BY
proposalPackage.proposalPackageID
Now, when I add the "AS distinctified" statement with a couple of JOINS to the subquery, I get a "SYNTAX ERROR near AS" error. I also get an "Expecting ( or SELECT" at each of the "HAVING" qualifiers.
I don't think I'm making this too complicated but that remains a possibility. It seems to me it is a matter (at this point) of overlooking a character somewhere.
Thanks in advance for the assist... AGAIN!!
This isn't really an answer to your much more complex example, but it should explain what the root cause is hopefully?
DECLARE #x TABLE (id INT);
INSERT INTO #x SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3;
DECLARE #y TABLE (id INT);
INSERT INTO #y SELECT 1 UNION ALL SELECT 3;
--This is what you are doing
SELECT * FROM #x WHERE EXISTS (SELECT * FROM #y);
--This is what you should be doing
SELECT * FROM #x x WHERE EXISTS (SELECT * FROM #y y WHERE y.id = x.id);
I really have no idea what you are trying to accomplish but here is what your query might look like with some aliases and formatting. I also moved the joins before the where clause and removed the alias on your EXISTS predicate. But you are referencing distinctified in the code which I just don't get. As such there are some issues in this query still but without an understanding of what the need is I don't know what to do to help.
SELECT pp.proposalPackageID
, pt.name
, pp.title
, u.lastName
, u.firstName
, u.email
, u.phone
, pra.approvalTypeID
FROM proposalReviewAction pra
--, proposalPackage pp --why is this table here? It is joined to again later
INNER JOIN refProposalType pt ON pp.proposalTypeID = pt.proposalTypeID
INNER JOIN proposalManagerAssignment ma ON pp.proposalPackageID = ma.proposalPackageID
INNER JOIN [user] u ON ma.userID = u.userID
INNER JOIN proposalPackage pp ON distinctified.proposalPackageID = pp.proposalPackageID
INNER JOIN refProposalApprovalType pat ON distinctified.approvalTypeID = pat.approvalTypeID
WHERE EXISTS
(
SELECT ls.*
FROM
(
SELECT r.proposalPackageID,
r.approvalTypeID,
RowNr = ROW_NUMBER() OVER (PARTITION BY r.proposalPackageID ORDER BY r.reviewedDate DESC)
FROM proposalReviewAction AS r
JOIN proposalPackage AS pp2 ON pp2.proposalPackageID = r.proposalPackageID
WHERE pp2.proposalCallID = 7
) AS ls
WHERE ls.RowNr = 1
AND ls.approvalTypeID = 50
)
GROUP BY pp.proposalTypeID
, u.lastName
, u.firstName
, u.email
, u.phone
, pp.title
, pt.name
, ma.isPrimary
, pp.proposalCallID
, approvalTypeID
, pp.proposalPackageID
, pra.approvalTypeID
HAVING ma.isPrimary = 1
AND pp.proposalCallID = 7
AND distinctified.approvalTypeID = 50
ORDER BY pp.proposalPackageID
So, I figured it out... once I realized I had the wrong understanding of EXISTS (thanks #Richard Hansel). Final query: (properly formatted and aliased)
SELECT pp.proposalPackageID
, r_pt.name
, pp.title
, u.lastName
, u.firstName
, u.email
, u.phone
, pra.approvalTypeID
FROM proposalReviewAction AS pra
, proposalPackage AS pp
INNER JOIN refProposalType AS r_pt ON pp.proposalTypeID = r_pt.proposalTypeID
INNER JOIN proposalManagerAssignment AS pma ON pp.proposalPackageID = pma.proposalPackageID
INNER JOIN [user] AS u ON pma.userID = u.userID
WHERE EXISTS
(SELECT ls.*
FROM
(SELECT r.proposalPackageID,
r.approvalTypeID,
RowNr = ROW_NUMBER() OVER (PARTITION BY r.proposalPackageID ORDER BY r.reviewedDate DESC)
FROM proposalReviewAction AS r
JOIN proposalPackage AS pp ON pp.proposalPackageID = r.proposalPackageID
WHERE pp.proposalCallID = 7) AS ls
WHERE ls.RowNr = 1
AND (ls.approvalTypeID = 50)
AND (pra.proposalPackageID = pp.proposalPackageID))
GROUP BY pp.proposalTypeID
, u.lastName
, u.firstName
, u.email
, u.phone
, pp.title
, r_pt.name
, pma.isPrimary
, pp.proposalCallID
, approvalTypeID
, pp.proposalPackageID
, pra.approvalTypeID
HAVING (pma.isPrimary = 1)
AND (pp.proposalCallID = 7)
AND (pra.approvalTypeID = 50)
ORDER BY pp.proposalTypeID

How to use CTE to get a query repeated for multiple inputs?

I have the following query:
SELECT **top 1** account, date, result
FROM table_1 as t1
JOIN table_2 at t2 ON t1.accountId = t2.frn_accountId
WHERE accountID = 1
ORDER BY date
This query returns the result that I want however I want that result for multiple accountID. They query should return the top 1 value for each accountID.
The query that produce the list of the accountID-s is:
SELECT accountID from lskin WHERE refname LIKE '%BHA%' and isactive = 1
How can I write this query so it can produce the desired result? I have been playing around with CTE but haven't been able to make it correct. It doesn't have to be with CTE, I just thought it can be easier using CTE...
Here is CTE solution.
SELECT *
FROM (SELECT account
, date
, result
, ROW_NUMBER() OVER (PARTITION BY t1.accountId ORDER BY date DESC) AS Rownum
FROM table_1 AS t1
INNER JOIN table_2 AS t2
ON t1.accountId = t2.frn_accountId
INNER JOIN lskin AS l
ON l.accountID = t1.accountID
WHERE l.refname LIKE '%BHA%'
) a
WHERE a.Rownum = 1;
Use max on your date and group by the account, or what ever columns are appropriate.
SELECT
account,
DT = max(date),
result
FROM table_1 as t1
JOIN table_2 as t2 ON t1.accountId = t2.frn_accountId
JOIN lskin as l on l.accountID = t1.accountID
WHERE l.refname like '%BHA%'
GROUP BY
account
,result
If the grouping isn't correct, just join to a sub-query to limit it with max date. Just change the table names as necessary.
SELECT
account,
date,
result
FROM table_1 as t1
JOIN table_2 as t2 ON t1.accountId = t2.frn_accountId
JOIN lskin as l on l.accountID = t1.accountID
INNER JOIN (select max(date) dt, accountID from table_1 group by accountID) tt on tt.dt = t1.accountId and tt.accountId = t1.accountId
WHERE l.refname like '%BHA%'
Ignore the CTE at the top. That's just test data.
/* CTE Test Data */
; WITH table_1 AS (
SELECT 1 AS accountID, 'acc1' AS account UNION ALL
SELECT 2 AS accountID, 'acc2' AS account UNION ALL
SELECT 3 AS accountID, 'acc3' AS account
)
, table_2 AS (
SELECT 1 AS frn_accountID, 'new1' AS result, GETDATE() AS [date] UNION ALL
SELECT 1 AS frn_accountID, 'mid1' AS result, GETDATE()-1 AS [date] UNION ALL
SELECT 1 AS frn_accountID, 'old1' AS result, GETDATE()-2 AS [date] UNION ALL
SELECT 2 AS frn_accountID, 'new2' AS result, GETDATE() AS [date] UNION ALL
SELECT 2 AS frn_accountID, 'mid2' AS result, GETDATE()-1 AS [date] UNION ALL
SELECT 2 AS frn_accountID, 'old2' AS result, GETDATE()-2 AS [date] UNION ALL
SELECT 3 AS frn_accountID, 'new3' AS result, GETDATE() AS [date] UNION ALL
SELECT 3 AS frn_accountID, 'mid3' AS result, GETDATE()-1 AS [date] UNION ALL
SELECT 3 AS frn_accountID, 'old3' AS result, GETDATE()-2 AS [date]
)
, lskin AS (
SELECT 1 AS accountID, 'purple' AS refName, 1 AS isActive UNION ALL
SELECT 2 AS accountID, 'blue' AS refName, 1 AS isActive UNION ALL
SELECT 3 AS accountID, 'orange' AS refName, 0 AS isActive UNION ALL
SELECT 4 AS accountID, 'blue' AS refName, 1 AS isActive
)
,
/* Just use the below and remove comment markers around WITH to build Orders CTE. */
/* ; WITH */
theCTE AS (
SELECT s1.accountID, s1.account, s1.result, s1.[date]
FROM (
SELECT t1.accountid, t1.account, t2.result, t2.[date], ROW_NUMBER() OVER (PARTITION BY t1.account ORDER BY t2.[date]) AS rn
FROM table_1 t1
INNER JOIN table_2 t2 ON t1.accountID = t2.frn_accountID
) s1
WHERE s1.rn = 1
)
SELECT lskin.accountID
FROM lskin
INNER JOIN theCTE ON theCTE.accountid = lskin.accountID
WHERE lskin.refName LIKE '%blue%'
AND lskin.isActive = 1
;
EDITED:
I'm still making a lot of assumptions about your data structure. And again, make sure you're querying what you need. CTEs are awesome, but you don't want to accidentally filter out expected results.

Use IF in SELECT part of query

I need to create something like this
SELECT x.id
, x.name
, x.type
,(
IF x.type = 1
(SELECT SUM(Col1) FROM TableA WHERE ... etc)
ELSE IF x.type = 2
(SELECT SUM(Col2) FROM TableB WHERE ... etc)
) AS Total
FROM TableX as x
So I am trying to select a different sub query according to the value of x.type
Wing
Try to use LEFT JOIN and COALESCE. Use your conditions of x.type to join the tables.
COALESCE (Transact-SQL): Evaluates the arguments in order and returns the current value of the first expression that initially does not evaluate to NULL.
https://msdn.microsoft.com/en-us/library/ms190349.aspx
SELECT x.id
, x.name
, x.type
, COALESCE(SUM(TableA.Column), SUM(TableB.Column)) as column_xyz
FROM TableX as x
LEFT JOIN TableA ON x.type = 1 AND ...
LEFT JOIN TableB ON x.type = 2 AND ...
You can also use CASE WHEN ... THEN ... instead of COALESCE to define which column to use.
Use CASE statement
SELECT x.id,
x.name,
x.type,
CASE
WHEN x.type = 1 THEN (SELECT Sum(Col1)
FROM TableA Where...)
WHEN x.type = 2 THEN (SELECT Sum(Col2)
FROM TableB Where .. )
END AS Total
FROM TableX AS x
You can use CASE WHEN as the below:
SELECT
x.id,
x.name,
x.type,
CASE
WHEN x.type = 1 THEN (SELECT SUM(A.Col1) FROM TableA A WHERE 1 = 1)
WHEN x.type = 2 THEN (SELECT SUM(B.Col2) FROM TableB B WHERE 1 = 1)
ELSE NULL END AS Total
FROM
TableX as x
You can use case expression:
select t.* ,
Case when t.type = 1 then (select sum(col1) ... TableA)
when t.type = 2 then (select sum(col2) ... TableB)
End as Total
From tableX t
By using variable -
DECLARE #SumA INT = SELECT SUM(Col1) FROM TableA WHERE ... etc
DECLARE #SumB INT = SELECT SUM(Col2) FROM TableB WHERE ... etc
SELECT x.id
, x.name
, x.type
,( CASE x.type
WHEN 1 THEN #SumA
WHEN 2 THEN #SumB
END
) AS Total
FROM TableX as x
Choose the datatype for Sum variable accordingly (if Decimal).

Resources