Grouping matching names with totals Firebird 2.5 - sql

I did a basic Firebird Report to call on all debtors and transactions
The report looks as follows
SELECT
POSPAY.TXNO,
DEBTORS.COMPANY,
POSPAY.AMOUNT,
POSINVTRANS.TXDATE
FROM
POSPAY
INNER JOIN DEBTORS ON (POSPAY.ACCTNUMBER = DEBTORS.ACCOUNT)
INNER JOIN POSINVTRANS ON (POSPAY.TXNO = POSINVTRANS.TXNO)
WHERE
PAYMNTTYPID = '7'
and
weekly = :weekly and
txdate >= :fromdate and
txdate <= :todate
This works correctly and gives me output on Debtor Name, TXNO, TXDATE, AMOUNT
I now want to write a similar report but need to group the debtors and give totals on the transactions ie I need the output Debtor name (If JOHN is twice, need to list once), Total ammount (Sum of John's transactions)
I still need innerjoin on debtors but no longer on posinvtrans, I was thinking it should look something like
SELECT
POSPAY.TXNO,
DEBTORS.COMPANY,
POSPAY.AMOUNT
FROM
POSPAY
INNER JOIN DEBTORS ON (POSPAY.ACCTNUMBER = DEBTORS.ACCOUNT)
WHERE
PAYMNTTYPID = '7'
and
weekly = :weekly and
txdate >= :fromdate and
txdate <= :todate
Group by DEBTORS.COMPANY
but no luck, get errors on Group by
'invalid expression in the select list (not containing in either an aggregate function or the GROUP BY CLAUSE)'
any suggestions?

The list of fields in the select list have to be either also listed in the group by list or be aggregate functions like count(*), max(amount), etc.
The problem is that you have not told Firebird what to do with POSPAY.TXNO and POSPAY.AMOUNT and it is not sufficient to tell what you do want to happen to them.
I suggest you remove those 2 fields from the query and have a select list of DEBTORS.COMPANY, sum(POSPAY.AMOUNT) as a starting point.

If you use GROUP BY you either need to include a column in the GROUP BY, or apply an aggregate function on the column. In your example you need to leave out POSPAY.TXNO, as that is transaction specific (or you could use the aggregate function LIST), and you need to apply the aggregate function SUM to AMOUNT to get the total:
SELECT
DEBTORS.COMPANY,
SUM(POSPAY.AMOUNT)
FROM
POSPAY
INNER JOIN DEBTORS ON (POSPAY.ACCTNUMBER = DEBTORS.ACCOUNT)
WHERE
PAYMNTTYPID = '7'
and
weekly = :weekly and
txdate >= :fromdate and
txdate <= :todate
Group by DEBTORS.COMPANY

Related

Two Table Join Into One Result

I have two tables where I am attempting to join the results into one. I am trying to get the INV_QPC which is the case pack size shown in the results (SEIITN and SKU) are the same product numbers.
The code below gives two results, but the goal is to get the bottom result into the main output, where I was hoping the join would be the lookup to show the case pack size in relation to SKU.
INV_QPC = case pack size
SKU = SKU/Product Number
SEIITN = SKU/Product Number
Thanks for looking.
SELECT
ORDER_QTY, SKU, INVOICE_NUMBER, CUSTOMER_NUMBER, ROUTE,
ALLOCATED_QTY, SHORTED_QTY, PRODUCTION_DATE,
DATEPART(wk, PRODUCTION_DATE) AS FISCAL_WEEK,
YEAR(PRODUCTION_DATE) AS FISCAL_YEAR,
CONCAT(SKU, CUSTOMER_NUMBER) AS SKU_STORE_WEEK
FROM
[database].[dbo].[ORDERS]
WHERE
[PRODUCTION_DATE] >= DATEADD(day, -3, GETDATE())
AND [PRODUCTION_DATE] <= GETDATE()
SELECT INV_QPC
FROM [database].[dbo].[PRODUCT_MASTER]
JOIN [database].[dbo].[ORDERS] ON ORDERS.SKU = PRODUCT_MASTER.SEIITN;
It looks like you are on the right track, but your second SQL statement is only returning the INV_QPC column, so it is not being joined to the first query. Here is an updated SQL statement that should give you the result you are looking for:
SELECT
ORD.ORDER_QTY, ORD.SKU, ORD.INVOICE_NUMBER, ORD.CUSTOMER_NUMBER, ORD.ROUTE,
ORD.ALLOCATED_QTY, ORD.SHORTED_QTY, ORD.PRODUCTION_DATE,
DATEPART(wk, ORD.PRODUCTION_DATE) AS FISCAL_WEEK,
YEAR(ORD.PRODUCTION_DATE) AS FISCAL_YEAR,
CONCAT(ORD.SKU, ORD.CUSTOMER_NUMBER) AS SKU_STORE_WEEK,
PROD.INV_QPC
FROM
[database].[dbo].[ORDERS] ORD
JOIN [database].[dbo].[PRODUCT_MASTER] PROD ON ORD.SKU = PROD.SEIITN
WHERE
ORD.PRODUCTION_DATE >= DATEADD(day, -3, GETDATE())
AND ORD.PRODUCTION_DATE <= GETDATE()
In this query, I have added the INV_QPC column to the SELECT statement, and also included the join condition in the JOIN clause. Additionally, I have given aliases to the tables in the FROM and JOIN clauses to make the query easier to read. Finally, I have updated the WHERE clause to reference the ORD alias instead of the table name directly.

SQL - Grouping by Last Day of Quarter

I currently have a query running to average survey scores for agents. We use the date range of the LastDayOfTheQuarter and 180 days back to calculate these scores. I ran into an issue for this current quarter.
One of my agents hasn't received any surveys in 2020 which is causing the query to not pull the current lastdayofquarter and 180 days back of results.
The code I am using:
SELECT
Agent,
U.Position,
U.BranchDescription,
(ADDDATE(LastDayOfQuarter, -180)) AS MinDate,
(LastDayOfQuarter) AS MaxDate,
COUNT(DISTINCT Response ID) as SurveyCount,
AVG(CASE WHEN Question ID = Q1_2 THEN Answer Value END) AS EngagedScore,
AVG(CASE WHEN Question ID = Q1_3 THEN Answer Value END) AS KnowledgableScore,
AVG(CASE WHEN Question ID = Q1_6 THEN Answer Value END) AS ValuedScore
FROM qualtrics_responses
LEFT JOIN date D
ON (D.`Date`) = (DATE(`End Date`))
LEFT JOIN `users` U
ON U.`UserID` = `Agent ID`
WHERE `Agent` IS NOT NULL
AND DATE(`End Date`) <= (`LastDayOfQuarter`)
AND DATE(`End Date`) >= (ADDDATE(`LastDayOfQuarter`, -180))
GROUP BY `Agent`, (ADDDATE(`LastDayOfQuarter`, -180))
i know the issue is due to the way I am joining the dates and since he doesn't have a result in this current year, the end date to date join isn't grabbing the desired date range. I can't seem to come up with any alternatives. Any help is appreciated.
I make the assumption that table date in your query is a calendar table, that stores the starts and ends of the quarters (most likely with one row per date in the quarter).
If so, you can solve this problem by rearranging the joins: first cross join the users and the calendar table to generate all possible combinations, then bring in the surveys table with a left join:
SELECT
U.UserID,
U.Position,
U.BranchDescription,
D.LastDayOfQuarter - interval 180 day AS MinDate,
D.LastDayOfQuarter AS MaxDate,
COUNT(DISTINCT Q.ResponseID) as SurveyCount,
AVG(CASE WHEN Q.QuestionID = 'Q1_2' THEN Q.Answer Value END) AS EngagedScore,
AVG(CASE WHEN Q.QuestionID = 'Q1_3' THEN Q.Answer Value END) AS KnowledgableScore,
AVG(CASE WHEN Q.QuestionID = 'Q1_6' THEN Q.Answer Value END) AS ValuedScore
FROM date D
CROSS JOIN users U
LEFT JOIN qualtrics_responses Q
ON Q.EndDate >= D.Date
AND Q.EndDate < D.Date + interval 1 day
AND U.UserID = Q.AgentID
AND Q.Agent IS NOT NULL
GROUP BY
U.UserID,
U.Position,
U.BranchDescription,
D.LastDayOfQuarter
Notes:
I adapted the date arithmetics - this assumes that you are using MySQL, as the syntax of the query suggests
You should really qualify all the columns in the query, by prefixing them with the alias of the table they belong to; this makes the query so much easier to understand. I gave a tried at it, you might need to review that.
All non-aggregated columns should appear in the group by clause (also see the comment from Eric); this is a a requirement in most databaseses, and good practice anywhere

Where clause within Case Statement

I need to drill into detail on to an existing report which shows me the total profitability per customer, per month on a telecoms company.
The company sells calls, service charges and has discounts.
This is the query that I'm using, using one customer in particular:
SELECT
custumer_name,
ISNULL(SUM(CASE CONVERT(VARCHAR(10), month_end, 103)
WHEN '31/10/2016'
THEN totcall + isnull(totrec, 0) - discount
ELSE 0
END), 0) AS '31/10/2016',
SUM(totcall) AS 'Total Calls',
SUM(totrec) AS 'Total Rec',
SUM(discount) AS 'Discounts'
FROM
total_sales
INNER JOIN
customer_details ON billingaddress = customer_details .siteid
INNER JOIN
sales_month d ON total_sales.periodid = d.monthid
INNER JOIN
customer b ON customer_details .id = b.id AND b.is_customer = 1
WHERE
b.custumer_name = '2
GROUP BY
b.custumer_name
ORDER BY
b.custumer_name ASC
This is bringing back the correct total however when I need to show the drilldown of the total on calls, rec & discounts it is showing me the actual sum of every months' data which is stored on that table.
I'm not sure how can I get the last 3 columns to itemise the total without specifying the actual month (as the report has columns for the last 12 months of data.
Please help?
Thank you!
PS. The DB is a SQL Server 2008

Get the price of purchase at the moment of the sale

I'm trying to get the price of purchase at the moment of the sale. There are differents prices of purchase for the same product in my table.
The price of purchase is defined by two dates. One date for the start of the validity and one other for the end of validity : DATE_DEB_VALID and DATE_FIN_VALID.
If I want to know how many I won at the time of sale (FLIGHT_DATE), I have to know the purchase price to the same period.
My flight date must be between "DATE_DEB_VALID" and "DATE_FIN_VALID"
The two tables :
TB_DW_VAB_SALES
- ID_TEC_SALES
- TRANSACTION_NUMBER
- CARRIER_CODE
- MASTER_FLIGHT_NUMBER
- FLIGHT_NUMBER
- FLIGHT_DATE
- FLIGHT_CITY_DEP
- FLIGHT_CITY_ARR
- PRODUCT_CODE
- QUANTITY
- UNIT_SALES_PRICE
- PROMOTION_CODE
- CREW_PRICE
- COMPLEMENTARY
- SALES_TYPE
- DATE_CHGT
TB_DW_VAB_PURCHASE
- PRODUCT_CODE
- PRODUCT_CODE_GUEST
- LIB_PRODUCT
- PRICE DATE_DEB_VALID
- DATE_FIN_VALID
- DATE_CHGT
My request :
SELECT (TB_DW_VAB_PURCHASE.PRODUCT_CODE),PRICE
FROM TB_DW_VAB_PURCHASE,TB_DW_VAB_SALES
WHERE FLIGHT_DATE BETWEEN DATE_DEB_VALID AND DATE_FIN_VALID AND to_char(FLIGHT_DATE,'YYYY')= '2016' OR to_char(FLIGHT_DATE,'YYYY')= '2015'
GROUP BY TB_DW_VAB_PURCHASE.PRODUCT_CODE, PRICE
Here We have the whole request (for informations) :
SELECT to_char(DATE_VENTE,'MM/YYYY'),sum(MARGE_TOTALE) FROM (
SELECT
CTE1.CA AS CHIFFRE_AFFAIRE_TOTAL,
CTE2.PRICE AS COUT_UNITAIRE,
CTE1.FLIGHT_DATE as DATE_VENTE,
CTE1.QTE*CTE2.PRICE AS COUT_ACHAT,
(CTE1.CA-CTE1.QTE*CTE2.PRICE) AS MARGE,
sum((CTE1.CA-CTE1.QTE*CTE2.PRICE)) as MARGE_TOTALE
FROM (
SELECT PRODUCT_CODE,
sum(QUANTITY*UNIT_SALES_PRICE) AS CA,
FLIGHT_DATE,
sum(QUANTITY) as QTE
FROM TB_DW_VAB_SALES
where SALES_TYPE = 'SALES' and to_char(FLIGHT_DATE,'YYYY')= '2015' OR to_char(FLIGHT_DATE,'YYYY')= '2016'
group by to_char(FLIGHT_DATE,'MM'),FLIGHT_DATE,PRODUCT_CODE
ORDER BY to_char(FLIGHT_DATE,'MM') ASC
)CTE1
inner join
(
SELECT (TB_DW_VAB_PURCHASE.PRODUCT_CODE),PRICE
FROM TB_DW_VAB_PURCHASE,TB_DW_VAB_SALES
WHERE FLIGHT_DATE BETWEEN DATE_DEB_VALID AND DATE_FIN_VALID AND to_char(FLIGHT_DATE,'YYYY')= '2016' OR to_char(FLIGHT_DATE,'YYYY')= '2015'
GROUP BY TB_DW_VAB_PURCHASE.PRODUCT_CODE, PRICE
) CTE2
on CTE1.PRODUCT_CODE=CTE2.PRODUCT_CODE
group by to_char(FLIGHT_DATE,'MM'), FLIGHT_DATE, 'MM', CTE1.FLIGHT_DATE, (CTE1.CA-CTE1.QTE*CTE2.PRICE),
CTE1.CA, CTE1.QTE, CTE2.PRICE, CTE1.QTE*CTE2.PRICE
) group by to_char(DATE_VENTE,'MM/YYYY') ORDER BY to_char(DATE_VENTE,'MM/YYYY') ASC;
Thank you !
As Nemeros already pointed out, you have a cross-join in your CTE2 subquery, as you aren't linking sales and purchases together - other than by the flight/valid dates, but across all products, which can't be what you intended.
It looks like you're calculating things in your inline views (naming them CTE1/2 is slightly confusing as that usually refers to common table expressions or subquery factoring) which you then use for further calculations, but I don't think the intermediate steps or values are needed.
It (looks* like your query can be simplified to something like:
select to_char(trunc(s.flight_date, 'MM'),'MM/YYYY') as mois_du_sales,
sum(s.quantity*(s.unit_sales_price - p.price)) as marge_totale
from tb_dw_vab_sales s
join tb_dw_vab_purchase p
on p.product_code = s.product_code
and s.flight_date between p.date_deb_valid and p.date_fin_valid
where s.sales_type = 'SALES'
and s.flight_date >= date '2015-01-01'
and s.flight_date < date '2017-01-01'
group by trunc(s.flight_date, 'MM')
order by trunc(s.flight_date, 'MM') asc;
Of course without sample data or expected results I can't verify I've interpreted it correctly. It may also not solve all of your performance problems - you still need to look at the tables and indexes and the execution plan to see what it's actually doing, and you might want to consider partitioning if you have a large volume of data and a convenient partition key (like sales year).
Using a filter like to_char(FLIGHT_DATE,'YYYY')= '2015' forces every value in that column (or at least those that match sales_type, if that is indexed and selective enough) to be converted to strings and then compared to the fixed value - twice as you're checking both years separately - and that stops any index on flight_date being used. Using a date range allows the index to be used, though unless you have data spanning many years it may still not be selective enough to be used, and the optimiser may still choose full table scans. But only one of each table, not two as you were potentially doing.
I will just fix your initial query till you just forgotted your basic algebra (AND have more priority than OR) :
SELECT (TB_DW_VAB_PURCHASE.PRODUCT_CODE),PRICE
FROM TB_DW_VAB_PURCHASE,TB_DW_VAB_SALES
WHERE FLIGHT_DATE BETWEEN DATE_DEB_VALID AND DATE_FIN_VALID AND
(to_char(FLIGHT_DATE,'YYYY')= '2016' OR to_char(FLIGHT_DATE,'YYYY')= '2015')
GROUP BY TB_DW_VAB_PURCHASE.PRODUCT_CODE, PRICE
Now if you had used normalized join, you would have also seen that you had forgotten a join clause on the product_code, so finally :
SELECT PUR.PRODUCT_CODE, PRICE
FROM TB_DW_VAB_PURCHASE PUR
inner join TB_DW_VAB_SALES SAL ON PUR.PRODUCT_CODE = PUR.PRODUCT_CODE
AND FLIGHT_DATE BETWEEN DATE_DEB_VALID AND DATE_FIN_VALID
WHERE to_char(FLIGHT_DATE,'YYYY')= '2016' OR to_char(FLIGHT_DATE,'YYYY')= '2015'
GROUP BY PUR.PRODUCT_CODE, PRICE

Displaying and Ordering based off the same column

At the moment, I have my data broken down into month intervals. This is how I want it to be displayed, but I'm trying to only display ordered_by for those that have 300 or more total in the LoadCount for the entire table. So basically, I want to throw out any of the ordered_by that dont have at least 300
SELECT YEAR(stop.actual_arrival) AS Year, MONTH(stop.actual_arrival) AS Month, COUNT(stop.id) AS DeliveryCount, orders.ordered_by, COUNT(DISTINCT orders.id)
AS LoadCount
FROM stop INNER JOIN
(SELECT company_id, order_id, tractor_id
FROM billing_history
GROUP BY order_id, tractor_id, company_id) AS derivedtbl_1 ON stop.company_id = derivedtbl_1.company_id AND stop.order_id = derivedtbl_1.order_id INNER JOIN
tractor ON derivedtbl_1.company_id = tractor.company_id AND derivedtbl_1.tractor_id = tractor.id INNER JOIN
orders ON derivedtbl_1.company_id = orders.company_id AND derivedtbl_1.order_id = orders.id
WHERE (orders.order_type_id IN ('12', '13')) AND (stop.stop_type = 'SO') AND (stop.actual_arrival >= DATEADD(month, - 18, GETDATE())) AND (orders.customer_id = 945000) AND
(orders.ordered_by IS NOT NULL)
GROUP BY YEAR(stop.actual_arrival), MONTH(stop.actual_arrival), orders.ordered_by
ORDER BY Year, Month, orders.ordered_by
I keep going back and forth over whether I need a derived table or what... any help would be really appreciated. Thanks guys.
I'm trying to throw out any of the ordered_by that don't add up to 300 in total. If they add up to 300 across the entire board, I want them to be displayed, even if they're at 5 for that line.
If I understand your question correctly, you could make use of the CASE statement inside your select clause to show NULL or orders.ordered_by depending on the value of LoadCount. Here's a page with some examples: http://msdn.microsoft.com/en-us/library/ms181765(v=sql.110).aspx
If all you are looking to do is throw out any results that don't have ordered_by less than 300, you can use a HAVING clause with the GROUP BY
<snip...>
GROUP BY YEAR(stop.actual_arrival), MONTH(stop.actual_arrival), orders.ordered_by
HAVING orders.ordered_by >= 300
ORDER BY Year, Month, orders.ordered_by