SQL: Dynamically Transposing Column as Row in Chartio - sql

I would love to have your help transposing this table's column into a row. I am trying to have the Project Names (column 1) each becoming their own column header with the MUVs value entered as the column value.
I have read through 20+ threads on this topic, but they are using different SQL packages and code that I am unable to use.
The ideal solution would modify this query to create the desired query output:
Current Query:
SELECT
cust_gae_account.project_name as "Project Name",
cust_be_project_usage.unique_visitor_count as "MUVs",
cust_be_project_usage.billing_period_start as "Month"
FROM
cust_be_project_usage
INNER JOIN
cust_gae_account
ON cust_gae_account.account_id = cust_be_project_usage.project_id
WHERE
cust_be_project_usage.admin_account_id = {ACCOUNT_ID}
Current Query Output:
Project Name | MUVs | Month
-----------------------------------------------------
ProjectAAAAAZ | 68000 | Jun 01, 2016
DynamicName | 3200 | Jun 01, 2016
ProjectAAAAAZ | 21000 | May 01, 2016
DynamicName | 4500 | May 01, 2016
CustomProject | 117000 | Jun 01, 2016
CustomProject | 118400 | May 01, 2016
Desired Query Output:
Project Name | CustomProject | ProjectAAAAAZ | DynamicName
-------------------------------------------------------------------
Jun 01, 2016 | 117000 | 68000 | 3200
May 01, 2016 | 118400 | 21000 | 4500
Notes:
We are using PostgreSQL via Chartio, we can only use base SQL
We cannot use custom functions sadly
Names and number of projects is dynamic, varies from 1 to 20
MUVs are always numeric, there are no duplicates

