Only run SQL Query if there are multiple rows - sql

I have a table similar to the below - OrderStatusId is the PK on the table. StatusId is a FK to the Reference Table that contains all the possible statuses. CustomerId is a FK to the Customer Details table
OrderStatusId StatusId CustomerId DateModified
1 1 1 05/09/2017
2 1 1 06/09/2017
3 2 1 07/09/2017
4 1 2 07/09/2017
What I want to do is run this query below to select the most recent Modified Date for that Customer for Status ID = 1. However, I only want to run this query if there is a count greater than 1 for the StatusId = 1 for that customer. So in my sample table above I would expect 06/09/2017 returned for CustomerId 1 but nothing to be returned for CustomerId 2. I tried a Having count(*) > 1 clause but could not get it working correctly.
SELECT TOP(1) os.DateModified AS LastModifiedDateForm
FROM [myTable].[OrderStatus] os WHERE os.CustomerId = 1 AND os.StatusID = 1
ORDER BY os.DateModified DESC

can you just use MAX instead of top 1? or are you returning more columns?
SELECT MAX(os.DateModified) AS LastModifiedDateForm
FROM [myTable].[OrderStatus] os
WHERE os.CustomerId = 1
AND os.StatusID = 1
GROUP BY CustomerId,
StatusId
HAVING COUNT(*) > 1

Maybe you are after the maximum date per customer, only if they have more than one date?
select max(os.DateModified) AS LastModifiedDateForm
from [myTable].[OrderStatus] os
group by os.CustomerId
having count(1) > 1
This will only give you data for customer with id 1, in the current situation, or every other customer id that has more than 1 row for some other sample data.

another approach using ROW_NUMBER()
with firstonly as (
select CustomerId, DateModified, ROW_NUMBER() over (partiton by CustomerId order by DateModified desc) rownumDesc, ROW_NUMBER() over (partiton by CustomerId order by DateModified) rownumAsc
FROM [myTable].[OrderStatus] os WHERE os.CustomerId = 1 AND os.StatusID = 1
)
select *
from firstonly
where rownumDesc = 1
and rownumAsc != 1
;
works for all customers at once

Related

Filter a grouped by result in PostgresSQL

I have a planning table like this:
ID
EmployeeID
ExternalID
JobID
abc
1
null
a
def
1
null
b
ghi
null
2
a
jkl
null
2
f
mno
5
null
d
I want to filter the rows by using an AND condition between EmployeeID and ExternalID grouping by the JobID. If I want the plannings for Employee 1 and External 2 the result should be:
ID
EmployeeID
ExternalID
JobID
abc
1
null
a
ghi
null
2
a
I'm trying using subqueries but the group by is making it hard to then filter the result with a simple WHERE EmployeeID = 1 AND ExternalID = 2.
Since the result should be displayed on a frontend table, where the filtering happens, I want it to return all the JobIDs that have the selected Employees and Externals. The JobID is not given beforehand.
We can try to use the condition aggregate function with your condition in a subquery and then filter the count whether greater than 0.
SELECT ID,EmployeeID,ExternalID,JobID
FROM (
SELECT *,
COUNT(CASE WHEN EmployeeID = 1 THEN 1 END) OVER(PARTITION BY JobID) cntEmployeeID,
COUNT(CASE WHEN ExternalID = 2 THEN 1 END) OVER(PARTITION BY JobID) cntExternalID
FROM T
) t1
WHERE cntEmployeeID > 0 AND cntExternalID > 0
sqlfiddle
or PostgreSQL support filter that we can try to use
SELECT ID,EmployeeID,ExternalID,JobID
FROM (
SELECT *,
COUNT(*) FILTER (WHERE EmployeeID = 1) OVER(PARTITION BY JobID) cntEmployeeID,
COUNT(*) FILTER (WHERE ExternalID = 2) OVER(PARTITION BY JobID) cntExternalID
FROM T
) t1
WHERE cntEmployeeID > 0 AND cntExternalID > 0
Found a solution to my problem with this query:
SELECT *
FROM
(
SELECT
JobID,
array_agg(EmployeeID) AS employee,
array_agg(ExternalID) AS external
FROM
plannings
GROUP BY
JobID
) t1
WHERE
array ['1', '5'] <# employee -- using array so I can look up multiple IDs
AND array ['2'] <# external
I had to convert the JobIDs to string in order to use the <# operator.
https://dbfiddle.uk/KPpiPgmo

