SQL - Query same column twice with different dates in where clause - mysql

I have tried searching all over for answers but none have answered my exact issue. I have what should be a relatively simple query. However, I am very new and still learning SQL.
I need to query two columns with different dates. I want to return rows with the current number of accounts and current outstanding balance and in the same query, return rows for the same columns with data 90 days prior. This way, we can see how much the number of accounts and balance increased over the past 90 days. Optimally, I am looking for results like this:
PropCode|PropCat|Accts|AcctBal|PriorAccts|PriorBal|
----------------------------------------------------
77 |Comm | 350 | 1,000| 275 | 750
Below is my starting query. I realize it's completely wrong but I have tried numerous different solution attempts but none seem to work for my specific problem. I included it to give an idea of my needs. The accts & AcctBal columns would contain the 1/31/14 data. The PriorAcct & PriorBal columns would contain the 10/31/13 data.
select
prop_code AS PropCode,
prop_cat,
COUNT(act_num) Accts,
SUM(act_bal) AcctBal,
(SELECT
COUNT(act_num)
FROM table1
where date = '10/31/13'
and Pro_Group in ('BB','FF')
and prop_cat not in ('retail', 'personal')
and Not (Acct_Code = 53 and ACTType in (1,2,3,4,5,6,7))
)
AS PriorAccts,
(SELECT
SUM(act_bal)
FROM table1
where date = '10/31/13'
and Pro_Group in ('BB','FF')
and prop_cat not in ('retail', 'personal')
and Not (Acct_Code = 53 and ACTType in (1,2,3,4,5,6,7))
)
AS PriorBal
from table1
where date = '01/31/14'
and Pro_Group in ('BB','FF')
and prop_cat not in ('retail', 'personal')
and Not (Acct_Code = 53 and ACTType in (1,2,3,4,5,6,7))
group by prop_code, prop_cat
order by prop_cat

You can use a CASE with aggregates for this (at least in SQL Server, not sure about MySQL):
...
COUNT(CASE WHEN date='1/31/14' THEN act_num ELSE NULL END) as 'Accts'
,SUM(CASE WHEN date='1/31/14' THEN act_bal ELSE NULL END) as 'AcctBal'
,COUNT(CASE WHEN date='10/31/13' THEN act_num ELSE NULL END) as 'PriorAccts'
,SUM(CASE WHEN date='10/31/13' THEN act_bal ELSE NULL END) as 'PriorAcctBal'
....
WHERE Date IN ('1/31/14', '10/31/13')

Related

How do I fix this SQL query (MariaDB) to produce the correct output?

I have been trying to extract some data from a database and produce the correct output. The following is what I am looking for
id | ref_no | Order time | discount code | total | items | donations
This is my SQL query so far:
SELECT
order.id,
order.ref_no,
order.cdate AS "Order time",
order.promocode AS "discount code",
order.orig_total as "total",
GROUP_CONCAT(order_item_ref.item_id ORDER BY order_item_ref.item_id) AS "items",
(CASE WHEN order_item_ref.item_id = "99"
THEN order_item_ref.quantity ELSE "0" END) AS "donations"
FROM `order`
INNER JOIN `order_item_ref`
ON order.id = order_item_ref.order_id
WHERE order.deleted = "0"
GROUP BY order.id;
Currently it doesn't quite work. The donation column is supposed to show 0 when an order does not contain an item with the item number 99 or the actual amount (order_item_ref.quantity) if it does. However the query only works sometimes to produce something like the following:
If I drop by the GROUP BY and GROUP CAT parts the query works as intended, however I need to keep the grouping intact.
How can I fix the query so the output is correct?
Edit: the output for line 48 is correct. The output for line 49 is not. There should be a non-zero number for the right hand column which should come from order_item_ref.quantity as per the SQL query above.
Since the question has been reopened I can post my own answer. After the explanation made by KIKO Software, I was looking for a way to aggregate (CASE WHEN order_item_ref.item_id = "99" THEN order_item_ref.quantity ELSE "0" END) AS "donations" and found that I was able to do it by simply summing over the output like so:
SUM((CASE WHEN order_item_ref.item_id = "99" THEN order_item_ref.quantity ELSE "0" END)) AS "donations"

