Incorrect Results using SUM() - sql

I am not sure where I have gone wrong. I am trying to count the number of hours and endpoints for a company, per agreement. My SUM() results, however, are wildly off-course.
Here is my code:
SELECT v_rpt_Company.Company_Name, v_rpt_Service.agreement_name,
COUNT(DISTINCT v_rpt_Service.TicketNbr) AS tickets,
SUM(ISNULL(v_rpt_Service.Hours_Agreement, 0)) AS hours,
SUM(ISNULL(AGR_Detail.AGD_Qty, 0)) AS endpoints
FROM AGR_Header
INNER JOIN v_rpt_Service
ON AGR_Header.AGR_Header_RecID = v_rpt_Service.AGR_Header_RecID
INNER JOIN v_rpt_Company
ON v_rpt_Service.company_recid = v_rpt_Company.Company_RecID AND
AGR_Header.Company_RecID = v_rpt_Company.Company_RecID
INNER JOIN AGR_Detail
ON AGR_Header.AGR_Header_RecID = AGR_Detail.AGR_Header_RecID
WHERE
(v_rpt_Service.date_entered >= DATEADD(day, - 30, GETDATE()))
AND
(v_rpt_Company.Company_RecID =
CASE
WHEN #Company <> - 1 THEN #Company
ELSE v_rpt_Company.Company_RecID
END)
AND
(v_rpt_Service.AGR_Header_RecID =
CASE
WHEN #Agreement <> - 1 THEN #Agreement
ELSE v_rpt_Service.AGR_Header_RecID
END)
GROUP BY v_rpt_Company.Company_Name, v_rpt_Service.agreement_name
ORDER BY v_rpt_Company.Company_Name, v_rpt_Service.agreement_name

To debug cases like this, you should set up a test database, probably on your development computer where you can drop all tables at any time, rebuild the schema and load test data with specific test cases.
Use tools like dbunit for this. That way, you can be sure that the SQL works as expected, even when the requirements change.
If your test cases work but the result in the production DB fails, copy part of the production DB into your development DB and create a new test case.

The second part of your WHERE clause isn't going to work as planned:
(v_rpt_Company.Company_RecID =
CASE
WHEN #Company <> - 1 THEN #Company
ELSE v_rpt_Company.Company_RecID
END)
the ELSE part will match the current record if #Company=-1, essentially making the result always true.

SELECT v_rpt_Company.Company_Name, v_rpt_Service.agreement_name, COUNT(DISTINCT v_rpt_Service.TicketNbr) AS tickets,
SUM(ISNULL(v_rpt_Service.Hours_Actual, 0)) AS hours,( SUM(ISNULL(AGR_Detail.AGD_Qty, 0))/COUNT(DISTINCT v_rpt_Service.TicketNbr)) AS endpoints
That ended up being the correct result. Thank you DavidFaber.

Related

Same Query, Same Server, different database query runs super slow

Wrote a view in a database. The view takes 0 seconds to run when called from 1 database and 2.5 minutes when called from another.
I have created a video that best describes this problem. Watch it here: https://youtu.be/jEqI2bUyelQ
I tired to re create the view by dropping it.
I tried to compare the query execution plans, they are different when run with 1 database vs the other.
I looked into the query it self and noticed that if you remove the where clause the performance is regained and it takes the same amount of time for both.
Expected results are that it should take 0 seconds to run from no matter what database the view is being called from.
Here is the SQL script:
SELECT
cus.MacolaCustNo,
dsp.cmp_code ,
count(distinct dsp.item_no) AS InventoryOnDisplay,
(SELECT max(dsp.LastSynchronizationDate)
FROM Hinkley.dbo.vw_HH_next_Capture_date ) AS UpdatedDate,
case
WHEN DATEADD(DAY, 90, isnull(max(dsp.LastSynchronizationDate),'1/1/1900')) >=
(SELECT max(dsp.LastSynchronizationDate)
FROM Hinkley.dbo.vw_HH_next_Capture_date )
THEN 'Compliant'
WHEN DATEADD(DAY, 90, isnull(max(dsp.LastSynchronizationDate),'1/1/1900')) <=
(SELECT max(dsp.LastSynchronizationDate)
FROM Hinkley.dbo.vw_HH_next_Capture_date )
AND DATEADD(DAY, 90, isnull(max(dsp.LastSynchronizationDate),'1/1/1900')) >= getdate()
THEN 'Warning'
ELSE 'Non-Compliant'
END AS Inventory_Status
FROM
Hinkley.dbo.HLIINVDSP_SQL dsp (nolock)
INNER JOIN
[DATA].dbo.vw_HLI_Customer (nolock) cus
ON cus.CusNo = dsp.cmp_code
WHERE
cus.cust_showroom = 1
AND
cus.active_y = 1
GROUP BY cus.MacolaCustNo,dsp.cmp_code

