Show 0 Not Null With Pivot - sql-server-2008

Why is my Coalesce() statement in my initial Select statement not replacing the Null with a 0?
Select * From
(
Select a.orderstatus As [Stat], Coalesce(Count(b.id), '0') As [Count], b.region
From orderstatus a
Left Join saleinfo b
on b.orderstatus = a.orderstatus
Group By a.orderstatus, b.region
) one
pivot ( Max([Count]) For region In ([East], [West], [North], [South]) ) pv

Because you are using it in the inner query, whereas the problem is that the record doesn't it exist so the PIVOT is creating the NULL after the inner query has been processed. If your query (one) returned:
Stat Count Region
-------------------------
Stat1 0 East
Stat2 2 East
Stat1 5 West
You will end up with a Pivot Table like
Stat East West North South
---------------------------------------
Stat1 0 5 NULL NULL
Stat2 2 NULL NULL NULL
For example you get NULL for (Stat2, West) because there is no result in your subquery, so the COALESCE does not help. Your work around would be to just use COUNT in the PIVOT itself:
SELECT pvt.OrderStatus, pvt.East, pvt.West, pvt.North, pvt.South
FROM ( SELECT os.OrderStatus, si.Region, si.ID
FROM OrderStatus AS os
LEFT JOIN SaleInfo AS si
ON si.OrderStatus = b.OrderStatus
) AS t
PIVOT
( COUNT(ID)
FOR Region IN ([East], [West], [North], [South])
) AS pvt;
Or to put the COALESCE in the outer select:
SELECT pvt.OrderStatus,
East = COALESCE(pvt.East, 0),
West = COALESCE(pvt.West, 0),
North = COALESCE(pvt.North, 0),
South = COALESCE(pvt.South, 0)
FROM ( SELECT os.OrderStatus, si.Region, [Count] = COUNT(si.ID)
FROM OrderStatus AS os
LEFT JOIN SaleInfo AS si
ON si.OrderStatus = b.OrderStatus
) AS t
PIVOT
( MAX([Count])
FOR Region IN ([East], [West], [North], [South])
) AS pvt;
I much prefer the first option though.
EDIT
Example showing 0 returned for non existent data when using COUNT:
SELECT pvt.Stat, pvt.East, pvt.West, pvt.North, pvt.South
FROM (VALUES
('Stat1', 'East', 1),
('Stat2', 'East', 2),
('Stat1', 'West', 3)
) t (Stat, Region, ID)
PIVOT
( COUNT(ID)
FOR Region IN ([East], [West], [North], [South])
) AS pvt;

Related

Group_Concat with multiple joined tables

