SQL Server : count ProductID to get total times sold - sql-server

I'm having trouble with what seems to be a simple query. I'm trying to get the amount of times an entire product has sold by counting and grouping by the ProductID. I've researched it online and every where I go it's just add a simple COUNT, but when I do it, it still outputs the same numbers of rows.
So if I don't use COUNT (for example) it outputs 1,000 rows, and if I DO use COUNT it outputs 1,000 rows and doesn't give me the correct times sold. They are all listed as "1" and not being grouped and counted. I'm guessing it has something to do with my joins but I can't figure it out.
Here's an example below of what I'm seeing after using the COUNT (I've removed brand and date_added just to make it easier to read). ProductID's are showing more than once even though they should be grouped together and counted.
times_sold | ProductID | title
---------- | --------- | ---------
1 | 17998 | title 2
1 | 13670 | title 3
1 | 17956 | title 4
1 | 4569 | title 5
1 | 12598 | title 1
1 | 12598 | title 1
1 | 17998 | title 2
And here's the query I'm running:
SELECT TOP (100) PERCENT
COUNT(s.ProductID) AS times_sold,
s.ProductID, p.title, p.brandname, p.date_added
FROM
dbo.TBL_OrderSummary AS s
INNER JOIN
dbo.jewelry AS p ON s.ProductID = p.ProductID
INNER JOIN
dbo.sent_items AS i ON s.InvoiceID = i.ID
GROUP BY
s.ProductID, p.title, p.brandname, p.flare_type, p.date_added,
i.date_order_placed, i.ship_code, p.jewelry
HAVING
(p.title LIKE '%stone%')
AND (i.date_order_placed > CONVERT(DATETIME, '2016-01-01 00:00:00', 102))
AND (i.ship_code = N'paid')
AND (p.flare_type = 'Single flare')
AND (p.jewelry LIKE '%plugs%')
Thanks for any help!

The reason why they aren't looking right is because the records aren't the same all the way across in the row. If you have a product name Widget 2 and year made is 2015 and you have another one product name widget and year made 2016 it is only going to count a 1 next to each product because the whole row only appears one time. You will need to limit your group by to get an accurate count.
GROUP BY s.productID, p.title, COUNT(s.productID)
This should give you an accurate count. You are just limiting your group by to a too large of sample to get any unique records. You will have to cut down what is in your select for this to work you need to have s.Product and p.title in your select to match the group by. Hope this helps.

Unless you are filtering by your aggregate function (ie. HAVING COUNT(s.ProductID) > 2) then you could move all of your selection criteria to the WHERE line.
So you could try:
select count(s.ProductID) times_sold, s.ProductID, p.title
from dbo.TBL_OrderSummary s inner join dbo.jewelry p on s.ProductID = p.ProductID
inner join dbo.sent_items i on s.InvoiceID = i.ID
where p.title like '%stone%'
and i.date_order_placed > CONVERT(DATETIME, '2016-01-01 00:00:00', 102)
and i.ship_code = N'paid'
and p.flare_type = 'Single flare'
and p.jewelry like '%plugs%'
group by s.ProductID, p.title

Related

SQL - Return first non-empty value for previous days

I'm currently working with an exchange rates table in SQL that has these fields:
| Country | ExchangeRateDt | ExchangeRateValue |
| DK | 202000601 | 0.2 |
| DK | 202000603 | 0.21 |
| HR | 202000601 | 0.10 |
| HR | 202000602 | 0.12 |
For each currency I don't have a value for any day of the year because of bank holidays or simply weekends.
I need to join it with an order table where some orders are placed on weekends and on a specific day I could not have an exchange rate to calculate taxes.
I need to take the first non missing value from the previous days (so in the examples should I have an order for day 2020-06-02 in Denmark I should exchange it using the rate 0.2)
I thought about using a calendar table but I can't manage to get the job done.
Can someone help me?
Thanks in advance,
R
To get the most recent value less than or equal to the current day:
SELECT
<whatever columns you need from order>
,exchange.ExchangeRateValue
FROM
<order table> order
LEFT JOIN
<exchange rate table> exchange
ON exchange.Country = order.Country
AND exchange.ExchangeRateDt =
(
SELECT
MAX(ExchangeRateDt)
FROM
<exchange rate table>
WHERE
Country = order.Country
AND ExchangeRateDt <= order.OrderDt
)
Ensure the clustered index on the exchange rate table is (Country, ExchangeRateDt).
I have this as a left join so you will still return order results if the currency information is somehow missing. You would have to refer to business rules on how to proceed if no exchange rate was available.
You would typically create a calendar table that stores all the days you are interested in, say dates, with each date on a separate row.
You would also probably have a table that lists the countries: I assumed countries.
Then, one option is a lateral join:
select c.country, d.date, t.ExchangeRateValue
from dates d
cross join countries c
outer apply (
select top (1) t.*
from mytable t
where t.country = c.country and t.ExchangeRateDt <= d.date
order by t.ExchangeRateDt desc limit 1
) t
If you don't have these two tables, or can't create them, then one option is a recursive query to generate the dates and a subquery to list the countries. For example, this would generate the data for the month of June:
with dates as (
select '20200601' date
union all
select dateadd(day, 1, date) from dates where date < '20200701'
)
select c.country, d.date, t.ExchangeRateValue
from dates d
cross join (select distinct country from mytable) c
outer apply (
select top (1) t.*
from mytable t
where t.country = c.country and t.ExchangeRateDt <= d.date
order by t.ExchangeRateDt desc limit 1
) t
You should be able to do the mapping between the transation date and the exchange rate date with this query:
select TAB.primary_key, TAB.TransationDate, max(EXR.ExchangeRateDt)
from yourtable TAB
inner join exchangerate EXR
on TAB.Country = EXR.Country and TAB.TransationDate >= EXR.ExchangeRateDt
group by TAB.primary_key, TAB.TransationDate

Select rows where a value is maximum, and a column is null

I have a table, products, that looks along these lines:
productID | version | done
1 | 1 | 2000-01-01
1 | 2 | NULL
2 | 1 | NULL
2 | 2 | 2000-01-01
Version is assumed to be increasing.
What I want is a query that returns a ProductID and its highest / current Version, if the Done column for that version is NULL. In plain English, I want all products where the latest version is not Done, and the corresponding version. The goal: among products, find the ones with a new version that have not been "done" / processed yet.
Note: in the example above, I would expect the query to return ProductID 1, Version 2 only. I do not want the highest not-done version of a product, I want the highest version of a product, if it is not-done. Sorry if the clarification is overkill.
I wrote a query which appears to do what I want:
SELECT productID ProductID, version Version
FROM products
WHERE done IS NULL
AND version IN (
SELECT MAX(version)
FROM products
GROUP BY productID
)
However, it also appears to not be very efficient. So my question is, is there a better way to approach this query?
We can try using ROW_NUMBER here:
WITH cte AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY productID ORDER BY version DESC) rn
FROM products
)
SELECT productID, version
FROM cte
WHERE rn = 1 AND done IS NULL;
Demo
The CTE above assigns a row number, starting with 1, to latest record for each product, according to version. Then, we subquery and retain only product records where the latest one happens to not have a value assigned to the done column.
Seems you are almost correct with your query, what's missing is the correlation between the productID of your subquery and your main table.
SELECT t.productID ProductID, t.version Version
FROM products t
WHERE t.done IS NULL
AND version IN (
SELECT MAX(p.version)
FROM products p
WHERE p.productID = t.productID
GROUP BY p.productID
)
Another solution is to use join
select t1.* from products t1
inner join
(select max(version) as versionId, productID
from products
group by productID) t2 on t2.productID = t1.productID and t2.versionId = t1.version
where coalesce(done, '') = ''

