SQL query for sales report by date - sql

I have a table of sales leads:
CREATE TABLE "lead" (
"id" serial NOT NULL PRIMARY KEY,
"marketer" varchar(500) NOT NULL,
"date_set" varchar(500) NOT NULL
)
;
INSERT INTO lead VALUES (1, 'Joe', '05/01/13');
INSERT INTO lead VALUES (2, 'Joe', '05/02/13');
INSERT INTO lead VALUES (3, 'Joe', '05/03/13');
INSERT INTO lead VALUES (4, 'Sally', '05/03/13');
INSERT INTO lead VALUES (5, 'Sally', '05/03/13');
INSERT INTO lead VALUES (6, 'Andrew', '05/04/13');
I want to produce a report that summarizes the number of records each marketer has for each day. It should look like this:
| MARKETER | 05/01/13 | 05/02/13 | 05/03/13 | 05/04/13 |
--------------------------------------------------------
| Joe | 1 | 1 | 1 | 0 |
| Sally | 0 | 0 | 2 | 1 |
| Andrew | 0 | 0 | 0 | 1 |
What's the SQL query to produce this?
I have this example set up on SQL Fiddle: http://sqlfiddle.com/#!12/eb27a/1

Pure SQL cannot produce such structure (it is two dimensional, but sql return plain list of records).
You could make query like this:
select marketer, date_set, count(id)
from lead
group by marketer, date_set;
And vizualise this data by your reporting system.

You can do it like this:
select
marketer,
count(case when date_set = '05/01/13' then 1 else null end) as "05/01/13",
count(case when date_set = '05/02/13' then 1 else null end) as "05/02/13",
count(case when date_set = '05/03/13' then 1 else null end) as "05/03/13",
count(case when date_set = '05/04/13' then 1 else null end) as "05/04/13"
from lead
group by marketer

Related

Grouping by column and rows

I have a table like this:
+----+--------------+--------+----------+
| id | name | weight | some_key |
+----+--------------+--------+----------+
| 1 | strawberries | 12 | 1 |
| 2 | blueberries | 7 | 1 |
| 3 | elderberries | 0 | 1 |
| 4 | cranberries | 8 | 2 |
| 5 | raspberries | 18 | 2 |
+----+--------------+--------+----------+
I'm looking for a generic request that would get me all berries where there are three entries with the same 'some_key' and one of the entries (within those three entries belonging to the same some_key) has the weight = 0
in case of the sample table, expected output would be:
1 strawberries
2 blueberries
3 cranberries
As you want to include non-grouped columns, I would approach this with window functions:
select id, name
from (
select id,
name,
count(*) over w as key_count,
count(*) filter (where weight = 0) over w as num_zero_weight
from fruits
window w as (partition by some_key)
) x
where x.key_count = 3
and x.num_zero_weight >= 1
The count(*) over w counts the number of rows in that group (= partition) and the count(*) filter (where weight = 0) over w counts how many of those have a weight of zero.
The window w as ... avoids repeating the same partition by clause for the window functions.
Online example: https://rextester.com/SGWFI49589
Try this-
SELECT some_key,
SUM(weight) --Sample aggregations on column
FROM your_table
GROUP BY some_key
HAVING COUNT(*) = 3 -- If you wants at least 3 then use >=3
AND SUM(CASE WHEN weight = 0 THEN 1 ELSE 0 END) >= 1
As per your edited question, you can try this below-
SELECT id, name
FROM your_table
WHERE some_key IN (
SELECT some_key
FROM your_table
GROUP BY some_key
HAVING COUNT(*) = 3 -- If you wants at least 3 then use >=3
AND SUM(CASE WHEN weight = 0 THEN 1 ELSE 0 END) >= 1
)
Try doing this.
Table structure and sample data
CREATE TABLE tmp (
id int,
name varchar(50),
weight int,
some_key int
);
INSERT INTO tmp
VALUES
('1', 'strawberries', '12', '1'),
('2', 'blueberries', '7', '1'),
('3', 'elderberries', '0', '1'),
('4', 'cranberries', '8', '2'),
('5', 'raspberries', '18', '2');
Query
SELECT t1.*
FROM tmp t1
INNER JOIN (SELECT some_key
FROM tmp
GROUP BY some_key
HAVING Count(some_key) >= 3
AND Min(Abs(weight)) = 0) t2
ON t1.some_key = t2.some_key;
Output
+-----+---------------+---------+----------+
| id | name | weight | some_key |
+-----+---------------+---------+----------+
| 1 | strawberries | 12 | 1 |
| 2 | blueberries | 7 | 1 |
| 3 | elderberries | 0 | 1 |
+-----+---------------+---------+----------+
Online Demo: http://sqlfiddle.com/#!15/70cca/26/0
Thank you, #mkRabbani for reminding me about the negative values.
Further reading
- ABS() Function - Link01, Link02
- HAVING Clause - Link01, Link02

