Entries in my SQL Query doubles the result values - sql

I have 3 Tables (Transaction, TransactionEntry and TenderEntry). When I run my SQL query, I got the sum values twice because of TransactionEntry Table.
So let's say I have 2 Items (Clothe worth $288.75 and Free Umbrella worth $50.00). I settled the payment using Cash worth $300 and GC worth $100, now I have a Cash change worth $61.25.
Here are the entries in the 3 tables
Transaction table
TransactionNumber|Total|
========================
1 |338.75|
TransactionEntry Table
TransactionNumber|ItemID|Price|
===============================
1 |245648|288.75|
1 |129 |50.00 |
Tender Entry Table
TransactionNumber|TenderID|Description|Amount|
==============================================
1 |1 |Cash |300.00|
1 |1 |Cash |-61.25|
1 |20 |GC |100.00|
Here is what I made so far
select [Transaction].TransactionNumber,
(case when [Transaction].RecallType = 0 then [Transaction].total else 0 end) as Sales,
sum(case when TransactionEntry.ItemID = 6922 then TransactionEntry.Price
when TransactionEntry.price = 0 then TransactionEntry.price else 0 end) as Free,
sum(case when TenderEntry.tenderID = 1 then TenderEntry.Amount else 0 end) as Cash,
sum(case when TenderEntry.tenderID = 20 then TenderEntry.Amount else 0 end) as GC
from [Transaction] inner join
TransactionEntry on [Transaction].transactionnumber = TransactionEntry.transactionnumber inner join
TenderEntry on [Transaction].transactionnumber = TenderEntry.transactionnumber
group by [Transaction].TransactionNumber,
(case when [Transaction].RecallType = 0 then [Transaction].total else 0 end)
I get this kind of output
TransactionNumber|Sales |Free |Cash |GC |
==============================================
1 |338.75|150.00|477.50|200.00|
Instead of this
TransactionNumber|Sales |Free |Cash |GC |
==============================================
1 |338.75|50.00|238.75 |100.00|

You're wanting one row per transaction, so I'd suggest you sum your joined tables to the transaction level before doing the joins as follows:
select t1.TransactionNumber,
case when t1.RecallType = 0 then t1.total else 0 end as Sales,
t2.Free,
t3.Cash,
t3.GC
from [Transaction] t1
inner join (select transactionnumber,
sum(case when ItemID = 6922 then Price else 0 end) as free
from TransactionEntry
group by transactionnumber) as t2
on t1.transactionnumber = t2.transactionnumber
inner join (select transactionnumber,
sum(case when tenderID = 1 then Amount else 0 end) as Cash,
sum(case when tenderID = 20 then Amount else 0 end) as GC
from TenderEntry
group by transactionnumber) as t3
on t1.transactionnumber = t3.transactionnumber
Note that I've used table aliases (t1, t2, t3) to condense the code and make it a bit easier to read. It looks as though "Transaction" is a reserved word, so probably not a great choice of table name as it means you always have to use the square brackets to enclose the name when referring to it.
There seems to be an issue with this piece of your code,
sum(case when TransactionEntry.ItemID = 6922 then TransactionEntry.Price
when TransactionEntry.price = 0 then TransactionEntry.price else 0 end) as Free,
because your Umbrella, which you say is Free, is item 129 not 6922. Also the "when TransactionEntry.price = 0 then ..." is setting the price to 0 regardless of the price. I'm not sure what you intended there?

Related

SQL: SUM OR COUNT with CASE WHEN condition in multiple criteria

