return results based on multiple record criteria - sql

I need to return invoices from a transaction list that have both product A and product B on it
example of the table
prod_code | invoice
apple | 100
banana | 100
orange | 100
apple | 101
kiwi | 101
grape | 101
apple | 102
banana | 102
grape | 102
I need to input 2 products and it must list the invoice numbers that have both products.
If i input apple and banana it must return 100 and 102
If i input apple and grape it returns 101 and 102
It seems like it should be very simple but for the life of me i cant think on how to do this.
SOLVED
Ok i solved my own question. Dont know why i didnt think of it earlier. As i thought it was pretty simple.
select invoice from transaction where prod_code="apple" and invoice in (select invoice from transaction where prod_code="banana")

SELECT
invoice
FROM
transactions
WHERE
prod_code IN ('apple', 'banana')
GROUP BY
invoice
HAVING
COUNT(DISTINCT prod_code) = 2
Note, however, that this is not a fast query, and with the structure that you have it's not easy to make significant performance gains.
By its nature the first step must by find all invoices with 'apple' <OR> 'banana' and only after that to filter to invoices that have both.
An alternative is...
SELECT
t_apple.invoice
FROM
transactions AS t_apple
INNER JOIN
transactions AS t_banana
ON t_apple.invoice = t_banana.invoice
WHERE
t_apple.prod_code = 'apple'
AND t_banana.prod_code = 'banana'
But that's less simple to generalise to n prod_codes.

Related

SQL query show customers who bought apples, but not potatoes

Not sure how to explain this..
I have a similar table, but i have simplified it with the following:
I have a table of goods shipped to different cusotmers. Some have bought apples only, others have bought apples and potates.
I want an SQL query to return only customers where "To be billed" = Yes AND the customer hasnt bought any vegetables.
So for example if the table looks like this:
Item
Name
Group
To_be_billed
CustomerNo.
2000
Apple
Fruit
Yes
1
2000
Apple
Fruit
No
2
2000
Apple
Fruit
No
3
2000
Apple
Fruit
Yes
4
2000
Apple
Fruit
Yes
5
4000
Potato
Vegetable
No
2
4000
Potato
Vegetable
No
4
I want the query to return:
Item
Name
Group
To_be_billed
CustomerNo.
2000
Apple
Fruit
Yes
1
2000
Apple
Fruit
Yes
5
The reason 4 has bought apples, and is to be billed, but the customer also bought Potatoes, so is to be ignored...
You can create a CTE to check for CustomerNo.s that you need to ignore, and then use not exists:
with bought_veg as
(
select "CustomerNo."
from tbl
where tbl."Group" like 'Vegetable'
)
select tbl.*
from tbl
where not exists (select 1 from bought_veg where tbl."CustomerNo." = bought_veg."CustomerNo.")
and tbl.To_be_billed = 'Yes'
Example without CTE:
select tbl.*
from tbl
where not exists (select "CustomerNo." from tbl t2 where tbl.[CustomerNo.] = t2.[CustomerNo.] and "Group" like 'Vegetable')
and tbl.To_be_billed = 'Yes'

Return product from order data from multiple record to columns