Can't use group by in SQL Server

I am learning how to use Group By in SQL Server and I am trying to write a Query that would let me get all the information from Alumns in a table in numbers.
My table is like the following:
Name | Alumn_ID | Course | Credits | Passed
Peter 1 Math 2 YES
John 2 Math 3 YES
Thomas 3 Math 0 NO
Peter 1 English 3 YES
Thomas 2 English 2 YES
John 3 English 0 NO
The result I want is the following one:
Alumn | Total_Credits | Courses | Passed | Not_Passed
Peter 5 2 2 0
John 5 2 2 0
Thomas 0 2 0 2
I know that I have to use Group By and COUNT but I'm stuck since I'm a beginner, I really don't know how can I separate Passed and Not_Passed in the result from the PASSED column in the table, thanks in advance
SELECT t.id, t.name AS alum,
SUM(credits) AS total_credits,
COUNT(*) AS courses,
SUM(CASE WHEN Passed = 'YES' THEN 1 ELSE 0 END) AS Passed,
SUM(CASE WHEN Passed = 'NO' THEN 1 ELSE 0 END) AS Reprobated
FROM t
GROUP BY t.id, t.name
I assume reprobated means not passed.
The example below will do that like you solicited.
create table Alumns
(
Name varchar(30) not null
,Alumn_Id int not null
,Course varchar(30) not null
,Credits int not null
,passed varchar(3) not null
)
GO
insert into Alumns
(Name, Alumn_ID, Course, Credits, Passed)
values
('Peter', 1, 'Math', 2, 'YES')
,('John', 2, 'Math', 3, 'YES')
,('Thomas', 3, 'Math', 0, 'NO')
,('Peter', 1, 'English', 3, 'YES')
,('John', 2, 'English', 2, 'YES')
,('Thomas', 3, 'English', 0, 'NO')
GO
select al.Alumn_Id,al.Name
, Sum(al.Credits) as [Total Credits]
, Count(al.Course) as Courses
, Sum(case al.passed when 'YES' then 1 else 0 end) as Passed
, Sum(case al.passed when 'NO' then 1 else 0 end) as [Not Passed]
from dbo.Alumns al
group by al.Alumn_Id, al.Name
but note you will get an error because you data is incorrect.
Look at your own example where John and Peter are with wrong Ids for the Math/English rows.
That way you will never end with the correct result and that's why it's a good practice to group by Ids.
Edit
I see you corrected your example data yes that way will fetch the exact results you want.
You can separate Passed and Not_Passed using a CASE function.
SELECT MAX([name]) AS [Name],
SUM(Credits) AS Total_Credits,
COUNT(Course) AS Courses,
SUM(CASE WHEN Passed='Yes' THEN 1 ELSE 0 END) AS Passed,
SUM(CASE WHEN Passed='No' THEN 1 ELSE 0 END) AS Not_Passed
FROM TableName
GROUP BY Alumn_ID
However, I do not think that values of your tables (both table) are correct. Please check them again. For example, according to your table, John has two Alumn_IDs (both 2 and 3). If these are two different Johns, then your desired outcome should be changed.
Result
+--------+---------------+---------+--------+------------+
| Name | Total_Credits | Courses | Passed | Not_Passed |
+--------+---------------+---------+--------+------------+
| Peter | 5 | 2 | 2 | 0 |
| John | 3 | 1 | 1 | 0 |
| Thomas | 2 | 3 | 1 | 2 |
+--------+---------------+---------+--------+------------+

Select rows into columns and show a flag in the column

