SQL filtering data - sql

I need some advice on how to query a database more efficiently.
The situation can be explained as this. If a customer comes into the store and buys something that creates a sale in the [sales table] this table links via transaction reference to [transactions table] but there may be a number of items purchased, each of these creates a row in the [products table].
To add to this there are also two promotion tables that indicate whether the product is on price promotion [promotions] or on a multibuy promotion[sales flag]. Both of these also link back to the sales table.
I want to be able to bring back all the rows so every product of every transaction that was in one of the promotions is available.
The conditions work as follows
Product ¦ Price promo (promotions-promotion no) ¦Multibuy(salesflag-On promotion)
-----------------------------------
1.Product A -1 0 = neither price promo nor multibuy
2.Product B -1 1 = multibuy only ie 0 false 1 true
3.Product C 8245 0 = price promo only can be any number other than -1
These are the three scenarios yet they could all happen in one customer transaction.
so how could I check every transaction for the circumstances of products B and C and return the full basket if either is present?
All I can think to do is a further select within the where clause but this would return all the relevant transaction numbers and the main query would then search the entire database for these transaction numbers it seems a very long winded way as it is a huge database I'm working on + I'm fairly basic level sql at the moment.
SELECT
TRANS.TransactionReference
,TRANS.TID
,CONVERT(VARCHAR(10),T.Day,103) as 'Date'
,PROD.HierarchyLevel2
,PROD.HierarchyLevel3
,PROD.HierarchyLevel4
,PROD.ProductDescription
,PROD.ExternalProductCode
,PROD.Size
,CUST.CardNumber
,S.SalesQty /*unless Group everything somehow will get multiple lines per qty sold all with qty of 1*/
,S.SalesValue
,STOR.Area
,STOR.StoreCode
,STOR.StoreName
FROM Reporting.Sales as S
INNER JOIN Reporting.Products as PROD
on S.ProductID = PROD.ProductID
INNER JOIN Reporting.Time as T
on S.DateID = T.DateID
INNER JOIN Reporting.Stores as STOR
on S.StoreID = STOR.StoreID
INNER JOIN Reporting.Promotions as PROM
on S.PromotionID = PROM.PromotionID
INNER JOIN Reporting.SalesFlags as FLAG
on S.SalesFlagID = FLAG.SalesFlagID
INNER JOIN Reporting.Transactions as TRANS
on S.TID = TRANS.TID
INNER JOIN Reporting.Customers as CUST
on S.CustomerID = CUST.CustomerID
WHERE
T.Day BETWEEN '2014-01-23' and '2014-02-11'
AND TRANS.TransactionReference IN(SELECT distinct TRANS.TransactionReference
FROM Reporting.Sales as S
INNER JOIN Reporting.Transactions as TRANS
on S.TID = TRANS.TID
INNER JOIN Reporting.Promotions as PROM
on S.PromotionID = PROM.PromotionID
INNER JOIN Reporting.SalesFlags as FLAG
on S.SalesFlagID = FLAG.SalesFlagID
INNER JOIN Reporting.Time as T
on S.DateID = T.DateID
WHERE
PROM.PromotionNumber = '-1'
AND FLAG.OnPromotion = 1)
AND T.Day BETWEEN '2014-01-23' and '2014-02-11'
ORDER BY
TRANS.TransactionReference
;
The above would only take account of the sales where a basket contained a multibuy and i havent worked through how to include the sales where the basket can also contain a price promo. But already I'm leaving the query for 10 minutes and getting no results back- just keeps running.
The inner joins I have used I believe are all primary keys as the database has been set up cleverly to all link back to the sales table through keys built into this table.
Thanks in advance,
Kind regards,
Adam.

Related

DVD rental SQL ###### QUERY

Trying to find how many active or inactive rentals there are for a specific store. 1 = active, 0 = inactive. the answer I get doubles the amount of customers and is not right. Can anyone help
SELECT COUNT(*)
FROM customer, store
WHERE active = 0
AND store_id = 2
ORDER BY customer_id;
The join is incorrect. There are no join criteria specified so the result is all possible combinations of customers and stores, filtered by where clause.
Correct way to join is:
...
from customer inner join store on store.store_id = customer.store_id
...
(assuming customer table has the store_id field).
And you probably want to group by store using the group by clause.

