SELECT 2 VALUES with different requirement - sql

I'm wondering if it is possible to select 2 values in a single select statement that have different criteria. I know I can achieve above with subquery, just wondering if there is a more efficient way to do it.
Consider the following table:
UserId | CreatedDate
1 | 2018-01-21
2 | 2018-02-21
3 | 2018-03-21
4 | 2018-11-21
5 | 2018-11-21
I wish to select total number of users and also total number of users that joined this month. I can do it with two queries like this:
SELECT COUNT(*)
FROM [Users]
SELECT COUNT(*)
FROM [Users]
WHERE MONTH(CreatedDate) = MONTH(GETDATE()) AND YEAR(CreatedDate) = YEAR(GETDATE())
However, can this be a single select as oppose to two queries?
EDIT: To clear confusion, I'm looking for 2 columns, not two rows.

I beleive this will do what you are looking for:
SELECT COUNT(*),
SUM(CASE WHEN MONTH[CreatedDate] = MONTH(GETDATE()) AND YEAR(CreatedDate) = YEAR(GETDATE()) THEN 1 ELSE 0 END) as SubCountWithCriteria
FROM [Users]

Use conditional aggregation:
SELECT COUNT(*),
SUM(CASE WHEN MONTH(CreatedDate) = MONTH(GETDATE()) AND YEAR(CreatedDate) = YEAR(GETDATE()) THEN 1 ELSE 0 END)
FROM [Users];
Note: MONTH[CreatedDate] makes no sense, so I replaced the square braces with parentheses.

Here is one way
(This gives you two rows, if you want two columns others have shown that solution using CASE)
SELECT 'Total Users', COUNT(*)
FROM [Users]
UNION ALL
SELECT 'Total this month', COUNT(*)
FROM [Users]
WHERE MONTH(CreatedDate) = MONTH(GETDATE()) AND YEAR(CreatedDate) = YEAR(GETDATE())

Related

How to generate a count based on Text data in SQL

I have the following table:
I basically want to Pivot the contents of the Status column and generate a count of the 'Issuer/Ticker' column. For example, I want to know for Allison Shenoy, what is the number of "Issuer/Ticker" that are Past Due and that are "Due within 3 months". So, my answer should read for this analyst: Past Due: 11 and Due within 3 months: 8 based on the data above.
Use conditional aggregation:
select
assigned_analyst,
sum(case when status = 'Past Due' then 1 else 0 end) past_due,
sum(case when status = 'Due within 3 Months' then 1 else 0 end) due_3_month
from mytable
group by assigned_analyst
To get the counts of Issuer/Ticker based on each analyst and status, try this:
SELECT
[Assigned Analyst]
,[Status]
COUNT(distinct [Issuer/Ticker]) [Count of Issuer/Ticker]
FROM TableName
GROUP BY [Assigned Analyst], [Status]
Otherwise, to get the counts for Allison Shenoy only, try this:
SELECT
[Status]
COUNT(distinct [Issuer/Ticker]) [Count of Issuer/Ticker]
FROM TableName
WHERE [Assigned Analyst] = 'Allison Shenoy'
GROUP BY [Status]
Obviously, replace 'TableName' with the actual name of the table

SQL Efficiency on Date Range or Separate Tables

I'm calculating historical amount from a table in years(ex. 2015-2016, 2014-2015, etc.) I would like to seek expertise if its more efficient to do it in one batch or repeat the query multiple times filtered by the date required.
Thanks in advance!
OPTION 1:
select
id,
sum(case when year(getdate()) - year(txndate) between 5 and 6 then amt else 0 end) as amt_6_5,
...
sum(case when year(getdate()) - year(txndate) between 0 and 1 then amt else 0 end) as amt_1_0,
from
mytable
group by
id
OPTION 2:
select
id, sum(amt) as amt_6_5
from
mytable
group by
id
where
year(getdate()) - year(txndate) between 5 and 6
...
select
id, sum(amt) as amt_1_0
from
mytable
group by
id
where
year(getdate()) - year(txndate) between 0 and 1
1.
Unless you have resources issues I would go with the CASE version.
Although it has no impact on the results, filtering on the requested period in the WHERE clause might have a significant performance advantage.
2. Your period definition creates overlapping.
select id
,sum(case when year(getdate()) - year(txndate) = 6 then amt else 0 end) as amt_6
-- ...
,sum(case when year(getdate()) - year(txndate) = 0 then amt else 0 end) as amt_0
where txndate >= dateadd(year, datediff(year,0, getDate())-6, 0)
from mytable
group by id
This may be help you,
WITH CTE
AS
(
SELECT id,
(CASE WHEN year(getdate()) - year(txndate) BETWEEN 5 AND 6 THEN 'year_5-6'
WHEN year(getdate()) - year(txndate) BETWEEN 4 AND 5 THEN 'year_4-5'
...
END) AS my_year,
amt
FROM mytable
)
SELECT id,my_year,sum(amt)
FROM CTE
GROUP BY id,my_year
Here, inside the CTE, just assigned a proper year_tag for each records (based on your conditions), after that select a summary for the CTE grouped by that year_tag.