Trying to get an output like the below:
| UserFullName | JAVA | DOTNET | C | HTML5 |
|--------------|--------|--------|--------|--------|
| Anne San | | | | |
| John Khruf | 1 | 1 | | 1 |
| Mary Jane | 1 | | | 1 |
| George Mich | | | | |
This shows the roles of a person. A person could have 0 or N roles. When a person has a role, I am showing a flag, like '1'.
Actually I have 2 blocks of code:
Block #1: The tables and a simple output which generates more than 1 rows per person.
SQL Fiddle
MS SQL Server 2008 Schema Setup:
CREATE TABLE AvailableRoles
(
id int identity primary key,
CodeID varchar(5),
Description varchar(500),
);
INSERT INTO AvailableRoles
(CodeID, Description)
VALUES
('1', 'JAVA'),
('2', 'DOTNET'),
('3', 'C'),
('4', 'HTML5');
CREATE TABLE PersonalRoles
(
id int identity primary key,
UserID varchar(100),
RoleID varchar(5),
);
INSERT INTO PersonalRoles
(UserID, RoleID)
VALUES
('John.Khruf', '1'),
('John.Khruf', '2'),
('Mary.Jane', '1'),
('Mary.Jane', '4'),
('John.Khruf', '4');
CREATE TABLE Users
(
UserID varchar(20),
EmployeeType varchar(1),
EmployeeStatus varchar(1),
UserFullName varchar(500),
);
INSERT INTO Users
(UserID, EmployeeType, EmployeeStatus, UserFullName)
VALUES
('John.Khruf', 'E', 'A', 'John Khruf'),
('Mary.Jane', 'E', 'A', 'Mary Jane'),
('Anne.San', 'E', 'A', 'Anne San'),
('George.Mich', 'T', 'A', 'George Mich');
Query 1:
SELECT
A.UserFullName,
B.RoleID
FROM
Users A
LEFT JOIN PersonalRoles B ON B.UserID = A.UserID
WHERE
A.EmployeeStatus = 'A'
ORDER BY
A.EmployeeType ASC,
A.UserFullName ASC
Results:
| UserFullName | RoleID |
|--------------|--------|
| Anne San | (null) |
| John Khruf | 1 |
| John Khruf | 2 |
| John Khruf | 4 |
| Mary Jane | 1 |
| Mary Jane | 4 |
| George Mich | (null) |
Block #2: An attempt to convert the rows into columns to be used in the final result
SQL Fiddle
MS SQL Server 2008 Schema Setup:
CREATE TABLE AvailableRoles
(
id int identity primary key,
CodeID varchar(5),
Description varchar(500),
);
INSERT INTO AvailableRoles
(CodeID, Description)
VALUES
('1', 'JAVA'),
('2', 'DOTNET'),
('3', 'C'),
('4', 'HTML5');
Query 1:
SELECT
*
FROM
(
SELECT CodeID, Description
FROM AvailableRoles
) d
PIVOT
(
MAX(CodeID)
FOR Description IN (Java, DOTNET, C, HTML5)
) piv
Results:
| Java | DOTNET | C | HTML5 |
|--------|--------|-------|--------|
| 1 | 2 | 3 | 4 |
Any help in mixing both blocks to show the top output will be welcome. Thanks.
Another option without PIVOT operator is:
select u.UserFullName,
max(case when a.CodeID='1' then '1' else '' end) JAVA,
max(case when a.CodeID='2' then '1' else '' end) DOTNET,
max(case when a.CodeID='3' then '1' else '' end) C,
max(case when a.CodeID='4' then '1' else '' end) HTML5
from
Users u
LEFT JOIN PersonalRoles p on (u.UserID = p.UserID)
LEFT JOIN AvailableRoles a on (p.RoleID = a.CodeID)
group by u.UserFullName
order by u.UserFullName
SQLFiddle: http://sqlfiddle.com/#!3/630c3/19
You can try this.
SELECT *
FROM
(
select u.userfullname,
case when p.roleid is not null then 1 end as roleid,
a.description
from users u
left join personalroles p
on p.userid = u.userid
left join availableroles a
on a.codeid = p.roleid
) d
PIVOT
(
MAX(roleID)
FOR Description IN (Java, DOTNET, C, HTML5)
) piv
Fiddle

Transpose rows to columns in Oracle 10g

