Take only rows with fields MAX date - sql

I have following query:
SELECT cl.[Name] Client, bt.Name BottleType, SUM(csi.Amount) Amount
FROM T_Clients cl
INNER JOIN T_ClientStore cs ON cs.FK_ClientId = cl.ClientID
INNER JOIN T_ClientStoreItem csi ON csi.FK_ClientStoreId = cs.ClientStoreId
INNER JOIN T_BottleType bt ON bt.BottleTypeId = csi.FK_BootleTypeID
GROUP BY cl.[Name], bt.Name
ORDER BY cl.[Name]
In the T_ClientStore table there is column DueDate. I want to take only the records grouped by cl.[Name] for MAX(DueDate). Where could i define that? Somethin like to add:
WHERE cs.DueDate is MAX

Starting from your existing query and based on your description, a straight-forward approach would be to just turn the join on T_ClientStore to a lateral join, which retrieves the latest record for the given client:
SELECT cl.[Name] Client, bt.Name BottleType, SUM(csi.Amount) Amount
FROM T_Clients cl
CROSS APPLY (
SELECT TOP(1) *
FROM T_ClientStore cs
WHERE cs.FK_ClientId = cl.ClientID
ORDER BY cs.DueDate DESC
) cs
INNER JOIN T_ClientStoreItem csi ON csi.FK_ClientStoreId = cs.ClientStoreId
INNER JOIN T_BottleType bt ON bt.BottleTypeId = csi.FK_BootleTypeID
GROUP BY cl.[Name], bt.Name
ORDER BY cl.[Name]

Related

How to create distinct count from queries with several tables

I am trying to create one single query that will give me a distinct count for both the ActivityID and the CommentID. My query in MS Access looks like this:
SELECT
tbl_Category.Category, Count(tbl_Activity.ActivityID) AS CountOfActivityID,
Count(tbl_Comments.CommentID) AS CountOfCommentID
FROM tbl_Category LEFT JOIN
(tbl_Activity LEFT JOIN tbl_Comments ON
tbl_Activity.ActivityID = tbl_Comments.ActivityID) ON
tbl_Category.CategoryID = tbl_Activity.CategoryID
WHERE
(((tbl_Activity.UnitID)=5) AND ((tbl_Comments.PeriodID)=1))
GROUP BY
tbl_Category.Category;
I know the answer must somehow include SELECT DISTINCT but am not able to get it to work. Do I need to create multiple subqueries?
This is really painful in MS Access. I think the following does what you want to do:
SELECT ac.Category, ac.num_activities, aco.num_comments
FROM (SELECT ca.category, COUNT(*) as num_activities
FROM (SELECT DISTINCT c.Category, a.ActivityID
FROM (tbl_Category as c INNER JOIN
tbl_Activity as a
ON c.CategoryID = a.CategoryID
) INNER JOIN
tbl_Comments as co
ON a.ActivityID = co.ActivityID
WHERE a.UnitID = 5 AND co.PeriodID = 1
) as caa
GROUP BY ca.category
) as ca LEFT JOIN
(SELECT c.Category, COUNT(*) as num_comments
FROM (SELECT DISTINCT c.Category, co.CommentId
FROM (tbl_Category as c INNER JOIN
tbl_Activity as a
ON c.CategoryID = a.CategoryID
) INNER JOIN
tbl_Comments as co
ON a.ActivityID = co.ActivityID
WHERE a.UnitID = 5 AND co.PeriodID = 1
) as aco
GROUP BY c.Category
) as aco
ON aco.CommentId = ac.CommentId
Note that your LEFT JOINs are superfluous because the WHERE clause turns them into INNER JOINs. This adjusts the logic for that purpose. The filtering is also very tricky, because it uses both tables, requiring that both subqueries have both JOINs.
You can use DISTINCT:
SELECT
tbl_Category.Category, Count(DISTINCT tbl_Activity.ActivityID) AS CountOfActivityID,
Count(DISTINCT tbl_Comments.CommentID) AS CountOfCommentID
FROM tbl_Category LEFT JOIN
(tbl_Activity LEFT JOIN tbl_Comments ON
tbl_Activity.ActivityID = tbl_Comments.ActivityID) ON
tbl_Category.CategoryID = tbl_Activity.CategoryID
WHERE
(((tbl_Activity.UnitID)=5) AND ((tbl_Comments.PeriodID)=1))
GROUP BY
tbl_Category.Category;