Get number of customers having 1,2 and more than 3 products

Trying to figure out a query which shows the number of customer having 1,2 and more than 3 products. Here are the table name and fields:
Product(prod_no, prod_cust_id)
Customer(cust_id)
Product
prod_no
prod_cust_id
Cheetos1
WR123
Cheetos2
WR123
Lay1
WP232
Prings
WP678
Customer
cust_id
WN999
WR123
WP232
WP678
Example of correct query I want to get is:
1 Product - 100 customer
2 Product - 52 customer
3 Products and above - 10 customer
Product
Customers
1
100
2
52
>=3
10
I tried with the following query
SELECT COUNT (DISTINCT PROD_NO)"Product", CUST_ID"Customers"
FROM PRODUCT, CUSTOMER
WHERE PROD_CUST_ID = CUST_ID
HAVING COUNT(PROD_NO) >= 3 --for 3 products and above
GROUP BY CUST_ID
But the result is not what I wanted, so close yet so far. I tried only for 3 products and above, but how to add together with 1 product and 2 products.
Please help me out thanks
One option would be starting with distinctly counting by each column ( prod_no,prod_cust_id ), and evaluating the three or more products as an individual case within the conditional such as
WITH prod_cust AS
(
SELECT COUNT(DISTINCT prod_no) AS prod_no,
DECODE( SIGN(COUNT(DISTINCT prod_cust_id)-2),1,'>=3',
COUNT(DISTINCT prod_cust_id) ) AS prod_cust_id
FROM product
GROUP BY prod_no
)
SELECT prod_cust_id AS "Product", SUM(prod_no) AS "Customers"
FROM prod_cust
GROUP BY prod_cust_id
ORDER BY 1
Demo
You can first count the no of customers in the product table and then can count them separately. You can try the below query -
WITH DATA AS (SELECT P.*, COUNT(*) OVER(PARTITION BY prod_cust_id) CNT
FROM Product P)
SELECT '1' Product, COUNT(CASE WHEN CNT = 1 THEN CNT ELSE NULL END) Customers
FROM DATA
UNION ALL
SELECT '2', COUNT(CASE WHEN CNT = 2 THEN CNT ELSE NULL END)
FROM DATA
UNION ALL
SELECT '>=3', COUNT(CASE WHEN CNT >= 3 THEN CNT ELSE NULL END)
FROM DATA;
Demo.

Update statement with lookup table

I have a SQL table Customer with the following columns:
Customer_ID, Actioncode
I have another table with 1000+ actioncodes. Now I want to update the records in the Customer table with a unique code from the actioncode table.
I use this select statement at the moment:
update t
set t.actiecode = (select top 1 actiecode from data_mgl_campagnemails_codes)
from data_mgl_campagnemails_transfer t;
The result is that all records are updated with the same actiecode. The top 1 is responsible for that. When I remove that I got an error:
Subquery returned more than 1 value
This seems logical. How can I do this without using a cursor?
There is no relationship between the Customer and Code table.
Table structure:
data_mgl_campagnemails_transfer
id customer_id actioncode actioncode_id
1 1 - -
2 3 - -
3 4 - -
data_mgl_campagnemails_codes
id actioncode active
1 TTTT
2 RRRR
3 VVVV
4 RRRW
The result should be:
data_mgl_campagnemails_transfer
id customer_id actioncode actioncode_id
1 1 TTTT 1
2 3 RRRR 2
3 4 VVVV 3
data_mgl_campagnemails_codes
id actioncode active
1 TTTT YES
2 RRRR YES
3 VVVV YES
4 RRRW
This can be a bit tricky using a single statement, because SQL Server likes to optimize things. So the obvious:
update t
set t.actiecode = (select top 1 actiecode
from data_mgl_campagnemails_codes
order by newid()
)
from data_mgl_campagnemails_transfer t;
Also doesn't work. One method is to enumerate things and use a join or correlated subquery:
with t as (
select t.*, row_number() over (order by newid()) as seqnum
from data_mgl_campagnemails_transfer t
),
a as (
select a.*, row_number() over (order by newid()) as seqnum
from data_mgl_campagnemails_codes a
)
update t
set t.actiecode = (select top 1 actiecode from a)
from t join
a
on t.seqnum = a.seqnum;
Another way is to "trick" SQL Server into running the correlated subquery more than once. I think something like this:
update t
set t.actiecode = (select top 1 actiecode
from data_mgl_campagnemails_codes
where t.CustomerId is not null -- references the outer table but really does nothing
order by newid()
)
from data_mgl_campagnemails_transfer t;