I have a table which looks like the following
po_num | terms type | terms description
-------------------------------------------
10 | 1 | Desc-10-1
10 | 2 | Desc-10-2
10 | 3 | Desc-10-3
20 | 1 | Desc-20-1
20 | 3 | Desc-20-3
30 | |
So, for each Purchase Order (PO_NUM) there could be several terms of agreements (maximum three - 1,2,3) or no terms of agreement at all. Now, what I need is to transpose rows into columns - that is, for each po_num, I would like to have a similar output like below.
po_num | terms1 | termsDesc2 | terms2 | termsDesc2 | terms3 |termsDesc3
---------------------------------------------------------------------------------------
10 | 1 | Desc-10-1 | 2 | Desc-10-2 | 3 |Desc10-3
20 | 1 | Desc-20-1 | | | 3 |Desc20-3
30 | | | | | |
I cannot use pivot since I don't have Oracle 11.2 installed. I do not wanna use scalar subqueries in the select because the performance degrades several times with that approach. I tried to use the following query to first concatenate all the fields, the split them with an outer query, but haven't been able to do it yet.
SELECT po_num,
RTRIM (
XMLAGG (
XMLELEMENT (
po_table,
po_table.terms_id || '|' || po_table.terms_description || '|')).
EXTRACT ('//text()'),
'|')
po_concat
FROM po_table
WHERE 1 = 1
GROUP BY PO_table.po_num
In 10g you can use DECODE function instead of PIVOT:
CREATE TABLE po_table (
po_num NUMBER,
terms_type NUMBER,
terms_description VARCHAR2(20)
);
INSERT INTO po_table VALUES(10, 1, 'Desc-10-1');
INSERT INTO po_table VALUES(10, 2, 'Desc-10-2');
INSERT INTO po_table VALUES(10, 3, 'Desc-10-3');
INSERT INTO po_table VALUES(20, 1, 'Desc-20-1');
INSERT INTO po_table VALUES(20, 3, 'Desc-20-3');
INSERT INTO po_table VALUES(30, NULL, NULL);
COMMIT;
SELECT
po_num,
MAX(DECODE(terms_type, 1, terms_type)) AS terms1,
MAX(DECODE(terms_type, 1, terms_description)) AS terms1Desc,
MAX(DECODE(terms_type, 2, terms_type)) AS terms2,
MAX(DECODE(terms_type, 2, terms_description)) AS terms2Desc,
MAX(DECODE(terms_type, 3, terms_type)) AS terms3,
MAX(DECODE(terms_type, 3, terms_description)) AS terms3Desc
FROM
po_table
GROUP BY po_num
ORDER BY po_num;
Output:
PO_NUM TERMS1 TERMS1DESC TERMS2 TERMS2DESC TERMS3 TERMS3DESC
---------- ------- ------------ ------- ------------ ------- ----------
10 1 Desc-10-1 2 Desc-10-2 3 Desc-10-3
20 1 Desc-20-1 3 Desc-20-3
30
Something like this:
SELECT
po_num,
MAX(CASE WHEN terms_id=1 THEN terms_description ELSE '' END) as termsDesc1,
MAX(CASE WHEN terms_id=2 THEN terms_description ELSE '' END) as termsDesc2,
MAX(CASE WHEN terms_id=3 THEN terms_description ELSE '' END) as termsDesc3
FROM po_table
GROUP BY po_num

Need some sort of "conditional grouping" in MySQL

I have Article table:
id | type | date
-----------------------
1 | A | 2010-01-01
2 | A | 2010-01-01
3 | B | 2010-01-01
Field type can be A, B or C.
I need to run a report that would return how many articles of each type there is per every day, like this:
date | count(type="A") | count(type="B") | count(type="C")
-----------------------------------------------------
2010-01-01 | 2 | 1 | 0
2010-01-02 | 5 | 6 | 7
Currently I am running 3 queries for every type and then manually merging the results
select date, count(id) from article where type="A" group by date
Is it possible to do this in one query? (in pure sql, no stored procedures or anything like that).
Thanks
A combination of SUM and CASE should do ya
select date
, sum(case when type ='A' then 1 else 0 end) as count_type_a
, sum(case when type ='B' then 1 else 0 end) as count_type_b
, sum(case when type ='C' then 1 else 0 end) as count_type_c
from article group by date
EDIT: Alex's answer above uses a better approach that the one in this answer. I'm leaving it here just because it also satisfies the question, in an alternative way:
You should be able to use sub queries, as follows:
SELECT DATE(a.date) as date,
(SELECT COUNT(a1.id) FROM articles a1 WHERE a1.type = 'A' AND a1.date = a.date) count_a,
(SELECT COUNT(a2.id) FROM articles a2 WHERE a2.type = 'B' AND a2.date = a.date) count_b,
(SELECT COUNT(a3.id) FROM articles a3 WHERE a3.type = 'C' AND a3.date = a.date) count_c
FROM articles a
GROUP BY a.date;
Test Case:
CREATE TABLE articles (id int, type char(1), date datetime);
INSERT INTO articles VALUES (1, 'A', '2010-01-01');
INSERT INTO articles VALUES (2, 'A', '2010-01-01');
INSERT INTO articles VALUES (3, 'B', '2010-01-01');
INSERT INTO articles VALUES (4, 'B', '2010-01-02');
INSERT INTO articles VALUES (5, 'B', '2010-01-02');
INSERT INTO articles VALUES (6, 'B', '2010-01-03');
INSERT INTO articles VALUES (7, 'B', '2010-01-01');
INSERT INTO articles VALUES (8, 'C', '2010-01-05');
Result:
+------------+---------+---------+---------+
| date | count_a | count_b | count_c |
+------------+---------+---------+---------+
| 2010-01-01 | 2 | 2 | 0 |
| 2010-01-02 | 0 | 2 | 0 |
| 2010-01-03 | 0 | 1 | 0 |
| 2010-01-05 | 0 | 0 | 1 |
+------------+---------+---------+---------+
4 rows in set (0.00 sec)