SQL query to filter by specific date criteria

SQL query to filter by specific date criteria
SQL Server Management Studio V17.7
Question: I am looking for guidance related to a view on how to select records where the Start_Date falls between a defined date range when either the Admit_Status = 1 or Admit_Status = 0 as shown below:
Criteria should be something like: if admit status = 1 then dbo.PT_ASSIGNMENT.START_DATE >= referral_date to ifnull dbo.PT_ADMISSION.TERMINATION_DATE then now() else dbo.PT_ADMISSION.TERMINATION_DATE or if admit status = 0 then start_date >= referral_date to ifnull dbo.PT_ADMISSION.PROSPECT_TERM_DATE then now() else dbo.PT_ADMISSION.PROSPECT_TERM_DATE
My SQL query (View) excluding the above question:
SELECT dbo.RES_BASIC.RESOURCE_ID,
dbo.PT_ADMISSION.ADMISSION_ID,
dbo.PT_ASSIGNMENT.START_DATE,
(CASE PT_ADMISSION.PROSPECT_ADMIT_DATE WHEN NULL THEN PT_ADMISSION.ADMIT_DATE ELSE PT_ADMISSION.PROSPECT_ADMIT_DATE END) AS REFERRAL_DATE,
dbo.PT_ADMISSION.ADMIT_DATE,
dbo.PT_ADMISSION.PROSPECT_ADMIT_DATE,
dbo.PT_ADMISSION.PROSPECT_TERM_DATE,
dbo.PT_ADMISSION.TERMINATION_DATE,
CASE WHEN PT_ADMISSION.ADMIT_DATE IS NOT NULL THEN 1 ELSE 0 END AS ADMIT_STATUS,
dbo.PT_BASIC.PATIENT_CODE,
dbo.RES_BASIC.NAME_FULL
FROM dbo.PT_BASIC
INNER JOIN dbo.PT_STATUS
ON dbo.PT_BASIC.PATIENT_ID = dbo.PT_STATUS.PATIENT_ID
INNER JOIN dbo.A_PATIENT_STATUS
ON dbo.PT_STATUS.ADMIN_SET_ID = dbo.A_PATIENT_STATUS.ADMIN_SET_ID
AND dbo.PT_STATUS.STATUS_CODE = dbo.A_PATIENT_STATUS.STATUS_CODE
INNER JOIN dbo.O_DATASET
ON dbo.PT_BASIC.DATASET_ID = dbo.O_DATASET.DATASET_ID
INNER JOIN dbo.PT_ADMISSION
ON dbo.PT_BASIC.PATIENT_ID = dbo.PT_ADMISSION.PATIENT_ID
AND dbo.PT_STATUS.ADMISSION_ID = dbo.PT_ADMISSION.ADMISSION_ID
INNER JOIN dbo.PT_ASSIGNMENT
ON dbo.PT_BASIC.PATIENT_ID = dbo.PT_ASSIGNMENT.PATIENT_ID
INNER JOIN dbo.A_ASSIGNMENT_TYPE
ON dbo.PT_ASSIGNMENT.ADMIN_SET_ID = dbo.A_ASSIGNMENT_TYPE.ADMIN_SET_ID
AND dbo.PT_ASSIGNMENT.ASSIGNMENT_TYPE = dbo.A_ASSIGNMENT_TYPE.TYPE_ID
INNER JOIN dbo.RES_BASIC
ON dbo.PT_ASSIGNMENT.RESOURCE_ID = dbo.RES_BASIC.RESOURCE_ID
WHERE (dbo.O_DATASET.DATASET_NAME = 'XXXXXXXXXX')
AND (dbo.A_ASSIGNMENT_TYPE.DESCRIPTION = 'REFERRING PHYSICIAN')
GROUP BY dbo.RES_BASIC.NAME_FIRST + ' ' + dbo.RES_BASIC.NAME_LAST,
dbo.RES_BASIC.RESOURCE_ID,
dbo.PT_ADMISSION.ADMISSION_ID,
dbo.PT_BASIC.PATIENT_CODE,
dbo.PT_ASSIGNMENT.START_DATE,
dbo.PT_ADMISSION.PROSPECT_TERM_DATE,
dbo.PT_ADMISSION.PROSPECT_ADMIT_DATE,
dbo.PT_ADMISSION.TERMINATION_DATE,
dbo.PT_ADMISSION.ADMIT_DATE,
dbo.RES_BASIC.NAME_FULL
You can put pretty much any "if" into a condition with simple AND and OR use.
I am having a hard time mentally parsing your "something like" portion, but to give a generic example.
IF A THEN B ELSE C
can be translated to (A AND B) OR (NOT A AND C)
Note: Bill Braskey's "comment" is also worth considering. If the conditional logic gets complicated enough, it can be less work for the database to UNION queries with simpler conditions. You'd still need the condition in one to be A AND B and the other to be NOT A AND C to apply the conditions appropriately, but you'd be simplifying from the overall condition (especially when you consider "C" could actually be a translation of IF D THEN E ELSE F.
Maybe just take the easy way out and write two queries with the separate requirements and do a union

