monthly checklist on msaccess - sql

I have a table of the form show below.My intention is to create a monthly checklist.
+-------------+----------------+-----------------+-----------------+
| id | mem_id | month_code | type |
+-------------+----------------+-----------------+-----------------+
| 1 | 1 | Jan | to |
| 2 | 2 | Feb | t |
| 3 | 1 | Feb | to |
| 4 | 3 | Jan | o |
| 5 | 1 | Mar | o |
+-------------+----------------+-----------------+-----------------+
The query used is
SELECT distinct(mem_id) as Member,
(SELECT type FROM test where mem_id=Member and month_code='Jan') as Jan,
(SELECT type FROM test where mem_id=Member and month_code='Feb') as Feb,
(SELECT type FROM test where mem_id=Member and month_code='Mar') as Mar
FROM test
The desired output is
+-------------+----------------+-----------------+-----------------+
| mem_id | Jan | Feb | Mar |
+-------------+----------------+-----------------+-----------------+
| 1 | to | to | o |
| 2 | | t | |
| 3 | o | | |
+-------------+----------------+-----------------+-----------------+
My problem however is that the code works fine on mysql but on msaccess I get a pop up of an input box asking me to enter the value of parameter Member. How can I have the correct output in Access?

You can do conditional aggregation instead :
select mem_id,
max(iif(month_code = 'Jan', type)) as Jan,
max(iif(month_code = 'Feb', type)) as Feb,
max(iif(month_code = 'Mar', type)) as Mar
from test t
group by mem_id;
For your query, the name Member doesn't exists in test table so, access considered it as parameter.
So, you probably need mem_id instead :
SELECT t.mem_id as Member,
(SELECT t1.type FROM test as t1 where t1.mem_id = t.mem_id and t1.month_code='Jan') as Jan,
(SELECT t1.type FROM test as t1 where t1.mem_id = t.mem_id and t1.month_code='Feb') as Feb,
(SELECT t1.type FROM test as t1 where t1.mem_id = t.mem_id and t1.month_code='Mar') as Mar
FROM test as t
GROUP BY t.mem_id;
The only problem is that with your version is, if one mem_id has duplicate types for same month_code then it will throw subquery error.
So, you need top clause in subquery.

Related

Pivot table in SQL but keep measure names in column