I have two main tables that comprise bookings for events.
A Registrants table (Bookings) R and an Events table E.
There are also two connected tables, Field_Values V and Event_Categories C
This diagram shows the relationship
What I am trying to do is create an Invoice query that mirrors the user's shopping cart. Often a user will book multiple events in one transaction, so my invoice should have columns for the common items e.g. User Name, User Email, Booking Date, Transaction ID and aggregated columns for the invoice line item values e.g. Quantity "1,2" Description "Desc1, Desc2" Price "10.00, 20.00" where there are two line items in the shopping cart.
The Transaction ID (dcea4_eb_registrant.transaction_id) is unique per Invoice and repeated per line item in that sale.
I have the following query which produces rows for each line item
SELECT
R.id as ID,
E.event_date as ServiceDate,
E.event_date - INTERVAL 1 DAY as DueDate,
Concat('Ad-Hoc Booking:',E.title) as ItemProductService,
Concat(R.first_name, ' ',R.last_name) as Customer,
R.first_name as FirstName,
R.last_name as LastName,
R.email,
R.register_date as InvoiceDate,
R.amount as ItemAmount,
R.comment,
R.number_registrants as ItemQuantity,
R.transaction_id as InvoiceNo,
R.published as Status,
E.event_date AS SERVICEDATE,
Concat('Ad-Hoc Booking:',E.title) AS DESCRIPTION,
R.number_registrants AS QUANTITY,
FORMAT(R.amount / R.number_registrants,2) AS RATE,
R.amount AS AMOUNT,
C.category_id as CLASS,
Concat(Group_Concat(V.field_value SEPARATOR ', '),'. ',R.comment) as Memo
FROM dcea4_eb_events E
LEFT JOIN dcea4_eb_registrants R ON R.event_id = E.id
LEFT JOIN dcea4_eb_field_values V ON V.registrant_id = R.id
LEFT JOIN dcea4_eb_event_categories C ON C.event_id = R.event_id
WHERE 1=1
AND V.field_id IN(14,26,27,15)
AND R.published <> 2 /*Including this line omits Cancelled Invoices */
AND R.published IS NOT NULL
AND (R.published = 1 OR R.payment_method = "os_offline")
AND (R.register_date >= CURDATE() - INTERVAL 14 DAY)
GROUP BY E.event_date, E.title, R.id, R.first_name, R.last_name, R.email,R.register_date, R.amount, R.comment
ORDER BY R.register_date DESC, R.transaction_id
This produces output like this
I'm using the following query to try to group together the rows with a common transaction_ID (rows two and three in the last picture) - I add group_concat on the columns I want to aggregate and change the Group By to be the transaction_id
SELECT
R.id as ID,
E.event_date as ServiceDate,
E.event_date - INTERVAL 1 DAY as DueDate,
Concat('Ad-Hoc Booking:',E.title) as ItemProductService,
Concat(R.first_name, ' ',R.last_name) as Customer,
R.first_name as FirstName,
R.last_name as LastName,
R.email,
R.register_date as InvoiceDate,
R.amount as ItemAmount,
R.comment,
R.number_registrants as ItemQuantity,
R.transaction_id as InvoiceNo,
R.published as Status,
Group_ConCat( E.event_date) AS SERVICEDATE,
Group_ConCat( Concat('Ad-Hoc Booking:',E.title)) AS DESCRIPTION,
Group_ConCat( R.number_registrants) AS QUANTITY,
Group_ConCat( FORMAT(R.amount / R.number_registrants,2)) AS RATE2,
Group_ConCat( R.amount) AS AMOUNT,
Group_ConCat( C.category_id) as CLASS,
Concat(Group_Concat(V.field_value SEPARATOR ', '),'. ',R.comment) as Memo
FROM dcea4_eb_events E
LEFT JOIN dcea4_eb_registrants R ON R.event_id = E.id
LEFT JOIN dcea4_eb_field_values V ON V.registrant_id = R.id
LEFT JOIN dcea4_eb_event_categories C ON C.event_id = R.event_id
WHERE 1=1
AND V.field_id IN(14,26,27,15)
AND R.published <> 2 /*Including this line omits Cancelled Invoices */
AND R.published IS NOT NULL
AND (R.published = 1 OR R.payment_method = "os_offline")
AND (R.register_date >= CURDATE() - INTERVAL 14 DAY)
GROUP BY R.transaction_id
ORDER BY R.register_date DESC, R.transaction_id
But this produces this output
It seems to be multiplying the rows. The Quantity column in the first row should just be 1 and in the second row it should be 2,1 .
I've tried using Group_Concat with DISTINCT but this doesn't work because often the values being concatenated are the same (e.g. the price for two events being booked are both the same) and the query only returns one value e.g. 10 and not 10, 10. The latter being what I need.
I'm guessing the issue is around the way the tables are joined but I'm struggling to work out how to get what I need.
Pointers in the right direction most appreciated.
You seem determined to go in what seems to me to be the wrong direction, so here's a gentle nudge down that hill...
Consider the following...
CREATE TABLE users
(user_id SERIAL PRIMARY KEY
,username VARCHAR(12) UNIQUE
);
INSERT INTO users VALUES
(101,'John'),(102,'Paul'),(103,'George'),(104,'Ringo');
DROP TABLE IF EXISTS sales;
CREATE TABLE sales
(sale_id SERIAL PRIMARY KEY
,purchaser_id INT NOT NULL
,item_code CHAR(1) NOT NULL
,quantity INT NOT NULL
);
INSERT INTO sales VALUES
( 1,101,'A',1),
( 2,103,'A',2),
( 3,103,'A',3),
( 4,104,'A',1),
( 5,104,'A',2),
( 6,104,'A',3),
( 7,103,'B',2),
( 8,103,'B',2),
( 9,104,'B',3),
(10,103,'B',2),
(11,104,'B',2),
(12,104,'B',1);
SELECT u.*
, x.sale_ids
, x.item_codes
, x.quantities
FROM users u
LEFT
JOIN
( SELECT purchaser_id
, GROUP_CONCAT(sale_id ORDER BY sale_id) sale_ids
, GROUP_CONCAT(item_code ORDER BY sale_id) item_codes
, GROUP_CONCAT(quantity ORDER BY sale_id) quantities
FROM sales
GROUP
BY purchaser_id
) x
ON x.purchaser_id = u.user_id;
+---------+----------+---------------+-------------+-------------+
| user_id | username | sale_ids | item_codes | quantities |
+---------+----------+---------------+-------------+-------------+
| 101 | John | 1 | A | 1 |
| 102 | Paul | NULL | NULL | NULL |
| 103 | George | 2,3,7,8,10 | A,A,B,B,B | 2,3,2,2,2 |
| 104 | Ringo | 4,5,6,9,11,12 | A,A,A,B,B,B | 1,2,3,3,2,1 |
+---------+----------+---------------+-------------+-------------+