Finding invoices without matching credits

The simplified table looks like that:
BillID|ProductID|CustomerID|Price|TypeID
------+---------+----------+-----+-------
111111|Product1 |Customer1 | 100| I
111112|Product1 |Customer1 | -100| C
111113|Product1 |Customer1 | 100| I
111114|Product1 |Customer1 | -100| C
111115|Product1 |Customer1 | 100| I
I need to find invoices (I) that have their matching credits (C) but not "odd" invoices without matching credits (the last record) - or the other way around (unmatched invoices without corresponding credits).
So far I've got this:
SELECT Invoices.billid, Credits.billid
FROM
(SELECT B1.billid
FROM billing B1
WHERE B1.typeid='I') Invoices
INNER JOIN
(SELECT B2.billid
FROM billing B2
WHERE B2.typeid='C') Credits
ON Invoices.customerid = Credits.customerid
AND Invoices.productid = Credits.productid
AND Invoices.price = -(Credits.price)
But it obviously doesn't work, as it returns something looking like:
billid | billid2
-------+ -------
111111 | 111112
111113 | 111114
111115 | 111114
What I would like to get is a list of unmatched invoices;
billid |
-------+
111115 |
Or alternatively only the matching invoices;
billid | billid2
-------+ -------
111111 | 111112
111113 | 111114
The invoice numbers (BillID) will not necessarily be consecutive of course, it's just a simplified view.
Any help would be appreciated.
This should work. I tested by adding a few consecutive invoices before a credit. The query below shows all invoices with matching credit and shows NULL for the aliased "bar" part of the query if a match doesn't exist.
SELECT * FROM (
SELECT
ROW_NUMBER() OVER(Partition By TypeID, CustomerID, ProductID, Price ORDER BY BillID ASC) AS rownumber,
*
FROM Billing
) AS foo
LEFT JOIN
(SELECT
ROW_NUMBER() OVER(Partition By TypeID, CustomerID, ProductID, Price ORDER BY BillID ASC) AS rownumber,
*
FROM Billing
) AS bar
on foo.CustomerID = bar.CustomerID and
foo.ProductID = bar.ProductID and
foo.rownumber = bar.rownumber and
foo.Price = -1*bar.Price
where foo.Price > 1
Here's the updated data that I used:
And Here are what my results looked like:
I wrote this a long time ago so there may be better ways to solve it now. Also I've attempted to adapt it to your table structure, so apologies if its not 100% there. I also assume that your BillID is sequential in date order i.e. larger numbers were entered later. I've also assumed that invoices are always positive and credit notes always negative - so I don't bother checking the type.
Essentially the query filters out any matched items.
Anyway here goes:
select *
from billing X
/* If we are inside the number of unmatched entries then show it. e.g. if there are 3 unmatched entries, and we are in the top 3 then display */
where (
/* Number of later entries relating that match this account entry e.g. Price/Product/Customer */
select count(*)
from billing Z
where Z.Customer = X.Customer and Z.ProductID = X.ProductID
and Z.Price = X.Price
and Z.BillID >= X.BillId
) <=
(
/* Number of unmatched entries for this Price/Product/Customer there are, and whether they are negative or positive. */
select abs(Y.Number)
from (
-- Works out how many unmatched billing entries for this Price/Product/Customer there are, and whether they are negative or positive
select ProductID, CustomerID, abs(Price) Price, sum(case when Price < 0 then -1 else +1 end) Number
from billing
group by ProductID, CustomerID, abs(Price)
having sum(Price) <> 0
) as Y
where X.ProductID = Y.ProductID
and X.CustomerID = Y.CustomerID
and X.Price = case when Y.Number < 0 then -1*Y.Amount else Y.Amount end
)
The odd/even thing concerns me a bit. But assuming this is an incremental key and your business logic is in place, try including this logic in the WHERE clause, the JOIN PREDICATE, or implementing a Lead/Lag function.
SELECT DISTINCT
Invoices.billid
,Credits.billid
FROM
(SELECT B1.billid
FROM billing B1
WHERE B1.typeid='I') Invoices
INNER JOIN (SELECT B2.billid
FROM billing B2
WHERE B2.typeid='C') Credits
ON Invoices.customerid = Credits.customerid
AND Invoices.productid = Credits.productid
AND Invoices.price = -(Credits.price)
AND (Invoices.Billid + 1) = Credits.Billid
Note: This is using your INNER JOIN, so we will get the cases where the invoices have a corresponding credit. You could also do a FULL OUTER JOIN instead, then include a WHERE CLAUSE that specifies WHERE Invoices.Billid IS NULL OR Credits.Billid IS NULL. That scenario would give you the trailing case where you don't have a match.