Access Subquery On mulitple conditions

This SQL query needs to be done in ACCESS.
I am trying to do a subquery on the total sales, but I want to link the sale to the province AND to product. The below query will work with one or the other: (po.product_name = allp.all_products) AND (p.province = allp.all_province); -- but it will no take both.
I will be including every month into this query, once I can figure out the subquery on with two criteria.
Select
p.province as [Province],
po.product_name as [Product],
all_price
FROM
(purchase_order po
INNER JOIN person p
on p.person_id = po.person_id)
left join
(
select
po1.product_name AS [all_products],
sum(pp1.price) AS [all_price],
p1.province AS [all_province]
from (purchase_order po1
INNER JOIN product pp1
on po1.product_name = pp1.product_name)
INNER JOIN person p1
on po1.person_id = p1.person_id
group by po1.product_name, pp1.price, p1.province
)
as allp
on (po.product_name = allp.all_products) AND (p.province = allp.all_province);
Make the first select sql into a table by giving it an alias and join table 1 to table 2. I don't have your table structure or data to test it but I think this will lead you down the right path:
select table1.*, table2.*
from
(Select
p.province as [Province],
po.product_name as [Product]
--removed this ,all_price
FROM
(purchase_order po
INNER JOIN person p
on p.person_id = po.person_id) table1
left join
(
select
po1.product_name AS [all_products],
sum(pp1.price) AS [all_price],
p1.province AS [all_province]
from (purchase_order po1
INNER JOIN product pp1
on po1.product_name = pp1.product_name)
INNER JOIN person p1
on po1.person_id = p1.person_id
group by po1.product_name, pp1.price, p1.province --check your group by, I dont think you want pp1.price here if you want to aggregate
) as table2 --changed from allp
on (table1.product = table2.all_products) AND (table1.province = table2.all_province);

Return rows where a customer bought things on same day

Can someone help me with the rest of my Query.
This query gives me Customer, AdressNr, Date, Employee, Article, ActivityNr
from all the sales in my Company.
SELECT ad.Name + ' ' + ad.Vorname AS Customer,
pa.Kunde AS CustomerNr,
CONVERT(VARCHAR(10),p.datum,126) AS Date,
(SELECT a.name + ' ' + a.Vorname AS Name FROM PRO_Mitarbeiter m LEFT JOIN ADR_Adressen a ON a.AdressNrADR=m.AdressNrADR WHERE m.MitNrPRO = l.MitNrPRO) as Employee,
p.Artikel_1 AS Article,
l.AufgabenNrCRM AS OrderNr
FROM ZUS_Therapie_Positionen p
INNER JOIN CRM_AufgabenLink l ON l.AufgabenNrCRM = p.Id_Aktivitaet
INNER JOIN CRM_Aufgaben ab ON ab.AufgabenNrCRM = p.Id_Aktivitaet
INNER JOIN PRO_Auftraege pa ON pa.AuftragNrPRO = ab.AuftragNrPRO
INNER JOIN ADR_Adressen ad ON ad.AdressNrADR = pa.Kunde
INNER JOIN ADR_GruppenLink gl ON gl.AdressNrADR = ad.AdressNrADR
INNER JOIN ADR_Gruppen g ON g.GruppeADR = gl.GruppeADR
WHERE l.MitNrPRO != 0
GROUP BY l.AufgabenNrCRM,ad.Name,ad.Vorname,pa.Kunde,p.datum,p.Artikel_1,l.MitNrPRO
ORDER BY pa.Kunde,p.datum,l.AufgabenNrCRM
My goal is to filter this so i get only rows back where the customer has bought more then 1 Thing on the same day. It doesn't matter if a customer bought the same Article twice on the same day. I want too see this also.
It's to complicated to write some SQL Fiddle for you but in this Picture you can see what my goal is. I want to take away all rows with an X on the left side and thoose with a Circle i want to Keep.
As I don't speak German, I won't target this specifically to your SQL. But see the following quasi-code for a similar example that you should be able to apply to your own script.
SELECT C.CustomerName, O.OrderDate, O.OrderNumber
FROM CUSTOMER C
JOIN ORDERS O ON O.Customer_ID = C.Customer_ID
JOIN
(SELECT Customer_ID, OrderDate
FROM ORDERS
GROUP BY Customer_ID, OrderDate
HAVING COUNT(*) > 1) SRC
ON SRC.Customer_ID = O.Customer_ID AND SRC.OrderDate = O.OrderDate
In the script above, the last query (a subquery) would only return results where a customer had more than one order in a given day. By joining that to your main query, you would effectively produce the result asked in the OP.
Edit 1:
Regarding your comment below, I really recommend just going over your datamodel, trying to understand what's happening here, and fixing it on your own. But there is an easy - albeit hardly optimal solution to this by just using your own script above. Note, while this is not disastrous performance-wise, it's obviously not the cleanest, most effective method either. But it should work:
;WITH CTE AS (SELECT ad.Name + ' ' + ad.Vorname AS Customer,
pa.Kunde AS CustomerNr,
CONVERT(VARCHAR(10),p.datum,126) AS [Date],
(SELECT a.name + ' ' + a.Vorname AS Name FROM PRO_Mitarbeiter m LEFT JOIN ADR_Adressen a ON a.AdressNrADR=m.AdressNrADR WHERE m.MitNrPRO = l.MitNrPRO) as Employee,
p.Artikel_1 AS Article,
l.AufgabenNrCRM AS OrderNr
FROM ZUS_Therapie_Positionen p
INNER JOIN CRM_AufgabenLink l ON l.AufgabenNrCRM = p.Id_Aktivitaet
INNER JOIN CRM_Aufgaben ab ON ab.AufgabenNrCRM = p.Id_Aktivitaet
INNER JOIN PRO_Auftraege pa ON pa.AuftragNrPRO = ab.AuftragNrPRO
INNER JOIN ADR_Adressen ad ON ad.AdressNrADR = pa.Kunde
INNER JOIN ADR_GruppenLink gl ON gl.AdressNrADR = ad.AdressNrADR
INNER JOIN ADR_Gruppen g ON g.GruppeADR = gl.GruppeADR
WHERE l.MitNrPRO != 0
GROUP BY l.AufgabenNrCRM,ad.Name,ad.Vorname,pa.Kunde,p.datum,p.Artikel_1,l.MitNrPRO
ORDER BY pa.Kunde,p.datum,l.AufgabenNrCRM)
SELECT C.*
FROM CTE C
JOIN (Select CustomerNr, [Date]
FROM CTE B
GROUP BY CustomerNr, [Date]
HAVING COUNT(*) > 1) SRC
ON SRC.CustomerNr = C.CustomerNr AND SRC.[Date] = C.[Date]
This should work directly. But as I said, this is an ugly workaround where we're basically all but fetching the whole set twice, as opposed to just limiting the sub query to just the bare minimum of necessary tables. Your choice. :)
Tried that also and it didnt work. I also made a new query trying to Keep it so simple as possible and it doesnt work either. It still give me Single values back..
SELECT p.Datum,a.AufgabenNrCRM,auf.Kunde FROM CRM_Aufgaben a
LEFT JOIN ZUS_Therapie_Positionen p ON p.Id_Aktivitaet = a.AufgabenNrCRM
LEFT JOIN PRO_Auftraege auf ON auf.AuftragNrPRO = a.AuftragNrPRO
LEFT JOIN
(SELECT pa.Datum,au.Kunde FROM CRM_Aufgaben aa
LEFT JOIN ZUS_Therapie_Positionen pa ON pa.Id_Aktivitaet = aa.AufgabenNrCRM
LEFT JOIN PRO_Auftraege au ON au.AuftragNrPRO = aa.AuftragNrPRO
GROUP BY pa.Datum,au.Kunde
HAVING COUNT(*) > 1) SRC
ON SRC.Kunde = auf.Kunde
WHERE p.datum IS NOT NULL
GROUP BY p.Datum,a.AufgabenNrCRM,auf.Kunde
ORDER BY auf.Kunde,p.Datum

SQL using SUM and INNER JOIN issue

I get this error when I try to execute the query shown below
Column 'dbo.Stock_Purchase.Supplier_ID' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
Query:
SELECT
dbo.Stock_Purchase.*, dbo.Stock_Purchase_Details.*,
dbo.Supplier.*,
SUM(Stock_Purchase_Details.Discount) AS totaldis
FROM
dbo.Stock_Purchase
INNER JOIN
dbo.Stock_Purchase_Details ON dbo.Stock_Purchase.Purchase_ID = dbo.Stock_Purchase_Details.Purchase_ID
INNER JOIN
dbo.Supplier ON dbo.Stock_Purchase.Supplier_ID = dbo.Supplier.Supplier_ID
GROUP BY
Stock_Purchase.Purchase_ID
You can only include the column in the GROUP BY as a "bare" column in the SELECT. So:
SELECT p.Purchase_ID, sum(pd.Discount) as totaldis
FROM dbo.Stock_Purchase p INNER JOIN
dbo.Stock_Purchase_Details pd
ON p.Purchase_ID = pd.Purchase_ID INNER JOIN
dbo.Supplier s
ON p.Supplier_ID = s.Supplier_ID
GROUP BY p.Purchase_ID ;
Also notice how table aliases make the query easier to read, write, and understand.
If you do want all the details, you can use window functions:
SELECT p.*, pd.*, s.*,
SUM(pd.Discount) OVER (PARTITION BY p.Purchase_ID) as totaldis
FROM dbo.Stock_Purchase p INNER JOIN
dbo.Stock_Purchase_Details pd
ON p.Purchase_ID = pd.Purchase_ID INNER JOIN
dbo.Supplier s
ON p.Supplier_ID = s.Supplier_ID;

Limiting result sets by future date - SQL

The Query below produces a record for each Entry in the SP_ScheduleEvent Table.
SELECT m.MaterialId, m.MaterialTitle, se.EventDateTime, c.ChannelName
FROM GB_Material m
LEFT OUTER JOIN SP_ScheduleEvent se on se.MaterialName = m.MaterialName
INNER JOIN SP_Schedule s on s.ScheduleID = se.ScheduleID
INNER JOIN GB_Channel c on c.ChannelID = s.ChannelID
WHERE LOWER(m.MaterialName) like '%foo%' OR LOWER(m.MaterialTitle) like '%foo%'
I want to limit the result set by the nearest future EventDateTime.
So per material name i would like to see one EventDateTime, which should be the nearest future date to the current time.
And lastly, a record may not exist in the SP_ScheduleEvent table for a particular materialname, in which case there should be null returned for the EventDateTime column
SQLFiddle
How would i go about doing this?
First, your LEFT JOIN is immaterial, because the subsequent joins make it an INNER JOIN. Either use LEFT JOIN throughout the FROM statement or switch to INNER JOIN.
I think you can use ROW_NUMBER():
SELECT t.*
FROM (SELECT m.MaterialId, m.MaterialName, m.MaterialTitle, se.EventDateTime,
ROW_NUMBER() over (PARTITION BY m.MaterialId OVER se.EventDateTime DESC) as seqnum
FROM GB_Material m INNER JOIN
SP_ScheduleEvent se
on se.MaterialName = m.MaterialName INNER JOIN
SP_Schedule s
on s.ScheduleID = se.ScheduleID INNER JOIN
GB_Channel c
on c.ChannelID = s.ChannelID
WHERE se.EventDateTime > getdate() AND
(LOWER(m.MaterialName) like '%foo%' OR LOWER(m.MaterialTitle) like '%foo%')
) t
WHERE seqnum = 1
ORDER BY se.EventDateTime;
Use the ROW_NUMBER() function:
WITH cte AS (
SELECT m.MaterialId, m.MaterialTitle, se.EventDateTime, c.ChannelName,
ROW_NUMBER() OVER (PARTITION BY m.MaterialId ORDER BY EventDateTime ASC) AS rn
FROM GB_Material m
LEFT OUTER JOIN SP_ScheduleEvent se on se.MaterialName = m.MaterialName
LEFT OUTER JOIN SP_Schedule s on s.ScheduleID = se.ScheduleID
LEFT OUTER JOIN GB_Channel c on c.ChannelID = s.ChannelID
WHERE LOWER(m.MaterialName) like '%foo%' OR LOWER(m.MaterialTitle) like '%foo%'
AND se.EventDateTime > GETDATE()
)
SELECT * FROM cte
WHERE rn=1