MySQL SELECT COUNT DISTINCT from 2 columns

I previously had 1 column country on top of which I performed COUNT(DISTINCT()) and GROUP_CONCAT(DISTINCT()).
SELECT
(SELECT COUNT(DISTINCT(country)) FROM flagevent AS f2
WHERE f2.user = f.user
) AS totalflags,
GROUP_CONCAT(DISTINCT(country) ORDER BY c.name) AS allcountries,
f.user, u.username
FROM flagevent AS f
INNER JOIN country AS c ON f.country = c.code
INNER JOIN user AS u ON f.user = u.id
WHERE f.user = 1
OR f.user in (1, 2, 3)
GROUP BY user
ORDER BY totalflags DESC;
That would give me this example result:
totalflags | allcountries | user | username
---------------------------------------------
5 | es,fr,it,de,pt | 1 | jagomf
Now instead of original country column, I have 2 columns country1 and country2 on top of which I have to perform same calculations, getting DISTINCT values of both 2 columns.
How can I apply same COUNT() and GROUP_CONCAT() on top of the distinct data of the 2 columns?
UPDATE: Table schemas:
flagevent (old):
- user: int(11)
- country: varchar(2)
flagevent (new):
- user: int(11)
- country1: varchar(2)
- country2: varchar(2)
user:
- username: varchar(45)
country:
- code: varchar(2)
- name: varchar(45)
Here is the easy way -- just change the new to look like the old:
Note, this was edited to use a CTE since you had the sub query with a count.
WITH flagevent_comb as (
SELECT user, country, COUNT(DISTINCT country) as cnt
FROM (
SELECT user, country1 AS country FROM flagevent WHERE country1 IS NOT NULL
UNION ALL
SELECT user, country2 AS country FROM flagevent WHERE country2 IS NOT NULL
) x
GROUP BY user, country
)
SELECT f.cnt as totalflags,
GROUP_CONCAT(DISTINCT(country) ORDER BY c.name) AS allcountries,
f.user, u.username
FROM flagevent_comb AS f
INNER JOIN country AS c ON f.country = c.code
INNER JOIN user AS u ON f.user = u.id
WHERE f.user = 1
OR f.user in (1, 2, 3)
GROUP BY user
ORDER BY totalflags DESC;
Note you might have to make the sub query more complicated. For example if sometimes country1 or country2 is null this would probably be better.
SELECT user, country1 AS country FROM flagevent WHERE country1 IS NOT NULL
UNION ALL
SELECT user, country2 AS country FROM flagevent WHERE country2 IS NOT NULL
Other business rules might apply.

Check whether particular name order is available in my table

