sql query help (count) - sql

I have 3 tables
CUSTOMERS (CUSTOMER_ID, LASTNAME, FIRSTNAME, ... other fields)
LICENSES(LICENSE_ID, CREATED_AT, RELEASE_ID, CUSTOMER_ID, ... other fields)
RELEASES(RELEASE_ID, RELEASE_NAME, RELEASE_NUMBER, ... other fields)
CREATED_AT in LICENSE is a DATE (NOT NULL) field.
the tables are related according to primary key/foreign key with the same name; a customer can have 0 or more licenses and every license has a release.
I would like to get from these tables:
the customer's firstname, lastname and the release_id of the last license that was created (according to CREATED_AT field in LICENSE to find the last one ) if there is one.
for this one i used this query:
SELECT CUSTOMERS.FIRSTNAME,
CUSTOMERS.LASTNAME,
(SELECT RELEASES.RELEASE_ID
FROM RELEASES
INNER JOIN LICENSES ON LICENSES.RELEASE_ID = RELEASES.RELEASE_ID
INNER JOIN CUSTOMERS AS t ON t.CUSTOMER_ID = LICENSES.CUSTOMER_ID
WHERE t.CUSTOMER_ID = CUSTOMERS.CUSTOMER_ID
ORDER BY LICENSES.CREATED_AT DESC LIMIT 1) AS REL_ID
FROM CUSTOMERS
WHERE EXISTS
(SELECT 1
FROM RELEASES
INNER JOIN LICENSES ON LICENSES.RELEASE_ID = RELEASES.RELEASE_ID
INNER JOIN CUSTOMERS AS t ON LICENSES.CUSTOMER_ID = t.CUSTOMER_ID
AND t.CUSTOMER_ID = CUSTOMERS.CUSTOMER_ID)
It seems to work but I ask if someone can confirm me this or if it is possible to make it simpler.
The other data I want to get from these tables are RELEASES.RELEASE_ID, RELEASES.RELEASE_NAME, RELEASES.RELEASE_NUMBER and the count of customers who last license(according to CREATED_AT in LICENSES) has this release.
I was not able to create this query.
I am using h2 database.
Thanks for the help

This should probably be equivalent
SELECT CUSTOMERS.FIRSTNAME,
CUSTOMERS.LASTNAME,
(SELECT LICENSES.RELEASE_ID
FROM LICENSES
WHERE LICENSES.CUSTOMER_ID = CUSTOMERS.CUSTOMER_ID
ORDER BY LICENSES.CREATED_AT DESC
LIMIT 1) AS REL_ID
FROM CUSTOMERS
WHERE EXISTS (
SELECT 1
FROM LICENSES
WHERE LICENSES.CUSTOMER_ID = CUSTOMER.CUSTOMER_ID
)
You don't need to join all those tables if you have foreign key constraints on LICENSES.RELEASE_ID and LICENSES.CUSTOMER_ID. In particular, there's no point in joining the RELEASES table, because the LICENSES.RELEASE_ID column already contains the wanted information.
LATERAL / CROSS APPLY join in other databases
For completeness' sake, if this were PostgreSQL, Oracle, SQL Server, etc., you could perform a lateral join, which is also known as CROSS APPLY
SELECT CUSTOMERS.FIRSTNAME,
CUSTOMERS.LASTNAME,
l.RELEASE_ID
FROM CUSTOMERS
CROSS JOIN LATERAL (
SELECT *
FROM LICENSES
WHERE LICENSES.CUSTOMER_ID = CUSTOMERS.CUSTOMER_ID
ORDER BY LICENSES.CREATED_AT DESC
LIMIT 1
) l

How about this:
SELECT c.FIRSTNAME, c.LASTNAME, r.RELEASE_ID
FROM CUSTOMERS c
INNER JOIN LICENSES l ON (c.CUSTOMER_ID = l.CUSTOMER_ID)
INNER JOIN RELEASES r ON (r.RELEASE_ID = l.RELEASE_ID)
WHERE r.CREATED_AT = ( SELECT MAX(t.CREATED_AT) FROM RELEASES t
WHERE t.RELEASE_ID = r.RELEASE_ID )
because of the inner join, only customers with releases will be listed.

Related

SQL Join two tables only if they both contain all records