SQL Query - combine 2 rows into 1 row

I have the following query below (view) in SQL Server. The query produces a result set that is needed to populate a grid. However, a new requirement has come up where the users would like to see data on one row in our app. The tblTasks table can produce 1 or 2 rows. The issue becomes when they're is two rows that have the same job_number but different fldProjectContextId (1 or 31). I need to get the MechApprovalOut and ElecApprovalOut columns on one row instead of two.
I've tried restructuring the query using CTE and over partition and haven't been able to get the necessary results I need.
SELECT TOP (100) PERCENT
CAST(dbo.Job_Control.job_number AS int) AS Job_Number,
dbo.tblTasks.fldSalesOrder, dbo.tblTaskCategories.fldTaskCategoryName,
dbo.Job_Control.Dwg_Sent, dbo.Job_Control.Approval_done,
dbo.Job_Control.fldElecDwgSent, dbo.Job_Control.fldElecApprovalDone,
CASE WHEN DATEDIFF(day, dbo.Job_Control.Dwg_Sent, GETDATE()) > 14
AND dbo.Job_Control.Approval_done IS NULL
AND dbo.tblProjectContext.fldProjectContextID = 1
THEN 1 ELSE 0
END AS MechApprovalOut,
CASE WHEN DATEDIFF(day, dbo.Job_Control.fldElecDwgSent, GETDATE()) > 14
AND dbo.Job_Control.fldElecApprovalDone IS NULL
AND dbo.tblProjectContext.fldProjectContextID = 31
THEN 1 ELSE 0
END AS ElecApprovalOut,
dbo.tblProjectContext.fldProjectContextName,
dbo.tblProjectContext.fldProjectContextId, dbo.Job_Control.Drawing_Info,
dbo.Job_Control.fldElectricalAppDwg
FROM dbo.tblTaskCategories
INNER JOIN dbo.tblTasks
ON dbo.tblTaskCategories.fldTaskCategoryId = dbo.tblTasks.fldTaskCategoryId
INNER JOIN dbo.Job_Control
ON dbo.tblTasks.fldSalesOrder = dbo.Job_Control.job_number
INNER JOIN dbo.tblProjectContext
ON dbo.tblTaskCategories.fldProjectContextId = dbo.tblProjectContext.fldProjectContextId
WHERE (dbo.tblTaskCategories.fldTaskCategoryName = N'Approval'
OR dbo.tblTaskCategories.fldTaskCategoryName = N'Re-Approval')
AND (CASE WHEN DATEDIFF(day, dbo.Job_Control.Dwg_Sent, GETDATE()) > 14
AND dbo.Job_Control.Approval_done IS NULL
AND dbo.tblProjectContext.fldProjectContextID = 1
THEN 1 ELSE 0
END = 1)
OR (dbo.tblTaskCategories.fldTaskCategoryName = N'Approval'
OR dbo.tblTaskCategories.fldTaskCategoryName = N'Re-Approval')
AND (CASE WHEN DATEDIFF(day, dbo.Job_Control.fldElecDwgSent, GETDATE()) > 14
AND dbo.Job_Control.fldElecApprovalDone IS NULL
AND dbo.tblProjectContext.fldProjectContextID = 31
THEN 1 ELSE 0
END = 1)
ORDER BY dbo.Job_Control.job_number, dbo.tblTaskCategories.fldProjectContextId
The above query gives me the following result set:
I've created a work around via code (which I don't like but it works for now) where i've used code to populate a "temp" table the way i need it to display the data, that is, one record if duplicate job numbers to get the MechApprovalOut and ElecApprovalOut columns on one row (see first record in following screen shot).
Example:
With the desired result set and one row per job_number, this is how the form looks with the data and how I am using the result set.
Any help restructuring my query to combine duplicate rows with the same job number where MechApprovalOut and ElecApproval out columns are on one row is greatly appreciated! I'd much prefer to use a view on SQL then code in the app to populate a temp table.
Thanks,
Jimmy
What I would do is LEFT JOIN the main table to itself at the beginning of the query, matching on Job Number and Sales Order, such that the left side of the join is only looking at Approval task categories and the right side of the join is only looking at Re-Approval task categories. Then I would make extensive use of the COALESCE() function to select data from the correct side of the join for use later on and in the select clause. This may also be the piece you were missing to make a CTE work.
There is probably also a solution that uses a ranking/windowing function (maybe not RANK itself, but something that category) along with the PARTITION BY clause. However, as those are fairly new to Sql Server I haven't used them enough personally to be comfortable writing an example solution for you without direct access to the data to play with, and it would still take me a little more time to get right than I can devote to this right now. Maybe this paragraph will motivate someone else to do that work.