union of two queries in one

I have two queries with different result sets but only one of the conditions is additional in one of the queries. Both queries are fetching from the same table.
first query:
SELECT cm.ctm, count_big(*) AS TOTAL
FROM dbo_cm.cm
WHERE
cm.a= 'abc' AND
cm.b= 1 AND
cm.ps IS NOT NULL AND
datepart(MONTH, cm.ps) = 7 AND
datepart(YEAR, cm.ps) = 2015
GROUP BY cm.ctm
second query:
SELECT cm.ctm, count_big(*) AS TOTAL
FROM dbo_cm.cm
WHERE
cm.a= 'abc' AND
cm.b= 1 AND
cm.ps IS NOT NULL AND
datepart(MONTH, cm.ps) = 7 AND
datepart(YEAR, cm.ps) = 2015 and
cm.as>cm.ps
GROUP BY cm.ctm
How do I make this query simpler by merging it into one? We use decode in Oracle for this purpose.
You can merge these two queries into one by moving the additional condition in the second query to a case expression and returning null when the condition is not met. count_big, like any aggregate function, will just ignore the nulls:
SELECT cm.ctm,
COUNT_BIG(*) AS total1,
COUNT_BIG(CASE WHEN cm.as > cm.ps THEN 1 ELSE NULL END) AS total2
FROM dbo_cm.cm
WHERE cm.a= 'abc' AND
cm.b= 1 AND
cm.ps IS NOT NULL AND
DATEPART(MONTH, cm.ps) = 7 AND
DATEPART(YEAR, cm.ps) = 2015 AND
GROUP BY cm.ctm
You can use SUM(CASE...) like:
SELECT
cm.ctm,
count_big(*) AS TOTAL1,
SUM(CASE WHEN cm.[as]>cm.[ps] THEN 1 ELSE 0 END) AS TOTAL2
FROM dbo_cm.cm
WHERE cm.a= 'abc'
AND cm.b= 1
AND cm.ps IS NOT NULL
AND datepart(MONTH, cm.ps) = 7
AND datepart(YEAR, cm.ps) = 2015
GROUP BY cm.ctm

How to group filtered rows against non filtered by month

I am trying to generate a report for accepted orders each month against the total orders for that month. For example, I have a table Orders like so:
Order_Id Submit_Date Order_Status
-------- ----------- ------------
1 20130501 Accepted
2 20130509 Rejected
3 20130610 Accepted
4 20130614 Accepted
5 20130626 Rejected
6 20130802 Accepted
7 20130801 Accepted
8 20131014 Accepted
9 20140116 Rejected
10 20140121 Rejected
And would like to get the results like so:
[Month] Accepted Total
------- -------- -----
2013-05 1 2
2013-06 2 3
2013-08 2 2
2013-10 1 1
2014-01 2 2
How do I go about it?
Assuming you will never have a time component, this should work just fine:
DECLARE #d TABLE([Order] INT, [Date] DATETIME, [Status] CHAR(8));
INSERT #d VALUES
(1 ,'20130501','Accepted'),
(2 ,'20130509','Rejected'),
(3 ,'20130610','Accepted'),
(4 ,'20130614','Accepted'),
(5 ,'20130626','Rejected'),
(6 ,'20130802','Accepted'),
(7 ,'20130801','Accepted'),
(8 ,'20131014','Accepted'),
(9 ,'20140116','Rejected'),
(10,'20140121','Rejected');
SELECT
[Month] = DATEADD(DAY, 1-DAY([Date]), [Date]),
Accepted = SUM(CASE WHEN [Status] = 'Accepted' THEN 1 ELSE 0 END),
COUNT(*)
FROM #d
GROUP BY DATEADD(DAY, 1-DAY([Date]), [Date])
ORDER BY [Month];
(And if you are on SQL Server 2008 or newer, you should use the DATE data type to prevent having to deal with any errant hours/minutes.)
If you can have hours/minutes sometimes, and you're not on 2008 or greater, then:
SELECT
[Month] = DATEADD(MONTH, DATEDIFF(MONTH, 0, [Date]), 0),
Accepted = SUM(CASE WHEN [Status] = 'Accepted' THEN 1 ELSE 0 END),
COUNT(*)
FROM #d
GROUP BY DATEADD(MONTH, DATEDIFF(MONTH, 0, [Date]), 0)
ORDER BY [Month];
I strongly recommend avoiding any solutions that group by using string conversions. Date/time math is much more efficient in SQL Server than converting to strings. Also if you want the client side to present things like 2013-05, use Format(), ToString() etc. to apply that string formatting on the client.
To get yyyy-dd format you can use this
SELECT CONVERT(VARCHAR(7),[Date],20)
,COUNT(CASE WHEN [status] = 'Accepted' THEN 1
ELSE NULL END) AS 'Accepted'
,COUNT(*) AS Total
FROM Orders
GROUP BY CONVERT(VARCHAR(7),[Date],20)
Try it:
SELECT
CONVERT(VARCHAR(4), YEAR(Date)) + '-' + CONVERT(VARCHAR(2), MONTH(Date)) Period,
SUM(CASE WHEN Status = 'Accepted' THEN 1 ELSE 0) Accepted,
COUNT(*) Total
FROM Orders
GROUP BY YEAR(Date), MONTH(Date)
ORDER BY YEAR(Date), MONTH(Date)