Selecting rows until a column value isn't the same

SELECT product.productID
, product.Name
, product.date
, product.status
FROM product
INNER JOIN shelf ON product.sheldID=shelf.shelfID
WHERE product.weekID = $ID
AND product.date < '$day'
OR (product.date = '$day' AND shelf.expire <= '$time' )
ORDER BY concat(product.date,shelf.expire)
I am trying to stop the SQL statement at a specific value e.g. bad.
I have tried using max-date, but am finding it hard as am making the time stamp in the query. (Combining date/time)
This example table shows that 3 results should be returned and if the status "bad" was the first result than no results should be returned. (They are ordered by date and time).
ProductID Date status
1 2017-03-27 Good
2 2017-03-27 Good
3 2017-03-26 Good
4 2017-03-25 Bad
5 2017-03-25 Good
Think I may have fixed it, I added this to my while loop.
The query gives the results in order by present to past using date and time, this while loop checks if the column of that row is equal to 'bad' if it is does something (might be able to use an array to fill it up with data). If not than the loop is broken.
I know it doesn't seem ideal but it works lol
while ($row = mysqli_fetch_assoc($result)) {
if ($row['status'] == "bad") {
$counter += 1;
}
else{
break;}
I will provide an answer just with your output as if it was just one table. It will give you the main ideia in how to solve your problem.
Basically I created a column called ord that will work as a row_number (MySql doesn't support it yet AFAIK). Then I got the minimum ord value for a bad status then I get everything from the data where ord is less than that.
select y.*
from (select ProductID, dt, status, #rw:=#rw+1 ord
from product, (select #rw:=0) a
order by dt desc) y
where y.ord < (select min(ord) ord
from (select ProductID, status, #rin:=#rin+1 ord
from product, (select #rin:=0) a
order by dt desc) x
where status = 'Bad');
Result will be:
ProductID dt status ord
-------------------------------------
1 2017-03-27 Good 1
2 2017-03-27 Good 2
3 2017-03-26 Good 3
Also tested with the use case where the Bad status is the first result, no results will be returned.
See it working here: http://sqlfiddle.com/#!9/28dda/1

Searching large (6 million) rows MySQL with stored queries?

I have a database with roughly 6 million entries - and will grow - where I'm running queries to return for a HighCharts charting functionality. I need to read longitudinally over years, so I'm running queries like this:
foreach($states as $state_id) { //php code
SELECT //mysql psuedocode
sum(case when mydatabase.Year = '2003' then 1 else 0 end) Year_2003,
sum(case when mydatabase.Year = '2004' then 1 else 0 end) Year_2004,
sum(case when mydatabase.Year = '2005' then 1 else 0 end) Year_2005,
sum(case when mydatabase.Year = '2006' then 1 else 0 end) Year_2006,
sum(case when mydatabase.Year = '2007' then 1 else 0 end) Year_2007,
sum(case when mydatabase.Year = '$more_years' then 1 else 0 end) Year_$whatever_year,
FROM mytable
WHERE State='$state_id'
AND Sex IN (0,1)
AND Age_segment IN (5,4,3,2,1)
AND "other_filters IN (etc, etc, etc)
} //end php code
But for various state at once... So returning lets say 5 states, each with the above statement but a state ID is substituted. Meanwhile the years can be any number of years, the Sex (male/female/other) and Age segment and other modifiers keep changing based on filters. The queries are long (at minimum 30-40seconds) a piece. So a thought I had - unless I'm totally doing it wrong - is to actually store the above query in a second table with the results, and first check that "meta query" and see if it was "cached" and then return the results without reading the db (which won't be updated very often).
Is this a good method or are there potential problems I'm not seeing?
EDIT: changed to table, not db (duh).
Table structure is:
id | Year | Sex | Age_segment | Another_filter | Etc
Nothing more complicated than that and no joining anything else. There are keys on id, Year, Sex, and Age_segment right now.
Proper indexing is what is needed to speed up the query. Start by doing an "EXPLAIN" on the query and post the results here.
I would suggest the following to start off. This way avoids the for loop and returns the data in 1 query. Not knowing the number of rows and cardinality of each column I suggest a composite index on State and Year.
SELECT mytable.State,mytable.Year,count(*)
FROM mytable
AND Sex IN (0,1)
AND Age_segment IN (5,4,3,2,1)
AND "other_filters IN (etc, etc, etc)
GROUP BY mytable.State,mytable.Year
The above query can be further optimised by checking the cardinality of some of the columns. Run the following to get the cardinality:
SELECT Age_segment FROM mytable GROUP BY Age_segment;
Pseudo code...
SELECT Year
, COUNT(*) total
FROM my_its_not_a_database_its_a_table
WHERE State = $state_id
AND Sex IN (0,1)
AND Age_segment IN (5,4,3,2,1)
GROUP
BY Year;

How to make a select that returns 4 totals from same table but with different filters

I'm trying to make a report in SSRS where I show some totals from the same table. I know I can use selects into select, but I've heard that could affect the performance and make it slow. That is why I decided to use store procedures but I'm not so familiar with it (I only did some basic SP) so some help will be apreciated:
This is what I need to get:
|--------------|------------------------- TOTALS AND PERCENTAGES ----------------------|
|COMPANY | PACKAGES | WEIGHT | PACKAGE_DELIVERED |% DELIVERED | ONTIME |% ONTIME |
These are the querys I did in a previous version of the report (using asp):
SELECT COMPANY_NAME, COUNT(ID) AS PACKAGES, SUM(WEIGHT) AS WEIGHT
FROM PACKAGE
WHERE ACTUAL_DELIVERY_DATE BETWEEN 'X' AND 'Y'
GROUP BY COMPANY_CODE, COMPANY_NAME
Then I put the results in arrays and then make a new select to get the rest of information adding the COMPANY as filter:
SELECT COMPANY_CODE, ESTIMATED_DELIVERY_DATE, ACTUAL_DELIVERY_DATE
FROM PACKAGE
WHERE ACTUAL_DELIVERY_DATE BETWEEN 'X' AND 'Y'
AND STATUS = 'DELIVERED'
AND COMPANY_CODE = 'DHL'
ORDER BY STATUS
For every row
PACKAGES_DELIVERED = + 1
IF ACTUAL_DELIVERY_DATE < ESTIMATED_DELIVERY_DATE THEN ONTIME = + 1
Next
Then I calculate the percentages and show all together in a table.
Somebody that can help me to put all this in a Store Procedure or maybe have another idea.
Thanks in advance.
I would add the following columns to the original SELECT, using SUM on a CASE statement:
, SUM ( CASE WHEN STATUS = 'DELIVERED' THEN 1 ELSE 0 END ) AS PACKAGES_DELIVERED
, SUM ( CASE WHEN STATUS = 'DELIVERED' AND ACTUAL_DELIVERY_DATE < ESTIMATED_DELIVERY_DATE THEN 1 ELSE 0 END ) AS ONTIME
This doesnt seem complex enough to bother with a Stored Procedure.

mysql - performing addition, subtraction etc on two rows

I have a following Mysql table storing meter readings of different power stations.
Date, station_name, reading
2013-05-06, ABC, 102
2013-05-06, PQR, 122
I want a SQL query with following result for a particular date.
Date, ABC, PQR, ABC-PQR
2013-05-06,102,122,-20
You could use CASE statements:
SELECT Date
, SUM(CASE WHEN station_name = 'ABC' THEN reading ELSE 0 END) as ABC
, SUM(CASE WHEN station_name = 'PQR' THEN reading ELSE 0 END) as PQR
, SUM(CASE WHEN station_name = 'ABC' THEN reading ELSE 0 END) - SUM(CASE WHEN station_name = 'PQR' THEN reading ELSE 0 END) as 'ABC-PQR'
FROM table
WHERE Date = '20130506'
GROUP BY Date
You can search for MySQL PIVOT to find out other methods people use.
I believe that it is not possible to do dynamic column based on value of row. I believe you should do it in application-layer rather than database-layer.
See this post: mysql select dynamic row values as column names, another column as value.