SQL Server Update via Select Statement

I have the following sql statement and I want to update a field on the rows returned from the select statement. Is this possible with my select? The things I have tried are not giving me the desired results:
SELECT
Flows_Flows.FlowID,
Flows_Flows.Active,
Flows_Flows.BeatID,
Flows_Flows.FlowTitle,
Flows_Flows.FlowFileName,
Flows_Flows.FlowFilePath,
Flows_Users.UserName,
Flows_Users.DisplayName,
Flows_Users.ImageName,
Flows_Flows.Created,
SUM(CASE WHEN [Like] = 1 THEN 1 ELSE 0 END) AS Likes,
SUM(CASE WHEN [Dislike] = 1 THEN 1 ELSE 0 END) AS Dislikes
FROM Flows_Flows
INNER JOIN Flows_Users ON Flows_Users.UserID = Flows_Flows.UserID
LEFT JOIN Flows_Flows_Likes_Dislikes ON
Flows_Flows.FlowID=Flows_Flows_Likes_Dislikes.FlowID
WHERE Flows_Flows.Active = '1' AND Flows_Flows.Created < DATEADD(day, -60, GETDATE())
Group By Flows_Flows.FlowID, Flows_Flows.Active, Flows_Flows.BeatID,
Flows_Flows.FlowTitle, Flows_Flows.FlowFileName, Flows_Flows.FlowFilePath,
Flows_Users.UserName, Flows_Users.DisplayName, Flows_Users.ImageName,
Flows_Flows.Created
Having SUM(CASE WHEN [Like] = 1 THEN 1 ELSE 0 END) = '0' AND SUM(CASE WHEN [Dislike] = 1
THEN 1 ELSE 0 END) >= '0'
This select statement returns exactly what I need but I want to change the Active field from 1 to 0.
yes - the general structure might be like this: (note you don't declare your primary key)
UPDATE mytable
set myCol = 1
where myPrimaryKey in (
select myPrimaryKey from mytable where interesting bits happen here )
Because you haven't made your question more clear in what result you want to achieve, I'll provide an answer with my own assumptions.
Assumption
You have a select statement that gives you stuffs, and it works as desired. What you want it to do is to make it return results and update those selected rows on the fly - basically like saying "find X, tell me about X and make it Y".
Anwser
If my assumption is correct, unfortunately I don't think there is any way you can do that. A select does not alter the table, it can only fetch information. Similarly, an update does not provide more detail than the number of rows updated.
But don't give up yet, depending on the result you want to achieve, you have alternatives.
Alternatives
If you just want to update the rows that you have selected, you can
simply write an UPDATE statement to do that, and #Randy has provided
a good example of how it will be written.
If you want to reduce calls to server, meaning you want to make just
one call to the server and get result, as well as to update the
rows, you can write store procedures to do that.
Store procedures are like functions you wrote in programming languages. It essentially defines a set of sql operations and gives them a name. Each time you call that store procedure, the set of operations gets executed with supplied inputs, if any.
So if you want to learn more about store procedures you can take a look at:
http://www.mysqltutorial.org/introduction-to-sql-stored-procedures.aspx
If I understand correctly you are looking for a syntax to be able to select the value of Active to be 0 if it is 1. The syntax for something like that is
SELECT
Active= CASE WHEN Active=1 THEN 0 ELSE Active END
FROM
<Tables>
WHERE
<JOIN Conditions>

MySQL to PostgreSQL: GROUP BY issues

So I decided to try out PostgreSQL instead of MySQL but I am having some slight conversion problems. This was a query of mine that samples data from four tables and spit them out all in on result.
I am at a loss of how to convey this in PostgreSQL and specifically in Django but I am leaving that for another quesiton so bonus points if you can Django-fy it but no worries if you just pure SQL it.
SELECT links.id, links.created, links.url, links.title, user.username, category.title, SUM(votes.karma_delta) AS karma, SUM(IF(votes.user_id = 1, votes.karma_delta, 0)) AS user_vote
FROM links
LEFT OUTER JOIN `users` `user` ON (`links`.`user_id`=`user`.`id`)
LEFT OUTER JOIN `categories` `category` ON (`links`.`category_id`=`category`.`id`)
LEFT OUTER JOIN `votes` `votes` ON (`votes`.`link_id`=`links`.`id`)
WHERE (links.id = votes.link_id)
GROUP BY votes.link_id
ORDER BY (SUM(votes.karma_delta) - 1) / POW((TIMESTAMPDIFF(HOUR, links.created, NOW()) + 2), 1.5) DESC
LIMIT 20
The IF in the select was where my first troubles began. Seems it's an IF true/false THEN stuff ELSE other stuff END IF yet I can't get the syntax right. I tried to use Navicat's SQL builder but it constantly wanted me to place everything I had selected into the GROUP BY and that I think it all kinds of wrong.
What I am looking for in summary is to make this MySQL query work in PostreSQL. Thank you.
Current Progress
Just want to thank everybody for their help. This is what I have so far:
SELECT links_link.id, links_link.created, links_link.url, links_link.title, links_category.title, SUM(links_vote.karma_delta) AS karma, SUM(CASE WHEN links_vote.user_id = 1 THEN links_vote.karma_delta ELSE 0 END) AS user_vote
FROM links_link
LEFT OUTER JOIN auth_user ON (links_link.user_id = auth_user.id)
LEFT OUTER JOIN links_category ON (links_link.category_id = links_category.id)
LEFT OUTER JOIN links_vote ON (links_vote.link_id = links_link.id)
WHERE (links_link.id = links_vote.link_id)
GROUP BY links_link.id, links_link.created, links_link.url, links_link.title, links_category.title
ORDER BY links_link.created DESC
LIMIT 20
I had to make some table name changes and I am still working on my ORDER BY so till then we're just gonna cop out. Thanks again!
Have a look at this link GROUP BY
When GROUP BY is present, it is not
valid for the SELECT list expressions
to refer to ungrouped columns except
within aggregate functions, since
there would be more than one possible
value to return for an ungrouped
column.
You need to include all the select columns in the group by that are not part of the aggregate functions.
A few things:
Drop the backticks
Use a CASE statement instead of IF() CASE WHEN votes.use_id = 1 THEN votes.karma_delta ELSE 0 END
Change your timestampdiff to DATE_TRUNC('hour', now()) - DATE_TRUNC('hour', links.created) (you will need to then count the number of hours in the resulting interval. It would be much easier to compare timestamps)
Fix your GROUP BY and ORDER BY
Try to replace the IF with a case;
SUM(CASE WHEN votes.user_id = 1 THEN votes.karma_delta ELSE 0 END)
You also have to explicitly name every column or calculated column you use in the GROUP BY clause.