change rows to columns and count

how to calculate count based on rows?
SOURCE TABLE
each employee can take 2 days off
Employee-----First_Day_Off-----Second_Day_Off
1------------10/21/2009--------12/6/2009
2------------09/3/2009--------12/6/2009
3------------09/3/2009--------NULL
4
5
.
.
.
Now i need a table that shows the dates and number of people taking off on that day
Date---------First_Day_Off-------Second_Day_Off
10/21/2009---1-------------------0
12/06/2009---1--------------------1
09/3/2009----2--------------------0
Any ideas?
Oracle 9i+, using Subquery Factoring (WITH):
WITH sample AS (
SELECT a.employee,
a.first_day_off AS day_off,
1 AS day_number
FROM YOUR_TABLE a
WHERE a.first_day_off IS NOT NULL
UNION ALL
SELECT b.employee,
b.second_day_off,
2 AS day_number
FROM YOUR_TABLE b
WHERE b.second_day_off IS NOT NULL)
SELECT s.day_off AS date,
SUM(CASE WHEN s.day_number = 1 THEN 1 ELSE 0 END) AS first_day_off,
SUM(CASE WHEN s.day_number = 2 THEN 1 ELSE 0 END) AS second_day_off
FROM sample s
GROUP BY s.day_off
Non Subquery Version
SELECT s.day_off AS date,
SUM(CASE WHEN s.day_number = 1 THEN 1 ELSE 0 END) AS first_day_off,
SUM(CASE WHEN s.day_number = 2 THEN 1 ELSE 0 END) AS second_day_off
FROM (SELECT a.employee,
a.first_day_off AS day_off,
1 AS day_number
FROM YOUR_TABLE a
WHERE a.first_day_off IS NOT NULL
UNION ALL
SELECT b.employee,
b.second_day_off,
2 AS day_number
FROM YOUR_TABLE b
WHERE b.second_day_off IS NOT NULL) s
GROUP BY s.day_off
It is a bit awkward to handle these queries, since you have days off stored in different columns. A better layout would be to have something like
EMPLOYEE_ID DAY_OFF
Then you would have multiple rows if an employee took multiple days off
EMPLOYEE_ID DAY_OFF
1 10/21/2009
1 12/6/2009
2 09/3/2009
2 12/6/2009
3 09/3/2009
...
In that case, you could find out how many days off each person took by using the following query:
SELECT EMPLOYEE_ID, COUNT(*) AS NUM_DAYS_OFF FROM DAYS_OFF_TABLE GROUP BY EMPLOYEE_ID
And the number of people who took days off on each date like this:
SELECT DAY_OFF, COUNT(*) AS NUM_PEOPLE FROM DAYS_OFF_TABLE GROUP BY DAY_OFF
But I digress...
You can try to use an SQL CASE statement to help with this:
SELECT Employee, CASE
WHEN First_Day_Off is NULL AND Second_Day_Off is NULL THEN 0
WHEN First_Day_Off is NOT NULL AND Second_Day_Off is NULL THEN 1
WHEN First_Day_Off is NULL AND Second_Day_Off is NOT NULL THEN 1
ELSE 2
END AS NUM_DAYS_OFF
FROM DAYS_OFF_TABLE
(note that you may need to change around the syntax slightly depending on your database.
Getting dates and number of people who took off on that day might be more complicated.
I don't know if this would work, but you can try it:
SELECT
Date_Off,
COUNT(*) AS Num_People
FROM
(SELECT
First_Day_Off, COUNT(*) AS Num_People FROM DAYS_OFF_TABLE WHERE First_Day_Off IS NOT NULL GROUP BY First_Day_Off
UNION
SELECT Second_Day_Off, COUNT(*) AS Num_People FROM DAYS_OFF_TABLE WHERE Second_Day_Off IS NOT NULL GROUP BY Second_Day_Off)
GROUP BY
Num_People