Group by rows with arrays with different values but one same value

I have an issue, we have trac postgresql db (version 8.4) and we need to get the worklog based on the tags(keywords) in tickets and group time spent on these tickets.
This is my query:
select round(sum(wl.endtime-wl.starttime)/3600.0, 2) AS sum_of_hours,
string_to_array(t.keywords, ',') AS keywords
from work_log AS wl
JOIN ticket AS t ON t.id = wl.ticket
where t.keywords SIMILAR TO '%SWA.IMPLEMENTATION%'
GROUP BY t.keywords
HAVING string_to_array(t.keywords, ',') #> ARRAY['SWA.IMPLEMENTATION'];
The output is:
sum_of_hours | keywords
--------------+------------------------------
950.08 | {Running,SWA.IMPLEMENTATION}
11.00 | {SWA.IMPLEMENTATION,Done}
341.63 | {SWA.IMPLEMENTATION}
49.25 | {SWA.IMPLEMENTATION,Running}
(4 rows)
My goal is to group all hours where "SWA.IMPLEMENTATION" is presented. So all those 4 lines should be group together.

Count each unique content

How do I count how many times the content of a field nameappears in my table?
Name | Other
Brad | smth
Brad | smth
Daniel | smth
Matt | smth
Matt | smth
Matt | smth
For example,for the above table I would like to know how many times I have 'Brad',how many times 'Daniel' and how many times 'Matt'.How do I do this with just one select?
I'm interested in this because I want do display only the Names that appear more times than a given value.
My actual code:
select director.LastName,director.FirstName,count(director.FirstName)as counter,film.title
from director,film
where film.Id_Director=director.id
group by director.LastName,director.FirstName,film.title
having count(Director.FirstName)>2
Baz Luhrmann 1 Paranormal activity 4
Baz Luhrmann 1 Struck by lightning
Baz Luhrmann 1 The big bang theory
Baz Luhrmann 1 The family
Baz Luhrmann 1 The Quarterback
Brad Falchuk 1 A Kitty or a Gaga
Brad Falchuk 1 All or nothing
Brad Falchuk 1 Bridesmaids
Brian Dan 1 All or nothing
I was expecting it to count exactly how many times 'Baz' appears in the table(this should be done for every name) and display only if the value of count > the 3 for example.
Group by the name and use a count()
select name, count(*) as name_count
from your_table
group by name
Aggregate functions like count are applied for each group.
To display only names that appear more than 1 time you can do
select name, count(*) as name_count
from your_table
group by name
having count(*) > 1
Having is like a where clause but for groups.
Edit
select d.LastName, d.FirstName, count(f.Id_Director) as counter
from director d
inner join film f on f.Id_Director = d.id
group by d.LastName, d.FirstName
having count(f.Id_Director) > 2
You had grouped by the film too. That won't work. You basically queried for directors that are more than 2 times part of a film.
The problem is you are grouping by film. Since there are a director/film is ill away count as 1.
You you want to keep the film names in that select result set I suggest you make a select movies and a subquery to count how many times that director can be joined to other movies.
Just writed a example at SQLFiddle
Example

Resources