Im having trouble pivoting a table correct.
My input is this raw data table:
+------+---------+------------+----------+
| YEAR | FACULTY | ADMISSIONS | DROPOUTS |
+------+---------+------------+----------+
| 2018 | LAW | 15 | 2 |
| 2019 | LAW | 18 | 4 |
| 2020 | LAW | 11 | 1 |
| 2018 | MATH | 19 | 1 |
| 2019 | MATH | 17 | 6 |
| 2020 | MATH | 24 | 5 |
+------+---------+------------+----------+
I want to pivot years to row but I also want to keep the measure for admissions and drop outs as row names. E.g I want a table as this:
+---------+------------+------+------+------+
| FACULTY | MEASURE | 2018 | 2019 | 2020 |
+---------+------------+------+------+------+
| LAW | ADMISSIONS | 15 | 18 | 11 |
| LAW | DROPOUTS | 2 | 4 | 1 |
| MATH | ADMISSIONS | 19 | 17 | 24 |
| MATH | DROPOUTS | 1 | 6 | 5 |
+---------+------------+------+------+------+
I can pivot years using:
SELECT *
FROM
(
SELECT FACULTY, YEAR, ADMINISSION, DROPPUTS
FROM TABLE
PIVOT (SUM (ADMISSIONS)
FOR YEAR IN (2018,2019,2020)
)
But I need to pivot both measures and still get the measure names column. Any ideas?
That's unpivoting, then pivoting. If your database supports lateral joins and values(), you can do:
select
t.faculty,
x.measure,
sum(case when t.year = 2018 then x.value end) value_2018,
sum(case when t.year = 2019 then x.value end) value_2019,
sum(case when t.year = 2020 then x.value end) value_2020
from mytable t
cross apply (values ('admission', admission), ('dropout', dropout)) as x(measure, value)
group by t.faculty, x.measure
I would unpivot using apply (assuming you are using SQL Server) and reaggregate:
select t.faculty, v.measure,
max(case when year = 2018 then val end) as [2018],
max(case when year = 2019 then val end) as [2019],
max(case when year = 2020 then val end) as [2020]
from t cross apply
(values ('ADMISSIONS', ADMISSIONS), ('DROPOUTS', DROPOUTS)
) v(measure, val)
group by t.faculty, v.measure

Get range (min - max) of values concatenated in a single row

given the following table
+-----------------------------+
| id | type | price | item_id |
|-----------------------------|
| 1 | 1 | 20 | 22 |
|-----------------------------|
| 2 | 1 | 22 | 22 |
|-----------------------------|
| 3 | 2 | 19 | 22 |
|-----------------------------|
| 4 | 2 | 11 | 22 |
|-----------------------------|
| 5 | 1 | 08 | 22 |
|-----------------------------|
| 6 | 2 | 25 | 22 |
+-----------------------------+
I am trying to select the data to create a view as follows in a single row
+-------------------------------------+
| type1_range | type2_range | item_id |
|-------------------------------------|
| 08 - 22 | 11 - 25 | 22 |
+-------------------------------------+
type1_range and type2_range are the minimum and maximum price for each types.
I can get the data in couple of rows using
SELECT type, MAX (price) , MIN (price)
FROM table
where item_id=22 GROUP BY type;
+----------------------------+
| type | max | min | item_id |
|----------------------------|
| 1 | 22 | 08 | 22 |
|----------------------------|
| 2 | 25 | 11 | 22 |
+----------------------------+
But I am trying to concat the rows like this:
+-------------------------------------+
| type1_range | type2_range | item_id |
|-------------------------------------|
| 08 - 22 | 11 - 25 | 22 |
+-------------------------------------+
What would be sql required for this?
Something like this:
SELECT
CONCAT(
MIN(CASE WHEN type = 1 THEN price END),
' - ',
MAX(CASE WHEN type = 1 THEN price END)
) as type1range,
CONCAT(
MIN(CASE WHEN type = 2 THEN price END),
' - ',
MAX(CASE WHEN type = 2 THEN price END)
) as type2range.
item_id
FROM table
WHERE item_id = 22
GROUP BY item_id
You've tagged two different database systems (please avoid doing this) but I believe they do both support CONCAT() for string concatenation
If you want to omit the item_id from the select list (you already know it's item 22) you can remove the GROUP BY. Alternatively if you remove the WHERE and leave the group by you'll get a row for each item_id
To get more of an idea as to how it works, remove the concat and the min/max - you'll see that the case when causes the price to show up only if the type is 1 (in the type 1 range column) otherwise it's null. It's the. Trivial for the min and max to work on just type 1 or just type 2 data for each column. It's actually a form of pivot query if you want to read up on them more
A straight forward approach would be having type1_range and type2_range as two sub-queries and join with the distinct id's like shown below,
SELECT t.item_id,type1_range,type2_range
FROM (Select distinct item_id from table) t
LEFT join
(SELECT item_id,type, concat(MIN(price),'-' ,MAX(price) ) as type1_range
FROM table
where type=1
GROUP BY item_id,type)type1 on type1.item_id=t.item_id
LEFT join
(SELECT item_id,type, concat(MIN(price),'-' ,MAX(price) ) as type2_range
FROM table
where type=2
GROUP BY item_id,type)type2 on type2.item_id=t.item_id

MSSQL Count Multiple Columns

Say I have a table like this in ms sql 2008:
+------+--------+---------+
| year | JAN | FEB |
+------+--------+---------+
| 2016 | 5K2 | 5K2 |
| 2016 | 5K2 | 5K2 |
| 2016 | 5K2 | 5K2 |
| 2016 | 8Z | 8Z |
| 2016 | R5205 | R5205 |
| 2016 | 5K2 | 5K2 |
| 2016 | 5K2 | 5K2 |
| 2016 | NULL | NULL |
| 2016 | TE | NULL |
| 2016 | TE | NULL |
| 2016 | 8Z | 8Z |
+------+--------+---------+
And I want to get a count for each column, something like this
+------+--------+---------+
| opt | JAN_cnt| FEB_cnt |
+------+--------+---------+
| 5K2 | 5 | 4 |
| 8Z | 2 | 2 |
| R5205| 1 | 1 |
| TE | 2 | 0 |
| NULL | 1 | 4 |
+------+--------+---------+
First, can this be done? Second, how? I have searched, but cant find exactly what I am looking for.
I think the simplest way is to use UNION ALL with conditional aggregation using CASE EXPRESSION :
SELECT s.opt,
COUNT(CASE WHEN s.ind_from = 1 THEN 1 END) as jan_cnt,
COUNT(CASE WHEN s.ind_from = 2 THEN 1 END) as feb_cnt
FROM (
SELECT t1.jan as opt,1 as ind_from FROM YourTable t1
UNION ALL
SELECT t2.feb,2 FROM YourTable t2) s
GROUP BY s.opt
I would advise putting the values into a different format:
opt
month
cnt
You can do this as:
select opt, mon, count(*) as cnt
from ((select jan as opt, 'jan' as mon from t) union all
(select feb as opt, 'feb' as mon from t)
) o
group by opt, mon;
It is easy enough to switch this to your format:
select opt, sum(jan) as jan, sum(feb) as feb
from ((select jan as opt, 1 as jan, 0 as feb from t) union all
(select feb as opt, 0, 1, from t)
) o
group by opt;
I just prefer the first format. It is easier to generalize to more columns.
SELECT COALESCE(t1.JAN, t2.FEB), t1.JAN_cnt, t2.FEB_cnt
FROM
(
SELECT JAN, COUNT(*) AS JAN_cnt
FROM yourTable
GROUP BY JAN
) t1
FULL OUTER JOIN
(
SELECT FEB, COUNT(*) AS FEB_cnt
FROM yourTable
GROUP BY FEB
) t2
ON t1.JAN = t2.FEB

SQL - Adding an avg column to a detail table

I'm on Teradata. I have an order table like the below.
custID | orderID | month | order_amount
-----------------------------------------
1 | 1 | jan | 10
1 | 2 | jan | 20
1 | 3 | feb | 5
1 | 4 | feb | 7
2 | 5 | mar | 20
2 | 6 | apr | 30
I'd like to add a column to the above table called "Avg order amount per month per customer". Since the table is at an order level, adding this column will cause duplicates like the below, which is ok.
custID | orderID | month | order_amount | avgOrdAmtperMonth
-------------------------------------------------------------
1 | 1 | jan | 10 | 15
1 | 2 | jan | 20 | 15
1 | 3 | feb | 5 | 6
1 | 4 | feb | 7 | 6
2 | 5 | mar | 20 | 20
2 | 6 | apr | 30 | 30
I want the output to have all the columns above, not just the custid and the new column. I'm not sure how to write this because one part of the table is an at order level and the new column needs to be grouped by customer+month. How would I do this?
This is a simple group average:
AVG(order_amount) OVER (PARTITION BY custID, month)
Why not just do the calculation when you query the table?
select t.*,
avg(order_amount) over (partition by custId, month) as avgOrderAmtPerMonth
from t;
You can add this into a view if you want to make it available to multiple downstream queries.
Actually adding the column to the table is a maintenance "nightmare". You have to add triggers to the table and update the value for updates, inserts, and deletes.

Using ORDER BY and getting most recent version of records

I have a database structured in the following way
ID | DATE | col_0 |
--------------------------
1 | 2014 | A_Ver2_data0 |
2 | 2014 | A_Ver2_data1 |
3 | 2014 | A_Ver2_data2 |
4 | 2013 | A_Ver1_data0 |
5 | 2013 | A_Ver1_data1 |
6 | 2012 | A_Ver0_data0 |
7 | 2012 | A_Ver0_data1 |
8 | 2013 | B_Ver3_data0 |
9 | 2013 | B_Ver3_data1 |
10 | 2013 | B_Ver3_data2 |
11 | 2010 | B_Ver2_data0 |
12 | 2010 | B_Ver2_data1 |
13 | 2009 | B_Ver1_data0 |
14 | 2007 | B_Ver0_data0 |
I need to write a query that will return the most recent version of the A_ and B_ prefixed data sets. So I was thinking something like SELECT * FROM db.table ORDER BY DATE DESC But I want to filter out expired versions. desired output should be:
ID | DATE | col_0 |
--------------------------
1 | 2014 | A_Ver2_data0 |
2 | 2014 | A_Ver2_data1 |
3 | 2014 | A_Ver2_data2 |
8 | 2013 | B_Ver3_data0 |
9 | 2013 | B_Ver3_data1 |
10 | 2013 | B_Ver3_data2 |
Any Ideas?
I think this does what you want. It parses the column to get the first and last parts and then finds the maximum "DATE" for each. It returns the row that matches the date:
select id, "DATE", COL_A
from (select v.*,
max("DATE") over (partition by substr(col_A, 1, 1),
substr(col_A, 8)
) as maxdate
from versiones v
) v
where "DATE" = maxdate;
The SQL Fiddle is here.
I am not sure but i think this would work : "HAVING date >= MAX(date)-1"
max(date)-1 will return 2014-1 = 2013 , which will eventually filter out the results based on date >= 2013 .
But this would list all the 2013,2014 entries ..
You could use an analytic function to get the maximum version, and then select the corresponding records, as below:
SELECT
*
FROM
db.table
WHERE
Col_0 IN
(
Select Distinct
Max(Col_0) Over (Partition By Replace(Col_0, Replace(Regexp_Substr(Col_0, '_[^,]+_'), '_', ''), '')
Order By REPLACE(Regexp_Substr(Col_0, '_[^,]+_'), '_', '') DESC) AS Col_0
FROM
db.table
);
Also, please note that you would not be able to name a column as DATE, because DATE is a reserved word.
Here is my answer:
select * from
versiones
where SUBSTR(COL_A,0,6)
in(
select version from
(
select SUBSTR(COL_A,0,1) letra,max(SUBSTR(COL_A,6,1)) maximo,
SUBSTR(COL_A,0,1)||'_Ver'||max(SUBSTR(COL_A,6,1)) version
from versiones
group by SUBSTR(COL_A,0,1)
)
cs
)
Sqlfiddle: http://sqlfiddle.com/#!4/84a8f/13