I have samples, sample_products, products, orders and order_products.
Samples <=> Products (through sample_products)
Order <=> Products (through order_products).
There is no connection between Order and Samples.
Now I need to find the most used Samples on an Order. This is particularly a problem because if we for example have a package of 2 products - product A and product B, an order needs to contain both of them, not only product A or not only product B.
An order has to contain only and exactly those 2 products to be considered a sample.
I tried joining them like this but this leads to the problem I mentioned before - it does not ensure the order does not contain other products or that both A and B are present.
SELECT * FROM samples
INNER JOIN sample_products ON sample_products.sample_id = samples.id
INNER JOIN products ON products.id = sample_products.product_id
INNER JOIN order_products ON order_products.product_id = products.id
INNER JOIN orders ON orders.id = order_products.order_id
How can I make sure that it only joins the table together if both product A and B are present?
If you want to count full sample set (if sample1 consits from A,B,C -> count oreder with A,B,C), you can use number of product for join:
SELECT order_sample_count.sample_id,
COUNT(order_sample_count.order_id) as sample_use
FROM
(
SELECT sample_id, count(product_id) as semple_numbers
FROM
(
SELECT DISTINCT sample_id, sample_products.product_id
FROM samples
INNER JOIN sample_products ON sample_products.sample_id = samples.id
) AS sample_distinct
GROUP BY sample_id
) AS semple_count
INNER JOIN
)
SELECT order_id, sample_id, count(product_id) as semple_numbers
FROM
(
SELECT DISTINCT order_products.order_id, sample_products.sample_id, order_products.product_id
FROM order_products
INNER JOIN sample_products ON sample_products.product_id = order_products.product_id
) AS order_sample_distinct
GROUP BY order_id, sample_id
) AS order_sample_count ON semple_count.sample_id = order_sample_count.sample_id
AND semple_count.semple_numbers = order_sample_count.semple_numbers
GROUP BY order_sample_count.sample_id

Fetch most recent records as part of Joins

I am joining 2 tables customer & profile. Both the tables are joined by a specific column cust_id. In profile table, I have more than 1 entry. I want to select the most recent entry by start_ts (column) when joining both the tables. As a result I would like 1 row - row from customer and most recent row from profile in the resultset. Is there a way to do this ORACLE SQL?
I would use window functions:
select . . .
from customer c join
(select p.*,
row_number() over (partition by cust_id order by start_ts desc) as seqnum
from profile
) p
on c.cust_id = p.cust_id and p.seqnum = 1;
You can use a left join if you like to get customers that don't have profiles as well.
One way (which works for all DB engines) is to join the tables you want to select data from and then join against the specific max-record of profile to filter out the data
select c.*, p.*
from customer c
join profile p on c.cust_id = p.cust_id
join
(
select cust_id, max(start_ts) as maxts
from profile
group by cust_id
) p2 on p.cust_id = p2.cust_id and p.start_ts = p2.maxts
Here is another way (if there exists no newer entry then it's the newest):
select
c.*,
p.*
from
customer c inner join
profile p on p.cust_id = c.cust_id and not exists(
select *
from profile
where cust_id = c.cust_id and start_ts > p.start_ts
)

Trying to Optimize PostgreSQL Nested WHERE IN

I have a Postgres (9.1) customer database similar to:
customers.id
customers.lastname
customers.firstname
invoices.id
invoices.customerid
invoices.total
invoicelines.id
invoicelines.invoiceid
invoicelines.itemcode
invoicelines.price
I built a search which lists all customers who have purchased a certain item (say 'abc').
Select * from customers WHERE customers.id IN
(Select invoices.customerid FROM invoices WHERE invoices.id IN
(Select invoicelines.invoiceid FROM invoicelines WHERE
invoicelines.itemcode = 'abc')
)
The search works fine and brings up the correct customers but takes about 10 seconds or so on a database of 2 million invoices and 2 million line items.
I was wondering if there was another approach that could trim that down a bit.
An alternative is to use EXISTS:
Select *
from customers
WHERE EXISTS (
Select invoices.customerid
FROM invoices
JOIN invoicelines
ON invoicelines.invoiceid = invoices.id AND
invoicelines.itemcode = 'abc' AND
customers.id = invoices.customerid)
You might switch to using exists instead. I suspect that this might work well:
Select c.*
from customers c
where exists (Select 1
from invoices i join
invoicelines il
on i.id = il.invoiceid and il.itemcode = 'abc'
where c.id = i.customerid
);
For this, you want to be sure you have the right indexes: invoices(customerid, id) and invoicelines(invoiceid, itemcode).
Do you want all of the rows and columns in customer where the itemcode for that customer's item is 'abc'? If you join on the customerid then you can find all of the customer information for those items. If you have duplicates within that list you can use DISTINCT which will only give you one entry per customerID.
SELECT
DISTINCT [List of customer columns]
FROM
customers
INNER JOIN
invoicelines
ON
customers.customerid = invoicelines.customerid
AND
invoicelines.itemcode = 'abc'

Combine Two Tables in Select (SQL Server 2008)

If I have two tables, like this for example:
Table 1 (products)
id
name
price
agentid
Table 2 (agent)
userid
name
email
How do I get a result set from products that include the agents name and email, meaning that products.agentid = agent.userid?
How do I join for example SELECT WHERE price < 100?
Edited to support price filter
You can use the INNER JOIN clause to join those tables. It is done this way:
select p.id, p.name as ProductName, a.userid, a.name as AgentName
from products p
inner join agents a on a.userid = p.agentid
where p.price < 100
Another way to do this is by a WHERE clause:
select p.id, p.name as ProductName, a.userid, a.name as AgentName
from products p, agents a
where a.userid = p.agentid and p.price < 100
Note in the second case you are making a natural product of all rows from both tables and then filtering the result. In the first case you are directly filtering the result while joining in the same step. The DBMS will understand your intentions (regardless of the way you choose to solve this) and handle it in the fastest way.
This is a very rudimentary INNER JOIN:
SELECT
products.name AS productname,
price,
agent.name AS agentname
email
FROM
products
INNER JOIN agent ON products.agentid = agent.userid
I recommend reviewing basic JOIN syntax and concepts. Here's a link to Microsoft's documentation, though what you have above is pretty universal as standard SQL.
Note that the INNER JOIN here assumes every product has an associated agentid that isn't NULL. If there are NULL agentid in products, use LEFT OUTER JOIN instead to return even the products with no agent.
select p.name productname, p.price, a.name as agent_name, a.email
from products p
inner join agent a on (a.userid = p.agentid)
This is my join for slightly larger tables in Prod.Hope it helps.
SELECT TOP 1000 p.[id]
,p.[attributeId]
,p.[name] as PropertyName
,p.[description]
,p.[active],
a.[appId],
a.[activityId],
a.[Name] as AttributeName
FROM [XYZ.Gamification.V2B13.Full].[dbo].[ADM_attributeProperty] p
Inner join [XYZ.Gamification.V2B13.Full].[dbo].[ADM_activityAttribute] a
on a.id=p.attributeId
where a.appId=23098;
select ProductName=p.[name]
, ProductPrice=p.price
, AgentName=a.[name]
, AgentEmail=a.email
from products p
inner join agent a on a.userid=p.agentid
If you don't want to use inner join (or don't have possibility to do it!) and would combine rows, you can use a cross join :
SELECT *
FROM table1
CROSS JOIN table2
or simply
SELECT *
FROM table1, table2

