SQL: Compare two result from two different selects - sql-server

I need to determine if our consultents are logging their work correctly. For that I have two different sources: one containing incoming calls and one their logs in our customer system.
Calls are linked to phone numbers and logs are linked to customer numbers. For each of the two sources I have a select that gives me the numbers of distinct combinations of weeknumber and (customer/phone)-numbers for each consultant to find total unique "weekcalls" for a full year. But how do I divide the results of unique logs with the unique calls? And for bonus difficulty, no temp tabels (doesn´t work in Excel)
Select #1
SELECT
count(distinct(concat(datepart(ww,call_datetime),phonenumber))) as
calls,consultant
FROM calltabel
group by consultant
Select #2
SELECT
count(distinct(concat(datepart(ww,log_datetime),phonenumber))) as
logs,consultant
FROM logtabel
group by consultant
results
select #1
consultant calls
eric 10
kimmie 20
select #2
consultant logs
eric 5
kimmie 20
The combined result should be
consultat calls logs result
eric 10 5 0.5
kimmie 20 20 1.0

You can join the queries like this
select t1.consultant, calls, logs, logs/calls as result
(SELECT
count(distinct(concat(datepart(ww,call_datetime),phonenumber))) as
calls,consultant
FROM calltabel
group by consultant) as t1
inner join
(SELECT
count(distinct(concat(datepart(ww,log_datetime),phonenumber))) as
logs,consultant
FROM logtabel
group by consultant) as t2 on t1.consultant=t2.consultant
Or you can do this:
select t1.consultant, calls, logs, logs/calls as result from
(
SELECT calltabel.consultant,
count(distinct(concat(datepart(ww,call_datetime),phonenumber))) as calls,
count(distinct(concat(datepart(ww,log_datetime),phonenumber))) as logs
FROM calltabel
inner join logtabel on logtabel.consultant= calltabel.consultant
group by calltabel.consultant
)

You can do inner join:
Select callTable.Consultant, callTable.calls, logTable.logs, logTable.logs/callTable.logs as ‘Result’ from (SELECT
count(distinct(concat(datepart(ww,call_datetime),phonenumber))) as
calls,consultant
FROM calltable
group by consultant) as callTable, (SELECT
count(distinct(concat(datepart(ww,log_datetime),phonenumber))) as
logs,consultant
FROM logtable
group by consultant) as logTable
Where logTable.consultant = callTable.consultant;

Related

Using SQL to combine detailed and aggregated results

I am developing a report against a SQL Server database. Using the query presented here...
SELECT
f.FacilityID as 'FID',
COUNT (DISTINCT f.PhoneTypeID) as 'Ptypes',
COUNT (DISTINCT f.PhoneID) as 'Pnumbers'
from dbo.FacilityPhones as f
inner join
dbo.Phones as ph
f.PhoneID = ph.PhoneID
group by f.FacilityID
having COUNT(DISTINCT f.PhoneTypeID)<>COUNT(DISTINCT f.PhoneId);
...I have identified 107 records where the number of phone numbers present for a Facility differs from the number of phone number types (e.g., there are two distinct phone numbers, both listed as primary).
I would like to be able to produce a detailed report that would list phone numbers and phone types for each facility, but ONLY when the distinct counts differ.
Is there a way to do this with a single query? Or would I need to save the summaries to a temp table, then join back to that temp table to get the details?
Not sure what fields exist in dbo.Phone; but assume the number comes from there... Likely need to join to the type table to get it's description as well...
This uses a common table expression to get your base list of items an then a correlated subquery to ensure only those facilities in your cte are displayed.
WITH CTE AS (
SELECT f.FacilityID as 'FID'
, COUNT (DISTINCT f.PhoneTypeID) as 'Ptypes'
, COUNT (DISTINCT f.PhoneID) as 'Pnumbers'
FROM dbo.FacilityPhones as f
GROUP BY f.FacilityID
HAVING COUNT(DISTINCT f.PhoneTypeID)<>COUNT(DISTINCT f.PhoneId))
SELECT *
FROM dbo.FaclityPhones FP
INNER JOIN dbo.Phones as ph
ON FP.PhoneID = ph.PhoneID
WHERE EXISTS (SELECT 1
FROM CTE
WHERE FID = FP.FacilityID)
The where clause here just says only show those FacilityID's and associated records if the FacilityID exists in your original query (CTE) (107) If we needed data from the CTE we'd join to it; but as it's simply restricting data placing it in the where clause and using an exists will likely be more efficient.

SQL performance problem: Select N rows until find distinct 200 customer