I have a SQL Server database which contains survey data and is very close to this question How to return ordered data from multiple records into one record in MySQL?
The data is almost identical. Again copied from the above question but with addition of millisecond and datetime2 column.
SURVEY_TAKER_ID | QUESTION_NUMBER | RESPONSE
----------------+-----------------+-----------
101 1 Apple
102 1 Orange
103 1 Banana
101 2 Morning
102 2 Evening
103 2 Afternoon
101 3 Red
102 3 Blue
103 3 Yellow
I am trying to use group by function but it is not grouping responses but showing responses in rows format.
select
s.survey_taker_ID, AVG(s.Millisecond)Duration,
(case when s.Question_Number = 1 then s.Answer end Product1,
(case when s.Question_Number = 2 then s.Answer end Product2
from
survey as s
group by
s.survey_taker_ID, s.Question_Number,s.Answer
Output:
Survey_Taker_ID | Duration | Product1 | Product2
----------------+----------+-----------+----------
101 | 11125 | Apple | Morning
102 | 12545 | Orange | Evening
Sad part is I have done this before but cannot seem to achieve it now. I know i am making some stupid mistake. Any sample code will help.
I think you want aggregation:
select s.survey_taker_ID, AVG(s.Millisecond) as Duration,
max(case when s.Question_Number = 1 then s.Answer end) as Product1,
max(case when s.Question_Number = 2 then s.Answer end) as Product2
from survey as s
group by s.survey_taker_ID;

Combining two tables in a query and creating new columns from that

I'm having issues with a query that I'm not ENTIRELY sure can be done with the way the database is set up. Basically, I'll be using two different tables in my query, let's say Transactions and Ticket Prices. They look like this (With some sample data):
TRANSACTIONS
Transation ID | Ticket Quantity | Total Price | Salesperson | Ticket Price ID
5489 250 250 Jim 8765
5465 50 150 Jim 1258
7898 36 45 Ann 4774
Ticket Prices
Ticket Price ID | Quantity | Price | Bundle Name
8765 1 1 1 ticket, $1
4774 12 15 5 tickets, $10
1258 1 3 1 ticket, $3
What I'm aiming for is a report, that breaks down each salesperson's sales by bundle type. The resulting table should be something like this:
Sales Volume/Salesperson
Name | Bundle A | Bundle B | Bundle C | Total
Jim 250 0 50 300
Ann 0 36 0 36
I've been searching the web, and it seems the best way of getting it like this is using various subqueries, which works well as far as getting the column titles properly displayed, but it doesn't work as far as the actual numerical totals. It basically combines the data, giving each salesperson a total readout (In this example, both Jim and Ann would have 250 sales in Bundle A, 36 in Bundle B, etc). Is there any way I can write a query that will give me the proper results? Or even something at least close to it? Thanks for any input.
You can use the PIVOT statement in Oracle to do this. A query might look something like this:
WITH pivot_data AS (
SELECT t.salesperson,p.bundle_name,t.ticket_quantity
FROM ticket_prices p, transactions t
where t.ticket_price_id = p.ticket_price_id
)
SELECT *
FROM pivot_data
PIVOT (
sum(ticket_quantity) --<-- pivot_clause
FOR bundle_name --<-- pivot_for_clause
IN ('1 ticket, $1','5 tickets, $10', '1 ticket, $3' ) --<-- pivot_in_clause
);
which would give you results like this:

SQL summary by ID with period to period comparison

I am a beginner in SQL, hope someone can help me on this:
I have a Items Category Table:
ItemID | ItemName | ItemCategory | Active/Inactive
100 Carrot Veg Yes
101 Apple Fruit Yes
102 Beef Meat No
103 Pineapple Fruit Yes
And I have a sales table:
Date | ItemID | Sales
01/01/2010 100 50
05/01/2010 101 200
06/01/2010 101 250
06/01/2010 102 300
07/01/2010 103 50
08/01/2010 100 100
10/01/2010 102 250
How Can I achieve a sales summary table by Item By Period as below (with only active item)
ItemID | ItemName | ItemCategory | (01/01/2010 – 07/01/2010) | (08/01/2010 – 14/01/1020)
100 Carrot Veg 50 100
101 Apple Fruit 450 0
103 Pineapple Fruit 0 0
A very dirty solution
SELECT s.ItemId,
(SELECT ItemName FROM Items WHERE ItemId = s.ItemId) ItemName,
ISNULL((SELECT Sum(Sales)FROM sales
WHERE [Date] BETWEEN '2010/01/01' AND '2010/01/07'
AND itemid = s.itemid
GROUP BY ItemId),0) as firstdaterange,
ISNULL((SELECT Sum(Sales)FROM sales
WHERE [Date] BETWEEN '2010/01/08' AND '2010/01/14'
AND itemid = s.itemid
GROUP BY ItemId), 0) seconddaterange
FROM Sales s
INNER JOIN Items i ON s.ItemId = i.ItemId
WHERE i.IsActive = 'Yes'
GROUP BY s.ItemId
Again a dirty solution, also the dates are hardcoded. You can probably turn this into a stored procedure taking in the dates as parameters.
I'm not too clued up on PIVOT command but maybe that will be worth a google.
You can pivot the data using the SQL PIVOT operator. Unfortunately, that operator has limited scope due to the requirement to pre-specify the output columns.
You normally achieve this by grouping on a calculated column (in this case, one that computes the week number or first day of the week in which each row falls). You can then either generate SQL on-the-fly with columns derived using SELECT DISTINCT week FROM result, or just drop the result into Excel and use its pivot table facility.

Group by with count

Say I have a table like this in my MsSql server 2005 server
Apples
+ Id
+ Brand
+ HasWorms
Now I want an overview of the number of apples that have worms in them per brand.
Actually even better would be a list of all the apple brands with a flag if they are unspoiled or not.
So if I had the data
ID| Brand | HasWorms
---------------------------
1 | Granny Smith | 1
2 | Granny Smith | 0
3 | Granny Smith | 1
4 | Jonagold | 0
5 | Jonagold | 0
6 | Gala | 1
7 | Gala | 1
I want to end up with
Brand | IsUnspoiled
--------------------------
Granny Smith | 0
Jonagold | 1
Gala | 0
I figure I should first
select brand, numberOfSpoiles =
case
when count([someMagic]) > 0 then 1
else 0
end
from apples
group by brand
I can't use a having clause, because then brands without valid entries would dissapear from my list (I wouldn't see the entry Gala).
Then I thought a subquery of some kind should do it, but then I can't link the apple id of the outer (grouped) query to the inner (count) query...
Any ideas?
select brand, case when sum(hasworms)>0 then 0 else 1 end IsUnSpoiled
from apples
group by brand
SQL server version, I did spoiled instead of unspoiled, this way I could use the SIGN function and make the code shorter
table + data (DML + DDL)
create table Apples(id int,brand varchar(20),HasWorms bit)
insert Apples values(1,'Granny Smith',1)
insert Apples values(2,'Granny Smith',0)
insert Apples values(3,'Granny Smith',1)
insert Apples values(4,'Jonagold',0)
insert Apples values(5,'Jonagold',0)
insert Apples values(6,'Gala',1)
insert Apples values(7,'Gala',1)
Query
select brand, IsSpoiled = sign(sum(convert(int,hasworms)))
from apples
group by brand
Output
brand IsSpoiled
----------------------
Gala 1
Granny Smith 1
Jonagold 0
SELECT Brand,
1-MAX(HasWorms) AS IsUnspoiled
FROM apples
GROUP BY Brand
SELECT brand,
COALESCE(
(
SELECT TOP 1 0
FROM apples ai
WHERE ai.brand = ao.brand
AND hasWorms = 1
), 1) AS isUnspoiled
FROM (
SELECT DISTINCT brand
FROM apples
) ao
If you have an index on (brand, hasWorms), this query will be super fast, since it does not count aggregates, but instead searches for a first spoiled apple within each brand ans stops.
I haven't tested this, and maybe I'm missing something. But wouldn't this work?
SELECT Brand, SUM(CONVERT(int, HasWorms)) AS SpoiledCount
FROM Apples
GROUP BY Brand
ORDER BY SpoiledCount DESC
I assume HasWorms is a bit field, hence the CONVERT statement. This should return a list of brands with the count of spoiled apples per brand. You should see the worst (most spoiled) at the top and the best at the bottom.
There are many ways to skin this cat. Depending on your RDBMS, different queries will give you the best results. On our Oracle box, this query performs faster than all the others listed, assuming that you have an index on Brand in the Apples table (an index on Brand, HasWorms is even faster, but that may not be likely; depending on your data distribution, an index on just HasWorms may be the fastest of all). It also assumes you have a table "BrandTable", which just has the brands:
SELECT Brand
, 1 IsSpoiled
FROM BrandTable b
WHERE EXISTS
( SELECT 1
FROM Apples a
WHERE a.brand = b.brand
AND a.HasWorms = 1
)
UNION
SELECT Brand
, 0
FROM BrandTable b
WHERE NOT EXISTS
( SELECT 1
FROM Apples a
WHERE a.brand = b.brand
AND a.HasWorms = 1
)
ORDER BY 1;
SELECT CASE WHEN SUM(HasWorms) > 0 THEN 0 ELSE 1 END AS IsUnspoiled, Brand
FROM apples
GROUP BY Brand