SQL Find next Date for each Customer, SQL For each?

I have some problems with an SQL statement. I need to find the next DeliveryDate for each Customer in the following setup.
Tables
Customer (id)
DeliveryOrder (id, deliveryDate)
DeliveryOrderCustomer (customerId, deliveryOrderId)
Each Customer may have several DeliveryOrders on the same deliveryDate. I just can't figure out how to only get one deliveryDate for each customer. The date should be the next upcoming DeliveryDate after today. I feel like I would need some sort of "for each" here but I don't know how to solve it in SQL.
Another simpler version
select c.id, min(o.date)
from customer c
inner join deliveryordercustomer co o on co.customerId = c.id
inner join deliveryorder o on co.deliveryOrderId = o.id and o.date>getdate()
group by c.id
This would give the expected results using a subselect. Take into account that current_date may be rdbms specific, it works for Oracle.
select c.id, o.date
from customer c
inner join deliveryordercustomer co o on co.customerId = c.id
inner join deliveryorder o on co.deliveryOrderId = o.id
where o.date =
(select min(o2.date)
from deliveryorder o2
where o2.id = co.deliveryOrderId and o2.date > current_date)
You need to use a group by. There's a lot of ways to do this, here's my solution that takes into account multiple orders on same day for customer, and allows you to query different delivery slots, first, second etc. This assumes Sql Server 2005 and above.
;with CustomerDeliveries as
(
Select c.id, do.deliveryDate, Rank()
over (Partition BY c.id order by do.deliveryDate) as DeliverySlot
From Customer c
inner join DeliveryOrderCustomer doc on c.id = doc.customerId
inner join DeliveryOrder do on do.id = doc.deliveryOrderId
Where do.deliveryDate>GETDATE()
Group By c.id, do.deliveryDate
)
Select id, deliveryDate
From CustomerDeliveries
Where DeliverySlot = 1