Excluding multiple results in specific column (SQL JOIN)

I'm taking my first steps in terms of practical SQL use in real life.
I have a few tables with contractual and financial information and the query works exactly as I need - to a certain point. It looks more or less like that:
SELECT /some columns/ from CONTRACTS
Linked 3 extra tables with INNER JOIN to add things like department names, product information etc. This all works but they all have simplish one-to-one relationship (one contract related to single department in Department table, one product information entry in the corresponding table etc).
Now this is my challenge:
I also need to add contract invoicing information doing something like:
inner join INVOICES on CONTRACTS.contnoC = INVOICES.contnoI
(and selecting also the Invoice number linked to the Contract number, although that's partly optional)
The problem I'm facing is that unlike with other tables where there's always one-to-one relationship when joining tables, INVOICES table can have multiple (or none at all) entries that correspond to a single contract no. The result is that I will get multiple query results for a single contract no (with different invoice numbers presented), needlessly crowding the query results.
Essentially I'm looking to add INVOICES table to a query to just identify if the contract no is present in the INVOICES table (contract has been invoiced or not). Invoice number itself could be presented (it is with INNER JOIN), however it's not critical as long it's somehow marked. Invoice number fields remains blank in the result with the INNER JOIN function, which is also necessary (i.e. to have the row presented even if the match is not found in INVOICES table).
SELECT DISTINCT would look to do what I need, but I seemed to face the problem that I need to levy DISTINCT criteria only for column representing contract numbers, NOT any other column (there can be same values presented, but all those should be presented).
Unfortunately I'm not totally aware of what database system I am using.
Seems like the question is still getting some attention and in an effort to provide some explanation here are a few techniques.
If you just want any contract with details from the 1 to 1 tables you can do it similarily to what you have described. the key being NOT to include any column from Invoices table in the column list.
SELECT
DISTINCT Contract, Department, ProductId .....(nothing from Invoices Table!!!)
FROM
Contracts c
INNER JOIN Departments D
ON c.departmentId = d.Department
INNER JOIN Product p
ON c.ProductId = p.ProductId
INNER JOIN Invoices i
ON c.contnoC = i.contnoI
Perhaps a Little cleaner would be to use IN or EXISTS like so:
SELECT
Contract, Department, ProductId .....(nothing from Invoices Table!!!)
FROM
Contracts c
INNER JOIN Departments D
ON c.departmentId = d.Department
INNER JOIN Product p
ON c.ProductId = p.ProductId
WHERE
EXISTS (SELECT 1 FROM Invoices i WHERE i.contnoI = c.contnoC )
SELECT
Contract, Department, ProductId .....(nothing from Invoices Table!!!)
FROM
Contracts c
INNER JOIN Departments D
ON c.departmentId = d.Department
INNER JOIN Product p
ON c.ProductId = p.ProductId
WHERE
contnoC IN (SELECT contnoI FROM Invoices)
Don't use IN if the SELECT ... list can return a NULL!!!
If you Actually want all of the contracts and just know if a contract has been invoiced you can use aggregation and a case expression:
SELECT
Contract, Department, ProductId, CASE WHEN COUNT(i.contnoI) = 0 THEN 0 ELSE 1 END as Invoiced
FROM
Contracts c
INNER JOIN Departments D
ON c.departmentId = d.Department
INNER JOIN Product p
ON c.ProductId = p.ProductId
LEFT JOIN Invoices i
ON c.contnoC = i.contnoI
GROUP BY
Contract, Department, ProductId
Then if you actually want to return details about a particular invoice you can use a technique similar to that of cybercentic87 if your RDBMS supports or you could use a calculated column with TOP or LIMIT depending on your system.
SELECT
Contract, Department, ProductId, (SELECT TOP 1 InvoiceNo FROM invoices i WHERE c.contnoC = i.contnoI ORDER BY CreateDate DESC) as LastestInvoiceNo
FROM
Contracts c
INNER JOIN Departments D
ON c.departmentId = d.Department
INNER JOIN Product p
ON c.ProductId = p.ProductId
GROUP BY
Contract, Department, ProductId
I would do it this way:
with mainquery as(
<<here goes you main query>>
),
invoices_rn as(
select *,
ROW_NUMBER() OVER (PARTITION BY contnoI order by
<<some column to decide which invoice you want to take eg. date>>) as rn
)
invoices as (
select * from invoices_rn where rn = 1
)
select * from mainquery
left join invoices i on contnoC = i.contnoI
This gives you an ability to get all of the invoice details to your query, also it gives you full control of which invoice you want see in your main query. Please read more about CTEs; they are pretty handy and much easier to understand / read than nested selects.
I still don't know what database you are using. If ROW_NUMBER is not available, I will figure out something else :)
Also with a left join you should use COALESCE function for example:
COALESCE(i.invoice_number,'0')
Of course this gives you some more possibilities, you could for example in your main select do:
CASE WHEN i.invoicenumber is null then 'NOT INVOICED'
else 'INVOICED'
END as isInvoiced
You can use
SELECT ..., invoiced = 'YES' ... where exists ...
union
SELECT ..., invoiced = 'NO' ... where not exists ...
or you can use a column like "invoiced" with a subquery into invoices to set it's value depending on whether you get a hit or not

Adding a field to a view based on another row in the group

I'm looking at data for ticket sales. I need to see all the tickets in a transactions where a discount was applied to at least one ticket. Only one discount per transaction is allowed, though multiple tickets may have that discount. (For example, a AAA discount can be applied to up to 4 adult tickets, though more tickets may be purchased. I need data on all tickets purchased when the AAA Discount was used.)
End users will access the data in a view through Excel, then pivot and slice based on discount type and date. In creating the view, how can I apply the discount code from one ticket row to all tickets in the transaction?
SELECT
JnlTickets.TransNo,
JnlHeaders.FiscalDate,
JnlTickets.DiscNo AS DiscountNumber,
LEFT(JnlHeaders.agency,1) AS Agency,
Discounts.Name
FROM dbo.JnlHeaders (nolock)
LEFT JOIN dbo.JnlTickets (nolock)
ON dbo.JnlHeaders.TranNo=dbo.JnlTickets.TransNo
LEFT JOIN dbo.Discounts (nolock)
ON dbo.JnlTickets.DiscNo=dbo.DISCOUNTS.DiscountID
GROUP BY JnlTickets.TransNo,
JnlTickets.DiscNo,
JnlHeaders.FiscalDate,
LEFT(JnlHeaders.agency,1),
Jnltickets.coupons,
Discounts.Name
HAVING(JnlHeaders.FiscalDate BETWEEN '2012-03-07' AND '2012-03-08')
AND
(Jnltickets.transno IN (SELECT JnlTickets.TransNo
FROM JnlTickets
WHERE JnlTickets.DiscNo > '1'));
One of the ways to do it is to derive a table from tickets and discounts and retrieving distinct transaction number and discount id and name, and then join it back to tickets on transaction number. This will work only if you really have only one discount type per transaction.
Note: last part of having filters out all transactions where no discount was applied. This can now be achieved by changing left join of derived table to inner join.
SELECT
JnlTickets.TransNo,
JnlHeaders.FiscalDate,
TransactionsWithDiscounts.DiscNo AS DiscountNumber,
LEFT(JnlHeaders.agency,1) AS Agency,
TransactionsWithDiscounts.Name
FROM dbo.JnlHeaders (nolock)
LEFT JOIN dbo.JnlTickets (nolock)
ON dbo.JnlHeaders.TranNo=dbo.JnlTickets.TransNo
left join
(
select distinct JnlTickets.TransNo, JnlTickets.DiscNo, Discounts.Name
from dbo.JnlTickets (nolock)
LEFT JOIN dbo.Discounts (nolock)
ON dbo.JnlTickets.DiscNo = dbo.DISCOUNTS.DiscountID
where JnlTickets.DiscNo > '1' -- or is not null?
) TransactionsWithDiscounts
ON JnlTickets.TransNo = TransactionsWithDiscounts.TransNo
GROUP BY JnlTickets.TransNo,
TransactionsWithDiscounts.DiscNo,
JnlHeaders.FiscalDate,
LEFT(JnlHeaders.agency,1),
Jnltickets.coupons,
TransactionsWithDiscounts.Name
HAVING(JnlHeaders.FiscalDate BETWEEN '2012-03-07' AND '2012-03-08')
AND
(Jnltickets.transno IN (SELECT JnlTickets.TransNo
FROM JnlTickets
WHERE JnlTickets.DiscNo > '1'));

sql left join 2 tables

I have a SQL query with a left join which works fine:
SELECT book.* FROM book
LEFT JOIN purchase ON book.book_id = purchase.book_id
WHERE purchase.user_id = 3
ORDER BY purchase.purchase_date
But I need also infos from purchase table, so I tried:
SELECT purchase.*, book.*
FROM purchase, book
LEFT JOIN purchase ON book.book_id = purchase.book_id
WHERE purchase.user_id = 3
ORDER BY purchase.purchase_date
But it does not work, I have this error message: #1066 - Not unique table/alias: 'purchase'
How can I do modify the first request to get data from purchase table also ?
Your 1st statement was nearly exactly what you want, you just need to name in the SELECT, which fields you want to return from purchase table.
e.g.
SELECT book.*, purchase.user_id
FROM book
LEFT JOIN purchase ON book.book_id = purchase.book_id
WHERE purchase.user_id = 3
ORDER BY purchase.purchase_date
You don't need to list "purchase" in the FROM clause as well as in the JOIN - because you have, that is why you are seeing the error.
You do not need to refer to the purchase table in the FROM clause - that would mean that you are both cross-joining book and purchase tables and then joining purchase table again. Because there are two instances of purchase table, they need to have unique alias - thus the error. You probably just need this:
SELECT purchase.*, book.*
FROM purchase LEFT JOIN purchase ON book.book_id = purchase.book_id
WHERE purchase.user_id = 3 ORDER BY purchase.purchase_date;

SQL JOIN, GROUP BY on three tables to get totals

I've inherited the following DB design. Tables are:
customers
---------
customerid
customernumber
invoices
--------
invoiceid
amount
invoicepayments
---------------
invoicepaymentid
invoiceid
paymentid
payments
--------
paymentid
customerid
amount
My query needs to return invoiceid, the invoice amount (in the invoices table), and the amount due (invoice amount minus any payments that have been made towards the invoice) for a given customernumber. A customer may have multiple invoices.
The following query gives me duplicate records when multiple payments are made to an invoice:
SELECT i.invoiceid, i.amount, i.amount - p.amount AS amountdue
FROM invoices i
LEFT JOIN invoicepayments ip ON i.invoiceid = ip.invoiceid
LEFT JOIN payments p ON ip.paymentid = p.paymentid
LEFT JOIN customers c ON p.customerid = c.customerid
WHERE c.customernumber = '100'
How can I solve this?
I am not sure I got you but this might be what you are looking for:
SELECT i.invoiceid, sum(case when i.amount is not null then i.amount else 0 end), sum(case when i.amount is not null then i.amount else 0 end) - sum(case when p.amount is not null then p.amount else 0 end) AS amountdue
FROM invoices i
LEFT JOIN invoicepayments ip ON i.invoiceid = ip.invoiceid
LEFT JOIN payments p ON ip.paymentid = p.paymentid
LEFT JOIN customers c ON p.customerid = c.customerid
WHERE c.customernumber = '100'
GROUP BY i.invoiceid
This would get you the amounts sums in case there are multiple payment rows for each invoice
Thank you very much for the replies!
Saggi Malachi, that query unfortunately sums the invoice amount in cases where there is more than one payment. Say there are two payments to a $39 invoice of $18 and $12. So rather than ending up with a result that looks like:
1 39.00 9.00
You'll end up with:
1 78.00 48.00
Charles Bretana, in the course of trimming my query down to the simplest possible query I (stupidly) omitted an additional table, customerinvoices, which provides a link between customers and invoices. This can be used to see invoices for which payments haven't made.
After much struggling, I think that the following query returns what I need it to:
SELECT DISTINCT i.invoiceid, i.amount, ISNULL(i.amount - p.amount, i.amount) AS amountdue
FROM invoices i
LEFT JOIN invoicepayments ip ON i.invoiceid = ip.invoiceid
LEFT JOIN customerinvoices ci ON i.invoiceid = ci.invoiceid
LEFT JOIN (
SELECT invoiceid, SUM(p.amount) amount
FROM invoicepayments ip
LEFT JOIN payments p ON ip.paymentid = p.paymentid
GROUP BY ip.invoiceid
) p
ON p.invoiceid = ip.invoiceid
LEFT JOIN payments p2 ON ip.paymentid = p2.paymentid
LEFT JOIN customers c ON ci.customerid = c.customerid
WHERE c.customernumber='100'
Would you guys concur?
I have a tip for those, who want to get various aggregated values from the same table.
Lets say I have table with users and table with points the users acquire. So the connection between them is 1:N (one user, many points records).
Now in the table 'points' I also store the information about for what did the user get the points (login, clicking a banner etc.). And I want to list all users ordered by SUM(points) AND then by SUM(points WHERE type = x). That is to say ordered by all the points user has and then by points the user got for a specific action (eg. login).
The SQL would be:
SELECT SUM(points.points) AS points_all, SUM(points.points * (points.type = 7)) AS points_login
FROM user
LEFT JOIN points ON user.id = points.user_id
GROUP BY user.id
The beauty of this is in the SUM(points.points * (points.type = 7)) where the inner parenthesis evaluates to either 0 or 1 thus multiplying the given points value by 0 or 1, depending on wheteher it equals to the the type of points we want.
First of all, shouldn't there be a CustomerId in the Invoices table? As it is, You can't perform this query for Invoices that have no payments on them as yet. If there are no payments on an invoice, that invoice will not even show up in the ouput of the query, even though it's an outer join...
Also, When a customer makes a payment, how do you know what Invoice to attach it to ? If the only way is by the InvoiceId on the stub that arrives with the payment, then you are (perhaps inappropriately) associating Invoices with the customer that paid them, rather than with the customer that ordered them... . (Sometimes an invoice can be paid by someone other than the customer who ordered the services)
I know this is late, but it does answer your original question.
/*Read the comments the same way that SQL runs the query
1) FROM
2) GROUP
3) SELECT
4) My final notes at the bottom
*/
SELECT
list.invoiceid
, cust.customernumber
, MAX(list.inv_amount) AS invoice_amount/* we select the max because it will be the same for each payment to that invoice (presumably invoice amounts do not vary based on payment) */
, MAX(list.inv_amount) - SUM(list.pay_amount) AS [amount_due]
FROM
Customers AS cust
INNER JOIN
Payments AS pay
ON
pay.customerid = cust.customerid
INNER JOIN ( /* generate a list of payment_ids, their amounts, and the totals of the invoices they billed to*/
SELECT
inpay.paymentid AS paymentid
, inv.invoiceid AS invoiceid
, inv.amount AS inv_amount
, pay.amount AS pay_amount
FROM
InvoicePayments AS inpay
INNER JOIN
Invoices AS inv
ON inv.invoiceid = inpay.invoiceid
INNER JOIN
Payments AS pay
ON pay.paymentid = inpay.paymentid
) AS list
ON
list.paymentid = pay.paymentid
/* so at this point my result set would look like:
-- All my customers (crossed by) every paymentid they are associated to (I'll call this A)
-- Every invoice payment and its association to: its own ammount, the total invoice ammount, its own paymentid (what I call list)
-- Filter out all records in A that do not have a paymentid matching in (list)
-- we filter the result because there may be payments that did not go towards invoices!
*/
GROUP BY
/* we want a record line for each customer and invoice ( or basically each invoice but i believe this makes more sense logically */
cust.customernumber
, list.invoiceid
/*
-- we can improve this query by only hitting the Payments table once by moving it inside of our list subquery,
-- but this is what made sense to me when I was planning.
-- Hopefully it makes it clearer how the thought process works to leave it in there
-- as several people have already pointed out, the data structure of the DB prevents us from looking at customers with invoices that have no payments towards them.
*/