select's MODEL clause to add columns on the resultset, not just new rows? - sql

PIVOT can only pivot one value into its one column.
Also PIVOT is not helpful if for examle we want to pivot and (group by) intersecting ranges of values.
So we're planning to use MODEL clause to overcome this limits.
We have to pivot into columns:
sum(), count() of data over separate quarters for last 2 years and
present each quarter's result into its own set of column;
sum(), count() of data over separate years for last 2 years and present each years's result into its own set of column;
sum(), count() of data over separate months for last 3 months and present each month's result into its own set of column.
The list of quarters / years / months in the bucketing is fixed and can be hard-coded in the MODEL clause (i.e. it'll be always to look back 2 years).
I have a working prototype doing above using three separate pivots, but it is very inefficient because each pivot has to pass our huge dataset again.
We expect MODEL might require just one pass over the dataset.
Questions:
Is it possible for MODEL clause to create new columns (ie. we group
by month and product category in a subquery, but MODEL should add
columns for the above quarter/year/month buckets)?
Can't think of a best way to define DIMENSION BY and MEASURES so we could
create new columns.. any ideas are highly appreciated.
Edit: posted the same question at https://community.oracle.com/message/13951429

You can PIVOT with multiple pivot columns and multiple result columns
with atc as
(select owner, table_name, column_name, data_type, nullable
from all_tab_columns
where table_name = 'ALL_TAB_COLUMNS')
select *
from atc
pivot
(max(column_name) as max_col_name, min(column_name) as min_col_name
for (data_type, nullable) in
(
('NUMBER','N') as dt_number_n, ('VARCHAR2','N') as dt_vc_n,
('NUMBER','Y') as dt_number_y, ('VARCHAR2','Y') as dt_vc_y
)
)

Related

What does SELECT Function is SQL actually produce? Does it produce a new table by default?

I am struggling to understand what the output of SELECT is meant to be in SQL (I am using MS ACCESS), and what sort of criteria this output needs to specify, if any. As a result, I don't understand why some queries work and others don't. So I know it retrieves data from a table, does calculations with it and displays it. But I don't understand the "inner" working of SELECT function. For instance, what is the name of data structure / entity it displays? Is it a "new" table?
And for example, suppose I have a table called "table_name", with 5 columns. One of the columns called "column_3", and there are 20 records.
SELECT column_3, COUNT(*) AS Count
FROM table_name;
Why does this query fail to run? By logic, I would expect it to display two columns: first column will be "column_3", containing 20 rows with relevant data, and second column will be "Count", containing just one non-empty row (displaying 20), and other 19 rows will be empty (or NULL maybe)?
Is it because SELECT is meant to produce equal number of rows for each column?
Your questions involve a basic understanding of SQL. SELECT statements do not create tables, but instead return virtual result sets. Nothing is persisted unless you change it to an INSERT.
In your example question, you will need to "tell" the SQL engine what you want a count "of". Because you added column_3, you need to write:
SELECT column_3, COUNT(*) AS Count
FROM table_name
GROUP BY column_3
If you wanted a count of all the rows, simply:
SELECT COUNT(*) FROM table_name

Pivot multiple rows (2 columns) into a single row

I have a table where it has only 2 columns, the first columns is a name identifier and the second column is a value for this identifier (basically the table acts as default values), below is a screenshot of that table.
What I want is to convert the table from multiple rows into a single row and the values would be columns with the first column as column name. Example, the current values to be transformed into the below.
I read about the PIVOT operator, however it requires an aggregate function in the pivot clause but I don't think I can use an aggregate function in this case, its just setting row values as column values.
Is this possible with PIVOT or is there another construct I should use to achieve this?
There is already a correct technical answer, showing how to pivot in your case.
Let me explain why this "pivoting" is indeed an aggregation, at a logical level.
You have a group of four rows, and you want to generate a "summary row" for the group. (Imagine, in parallel, that you had several employees identified by employee id, in an additional column; each employee had up to four rows, for the same attributes. Then you are grouping by employee id, each group has up to four rows - fewer if there are missing attributes - and you want to get a "summary row" for each group.)
This is a form of aggregation. But for what aggregate function? You seem to only have one value for AGE, only one value for STATUS, etc.
In fact, you can think of AGE as existing in each of the four rows. When the CODE is 'AGE' then the value is 42, and when the CODE is something else then the value is NULL. You could use SUM(), AVG(), MIN(), MAX() over these four values (one is 42, the rest are NULL); they would all return the same answer, 42 - since all aggregate functions ignore NULL.
What if the values are strings, not numbers? Answer: same thing - except you can't use SUM() or AVG(). You still have MIN() and MAX(). In fact you could use other aggregate function too - they just have to be string aggregates. For example you could use LISTAGG(). Again, you are aggregating a single non-NULL string, the others are NULL, so the result will be just that one non-NULL string.
Before Oracle introduced the PIVOT operator in version 11.1 of the database, programmers were already able to pivot - using a conditional aggregation just like I explained. Something like
select max(case when code = 'AGE' then AGE end) as AGE,
...
from ...
group by EMPLOYEE_ID -- in the more general case
(in your simple case you don't need to group by anything.)
You can use pivot clause for that purpose, like below (Your table has only 2 columns and I assume you don't have any duplicate code)
select *
from Yourtable
pivot (
max(value) for code in (
'AGE' as AGE
, 'FIRST_NAME' as FIRST_NAME
, 'LAST_NAME' as LAST_NAME
, 'STATUS' as STATUS
)
)

SQL-Server 2012 Subtracting Rows

I have some counts that get periodically recorded into SQL and I'm trying to find out the difference between the start count and the final count.
Raw Data Below
This table has around 30 columns but they are the more of the same just different counts.
I want to take the min and max row for a time period based on user input from an SQL report I can filter out the data with the code below. (I can also filter it into two different tables, one with min, and one with max if that is easier.)
SELECT *
FROM #tempTable
WHERE TableIndex = (SELECT min(TableIndex) FROM #tempTable)
or TableIndex = (SELECT max(TableIndex) FROM #tempTable)
Filtered Data Below
The end goal is the difference between these two rows, I would then give that data to an SQL report to display a bar graph.
I've seen solutions but they seemed overly complex for what I'm trying to do and I would need to define each column I'm subtracting vs using *. Some of the tables have a couple hundred columns.
How about joining these together?
SELECT tt.*, ttm.*
FROM #tempTable tt
(SELECT min(TableIndex) as minti, max(TableIndx) as maxti
FROM #tempTable
) ttm
ON ttmTableIndex IN (ttm.minti, ttm.maxti);
You can then do whatever arithmetic operations you like with the values in the same row.
Personally, I would find it easier to just put the two rows into a spreadsheet and subtract the values using a formula.

SQL - Count(*) not behaving in expected manner [duplicate]

This question already has an answer here:
Access query producing results like ROW_NUMBER() in T-SQL
(1 answer)
Closed 7 years ago.
I have the following code
SELECT C_Record.BunchOfColumns, Count(*) AS Degrees
FROM C_Record
WHERE (((C_Record.[C#])=[Enter Value])) //Parameter Input from User
GROUP BY C_Record.BunchofColumns;
My Degrees column never increments, it shows 1 always no matter how many rows are returned from the query. I am suspecting that I have not implemented my GROUP BY method properly. If I understand it correctly, all columns that are selected and are not part of the aggregate function (COUNT in my case) should be put together in GROUP BY. Any help is much appreciated. Thanks in advance
Edit: What I am trying to achieve is to check how many rows have a particular value for a column, then select all other relevant columns and create a Index columns. For example if there are three rows that meet my requirement
Col1 Col2 Degrees
A X 1
B Y 2
C Z 3
and if only 2 rows meet my requirement then
Col1 Col2 Degrees
P X 1
Q Y 2
P.S - my C_Record.BunchofColumns consists of about 10 columns that I did not include for the sake of brevity.
P.P.S - If I try to skip out on any column it gives me the error You Tried to execute a query that does not include the specified expression <<column_name>> as part of an aggregate function
When you use Count() with a GROUP BY the count returned is the number of rows in each group. So to get a count greater than one you would have to have more than one row in your table that had exactly the same values. If you are selecting 10 different columns it seems likely that you have no two columns in the database that have exactly those 10 same values.
If you start with a selecting and grouping by a single column you will see count's of more than one.
That is not how GROUP BY works.
GROUP BY completely changes the meaning of your query. Each row of the result is an "aggregate grouping" of the original rows. Each aggregate grouping consists of all the rows with a particular combination of values for their GROUP BY columns. So if you GROUP BY ten columns, each grouping will consist of rows which are identical on all ten columns.
Once these groupings have been formed, you SELECT various aggregate values like count() or sum(), which provide you with information about the group as a whole. count(*) gives you the number of rows in the group, while count(column) gives you the number of rows in which column is non-NULL. You can also select any of the columns which appear in the GROUP BY clause, because those columns are identical across the whole group.
You are getting a count(*) of one because each of your groups only contains a single row. This is probably because you are grouping by ten columns, and there are no two rows which are identical for all ten columns.
If you just want a count of how many rows satisfy some query, and you don't want this aggregation at all, you write it like this:
SELECT count(*)
FROM something
WHERE something
-- no GROUP BY
;
That will form a single aggregate group of your whole query, and count the rows.
If you want something else, you will need to further explain what you're trying to do.

pivot running total in reverse order

I have a pivot table where I want to preserve the order of the columns, but show a running total in reverse order (i.e. the values decrease over time).
I know I can reverse the order of the columns and set up a running total, or create a separate report that references the pivot table, but I'm interested to know if it can be done within the pivot table itself keeping Months Since in ascending order (maybe using a calculated field). Below is the pivot table I have and the one that I want
You could add a calculated field in the pivot:
The field YTD is calculated as such:
In D2 cell insert (change are probably needed to fit to your table):
=SUMIFS($C$2:$C$16,$A$2:$A$16,A2,$B$2:$B$16,">="&B2)
Then drag down from D2 to the bottom of the table.
Widen and refresh your pivot.
If data are aggregated inside the pivot (i.e. there are more rows that compose what in the image is a row), you could take MAX() o MIN() in the pivot (as you discovered yourself ;) )