You can use crosstab https://www.postgresql.org/docs/9.1/static/tablefunc.html , but it will only work with a static list of columns. So you need a dynamic query. You need to define that query, and execute it.
EXECUTE ('SELECT * FROM crosstab(
\'SELECT
cust_be_project_usage.billing_period_start as "Month",
cust_gae_account.project_name as "Project Name",
cust_be_project_usage.unique_visitor_count as "MUVs"
FROM
cust_be_project_usage
INNER JOIN
cust_gae_account
ON cust_gae_account.account_id = cust_be_project_usage.project_id
WHERE
cust_be_project_usage.admin_account_id = {ACCOUNT_ID}\',
\'SELECT
cust_gae_account.project_name as "Project Name"
FROM
cust_be_project_usage
INNER JOIN
cust_gae_account
ON cust_gae_account.account_id = cust_be_project_usage.project_id
WHERE
cust_be_project_usage.admin_account_id = {ACCOUNT_ID}\')
AS ( "Project Name" date, ' ||
(SELECT
string_agg(cust_gae_account.project_name, ' int,')
FROM
cust_be_project_usage
INNER JOIN
cust_gae_account
ON cust_gae_account.account_id = cust_be_project_usage.project_id
WHERE
cust_be_project_usage.admin_account_id = {ACCOUNT_ID}) || ' int'
|| ' )')
I am not able to test it though. It SHOULD work, but my suspicions are that it might not, in these situations:
Concatenating to a scalar formed by a query (might need to use a variable for that one)
I might have mixed up a column somewhere
Not sure how you pass the {ACCOUNT_ID} parameter so you might need to change that too

Related

Can't a non-primary column be use as a bound column in MS Access?

I have the following profile table in my db:
Number | LastName | Year
1 | Kim | 2015
2 | Song | 2018
3 | Park | 2015
4 | Ahn | 2015
What im trying to display is list of names in year 2015, for example. The output will display names "Kim, Park and Ahn".
I have a form with the combobox and the subform of a datasheet. Whenever I clicked a year in the combobox, the names in the datasheet will change.
During creating the combobox, i use the "Find a record on my form based on the value I selected in my combobox". I used the column year that will appear on my combobox.
When i check the row source of the combobox, the query goes like this:
SELECT number, year
FROM profile;
Since, the year 2015 appeared 3 times, i need to group it. But i cannot group it if the primary key 'number' is included in the select sql. So therefore, i removed it and set the bound column to the year. However, it will not work.
Does that mean, ms access support only primary keys?
Select one arbitrary row only for each year:
SELECT
First(number),
year
FROM
profile
GROUP By
year

Access SQL/Logic for Incrementing Key/Field based on composite key

Suppose you have a table in a database that tracks ticket appearances across 9 office locations for between 4 and 5 times periods every day. In each of these time periods, people might appear for one ticket (I guess we can use a transaction model), or they can appear for many (I've seen some with 15, heard of others with up to 50).
A query was created using Access 2013 to capture the data from this table. This is done for archiving (the current date table records are purged after 24 hours), and general analysis.
The current query logic is as follows:
SELECT Format([T1]![APP_DTE],"Short Date") AS APPEAR_DTE,
T1.OFFICE, OFFCDES.OFFICE_NAME, OFFCDES.LOC_CDE, OFFCDES.LOC_NAME,
T1.APP_TIME_PER, T1.ROOM_NUM, T1.INCL_CDE, T1.ID_NUMB,
T1.ADJ_APPEAR_NUM, T2.APPEAR_CDE,
IIf([T1]![APPEAR_LOGIN_TIME] Is Null,"",[T1]![APPEAR_LOGIN_TIME]) AS LOGIN_TIME1,
IIf([T1]![OFFICE_LOGIN] Is Null,"",[T1]![OFFICE_LOGIN]) AS OFFICE_LOGIN,
IIf([T1]![DECISION_TIME] Is Null,"",[T1]![DECISION_TIME]) AS DECISION_TIME,
[T1]![ID_NUMB] & "-" & CStr([T2]![OFFICE_REP_ID]) & "-" & CStr([T1]![APP_DTE]) AS CASEKEY,
Weekday([APP_DTE]) AS NUM_DAY,
[T1]![OFFICE] & "-" & Weekday([T1]![APP_DTE]) & "-" & [T1]![APP_TIME_PER] AS OFFICE_TP_KEY, "" AS RecordKey
FROM (T1 INNER JOIN OFFCDES ON T1.OFFICE = OFFCDES.OFFICE)
INNER JOIN T2 ON T1.APPEAR_NUM = T2.APPEAR_NUM
WHERE (((Format([T1]![APP_DTE],"Short Date"))=Format(Date(),"Short Date")));
I realize that the above code will need to be cleaned up for readability, and that it's probably not as efficient as it could be. That part I can't take credit for, I inherited all of this from a coworker and have just started to wrap my head around it.
The query result includes a ton of information, some (most) of which is irrelevant to what I'm trying to accomplish. It's important to capture all of this information since the information, once gone, is gone. With that stated:
What I'm trying to do:
I'm trying to incorporate a key/sequentially numbered column that would tell me how many times a particular ID_NUMB has occurred at a particular Office/Time Period on the date pulled. I'm also not looking for a total, rather something along the lines of the SEQ column here:
ID_NUMB | APPEAR_NUM | OFFICE | TP | DATE | SEQ
1 | 1XZ2 | 01 | 01 | 9/7/2016 | 1
1 | 1XZ3 | 01 | 01 | 9/7/2016 | 2
2 | 3MN1 | 03 | 01 | 9/7/2016 | 1
3 | J915 | 02 | 01 | 9/7/2016 | 1
1 | 1TY1 | 01 | 01 | 9/7/2016 | 1
3 | P91D | 05 | 01 | 9/7/2016 | 1
2 | U11E | 11 | 05 | 9/7/2016 | 1
While I have looked around and found many fine numbering solutions, most aren't quite what I'm looking to accomplish due to how fine grained the count seems to be.
I've thought about writing the data to a temp table, but using some of the information that I've found didn't lead to any kind of solution. Similarly,
I tried writing a separate query that could retrieve this information and add the sequential number in, kind of an adaption of the solution found on the MS Answers site. I put that aside as it seemed like the wrong approach.
I even toyed with writing the query to a temp table and then having a piece of VBA handle the sequential numbering, but that approach too, didn't get too far.
What would the right approach to generate a sequential number in a column as I outlined be? I'm at a loss.
I think this SQL might be close to what you want (based on the limited example) It does an INNER JOIN on the table T1 - and does a simple sort before it builds the SEQ on the fly
SELECT
a.ID_NUMB,
a.ADJ_APPEAR_NUM,
a.OFFICE,
a.APP_TIME_PER AS TP,
Format(a.[APP_DTE],"Short Date") AS APP_DATE,
(SELECT Count(ID_NUMB)
FROM T1 b
WHERE (b.ADJ_APPEAR_NUM <= a.ADJ_APPEAR_NUM
AND b.ID_NUMB = a.ID_NUMB
AND b.OFFICE = a.OFFICE
AND b.APP_TIME_PER = a.APP_TIME_PER
AND b.APP_DTE = a.APP_DTE)) AS SEQ
FROM T1 AS a
ORDER BY a.ID_NUMB, a.OFFICE, a.ADJ_APPEAR_NUM;
Query Results (using your data for T1)
For this kind of tasks, the most straight-forward method is usually to open a recordset using DAO with the records ordered as to the sequence you need.
Then loop through the recordset, for each record keeping track of the conditions that will force a new sequence number or just add one to the previous sequence number. Now, build the sequence number, update the record, and move on until no more records.
Edit: Example looping records:
Dim rst As DAO.Recordset
'Me.RecordsetClone
Set rst = Me.RecordsetClone
rst.Sort = "Dato, ID asc"
Set rst = rst.OpenRecordset()
rst.MoveFirst
While rst.EOF = False
Debug.Print rst!Dato.Value, rst!Id.Value
' If SomeCondition = True Then
rst.Edit
rst!Dec1.Value = rst!Id.Value
rst.Update
' End If
rst.MoveNext
Wend

Is there a way to do a SQL sum(case when A=B then 1 else 0) where you get a column for every value that A can take on?

Rather than doing:
select sum(case when a=b then 1 else 0),
sum(case when a=c then 1 else 0),
sum(case when a=d then 1 else 0)
etc.
I want one statement does this for all values that a can take on.
You really need to tell us what flavor of db you're in. However, in postgres, we have a crosstab(..) function. From the docs:
CREATE TABLE cth(rowid text, rowdt timestamp, attribute text, val text);
INSERT INTO cth VALUES('test1','01 March 2003','temperature','42');
INSERT INTO cth VALUES('test1','01 March 2003','test_result','PASS');
INSERT INTO cth VALUES('test1','01 March 2003','volts','2.6987');
INSERT INTO cth VALUES('test2','02 March 2003','temperature','53');
INSERT INTO cth VALUES('test2','02 March 2003','test_result','FAIL');
INSERT INTO cth VALUES('test2','02 March 2003','test_startdate','01 March 2003');
INSERT INTO cth VALUES('test2','02 March 2003','volts','3.1234');
SELECT * FROM crosstab
(
'SELECT rowid, rowdt, attribute, val FROM cth ORDER BY 1',
'SELECT DISTINCT attribute FROM cth ORDER BY 1'
)
AS
(
rowid text,
rowdt timestamp,
temperature int4,
test_result text,
test_startdate timestamp,
volts float8
);
rowid | rowdt | temperature | test_result | test_startdate | volts
-------+--------------------------+-------------+-------------+--------------------------+--------
test1 | Sat Mar 01 00:00:00 2003 | 42 | PASS | | 2.6987
test2 | Sun Mar 02 00:00:00 2003 | 53 | FAIL | Sat Mar 01 00:00:00 2003 | 3.1234
(2 rows)
As Martin Smith's comment states, you are looking for a dynamic pivot. As mentioned this is almost definitely a duplicate, but for your convenience I thought I'd include this reference. This article explains in detail with example data and code how to accomplish what you want.
You will likely want to put your aggregation in the CTE like so:
WITH PivotData AS
(
SELECT
a,
COUNT(1)
FROM MyTable
GROUP BY a
)
If you are not used to writing dynamic sql (SET #SQL='[...]'; EXEC(#SQL);) it's usually a good idea to test individual statements before throwing them all inside a string as all formatting and coloring your editor provides will disappear and debugging can be difficult.
EDIT: If you are not using T-SQL the Pivot function will not be available to you. However, it is relatively easy to find references on how to reproduce this behavior in other SQL environments. The answer here, for example, actually explains how to do the full dynamic pivot in MySQL. On a conceptual level the idea is the same, however the syntax may look very different.

Access Join Query

I have two tables tbl_DaysWeeksMonths (Left Table) and tbl_Telephony (Right Table). tbl_DaysWeeksMonths has a record of every day of the year with columns (Row_Date/Week/Month) whilst tbl_Telephony has telephony data for hundreds of agents by day with columns (row_date/agent/calls/talk time)(Note: Each agent only has records for 5-6 days of the week instead of everyday).
I want to join the two tables so that each agent has a record for every day of the week regardless if they took calls on a day or not. I want to display blank records (except for the date field) for days which consultants did not take calls. E.g:
## Date ## ## Agent ## ## Calls ## ## Talk Time ##
1. 26/05/2012 | James | 40 | 560
2. 27/05/2012 | James | |
3. 28/05/2012 | James | 34 | 456
4. 29/05/2012 | James | |
5. 30/05/2012 | James | 40 | 643
6. 31/05/2012 | James | 36 | 345
7. 01/06/2012 | James | 31 | 160
I'm trying to use the below code but I don't think it's correct. Any suggestions on a better code to use. Please help.
SELECT tbl_DaysWeeksMonths.Row_Date,
[tbl_Telephony].Consultant,
[tbl_Telephony].i_acdtime
FROM tbl_DaysWeeksMonths
LEFT JOIN [tbl_Telephony]
ON tbl_DaysWeeksMonths.Row_Date = [tbl_Telephony].row_date;
Step 1 -- Assuming you have a table (say, named Consultants) that lists each distinct consultant, create a query (say, named Consultant Days) that generates all possible combinations of days and consultants. It might look something like this:
SELECT [Consultants].Consultant, tbl_DaysWeeksMonths.Row_Date
FROM [Consultants], tbl_DaysWeeksMonths;
If you don't have a Consultants table, you could substitute it with a query that selects the distinct consultants listed in tbl_Telephony. In other words, you can create a Consultants query that looks something like this:
SELECT DISTINCT Consultant FROM [tbl_Telephony];
Step 2 -- Create a query that outer joins tbl_Telephony to Consultant Days. It might look something like this:
SELECT [Consultant Days].Row_Date, [Consultant Days].Consultant, [tbl_Telephony].i_acdtime
FROM [Consultant Days]
LEFT JOIN [tbl_Telephony]
ON [Consultant Days].Consultant = [tbl_Telephony].Consultant
AND [Consultant Days].Row_Date = [tbl_Telephony].row_date;
This also assumes that the row_date values in tbl_Telephony match the Row_Date values in tbl_DaysWeeksMonths -- in other words, that row_date values in tbl_Telephony are whole days (that is, do not contain a time-of-day component). This also assumes that i_acdtime values in tbl_Telephony are the total talk time for the given consultant and day (as opposed to the talk time for a given call). Presumably there is another column in tbl_Telephony that will give the the total number of cals for the given consultant and day which you could add to the query to get the "Calls" column you said you wanted your question.
You can also use this query.
SELECT tbl_DaysWeeksMonths.Row_Date, [tbl_Telephony].Consultant, [tbl_Telephony].i_acdtime
FROM tbl_DaysWeeksMonths
LEFT OUTER JOIN [tbl_Telephony] ON tbl_DaysWeeksMonths.Row_Date = [tbl_Telephony].row_date;

Counting occurrences in a table

Lets say I want to count the total number of occurrences of a name contained within a string in a column and display that total next to all occurrences of that name in a new column beside it. For example, if I have:
Name | Home Address | Special ID
==================================
Frank | 152414 | aTRF342
Jane | 4342342 | rRFC432
Mary | 423432 | xTRF353
James | 32111111 | tLZQ399
May | 4302443 | 3TRF322
How would I count the occurrences of special tags like 'TRF', 'RFC', or 'LZQ' so the table looks like this:
Name | Home Address | Special ID | Occurrences
================================================
Frank | 152414 | aTRF342 | 3
Jane | 4342342 | rRFC432 | 1
Mary | 423432 | xTRF353 | 3
James | 32111111 | tLZQ399 | 1
May | 4302443 | 3TRF322 | 3
Currently using Access 2007. Is this even possible using a SQL query?
Using Access 2007, I stored your sample data in a table named tblUser1384831. The query below returns this result set.
Name Home Address Special ID special_tag Occurrences
---- ------------ ---------- ----------- -----------
Frank 152414 aTRF342 TRF 3
Jane 4342342 rRFC432 RFC 1
Mary 423432 xTRF353 TRF 3
James 32111111 tLZQ399 LZQ 1
May 4302443 3TRF322 TRF 3
Although your question has a vba tag, you don't need to use a VBA procedure for this. You can do it with SQL and the Mid() function.
SELECT
base.[Name],
base.[Home Address],
base.[Special ID],
base.special_tag,
tag_count.Occurrences
FROM
(
SELECT
[Name],
[Home Address],
[Special ID],
Mid([Special ID],2,3) AS special_tag
FROM tblUser1384831
) AS base
INNER JOIN
(
SELECT
Mid([Special ID],2,3) AS special_tag,
Count(*) AS Occurrences
FROM tblUser1384831
GROUP BY Mid([Special ID],2,3)
) AS tag_count
ON base.special_tag = tag_count.special_tag;
You would have to GROUP BY the substring of Special ID. In MS Access, you can read about how to compute substrings here.
The problem in your case is that your data in Special ID column does not follow a standard pattern, one which easy to extract via the substring function. You might need to use regular expressions to extract such values, and later apply the GROUP BY to them.
With MSSQL, Oracle, PostgreSQL you would be able to declare a stored procedure (example CLR function in MS SQL Server) that would do this for you. Not sure with MS Access.
you can do something like this:
select Name, [Home Address], [Special ID],
(select count(*) from [your table] where [Special ID] = RemoveNonAlphaCharacters([Special ID]) ) as Occurrences
from [your table]
auxiliar function (got from this link):
Create Function [dbo].[RemoveNonAlphaCharacters](#Temp VarChar(1000))
Returns VarChar(1000)
AS
Begin
While PatIndex('%[^a-z]%', #Temp) > 0
Set #Temp = Stuff(#Temp, PatIndex('%[^a-z]%', #Temp), 1, '')
Return #Temp
End
lets say your first table is called 'table_with_string'
the following code will show the occurance based on the first 3 charecters of string in Special ID column. since it is not clear how exactly you are passing the string to match
select tws.Name,tws.HomeAddress,tws.SpecialID,str_count.Occurrences from
table_with_string tws
left join
(select SpecialID,count(*) from table_with_string where specialID like(substring
(specialid,0,3))
group by specialId) as str_count(id,Occurrences)
on str_count.id=tws.SpecialID
I would suggest doing this explicitly as a join, so you are clear on how it works:
select tws.Name, tws.HomeAddress, tws.SpecialID, str_count.Occurrences
from table_with_string tws
join
(
select substring(spcecialid, 2, 3) as code, count(*) as Occurrences
from table_with_string tws
group by substring(spcecialid, 2, 3)
) s
on s.code = substring(tws.spcecialid, 2, 3)