I have table ORDERS with these columns:
id | Customer | product
I want to calculate N number of rows in ORDERS table. N should be big enough to contain 200 distinct CUSTOMER from bottom of table. I have wrote the following query using max(ID) but It takes too many seconds to run this query. I think this is not optimized because I have thousands of rows and every time I have to use group by on whole table to find just an ID:
select count(*) as N from ORDERS where id > (
select top 1 id from
(select distinct top 200 CUSTOMER,max(id) as maxid from ORDERS group by CUSTOMER order by maxid desc) x
order by id asc
)
Is there another way to handle this with better performance?
This is a huge guess, but this will at least return a result:
WITH Customers AS(
SELECT TOP 200
CUSTOMER,
MAX(id) AS MaxID
FROM ORDERS
GROUP BY CUSTOMER
ORDER BY MaxID DESC)
SELECT COUNT(*)
FROM ORDERS O
WHERE EXISTS (SELECT 1
FROM Customers C
WHERE C.MaxID = O.id);
If it returns the correct results, but it still runs slowly, post the DDL of your table, and include the DDL for your indexes. I also suggest posting the query plan by using Paste the Plan

SQL - Filter calculated column with calculated column

I'm trying to find out the most dosed patients in a database. The sum of the doses has to be calculated and then I have to dynamically list out the patients who have been dosed that much. The query has to be dynamic, and there can be more than 5 patients listed - For example, the 5 most doses are 7,6,5,4,3 doses, but 3 people have gotten 5 doses, so I'd have to list out 7 people in total (the patients getting 7,6,5,5,5,4,3 doses). I'm having issues because you cannot refer to a named column in a where clause and I have no idea how to fix this.
The query goes like this:
SELECT
info.NAME, SUM(therapy.DOSE) AS total
FROM
dbo.PATIENT_INFORMATION_TBL info
JOIN
dbo.PATIENT_THERAPY_TBL therapy ON info.HOSPITAL_NUMBER = therapy.HOSPITAL_NUMBER
LEFT JOIN
dbo.FORMULARY_CLINICAL clinical ON clinical.ITEMID = therapy.ITEMID
WHERE
total IN (SELECT DISTINCT TOP 5 SUM(t.DOSE) AS 'DOSES'
FROM dbo.PATIENT_INFORMATION_TBL i
JOIN dbo.PATIENT_THERAPY_TBL t ON i.HOSPITAL_NUMBER = t.HOSPITAL_NUMBER
LEFT JOIN dbo.FORMULARY_CLINICAL c ON c.ITEMID = t.ITEMID
GROUP BY NAME
ORDER BY 'DOSES' DESC)
GROUP BY
info.NAME
ORDER BY
total DESC
The database looks like this:
The main question is: how can I use a where/having clause where I need to compare a calculated column to a list of dynamically calculated values?
I'm using Microsoft's SQL Server 2012. The DISTINCT in the subquery is needed so that only the top 5 dosages appear (e.g. without DISTINCT I get 7,6,5,4,3 with DISTINCT I get 7,6,6,5,4 and my goal is the first one).
Most DBMSes support Standard SQL Analytical Functions like DENSE_RANK:
with cte as
(
SELECT info.NAME, SUM(therapy.DOSE) as total,
DENSE_RANK() OVER (ORDER BY SUM(therapy.DOSE) DESC) AS dr
FROM dbo.PATIENT_INFORMATION_TBL info
JOIN dbo.PATIENT_THERAPY_TBL therapy ON info.HOSPITAL_NUMBER=therapy.HOSPITAL_NUMBER
LEFT JOIN dbo.FORMULARY_CLINICAL clinical ON clinical.ITEMID=therapy.ITEMID
GROUP BY info.NAME
)
select *
from cte
where dr <= 5 -- only the five highest doses
ORDER BY total desc
Btw, you probably don't need the LEFT JOIN as you're not selecting any column from dbo.FORMULARY_CLINICAL

Is it possible to get two sets of data with one query