I have the following table stops how can I check whether the following stops name order GHI, JKL, MNO is available in my stops table?
stops table:
CREATE TABLE IF NOT EXISTS stops
(
stop_id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
name varchar(30) NOT NULL,
lat double(10,6),
longi double(10,6)
);
Simple:
1 ABC
2 DEF
3 GHI
4 JKL
5 MNO
6 PQR
7 SDU
8 VWX
This query will return 1 when there is an ordered of 'GHI','JKL','MNO':
SELECT 1
FROM stops s1
JOIN stops s2 ON s1.stop_id = s2.stop_id - 1
JOIN stops s3 ON s2.stop_id = s3.stop_id - 1
WHERE CONCAT(s1.name, s2.name, s3.name) = CONCAT('GHI','JKL','MNO')
SQL Fiddle Demo
This is a variation of the well known "find equal sets" task.
You need to insert the searched route into a table with a sequenced stop_id:
create table my_stops(stop_id INT NOT NULL,
name varchar(30) NOT NULL);
insert into my_stops (stop_id, name)
values (1, 'GHI'),(2, 'JKL'),(3, 'MNO');
Then you join and calculate the difference between both sequences. This returns a totally meaningless number, but always the same for consecutive values:
select s.*, s.stop_id - ms.stop_id
from stops as s join my_stops as ms
on s.name = ms.name
order by s.stop_id;
Now group by that meaningless number and search for a count equal to the number of searched steps:
select min(s.stop_id), max(s.stop_id)
from stops as s join my_stops as ms
on s.name = ms.name
group by s.stop_id - ms.stop_id
having count(*) = (select count(*) from my_stops)
See Fiddle
Another alternative:
select 1
from stops x
where x.name = 'GHI'
and (select GROUP_CONCAT(name order by y.stop_id)
from stops y where y.stop_id between x.stop_id + 1
and x.stop_id + 2
) = 'JKL,MNO';

I need help regarding JOIN query in mysql