Course name
Section number
Course type
MATH 101
1
In person
MATH 101
2
In person
MATH 101
3
Online
MATH 101
4
In person
SOC 101
1
In person
SOC 101
2
In person
SOC 101
3
In person
ENGL 201
1
In person
ENGL 201
2
Online
ENGL 201
3
Online
ENGL 201
4
In person
PHY 101
1
Online
PHY 101
2
Online
From this table, I'd like to count Courses with only an 'In person' course, an 'Online' course, and both course types.
The query I tried is below.
SELECT
SUM(CASE WHEN coursetype = 'Inperson' AND coursetype = 'Online' THEN 1 ELSE 0 END) AS bothtype,
SUM(CASE WHEN coursetype = 'Online' THEN 1 ELSE 0 END) AS Onlineonly,
SUM(CASE WHEN coursetype = 'Inperson' THEN 1 ELSE 0 END) AS Onlineonly
From Course
The result what I expected is
bothtpye
Onlineonly
Inpersononly
2
1
1
but I got
bothtpye
Onlineonly
Inpersononly
0
7
6
Please advise me to get through this.
Thank you.
My solution uses double conditional aggregation.
SELECT SUM (CASE WHEN In_Person > 0 AND Online > 0 THEN 1 ELSE 0 END) as bothtype,
SUM (CASE WHEN In_Person > 0 AND Online = 0 THEN 1 ELSE 0 END) as inpersononly,
SUM (CASE WHEN In_Person = 0 AND Online > 0 THEN 1 ELSE 0 END) as onlineonly
FROM (
SELECT Course_name,
SUM(CASE WHEN Course_type='In Person' THEN 1 ELSE 0 END) as In_Person,
SUM(CASE WHEN Course_type='Online' THEN 1 ELSE 0 END) as Online
FROM Course
GROUP BY Course_name
) tot
DEMO Fiddle
SUGGESTION ( using PL/SQL ! ) :
CREATE PROCEDURE countCourses(OUT bothtype INT,OUT Inpersononly INT,OUT Onlineonly INT)
begin
SELECT COUNT(*) INTO bothtype FROM Course;
select COUNT(*) INTO Inpersononly FROM Course
WHERE courseType = "In person";
select COUNT(*) INTO Onlineonly FROM Course
WHERE courseType = "Online";
end;
call countCourses(#bothtype,#Inpersononly,#Onlineonly);
SELECT #bothtype,#Inpersononly,#Onlineonly;
EXPLICATION :
Creating procedure to store the count of each type of course in OUT variable
Call the procedure with convenient parameters
Select out given parameters

SQL - Making 4 new columns in a result from another column

So, I'm making a data base for my college class, it's about a foreign languages school, and I need to ( using a single query ), have a number of people that are attending a certain language class, but it has to be seperated by the age group. For example, this is how the result table should look like:
Language | 14-25 | 25-35 | 35-50 | 50+ |
German | 1 | 0 | 0 | 0 |
Italian | 2 | 1 | 0 | 0 |
English | 5 | 0 | 0 | 0 |
I need to do this by joining the tables "Class" that has attributes (Language, Number of students), and "Student" that has attributes (ID, name, surname, age, prior knowledge ( eg. A1, B2, ... ))
So I somehow have to figure out in which age group a certain individual goes to, then if he goes there, increment the number of students for that age group by one.
You can build the sum and group the entries using CASE WHEN, so your query will look like this:
SELECT c.language,
SUM(CASE WHEN s.age BETWEEN 14 AND 25 THEN 1 ELSE 0 END) AS '14-25',
SUM(CASE WHEN s.age BETWEEN 25 AND 35 THEN 1 ELSE 0 END) AS '25-35',
SUM(CASE WHEN s.age BETWEEN 35 AND 50 THEN 1 ELSE 0 END) AS '35-50',
SUM(CASE WHEN s.age >= 50 THEN 1 ELSE 0 END) AS '50+'
FROM class c
JOIN student_class sc ON c.language = sc.class_language
JOIN student s ON s.id = sc.student_id
GROUP BY c.language;
You have to take care because as example a person whose age is 25 will be selected in both groups "15-25" and "25-35". If this is not intended, you could do something like this:
...SUM(CASE WHEN s.age BETWEEN 14 AND 25 THEN 1 ELSE 0 END) AS '14-25',
SUM(CASE WHEN s.age BETWEEN 26 AND 35 THEN 1 ELSE 0 END) AS '25-35',
SUM(CASE WHEN s.age BETWEEN 36 AND 50 THEN 1 ELSE 0 END) AS '35-50',
SUM(CASE WHEN s.age > 50 THEN 1 ELSE 0 END) AS '50+'...
Please see the working example here: db<>fiddle
You could add an ORDER BY c.language at the end if you want.
A last note: The column aliases shown here ('14-25' etc.) will not work on every DB type and might be replaced depending on DB type and personal "taste".
Assuming you have a table called something like ClassStudent which is linking the individual students to the class (which you absolutely need to fulfil this requirement)...
SELECT c.Language,
[14-25] = SUM(IIF(s.age BETWEEN 14 AND 25, 1, 0)),
[25-35] = SUM(IIF(s.age BETWEEN 25 AND 35, 1, 0)),
[35-50] = SUM(IIF(s.age BETWEEN 35 AND 50, 1, 0)),
[50+] = SUM(IIF(s.age >= 50, 1, 0)),
FROM Class c
INNER JOIN ClassStudent cs ON c.Language = cs.Language /* you need this table */
INNER JOIN Student s ON cs.StudentID = s.ID
GROUP BY c.Language
Here, IIF is like a ternary operator in SQL form, and the SUM lets you count up where the condition is met.

Creating extra columns based on condition (Case When Sum)

I have survey table where someone is asked roughly 5 questions. 4 of those questions are the same questions, but the options to their answers are different since they were to understand their purchase.
Here are the questions:
ID Question qid Answer
101005 what brands did you purchase the past 5 months 1 Coca-Cola or Pepsi or Dr.Pepper
101005 what brands did you purchase the past 5 months 1 Dr.Pepper
101005 what brands did you purchase the past 5 months 1 store brand
101005 what brands did you purchase the past 5 months 1 Coca-Cola
101005 how many people live in your house 4 4
101005 what is your prefer retailers 8 walmart
The goal is to create four extra columns based on their answer and they will be assigned a 1 or 0. Since this person's answer is coca cola, I want to assign them in the column of current_buyer and give them 1 and 0 will be new_buyer column. I also want to make sure that even though he answer Dr.Pepper in the second row, it still recognizes him as a current_buyer. In the same breath I want to assign this person a 1 in the 3rd column as a drinker and 0 4th column in prospect.
Here is how the table should look like
ID Question qid answer Current_buyer New_buyer drinker prospect
101005 what brands did you purchase the past 5 months 1 Coca-Cola or Pepsi or Dr.Pepper 1 0 1 0
101005 what brands did you purchase the past 5 months 1 Dr.Pepper 1 0 1 0
101005 how many people live in your house 4 4 1 0 1 0
The goal is to see if ID bought coca-cola the past 5 months, they are a current buyer (1) and drinker (1) and will have (0) for new_buyer and prospect in their entire profile.
Here is the code that I try:
select ID,qid,question,answer,s_date,
Case when Sum(Case when [answer] like'%coca-cola%' then 1 else 0 end)>=1 then 1
else 0 end current_buyer
,Case when Sum(Case when [answer] like'%coca-cola%' then 1 else 0 end)=0 then 1
else 0 end New_buyer
,Case when Sum(Case when [answer] like'%coca-cola,Dr.pepper,pepsi%' then 1 else 0 end)>=1 then 1
else 0 end drinker
,Case when Sum(Case when [answer] like'%coca-cola,Dr.pepper,pepsi%' then 1 else 0 end)=0 then 1
else 0 end Prospect
Unfortunately, using this code I'm getting 0 in the drinker column even though people selected coca-cola. Any help would be appreciated.
I'm not 100% sure that I understand the logic behind assigning the various values, but in general it seems that you are assigning the value based on an aggregation of multiple rows. Therefore you need to do the aggregation first (in this case in a CTE) and then join back to that for your query.
Also worth noting is that even though your are doing your matching with a LIKE operator - everything between the [%] characters will need to match exactly somewhere in your [answer]. I don't think that this is what you want - so I've split them out. However I'm not 100% sure if I've got the logical operators completely correct.
Do you need something like:
;with analysisCTE as (select ID,qid,
Case when Sum(Case when [answer] like'%coca-cola%' then 1 else 0 end)>=1 then 1
else 0 end current_buyer
,Case when Sum(Case when [answer] like'%coca-cola%' then 1 else 0 end)=0 then 1
else 0 end New_buyer
,Case when Sum(Case when [answer] like'%coca-cola%' OR [answer] like '%Dr.pepper%' OR [answer] like '%pepsi%' then 1 else 0 end)>=1 then 1
else 0 end drinker
,Case when Sum(Case when [answer] like'%coca-cola%' OR [answer] like '%Dr.pepper%' OR [answer] like '%pepsi%' then 1 else 0 end)=0 then 1
else 0 end Prospect
from drinks
group by id, qid)
select d.ID,d.qid,d.question,d.answer, a.current_buyer, a.New_buyer, a.drinker, a.Prospect
from drinks d
inner join analysisCTE a on d.id = a.id and d.qid = a.qid

group by in case of nested cases with conditions on different tables

My question is a bit similar to this question but with a caveat. In my case the conditions are dependent on different tables, not one table. The part which is giving me trouble is the GROUP BY part. Here is the query:
SELECT
CASE
WHEN T1.ImportantColumn = 'Y'
THEN 'Good'
ELSE
CASE
WHEN T2.ImportantColumn = 1
THEN 'Very Good'
ELSE
CASE
WHEN T3.ImportantColumn IS NULL
THEN 'Bad'
ELSE T3.ImportantColumn
END
END
END AS WorkStatus,
SUM(case when T2.sex = 'M' THEN 1 ELSE 0 END) male ,
SUM(case when T2.sex = 'F' THEN 1 ELSE 0 END) female ,
COUNT(WorkStatus) AS [CountWorkStatus]
FROM
Condition1Table T1
RIGHT JOIN Condition2Table T2 ON T1.city = T2.Code_id AND T1.field_name = 'cities'
INNER JOIN Condition3Table T3 ON T2.student_id = T3.student_id
GROUP BY T3.ImportantColumn, T2.ImportantColumn, T1.ImportantColumn -- <-- wrote this but I know it's wrong
It is sort of IF ELSE scenario. If Condition1Table.ImportantColumn is 'Y' then 'Good', else if Condition2Table.ImportantColumn is 1 then 'Very Good', else if Condition3Table.ImportantColumn is NULL then 'bad', Else the value in Condition3Table.ImportantColumn. The hard part is the grouping of data in a desired format which is below:
WorkStatus | male | female | CountWorkStatus
---------- ----- ------ ---------------
Good | 3 | 7 | 10
Very Good | 11 | 2 | 13
Bad | 5 | 0 | 5
Val1 | 1 | 9 | 10
Val2 | 41 | 23 | 64
You seem to be asking "how do I group by a huge CASE statement without repeating the whole CASE statement"?
If so, just use a sub-query.
Then the result of the CASE statement has a column name that you can refer to.
There is near zero performance penalty here, sub-queries are expanded out macro-like. SQL is a declarative language, it's just a syntax for expressing a problem to be solved. When that's compiled down there's a program to run. So, while thinking about the SQL, you just need the syntax to express your problem.
SELECT
WorkStatus,
SUM(case when sex = 'M' THEN 1 ELSE 0 END) male ,
SUM(case when sex = 'F' THEN 1 ELSE 0 END) female ,
COUNT(WorkStatus) AS [CountWorkStatus]
FROM
(
SELECT
CASE
WHEN T1.ImportantColumn = 'Y'
THEN 'Good'
ELSE
CASE
WHEN T2.ImportantColumn = 1
THEN 'Very Good'
ELSE
CASE
WHEN T3.ImportantColumn IS NULL
THEN 'Bad'
ELSE T3.ImportantColumn
END
END
END AS WorkStatus,
T2.sex
FROM
Condition1Table T1
RIGHT JOIN Condition2Table T2 ON T1.city = T2.Code_id AND T1.field_name = 'cities'
INNER JOIN Condition3Table T3 ON T2.student_id = T3.student_id
)
AS StatusBySex
GROUP BY
WorkStatus

Grouping the data by (day) and then Join the 2 tables together in Oracle

There are Two tables licenses and organization
licenses table contains 5 columns
oracle_apps tableau sab_bi tririga time_snapshot
0 1 1 1 2017-06-13 08:12:02.640
0 0 0 1 2017-06-13 09:12:02.640
0 0 1 0 2017-06-13 11:52:02.640
0 1 0 1 2017-06-14 09:12:02.640
0 0 1 0 2017-06-14 10:12:02.640
organization table has 2 columns
license_name license_count
oracle_ 5.0000000
tableau_ 1.0000000
sab_ 20.0000000
tririga_ 10.0000000
So Output will an select query or pl SQL stored procedure that will contain the join of both the tables and it will show the how many licenses are used in a day (count)
Output:
oracle_apps tableau sab_bi tririga time_snapshot oracle_ tableau_ sap_ tririga_
0 1 2 2 2017-06-13 5.0000000 1.0000000 20.0000000 10.0000000
0 1 1 1 2017-06-14 5.0000000 1.0000000 20.0000000 10.0000000
For Ex: on 2017-06-13 total 5 licenses are used and individually 0,1,2,2 i.e. sum(oracle_apps), sum(tableau), sum(sab_bi), sum(tririga)
and lly, on 2017-06-14 total 3 licenses are used i.e. (0, 1, 1, 1)
That thing was easy by using group by to_char(time_snapshot, yyyy-mm-dd) but I'm not able to join the tables and use row as a column because in organization table the row will column in output and I know one way to do it using PIVOT but I'm confused how to get that in outcome and for each and every row the license_name values will be same i.e. ( 5.0000000 | 1.0000000 | 20.0000000
| 10.0000000)
So is there a solution of Grouping the data and Joining the table together?
Use CROSS JOIN, SUM, MAX, CASE..WHEN..THEN.. and GROUP BY
SELECT trunc( l.time_snapshot) As time_snapshot,
sum( l.oracle_apps ) As oracle_apps,
sum( l.tableau ) As tableau,
sum( l.sab_bi ) As sab_bi,
sum( l.tririga ) As tririga,
max( CASE WHEN o.license_name = 'oracle_' THEN o.license_count END ) As oracle_,
max( CASE WHEN o.license_name = 'tableau_' THEN o.license_count END ) As tableau_,
max( CASE WHEN o.license_name = 'sab_' THEN o.license_count END ) As sab_,
max( CASE WHEN o.license_name = 'tririga_' THEN o.license_count END ) As tririga_
FROM licenses l
CROSS JOIN organization o
GROUP BY trunc( l.time_snapshot)
Demo: http://sqlfiddle.com/#!4/24b53/5
I would recommend that you pivot the data before joining:
select l.dte, l.oracle_apps, l.tableau, l.sab_bi, l.tririga,,
o.oracle_, o.tableau_, o.sab_, o.tririga_
from (select trunc(l.time_snapshot) as dte,
sum(l.oracle_apps) as oracle_apps,
sum(l.tableau) as tableau,
sum(l.sab_bi) as sab_bi,
sum(l.tririga) as tririga
from licenses l
group by trunc(l.time_snapshot)
) l cross join
(select sum(case when license_name = 'oracle_' then license_count else 0 end) as oracle_,
sum(case when license_name = 'tableau_' then license_count else 0 end) as tableau_,
sum(case when license_name = 'sab_' then license_count else 0 end) as sab_,
sum(case when license_name = 'tririga_' then license_count else 0 end) as tririga_
from organization o
) o;
Aggregating on the individual tables seems safer to me (if oracle_ appeared twice in the organization table, your license counts would be off). In addition, the resulting queries may be faster because optimizations on a single table are usually easier than on multiple tables.