display max on one columns with multiple columns in output

How can I display maximum OrderId for a CustomerId with many columns?
I have a table with following columns:
CustomerId, OrderId, Status, OrderType, CustomerType
A customer with Same customer id could have many order ids(1,2,3..) I want to be able to display the max Order id with the rest of the customers in a sql view. how can I achieve this?
Sample Data:
CustomerId OrderId OrderType
145042 1 A
110204 1 C
145042 2 D
162438 1 B
110204 2 B
103603 1 C
115559 1 D
115559 2 A
110204 3 A
I'd use a common table expression and ROW_NUMBER:
;With Ordered as (
select *,
ROW_NUMBER() OVER (PARTITION BY CustomerID
ORDER BY OrderId desc) as rn
from [Unnamed table from the question]
)
select * from Ordered where rn = 1
select * from table_name
where orderid in
(select max(orderid) from table_name group by customerid)
One way to do this is with not exists:
select t.*
from table t
where not exists (select 1
from table t2
where t2.CustomerId = t.CustomerId and
t2.OrderId > t.OrderId
);
This is saying: "get me all rows from t where there is no higher order id for the customer."

SQL: JOIN two tables with distinct rows from one table

This might be a very simple problem but I can't seem to get my head around this since last night.
I have 3 tables
VirtualLicense
VirtualLicenseId ProductName
-----------------------------------
1 Transaction
2 Query
3 Transaction
Product
ProductId Name
---------------------------
1 Transaction
2 Query
License
LicenseId ExpiryDate ProductId
-----------------------------------------
1 14/07/2013 1
2 13/07/2013 1
3 13/07/2013 2
4 14/07/2013 2
The VirtualLicense and License are joined using ProductName and ProductId mapping using the Product table.
I want to get combination of VirtualLicenseId and LicenseId, where I can basically assign the VirtualLicenseId to a LicenseId. Once a licenseid is assigned to a VirtualLicenseId, it should not be available for the following VirtualLicenseIds. Also, I want that the licenseid for which the expirydate is nearer(smaller) should be assigned first.
So, the result for my example data set should be
VirtualLicenseId LicenseId
---------------------------------
1 2
2 3
3 1
I do not want to loop over any of the tables for this.
I hope my problem is clear from my description and data.
You can do something like this:
In first CTE - assign rankings for VirtualLicenses within the Product groups.
In second CTE - assign rankings for Licensce within the Product groups (order by exp. date)
And at the end just join the two subqueries on productID and ranking.
WITH CTE_VL AS
(
SELECT *, ROW_NUMBER() OVER (PARTITION BY ProductId ORDER BY vl.VirtualLicenseId ASC) RN
FROM dbo.VirtualLicense vl
LEFT JOIN dbo.Product p ON vl.ProductName = p.Name
)
,CTE_License AS
(
SELECT *, ROW_NUMBER() OVER (PARTITION BY ProductId ORDER BY ExpiryDate ASC) RN
FROM dbo.License
)
SELECT VirtualLicenseId, LicenseId
FROM CTE_VL vl
LEFT JOIN CTE_License l ON vl.ProductId = l.ProductID AND vl.RN = l.RN
SQLFiddle DEMO