I would like to find out product purchases for 2 financial years (FY16-17 & FY17-18).
To go about it:
OwnerID: 101, the first purchase is in 2014 with 3 purchases in FY17-18.
OwnerID: 102, the first purchase is in 2011 with 1 purchase in FY16-17, 1 purchase in FY17-18.
OwnerID: 103, the first purchase is in 2017 however should not be considered as he's a new customer with only 1 purchase in FY17-18. (i.e. first purchase not considered if new customer)
OwnerID: 104, the first purchase is in 2016 but made 3 more purchases in FY16-17.
Code:
CREATE TABLE Test
(
OwnerID INT,
ProductID VARCHAR(255),
PurchaseDate DATE
);
INSERT INTO Test (OwnerID, ProductID, PurchaseDate)
VALUES (101, 'P2', '2014-04-03'), (101, 'P9', '2017-08-09'),
(101, 'P11', '2017-10-05'), (101, 'P12', '2018-01-15'),
(102, 'P1', '2011-06-02'), (102, 'P3', '2016-06-03'),
(102, 'P10', '2017-09-01'),
(103, 'P8', '2017-06-23'),
(104, 'P4', '2016-12-17'), (104, 'P5', '2016-12-18'),
(104, 'P6', '2016-12-19'), (104, 'P7', '2016-12-20');
Desired output:
FY16-17 FY17-18
-----------------
5 4
I tried the below query to fetch records that aren't first occurrence and there by fetching the count within financial years:
SELECT *
FROM
(SELECT
ROW_NUMBER() OVER(PARTITION BY OwnerID ORDER BY PurchaseDate) AS OCCURANCE
FROM Test
GROUP BY OwnerID, PurchaseDate)
WHERE
OCCURANCE <> 1
However it throws an error:
Msg 102, Level 15, State 1, Line 5
Incorrect syntax near ')'.
The subquery needs to have an alias - try this:
SELECT *
FROM
(SELECT
ROW_NUMBER() OVER(PARTITION BY OwnerID ORDER BY PurchaseDate) AS OCCURRENCE
FROM Test
GROUP BY OwnerID, PurchaseDate) subQry
WHERE
subQry.OCCURRENCE <> 1
I am using IIF to separate the two fiscal years and subquery to filter out those with only one purchase
SELECT SUM(IIF(PurchaseDate >= '2016-04-01' AND PurchaseDate < '2017-04-01',1,0)) AS 'FY16-17',
SUM(IIF(PurchaseDate >= '2017-04-01' AND PurchaseDate < '2018-04-01',1,0)) AS 'FY17-18'
FROM test t1
JOIN (SELECT ownerID, COUNT(*) count
FROM test
GROUP BY ownerID) t2 on t1.ownerID = t2.ownerID
WHERE t2.count > 1
Related
Trying to identify a list of customers who's quantity decreases from their previous purchase.
In this example we see that with each new purchase Mary's quantity decreases over time. However, while Bob shows a decline, he would not yield in the results because on 9/19 he purchased 8 quantities which is greater than his previous purchase of 5.
I'm trying to figure out a query for this for the life of me I can't seem to get it together
Customer PurchaseDate Quantity
Bob 9/1/2021 10
Bob 9/10/2021 6
Bob 9/18/2021 5
Bob 9/19/2021 8
Mary 9/1/2021 10
Mary 9/10/2021 6
Mary 9/18/2021 5
Mary 9/19/2021 3
Frank 9/1/2021 5
Lucus 9/1/2021 5
Lucus 9/10/2021 6
Lucus 9/18/2021 10
End results should be
Customer
Mary
This is a bit tricky, and to find results that are steadily increasing or decreasing you would probably want to use the MATCH_RECOGNIZE clause, which MySQL doesn't (yet) support. This way you can define a pattern whereby each qty is less than than the previous value. Additionally, you could probably do this with a recursive cte, but that would be outside of my abilities.
Here is what I came up with, with the caveat that it only compares the first and last values:
WITH
tbl (customer, purchasedate, quantity) AS (
SELECT * FROM VALUES
('Bob', '9/1/2021', 10),
('Bob', '9/10/2021', 6),
('Bob', '9/18/2021', 5),
('Bob', '9/19/2021', 8),
('Mary', '9/1/2021', 10),
('Mary', '9/10/2021', 6),
('Mary', '9/18/2021', 5),
('Mary', '9/19/2021', 3),
('Frank', '9/1/2021', 5),
('Lucus', '9/1/2021', 5),
('Lucus', '9/10/2021', 6),
('Lucus', '9/18/2021', 10)
)
SELECT
DISTINCT customer
FROM
tbl
QUALIFY
FIRST_VALUE(quantity) OVER (partition BY customer ORDER BY purchasedate)
> LAST_VALUE(quantity) OVER (PARTITION BY customer ORDER BY purchasedate)
Which gives:
CUSTOMER
Bob
Mary
Or, to get strictly decreasing with a known max, you can chain them all together which gets pretty ugly:
WITH
tbl (customer, purchasedate, quantity) AS (
SELECT * FROM VALUES
('Bob', '9/1/2021', 10),
('Bob', '9/10/2021', 6),
('Bob', '9/18/2021', 5),
('Bob', '9/19/2021', 8),
('Mary', '9/1/2021', 10),
('Mary', '9/10/2021', 6),
('Mary', '9/18/2021', 5),
('Mary', '9/19/2021', 3),
('Frank', '9/1/2021', 5),
('Lucus', '9/1/2021', 5),
('Lucus', '9/10/2021', 6),
('Lucus', '9/18/2021', 10)
)
SELECT
DISTINCT customer
FROM
tbl
qualify
(NTH_VALUE(quantity, 1) OVER (partition BY customer ORDER BY purchasedate) >= NTH_VALUE(quantity, 2) OVER (partition BY customer ORDER BY purchasedate))
and ((NTH_VALUE(quantity, 2) OVER (partition BY customer ORDER BY purchasedate) >= NTH_VALUE(quantity, 3) OVER (partition BY customer ORDER BY purchasedate)) or (NTH_VALUE(quantity, 3) OVER (partition BY customer ORDER BY purchasedate) is null))
and ((NTH_VALUE(quantity,3) OVER (partition BY customer ORDER BY purchasedate) >= NTH_VALUE(quantity, 4) OVER (partition BY customer ORDER BY purchasedate)) or (NTH_VALUE(quantity, 4) OVER (partition BY customer ORDER BY purchasedate) is null))
Which gives:
CUSTOMER
Mary
Though for an unknown amount I would think match_recognize would be the best solution (or you could add in some recursion or a custom function).
SELECT Customer
FROM ( SELECT CASE WHEN Customer = #customer AND Quantity > #quantity
THEN 1
ELSE 0
END AS increase_detected,
#customer := Customer Customer,
PurchaseDate,
#quantity := Quantity Quantity
FROM test
CROSS JOIN ( SELECT #customer := NULL, #quantity := NULL ) init_variables
ORDER BY Customer, PurchaseDate
) subquery
GROUP BY Customer
HAVING NOT SUM(increase_detected);
https://dbfiddle.uk/?rdbms=mysql_5.6&fiddle=68b75b0df7fe4b383896e78db0caa569
I have a pricing table as follows,
Pricing Table
id productId ContractId ageGroup ageFrom ageTo sellingPrice specialPrice
1 1 1 1 0 2 0 0
2 1 1 1 3 13 20 0
3 1 1 2 18 55 80 0
4 1 1 3 56 119 60 0
5 1 1 1 0 2 0 0
6 1 2 2 18 55 85 0
7 2 2 3 55 119 90 0
8 2 2 2 18 55 90 0
I need to find the list of Contract Ids and Ids for given age Group (1-adult or 2-child or 3-senior). For the children the age range (from - to) need to be considered as well.
The following query (1 adult, 2 children with the ages 2 & 4 and 1 senior) seems to be working but returns only the ids matching the age group 1.
SELECT contractId,id
FROM tbl_contract_price cp1
WHERE contractId IN
(SELECT contractId FROM tbl_contract_price cp2
WHERE contractId IN
(SELECT contractId FROM tbl_contract_price cp3
WHERE cp1.ageGroup = 1 AND (cp2.ageGroup = 2 AND cp2.ageFrom <= 2 AND 2 <= cp2.ageTo OR cp2.ageGroup = 2 AND cp2.ageFrom <= 4 AND 4 <= cp2.ageTo ) AND cp3.ageGroup = 3))
Is there anything I am missing?
Based on some assumptions, I have created the following to help you get started. Please note that you will need to enforce your data integrity (i.e., ensuring that for each product, all possible ages are covered by a price, etc.)
I suggest that you use a temporary quote table so that you can have more flexibility on the number of inputs. You can see the data example below. Or, better yet, handle that logic within your Business Logic Layer.
You will need to apply any tie-breaker logic if two contracts yield the same price, etc.
CREATE TABLE Pricing (
ID int not null,
productId int not null,
ContractId int not null,
ageGroup int not null,
ageFrom int not null,
ageTo int not null,
sellingPrice int not null,
PRIMARY KEY (ID)
);
INSERT INTO Pricing (ID, productId, ContractId, ageGroup, ageFrom, ageTo, sellingPrice) Values (1, 1, 1, 1, 0, 2, 0);
INSERT INTO Pricing (id, productId, ContractId, ageGroup, ageFrom, ageTo, sellingPrice) Values (2, 1, 1, 1, 3, 13, 20);
INSERT INTO Pricing (id, productId, ContractId, ageGroup, ageFrom, ageTo, sellingPrice) Values (3, 1, 1, 2, 18, 55, 80);
INSERT INTO Pricing (id, productId, ContractId, ageGroup, ageFrom, ageTo, sellingPrice) Values (4, 1, 1, 3, 56, 119, 60);
INSERT INTO Pricing (id, productId, ContractId, ageGroup, ageFrom, ageTo, sellingPrice) Values (5, 1, 2, 1, 3, 13, 0);
INSERT INTO Pricing (id, productId, ContractId, ageGroup, ageFrom, ageTo, sellingPrice) Values (6, 1, 2, 2, 18, 55, 85);
INSERT INTO Pricing (id, productId, ContractId, ageGroup, ageFrom, ageTo, sellingPrice) Values (7, 2, 2, 3, 55, 119, 90);
INSERT INTO Pricing (id, productId, ContractId, ageGroup, ageFrom, ageTo, sellingPrice) Values (8, 2, 2, 2, 18, 55, 90);
CREATE TABLE ValidDates (
ID int not null,
priceId int not null,
fromDate date not null,
toDate date not null,
PRIMARY KEY (ID)
);
INSERT INTO ValidDates (id, priceId, fromDate, toDate) VALUES (1, 1, '2018-06-01', '2018-06-30');
INSERT INTO ValidDates (id, priceId, fromDate, toDate) VALUES (2, 2, '2018-06-01', '2018-06-30');
INSERT INTO ValidDates (id, priceId, fromDate, toDate) VALUES (3, 2, '2018-07-01', '2018-07-31');
INSERT INTO ValidDates (id, priceId, fromDate, toDate) VALUES (4, 3, '2018-06-01', '2018-06-30');
INSERT INTO ValidDates (id, priceId, fromDate, toDate) VALUES (5, 3, '2018-07-01', '2018-07-31');
INSERT INTO ValidDates (id, priceId, fromDate, toDate) VALUES (6, 4, '2018-06-01', '2018-06-30');
INSERT INTO ValidDates (id, priceId, fromDate, toDate) VALUES (7, 5, '2018-06-01', '2018-06-30');
INSERT INTO ValidDates (id, priceId, fromDate, toDate) VALUES (8, 5, '2018-07-01', '2018-07-31');
INSERT INTO ValidDates (id, priceId, fromDate, toDate) VALUES (9, 6, '2018-06-01', '2018-06-30');
INSERT INTO ValidDates (id, priceId, fromDate, toDate) VALUES (10, 6, '2018-07-01', '2018-07-31');
CREATE TABLE Products (
ID int not null,
PRIMARY KEY (ID)
);
CREATE TABLE Quotes (
ID int not null,
age int
);
INSERT INTO Quotes (Id, age) VALUES (1, 70);
INSERT INTO Quotes (Id, age) VALUES (1, 25);
INSERT INTO Quotes (Id, age) VALUES (1, 1);
INSERT INTO Quotes (Id, age) VALUES (1, 4);
Then, you can use the following query to calculate your total price based on the product id, selected date, and your quote id (which has all the ages for the particular quote)
Scenario: tour date = Jun 22, 2018; product = 1, quote = 1 with age = 1, 4, 25, 70
SELECT #tourdate := '2018-06-22', #productid := 1, #quoteid := 1;
First query to show how the relevant information is retrieved
SELECT productid, contractId, ageGroup, ageFrom, ageTo,
SUM(CASE WHEN age BETWEEN ageFrom AND ageTo THEN 1 ELSE 0 END) AS PAXCount, sellingPrice
FROM ValidDates
LEFT JOIN Pricing
ON priceId = Pricing.ID
LEFT JOIN Products
ON productId = Products.ID
LEFT JOIN Quotes
ON Quotes.ID = #quoteid
WHERE (#tourdate BETWEEN fromDate AND toDate) AND productid = #productid
GROUP BY productid, contractid, ageGroup, ageFrom, ageTo, sellingPrice;
second query is built upon the first query, aggregating the total so that you have the total cost for ranking
SELECT contractId, SUM(sellingPrice * PAXCount) FROM (
SELECT productid, contractId, ageGroup,
SUM(CASE WHEN age BETWEEN ageFrom AND ageTo THEN 1 ELSE 0 END) AS PAXCount, sellingPrice
FROM ValidDates
LEFT JOIN Pricing
ON priceId = Pricing.ID
LEFT JOIN Products
ON productId = Products.ID
LEFT JOIN Quotes
ON Quotes.ID = #quoteid
WHERE (#tourdate BETWEEN fromDate AND toDate) AND productid = #productid
GROUP BY productid, contractid, ageGroup, sellingPrice) P
GROUP BY contractid
ORDER BY SUM(sellingPrice * PAXCount)
#LIMIT 1;
You can uncomment the #Limit 1 to get only the cheapest package, but you need to be aware of the limitation
You will need to ensure that your data integrity is enforced, i.e., for each product and date range, all possible age needs to be covered by
Note that because the child aged 0 and the senior aged 70 were not covered by contract id 2, the $85 total is misleading. You can add logic to check if a contract can fulfil all ages (if input count is 4, check if the contract does indeed include four people, etc.)
You might need to clean up the quotes tables as required. It is not the most efficient approach for sure (but it should work according to your requirements).
For example, change the query to something like this:
SELECT #PAXCount := COUNT(*) FROM Quotes WHERE id = #quoteid;
Or you can probably pass that in from your application fairly easily.
Then, check to make sure that the count matches.
SELECT contractId, SUM(sellingPrice * PAXCount) AS TotalPrice, SUM(PAXCount) AS TotalPAXCOUNT
FROM (
SELECT productid, contractId, ageGroup,
SUM(CASE WHEN age BETWEEN ageFrom AND ageTo THEN 1 ELSE 0 END) AS PAXCount, sellingPrice
FROM ValidDates
LEFT JOIN Pricing
ON priceId = Pricing.ID
LEFT JOIN Products
ON productId = Products.ID
LEFT JOIN Quotes
ON Quotes.ID = #quoteid
WHERE (#tourdate BETWEEN fromDate AND toDate) AND productid = #productid
GROUP BY productid, contractid, ageGroup, sellingPrice) P
GROUP BY contractid
HAVING #PAXCount = SUM(PAXCount)
ORDER BY SUM(sellingPrice * PAXCount)
#LIMIT 1;
This way, only contract id covering all passengers will be shown.
Try it in the DB Fiddler
I wrote a query to report credit-cards that were due to expire in the year 2016. It runs, but I do receive a warning, and I'd like to know more about why. I assume it's because of the subquery.
WARNING: Incorrect data value: '2016%' for column 'exp_date' at row 1
I only have one value that meets the requirement of 2016, but is the warning appearing because of possible future values that may meet the same condition?
SELECT customer_id as 'Customer'
FROM customers
WHERE credit_card_id = (
SELECT credit_card_id
FROM credit_cards
WHERE exp_date LIKE '2016%'
LIMIT 1
);
Credit-Card values:
INSERT INTO credit_cards VALUES
(1, '0025184796520000', '2016-08-13', 'Sarah', 'Jones', 3351, '2490 Paseo Verde parkway, suite 150', 'San Diego','CA',92124),
(2, '7896541232548526', '2017-09-21', 'Desmond', 'Lowell', 1204, '3201 Kelsey Street, suite 109', 'San Diego','CA',92174),
(3, '1234567890123456', '2018-02-11', 'Mark', 'Jefferson', 1591, '876 Silverado Street, suite 304', 'Henderson','NV',89162),
(4, '4001330852539605', '2017-01-10', 'Jaime', 'Evans', 8879, '924 Shady Pines Circle, suite 120', 'Summerlin','NV',89074);
The problem is with datatypes DATE <> TEXT and implicit conversion from '2016%' toDATE` which has to fail.
You could use (not SARGable):
SELECT customer_id as 'Customer'
FROM customers
WHERE credit_card_id = (
SELECT credit_card_id
FROM credit_cards
WHERE YEAR(exp_date) = 2016
);
Second improvement is to use JOIN:
SELECT DISTINCT c.customer_id AS `Customer`
FROM customers c
JOIN credit_cards cc
ON cc.credit_card_id = c.credit_card_id
WHERE YEAR(cc.exp_date) = 2016;
And finaly to make it SARGable you could use:
WHERE cc.exp_date >= '2016-01-01' AND cc.exp_date < '2017-01-01'
Let's say I have the follwing table:
id | fb_id | date |
---- ---------- ---------
1 1123 2009-1-1
2 1145 2009-1-1
3 1123 2009-1-2
4 1176 2009-1-2
I want to count the total users for each date, the total unique users and the returning users.
My code righte now is this one:
SELECT count(DISTINCT fb_id) as uniqueUsers, count(fb_id) as totalUsers, DATE_FORMAT(date, '%d %b %y') as zoom FROM ".PREFIX."zoom GROUP BY YEAR(date), MONTH(date), DAY(date)
I am expecting the following results:
Group 2009-1-1:
-total users: 2
-unique users: 2
-returning users:0
Group 2009-1-2:
-total users: 2
-unique users: 1
-returning users:1 (total users - unique users)
But instead I am getting:
Group 2009-1-1:
-total users: 2
-unique users: 2
-returning users:0
Group 2009-1-2:
-total users: 2
-unique users: 2
-returning users:0 (total users - unique users)
Any thoughts how I can make this work?
You can do a self join. Something like this
Sample Data
CREATE TABLE zoom
(`id` int, `fb_id` int, `date` datetime);
INSERT INTO zoom
(`id`, `fb_id`, `date`)
VALUES
(1, 1123, '2009-01-01 00:00:00'),
(2, 1145, '2009-01-01 00:00:00'),
(3, 1123, '2009-01-02 00:00:00'),
(4, 1176, '2009-01-02 00:00:00');
Query
SELECT
count(Znew.fb_id) as totalUsers,
count(Zold.fb_id) as returningUsers,
count(Znew.fb_id) - count(Zold.fb_id) as uniqueUsers,
DATE_FORMAT(Znew.date, '%d %b %y') as zoom
FROM zoom Znew
LEFT JOIN zoom Zold
ON Zold.date < Znew.date
AND Zold.fb_id = Znew.fb_id
GROUP BY Znew.date;
SQL Fiddle
Output
totalUsers returningUsers uniqueUsers zoom
2 0 2 01 Jan 09
2 1 1 02 Jan 09
That's because you were doing GROUP BY on YEAR(date),MONTH(date)etc...
Where you should do on 'DATE(date)' only
SELECT count(DISTINCT fb_id) as uniqueUsers,
count(fb_id) as totalUsers,
DATE_FORMAT(date, '%d %b %y') as zoom FROM ".PREFIX."zoom GROUP BY DATE(date)
Hope this helps
http://sqlfiddle.com/#!2/6a6b1
The scheme is given above.. all I want to do is get the results as the total of sales/month... the user will enter a start date and end date and I can generate (in PHP) all the month and years for those dates. For example, if I want to know the total number of "sales" for 12 months, I know I can run 12 individual queries with start and end dates, but I want to run only one query where the result will look like:
Month numofsale
January - 2
Feb-1
March - 23
Apr - 10
and so on...
or just a list of sales without the months, I can then pair it to the array of months generated in the PHP ...any ideas...
Edit/schema and data pasted from sqlfiddle.com:
CREATE TABLE IF NOT EXISTS `lead_activity2` (
`lead_activity_id` int(11) NOT NULL AUTO_INCREMENT,
`sp_id` int(11) NOT NULL,
`act_date` datetime NOT NULL,
`act_name` varchar(255) NOT NULL,
PRIMARY KEY (`lead_activity_id`),
KEY `act_date` (`act_date`),
KEY `act_name` (`act_name`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 ;
INSERT INTO `lead_activity2` (`lead_activity_id`, `sp_id`, `act_date`, `act_name`) VALUES
(1, 5, '2012-10-16 16:05:29', 'sale'),
(2, 5, '2012-10-16 16:05:29', 'search'),
(3, 5, '2012-10-16 16:05:29', 'sale'),
(4, 5, '2012-10-17 16:05:29', 'DNC'),
(5, 5, '2012-10-17 16:05:29', 'sale'),
(6, 5, '2012-09-16 16:05:30', 'SCB'),
(7, 5, '2012-09-16 16:05:30', 'sale'),
(8, 5, '2012-08-16 16:05:30', 'sale'),
(9, 5,'2012-08-16 16:05:30', 'sale'),
(10, 5, '2012-07-16 16:05:30', 'sale');
SELECT DATE_FORMAT(date, "%m-%Y") AS Month, SUM(numofsale)
FROM <table_name>
WHERE <where-cond>
GROUP BY DATE_FORMAT(date, "%m-%Y")
Check following in your fiddle demo it works for me (remove where clause for testing)
SELECT DATE_FORMAT(act_date, "%m-%Y") AS Month, COUNT(*)
FROM lead_activity2
WHERE <where-cond-here> AND act_name='sale'
GROUP BY DATE_FORMAT(act_date, "%m-%Y")
It returns following result
MONTH COUNT(*)
07-2012 1
08-2012 2
09-2012 1
10-2012 3
You can try query as given below
select SUM(`SP_ID`) AS `Total` , DATE_FORMAT(act_date, "%M") AS Month, Month(`ACT_DATE`) AS `Month_number` from `lead_activity2` WHERE `ACT_DATE` BETWEEN '2012-05-01' AND '2012-12-17' group by Month(`ACT_DATE`)
Here 2012-05-01 and 2012-12-17 are date input from form. and It will be return you the sum of sales for particular month if exist in database.
thanks
Try this query -
SELECT
MONTH(act_date) month, COUNT(*)
FROM
lead_activity2
WHERE
YEAR(act_date) = 2012 AND act_name = 'sale'
GROUP BY
month
Check WHERE condition if it is OK for you - act_name = 'sale'.
If you want to output month names, then use MONTHNAME() function instead of MONTH().
SELECT YEAR(act_date), MONTH(act_date), COUNT(*)
FROM lead_activity2
GROUP BY YEAR(act_date), MONTH(act_date)
For getting data by month or any other data based on column you have to add GROUP BY.
You can add many columns or calculated values to GROUP BY.
I assume that "num of sales" means count of rows.
Sometimes you might want the month names as Jan, Feb, Mar .... Dec possibly for a Chart likeFusionChart
SELECT DATE_FORMAT(date, "%M") AS Month, SUM(numofsale)
FROM <Table_name>
GROUP BY DATE_FORMAT(date, "%M")
Results would look like this on table
MONTH COUNT(*)
Jul 1
Aug 2
SEP 1
OCT 3