I need to get some items from database with top three comments for each item.
Now I have two stored procedures GetAllItems and GetTopThreeeCommentsByItemId.
In application I get 100 items and then in foreach loop I call GetTopThreeeCommentsByItemId procedure to get top three comments.
I know that this is bad from performance standpoint.
Is there some technique that allows to get this with one query?
I can use OUTER APPLY to get one top comment (if any) but I don't know how to get three.
Items {ItemId, Title, Description, Price etc.}
Comments {CommentId, ItemId etc.}
Sample data that I want to get
Item_1
-- comment_1
-- comment_2
-- comment_3
Item_2
-- comment_4
-- comment_5
One approach would be to use a CTE (Common Table Expression) if you're on SQL Server 2005 and newer (you aren't specific enough in that regard).
With this CTE, you can partition your data by some criteria - i.e. your ItemId - and have SQL Server number all your rows starting at 1 for each of those "partitions", ordered by some criteria.
So try something like this:
;WITH ItemsAndComments AS
(
SELECT
i.ItemId, i.Title, i.Description, i.Price,
c.CommentId, c.CommentText,
ROW_NUMBER() OVER(PARTITION BY i.ItemId ORDER BY c.CommentId) AS 'RowNum'
FROM
dbo.Items i
LEFT OUTER JOIN
dbo.Comments c ON c.ItemId = i.ItemId
WHERE
......
)
SELECT
ItemId, Title, Description, Price,
CommentId, CommentText
FROM
ItemsAndComments
WHERE
RowNum <= 3
Here, I am selecting up to three entries (i.e. comments) for each "partition" (i.e. for each item) - ordered by the CommentId.
Does that approach what you're looking for??
You can write a single stored procedure which calls GetAllItems and GetTopThreeeCommentsByItemId, takes results in temp tables and join those tables to produce the single resultset you need.
If you do not have a chance to use a stored procedure, you can still do the same by running a single SQL script from data access tier, which calls GetAllItems and GetTopThreeeCommentsByItemId and takes results into temp tables and join them later to return a single resultset.
This gets two elder brother using OUTER APPLY:
select m.*, elder.*
from Member m
outer apply
(
select top 2 ElderBirthDate = x.BirthDate, ElderFirstname = x.Firstname
from Member x
where x.BirthDate < m.BirthDate
order by x.BirthDate desc
) as elder
order by m.BirthDate, elder.ElderBirthDate desc
Source data:
create table Member
(
Firstname varchar(20) not null,
Lastname varchar(20) not null,
BirthDate date not null unique
);
insert into Member(Firstname,Lastname,Birthdate) values
('John','Lennon','Oct 9, 1940'),
('Paul','McCartney','June 8, 1942'),
('George','Harrison','February 25, 1943'),
('Ringo','Starr','July 7, 1940');
Output:
Firstname Lastname BirthDate ElderBirthDate ElderFirstname
-------------------- -------------------- ---------- -------------- --------------------
Ringo Starr 1940-07-07 NULL NULL
John Lennon 1940-10-09 1940-07-07 Ringo
Paul McCartney 1942-06-08 1940-10-09 John
Paul McCartney 1942-06-08 1940-07-07 Ringo
George Harrison 1943-02-25 1942-06-08 Paul
George Harrison 1943-02-25 1940-10-09 John
(6 row(s) affected)
Live test: http://www.sqlfiddle.com/#!3/19a63/2
marc's answer is better, just use OUTER APPLY if you need to query "near" entities (e.g. geospatial, elder brothers, nearest date to due date, etc) to the main entity.
Outer apply walkthrough: http://www.ienablemuch.com/2012/04/outer-apply-walkthrough.html
You might need DENSE_RANK instead of ROW_NUMBER/RANK though, as the criteria of a comment being a top could yield ties. TOP 1 could yield more than one, TOP 3 could yield more than three too. Example of that scenario(DENSE_RANK walkthrough): http://www.anicehumble.com/2012/03/postgresql-denserank.html
Its better that you select the statement by using the row_number statement and select the top 3 alone
select a.* from
(
Select *,row_number() over(partition by column)[dup]
) as a
where dup<=3

SQL Server JOIN adds results?

I have a table which basically contains a Person_ID, Action, TimeStamp and using Microsoft ReportBuilder I have a report which tables each Person and the COUNT for their Actions.
Person Action X ActionY
1 3 5
2 0 4
Now I need to filter the results to only show people in a certain group which is defined by another table containing Person_ID, Group_ID.
When I do a JOIN and filter results based on the Group_ID = x the counts are very high, although it does filter correctly.
I run the query manually in SQL Server Manager and it is returning the same row multiple times?
EDIT:
My current SQL is
select t1.personid, t1.action, t2.personid, t2.groupid
from t1
inner join t2 on t1.personid = t2.personid
where t2.groupid = 1
This returns each line multiple times, forgetting the count part as this is in the report builder I would like to understand why the same row is returned multiple times as this is what breaks the report.
Do a DISTINCT
SO..
SELECT PersonID, ActionX = COUNT(distinct varname), ActionY = SUM(distint varname)
FROM tblName1 a
INNER JOIN tblName2 b ON a.PersonID = b.PersonID
WHERE b.Group_ID = 'groupvar'

Resources