I have started learning MySQL and I'm having a problem with JOIN.
I have two tables: purchase and sales
purchase
--------------
p_id date p_cost p_quantity
---------------------------------------
1 2014-03-21 100 5
2 2014-03-21 20 2
sales
--------------
s_id date s_cost s_quantity
---------------------------------------
1 2014-03-21 90 9
2 2014-03-22 20 2
I want these two tables to be joined where purchase.date=sales.date to get one of the following results:
Option 1:
p_id date p_cost p_quantity s_id date s_cost s_quantity
------------------------------------------------------------------------------
1 2014-03-21 100 5 1 2014-03-21 90 9
2 2014-03-21 20 2 NULL NULL NULL NULL
NULL NULL NULL NULL 2 2014-03-22 20 2
Option 2:
p_id date p_cost p_quantity s_id date s_cost s_quantity
------------------------------------------------------------------------------
1 2014-03-21 100 5 NULL NULL NULL NULL
2 2014-03-21 20 2 1 2014-03-21 90 9
NULL NULL NULL NULL 2 2014-03-22 20 2
the main problem lies in the 2nd row of the first result. I don't want the values
2014-03-21, 90, 9 again in row 2... I want NULL instead.
I don't know whether it is possible to do this. It would be kind enough if anyone helps me out.
I tried using left join
SELECT *
FROM sales
LEFT JOIN purchase ON sales.date = purchase.date
output:
s_id date s_cost s_quantity p_id date p_cost p_quantity
1 2014-03-21 90 9 1 2014-03-21 100 5
1 2014-03-21 90 9 2 2014-03-21 20 2
2 2014-03-22 20 2 NULL NULL NULL NULL
but I want 1st 4 values of 2nd row to be NULL
Since there are no common table expressions or full outer joins to work with, the query will have some duplication and instead need to use a left join unioned with a right join;
SELECT p_id, p.date p_date, p_cost, p_quantity,
s_id, s.date s_date, s_cost, s_quantity
FROM (
SELECT *,(SELECT COUNT(*) FROM purchase p1
WHERE p1.date=p.date AND p1.p_id<p.p_id) rn FROM purchase p
) p LEFT JOIN (
SELECT *,(SELECT COUNT(*) FROM sales s1
WHERE s1.date=s.date AND s1.s_id<s.s_id) rn FROM sales s
) s
ON s.date=p.date AND s.rn=p.rn
UNION
SELECT p_id, p.date p_date, p_cost, p_quantity,
s_id, s.date s_date, s_cost, s_quantity
FROM (
SELECT *,(SELECT COUNT(*) FROM purchase p1
WHERE p1.date=p.date AND p1.p_id<p.p_id) rn FROM purchase p
) p RIGHT JOIN (
SELECT *,(SELECT COUNT(*) FROM sales s1
WHERE s1.date=s.date AND s1.s_id<s.s_id) rn FROM sales s
) s
ON s.date=p.date AND s.rn=p.rn
An SQLfiddle to test with.
In a general sense, what you're looking for is called a FULL OUTER JOIN, which is not directly available in MySQL. Instead you only get LEFT JOIN and RIGHT JOIN, which you can UNION together to get essentially the same result. For a very thorough discussion on this subject, see Full Outer Join in MySQL.
If you need help understanding the different ways to JOIN a table, I recommend A Visual Explanation of SQL Joins.
The way this is different from a regular FULL OUTER JOIN is that you're only including any particular row from either table at most once in the JOIN result. The problem being, if you have one purchase record and two sales records on a particular day, which sales record is the purchase record associated with? What is the relationship you're trying to represent between these two tables?
It doesn't sound like there's any particular relationship between purchase and sales records, except that some of them happened to take place on the same day. In which case, you're using the wrong tool for the job. If all you want to do is display these tables side by side and line the rows up by date, you don't need a JOIN at all. Instead, you should SELECT each table separately and do your formatting with some other tool (or manually).
Here's another way to get the same result, but the EXPLAIN for this is horrendous; and performance with large sets is going to be atrocious.
This is essentially two queries UNIONed together. The first query is essentially "purchase LEFT JOIN sales", the second query is essentially "sales ANTI JOIN purchase".
Because there is no foreign key relationship between the two tables, other than rows matching on date, we have to "invent" a key we can join on; we use user variables to assign ascending integer values to each row within a given date, so we can match row 1 from purchase to row 1 from sales, etc.
I wouldn't normally generate this type of result using SQL; it's not a typical JOIN operation, in the sense of how we traditionally join tables.
But, if I had to produce the specified resultset using MySQL, I would do it like this:
SELECT p.p_id
, p.p_date
, p.p_cost
, p.p_quantity
, s.s_id
, s.s_date
, s.s_cost
, s.s_quantity
FROM ( SELECT #pl_i := IF(pl.date = #pl_prev_date,#pl_i+1,1) AS i
, #pl_prev_date := pl.date AS p_date
, pl.p_id
, pl.p_cost
, pl.p_quantity
FROM purchase pl
JOIN ( SELECT #pl_i := 0, #pl_prev_date := NULL ) pld
ORDER BY pl.date, pl.p_id
) p
LEFT
JOIN ( SELECT #sr_i := IF(sr.date = #sr_prev_date,#sr_i+1,1) AS i
, #sr_prev_date := sr.date AS s_date
, sr.s_id
, sr.s_cost
, sr.s_quantity
FROM sales sr
JOIN ( SELECT #sr_i := 0, #sr_prev_date := NULL ) srd
ORDER BY sr.date, sr.s_id
) s
ON s.s_date = p.p_date
AND s.i = p.i
UNION ALL
SELECT p.p_id
, p.p_date
, p.p_cost
, p.p_quantity
, s.s_id
, s.s_date
, s.s_cost
, s.s_quantity
FROM ( SELECT #sl_i := IF(sl.date = #sl_prev_date,#sl_i+1,1) AS i
, #sl_prev_date := sl.date AS s_date
, sl.s_id
, sl.s_cost
, sl.s_quantity
FROM sales sl
JOIN ( SELECT #sl_i := 0, #sl_prev_date := NULL ) sld
ORDER BY sl.date, sl.s_id
) s
LEFT
JOIN ( SELECT #pr_i := IF(pr.date = #pr_prev_date,#pr_i+1,1) AS i
, #pr_prev_date := pr.date AS p_date
, pr.p_id
, pr.p_cost
, pr.p_quantity
FROM purchase pr
JOIN ( SELECT #pr_i := 0, #pr_prev_date := NULL ) prd
ORDER BY pr.date, pr.p_id
) p
ON p.p_date = s.s_date
AND p.i = s.i
WHERE p.p_date IS NULL
ORDER BY COALESCE(p_date,s_date),COALESCE(p_id,s_id)

get latest date row values

select s.s_nric as NRIC
, s.s_name as NAME
, status.st_status
, DATE_FORMAT(status.st_fromdate,'%d-%m-%Y') as from_date
, DATE_FORMAT(status.ST_ON,'%d-%m-%Y') as ST_ON
FROM si_student_data AS s
LEFT JOIN si_student_status As st
ON st.st_nric=s.s_nric
INNER JOIN
( SELECT t.st_nric
, t.st_fromdate
, t.st_status
, MAX(t.st_todate) as ST_ON
FROM si_student_status t
GROUP BY t.st_nric
) AS status
ON ( s.s_nric=status.st_nric
AND status.ST_ON=st.st_todate )
LEFT JOIN si_student_changes as s1
ON s1.ch_nric = s.s_nric
where 1=1
AND s1.ch_class='2S1'
AND s1.ch_year='2011'
GROUP BY s.s_nric
ORDER BY s1.ch_class
, s.s_gender
, s.s_name asc
When I use this query, I can get maximum date value with respective nric number. But I cannot get other values with related to date. It picked up only the maximum date with defferent row values. I want the related values( status) to the date
my sample table:
First table: si_student_data
s_nric s_name
1 Suba
2 Felix
3 welcome
Second tabe: si_student_changes
ch_nric ch_year ch_class
1 2011 2S1
2 2011 2S1
3 2011 2S1
4 2010 1A1
5 2011 2T3
1 2010 1A1
Third table: si_student_status
st_nric st_status st_fromdate st_todate
1 Active 10-10-2011 10-11-2011
1 Inactive 11-11-2011 12-12-2011
1 PRO 13-12-2011 22-12-2011
2 LWR 10-10-2011 10-11-2011
2 Inactive 11-11-2011 12-12-2011
2 ATTR 13-12-2011 20-12-2011
3 Active 04-01-2011 10-05-2011
3 Inactive 11-05-2011 12-08-2011
3 PRO 13-08-2011 20-10-2011
my Expecting output
s_nric s_name st_status st_fromdate st_todate
1 Suba PRO 13-12-2011 22-12-2011
2 Felix ATTR 13-12-2011 20-12-2011
3 welcome PRO 13-08-2011 20-10-2011
pls explain how can get maximum date value record. I want maximum date and same row values..
Just add the fields you want from table st. And don't use the status.* in the SELECT list :
select s.s_nric as NRIC
, s.s_name as NAME
, st.st_status
, DATE_FORMAT(st.st_fromdate,'%d-%m-%Y') as from_date
, DATE_FORMAT(st.st_todate,'%d-%m-%Y') as ST_ON
So, the whole query could be written as:
SELECT s.s_nric AS NRIC
, s.s_name AS NAME
, st.st_status
, DATE_FORMAT(st.st_fromdate,'%d-%m-%Y') AS from_date
, DATE_FORMAT(st.st_todate,'%d-%m-%Y') AS ST_ON
FROM si_student_data AS s
LEFT JOIN si_student_status AS st
ON st.st_nric = s.s_nric
INNER JOIN
( SELECT t.st_nric
, MAX(t.st_todate) AS ST_ON
FROM si_student_status t
GROUP BY t.st_nric
) AS status
ON ( s.s_nric = status.st_nric
AND status.ST_ON = st.st_todate )
LEFT JOIN si_student_changes as s1
ON s1.ch_nric = s.s_nric
WHERE 1=1
AND s1.ch_class='2S1'
AND s1.ch_year='2011'
GROUP BY s.s_nric
ORDER BY s1.ch_class
, s.s_gender
, s.s_name asc
SELECT s.*,p.name, FROM `status` as s
left join profile as p ( s.id = p.id)
WHERE s.date= ( select MAX(s.date) from status)