I have struggled with this issue for several weeks. I have Googled it and have zero relevant results (which probably means I can't figure out how to Google it - not that the answer isn't out there!)
Assume four tables
Table 1 - templates
Template 1...
Template 2...
Template 3...
Template 4...
Table 2 - Periods
Period 1...
Period 2...
Period 3...
Period 4...
Table 3 - Type
Type 1...
Type 2...
Table 4 - TemplateMatrix
Contains the matrix for which templates are applicable to which period and type. Most of the templates are applicable to all periods except 1 and there are specific templates for that period. Rather than create a templateMatrix record for every template for every period, I create one record for each specific period template with that periodID
EX:
PeriodID, TypeID, TemplateID
1 , 1 , 1
2 , 1 , 2
and a record with a NULL for PeriodID for all other periods for their templates.
EX:
PeriodID, TypeId, TemplateID
NULL , 1, 3
NULL , 2, 4
There are actually about 40 Periods and about 8 Templates and 2 types.
What I need is a view that will give me all possible Template and Period combinations- those that are expressly defined and all others.
In other words,
Period 1, Type 1, Template 1
Period 2, Type 1, Template 2
Period 3, Type 1, Template 3
Period 3, Type 2, Template 4
Period 4, Type 1, Template 3
Period 4, Type 2, Template 4
Period 5, Type 1, Template 3
Period 5, Type 2, Template 4
Period 6, Type 1, Template 3
Period 6, Type 2, Template 4
Worst case scenario, I could add all possible records to the matrix; however, that doesn't seem very normalized to me since the only difference between the bulk of the records would be the Period ID.
Any ideas? I really am at my wits end.
Thanks,
John
You've struggled for weeks with a problem that could be solved by simply storing important business data directly in a table. We're talking about, at most, around 360 rows, right? Just insert the rows. Jeez.
The root cause of your problem is that a) you're trying to shoehorn two predicates into one table, and b) you're hoping to redefine what NULL means.
PeriodID, TypeID, TemplateID
1 , 1 , 1
NULL , 1 , 3
You want the first row to mean something like this:
Template [TemplateID] is used in
period [PeriodID] for something of
type [TypeID]
But you want the second row to mean something like
Template [TemplateID] is used in every
period that hasn't been identified by [PeriodID] in some other row
for something of type [TypeID]
Trying stuff like that will almost always lead to grief.
The following select should work for you:
SELECT DISTINCT p.PeriodID, ty.TypeID, tmpl.TemplateID
FROM TemplateMatrix AS matrix
JOIN Periods AS p ON
(
-- Defined Periods
(matrix.PeriodID = p.PeriodID) OR
-- Undefined Periods, excluding defined cases
(
IsNull(matrix.PeriodID, p.PeriodID) = p.PeriodID
AND p.PeriodID NOT IN (SELECT DISTINCT defMtrx.PeriodID FROM TemplateMatrix AS defMtrx)
)
)
JOIN Type AS ty ON (matrix.TypeID = ty.TypeID)
JOIN Templates AS tmpl ON (matrix.TemplateID = tmpl.TemplateID)
This should only map NULL for PeriodID to the values for Period that aren't explicitly defined in your TemplateMatrix table, and use the defined combinations for Period/Type/Template when available.
Related
I'm trying to generate a result set from a table with effectively a unique/primary key as billyear, billmonth and type along with cost and consumption. So there could be 3 bill year and bill month identical entries but the type could be one of three values: E, W or NG.
I need to create a result set that has just one row per billyear and billmonth entry.
(
select month as billmonth, year as billyear, cost_estimate as eleccost, consumption_estimate as eleccons from tblbillforecast where buildingid=19 and type='E'
)
UNION (
select month as billmonth, year as billyear, cost_estimate as gascost, consumption_estimate as gascons from tblbillforecast where buildingid=19 and type='NG'
)
UNION (
select month as billmonth, year as billyear, cost_estimate as watercost, consumption_estimate as watercons from tblbillforecast where buildingid=19 and type='W'
)
This generates a result set with only billmonth, billyear, eleccost and eleccons columns. I've tried all kinds of solutions but the above example is the simplest to show where it's going wrong.
Additionally it still has 3 rows per billmonth/billyear unique combination instead of merging to one.
UPDATE:
Sample data
SELECT month AS billmonth,
year AS billyear,
SUM(CASE type WHEN 'E' THEN cost_estimate END) AS eleccost,
SUM(CASE type WHEN 'NG' THEN cost_estimate END) AS gascost,
SUM(CASE type WHEN 'W' THEN cost_estimate END) AS watercost
FROM tblbillforecast
WHERE buildingid=19
GROUP BY billmonth, billyear;
Result:
Expected result, eg:
year | month | eleccost | gascost | watercost
2018 | 1 | 32800 | 4460 | 4750
This is behaving correctly. An SQL query result set has one name per column, and this name applies to all the rows. So if you try to rename the column in the second or subsequent queries of the UNION, those new names are ignored. The name of the column is determined only by the first query of the UNION.
Additionally it still has 3 rows per billmonth/billyear unique combination instead of merging to one.
That's also correct behavior, according to the query you tried. UNION does not merge multiple rows into one, it only appends sets of rows.
As Akina hinted in the comments above, you may use multiple columns:
SELECT month AS billmonth,
year AS billyear,
SUM(CASE type WHEN 'E' THEN cost_estimate END) AS eleccost,
SUM(CASE type WHEN 'NG' THEN cost_estimate END) AS gascost,
SUM(CASE type WHEN 'W' THEN cost_estimate END) AS watercost
FROM tblbillforecast
WHERE buildingid=19
GROUP BY billmonth, billyear;
This uses GROUP BY to "merge" rows together, so you get one row in the result per month/year.
A quick bit of guidance on various data shaping operations in SQL:
JOIN - makes resultsets wider (more columns) by bringing together tables/resultsets in a side-by-side fashion generating output rows that have all the columns of the two input column sets
SELECT - typically makes resultsets narrower by allowing you to specify which columns you're interested in and which you are not; by not mentioning an available column it disappears meaning you output fewer columns
UNION - makes resultsets taller (more rows) by bringing together resultsets and outputting one on top of the other. Because columns always have a fixed data type and one name, you must have the same number of and type of, and order of columns
WHERE - makes resultsets shorter (fewer rows) by allowing you to specify truth based filters that exclude rows
It's not hard and fast; you can use select to create more columns too, but just in a very rudimentary sense these concepts hold true - JOIN to widen, UNION for taller, SELECT for narrower and WHERE for shorter. All the work you do with SQL is a data shaping exercise; you're either paring a rectangular block of data down or extending it, and in either a vertical or horizontal direction (or a mix).
I'm not going to get into grouping because that mixes rows up, and isn't something you tried in the question.. The reason for me writing this out was purely because you'd attempted to use a UNION (height-increasing) operation when you actually wanted a widen which, regardless of how it is done (JOIN or as per Bill's answer a SELECT+GROUP, which is valid, but relies on the "mixes rows up" aspect of grouping), specifically isn't done with a UNION. Union only makes stuff taller.
To give an example of how it might be done in an alternative way to Bill's approach, this task of yours has one huge table that is "too tall" - it uses 3 rows where 1 would do, if only it were a bit wider. That is to say if only there were 3 columns for electric/gas/water then we wouldn't need 3 rows with 1 utility in each.
Of course, we have this "one utility per row" because it is very flexible. Database tables don't have varying numbers of columns but they DO have varying numbers of rows. If a new bill type came along tomorrow - internet - no table changes are needed to accommodate it; add a new type I, and away you go, adding another row. We now store 4 rows of 1 utility where 1 row with 4 columns would do, but crucially we didn't have to change the table structure. We could have infinite different kinds of bills, and not need infinite columns because we can already have infinite rows
So you want to reshape your data from 4-rows-by-1-column to 1-row-by-4-columns. It could be solved as :
narrow the table to just year,month,building,type,cost AND shorten it to just electricity
separately narrow the table to just year,month,building,type,cost AND shorten it to just gas
separately narrow the table to just year,month,building,type,cost AND shorten it to just water
join (widening) all these newly created result sets , then narrow to remove the repeated year,month,building,type columns
That would look like:
SELECT e.year, e.month, e.building, e.cost, g.cost, w.cost
FROM
(SELECT year,month,building,cost FROM t WHERE type = 'E') e
JOIN
(SELECT year,month,building,cost FROM t WHERE type = 'NG') g
ON
e.year = g.year AND e.month = g.month AND e.building = g.building
JOIN
(SELECT year,month,building,cost FROM t WHERE type = 'W') w
ON
e.year = w.year AND e.month = w.month AND e.building = w.building
WHERE
e.building = 19
You can see clearly the 3 narrowing-and-shortening operations that pick out "just the gas", "just the electric", and "just the water" - they're the (SELECT year,month,building,cost FROM t WHERE type = 'NG') and that's what reduces the height of the original table, making it three times shorter than it was in each case. If we had 999 rows X 5 cols in the big table it goes to 3 sets of 333 x 5 rows each
You can see that we then JOIN these together to widen the results - our e.g 3 sets of 333 x 5 rows each widens to 333 x 15 when JOINed..
Then went from 333x15 down to 333 X 7 when SELECTed to ditch the repeated columns
It's likely not perfect (I'd perhaps left join all 3 onto a 4th set of numbers that are just the common columns in case some utilities aren't present for a particular month), and perhaps some people will come along complaining that it's less performant because it hits the table 3 times.. All that is accessory to the point I'm making about SQL being an exercise in reshaping data - tables are the starting blocks of data and you cut them up narrower and shorter, then stick them together side by side, or on top of each other and that becomes your new data block that's maybe wider, higher, both.. In any case it's definitely a different shape to what you started with. And then you can cut and shape again, and again..
Go with Bill's conditional agg (though this way would be fine if there is one row per building/year/month) but take away a stronger notion about in what direction these common operations (SELECT/JOIN/WHERE/UNION) reshape your data
Footnote about Bill's conditional aggregation (I know I said I wouldn't talk about it but it might make more sense to now). If you have:
Type, Cost
E, 123
NG, 456
W, 789
And you do a
SELECT
CASE WHEN Type = 'E' THEN Cost END as CostE,
CASE WHEN Type = 'NG' THEN Cost END as CostG,
CASE WHEN Type = 'W' THEN Cost END as CostW
...
It spreads the data out over more columns - the data has "gone from vertical to diagonal"
CostE, CostNG, CostW
123, NULL, NULL
NULL, 456, NULL
NULL, NULL, 789
But it's still too tall. If you then run a GROUP BY, which mixes rows up and ask for e.g. just the MAX from each column, then all the NULLs will disappear (because there is a non null somewhere in the column, and NULL is lost if there is a non null, no matter what you're doing) and the rows collapse, mixing together, into one:
CostE, CostNG, CostW
123, 456, 789
The data has pivoted round from being vertical, to being horizontal - another data shaping. It was pulled wider, and squashed flatter
Headers might be the wrong word but if I have a table of shifts for a job and a table of customers I'm trying to figure out how to select a row displaying the customer information in between customers.
[Shift Table]
shift_id, customer_id, date
[Shift_Needs_Workers]
id, shift_id, people_id (nullable = true)
[Customers]
customer_id, customer_name
Customers can have multiple shifts and Shifts can have multiple workers needed
I'm wanting to run a SELECT statement that can output
Customer A
Shift 1, Worker 1
Shift 1, Worker 2
Shift 2, Worker 1
Shift 2, Worker 2
Shift 2, Worker 3
Customer B
Shift 1, Worker 1
Shift 2, Worker 2
etc
I looked into with rollup but that seems to need something to group by and I'm the opposite I want all the rows but then an extra one. I looked into Union but it looked like it was one additional query or multiple separate queries and I want to "union" in the middle of my queries where the customer_id changes.
Any help or other links that might help would be awesome.
You could build your reports with jasper reports. You would create one main report for the customers and a subreport for the shift information.
If you want to do it in one statement, you would first have to generate a structure like this
CustomerA, null, null, 0
CustomerA, Shift1, Worker1, 1
CustomerA, Shift1, Worker2, 1
CustomerB, null, null, 0
The last column is the level of the information.
After you have this structure, which you can easily generate using unions and joins
you can generate an output column with a case or if then else
case levelNr when 0 then customer_name else concat(shift_id, ' ', worker_id) end
hello I have an as3 app. this app divides the users into groups, every group consisting of 3 users.
in my mysql database there is a field in "users_into" table that identifies the number of user in his group.
this field is called "num_in_group" and its value must be a number between 1 and 3 for every user.
For clarification
The first user who registered in the application will have number 1 and the second one will have number 2 and the third one will have number 3 ---and the forth one will have 1 (not 4) ----- and fifth one will have 2 and sixth one will have 3 and seventh one will have 1 again and so on ......
so my question is how can I make the field have numbers 1 , 2 , 3 , 1 , 2 , 3 in the order constantly
One option which you might find acceptable would be to use ROW_NUMBER() and generate this value at the time you query:
SELECT
id,
1 + (ROW_NUMBER() OVER (ORDER BY id) - 1) % 3 AS seq
FROM yourTable
ORDER BY id;
The above assumes that id is an auto increment column, with a unique idea for each user. The only potential problem with this approach is that the id might not always be sequential or increasing. Assuming you can tolerate the vast majority of users have an even spread of 1, 2, and 3 values, then the above might be acceptable to you.
I am developing software for warehouse management.
I got three tables: itemstock, documents, doc_pcs
itemstock fields (not all of them are shown):
rfid_no (which is unique and primary key)
item_name
status
documents fields:
id
doc_date
doc_type
doc_pcs fields:
id
id_doc | these two are primary key
pos | (id_doc is connected to documents.id)
rfid_no (from itemstock.rfid_no)
First table is for collecting records of items, nothing is ever erased from this table, there's just status change when something happens with particular item (goes out to client, comes back etc).
Documents and doc_pcs tables store document data.
doc_type is for marking whether items on document were going in or out of the warehouse.
The problem is:
I need to create query that shows all the stock as of given date.
In other words:
It should show all items from today's itemstock with status=6 ("stored") MINUS records with rfid_no that have matches in doc_pcs related to documents dated between today and a given date and with doc_type="out" PLUS records with rfid_no that have matches in doc_pcs related to documents dated between today and a given date and with doc_type="in"
Was searching for any clue here and there for few days now and I could not find anything that will lead me to any solution. I will be gratefull for any help!
Sample data:
itemstock (as of today):
00300D0909DA, "tshirt", 6
00300D0909DB, "apron", 6
00300D0909DC, "tshirt", 6
00300D0909DD, "trousers", 6
00300D0909DE, "tshirt", 1
00300D0909DF, "trousers", 1
00300D0909E0, "trousers", 6
documents:
0,2015-08-01,"in"
1,2015-08-02,"in"
2,2015-08-03,"out"
3,2015-08-04,"in"
4,2015-08-05,"out"
5,2015-08-06,"in"
doc_pcs:
0, 1, 00300D0909DA
0, 2, 00300D0909DE
1, 1, 00300D0909DF
1, 2, 00300D0909DD
2, 1, 00300D0909DE
3, 1, 00300D0909DB
4, 1, 00300D0909DF
5, 1, 00300D0909DC
5, 2, 00300D0909E0
query results for given doc_date=2015-08-04 including fields rfid_no and item_name should be:
00300D0909DA, "tshirt"
00300D0909DB, "apron"
00300D0909DD, "trousers"
00300D0909DF, "trousers"
EDIT:
I've managed to make following query but it executes endlessly hence I believe something is wrong:
SELECT DISTINCT c.rfid_no
FROM itemstock c LEFT JOIN doc_pcs d
ON c.rfid_no=d.rfid_no
WHERE c.status=6
OR
(d.id IN
(SELECT dk.id
FROM documents dk
WHERE DATE(dk.doc_date)>='2015-08-04'
AND dk.doc_type="out")
AND
d.id NOT IN
(SELECT dk.id
FROM documents dk
WHERE DATE(dk.doc_date)>='2015-08-04'
AND dk.doc_type="in"))
It seems like you could either go from today's values and work backwards or you could start from 'day 0' and work forwards. By storing the stock count as of 'today' you are denormalizing the data so I'd opt for the second approach (and maybe you could save yourself a table).
Assume that as of 'day 0' all stock is empty. To get the stock count as of a given date you would (p-query):
select sum((select count inbound where date <= inventory_date) - (select count outbound where date <= inventory_date))
I've currently got a table as follows,
Column Type
time datetime
ticket int(20)
agentid int(20)
ExitStatus varchar(50)
Queue varchar(50)
I want to write a query which will break this down by week, providing a column with a count for each ExitStatus. So far I have this,
SELECT ExitStatus,COUNT(ExitStatus) AS ExitStatusCount, DAY(time) AS TimePeriod
FROM `table`
GROUP BY TimePeriod, ExitStatus
Output:
ExitStatus ExitStatusCount TimePeriod
NoAgentID 1 4
Success 3 4
NoAgentID 1 5
Success 5 5
I want to change this so it returns results in this format:
week | COUNT(NoAgentID) | COUNT(Success) |
Ideally, I'd like the columns to be dynamic as other ExitStatus values may be possible.
This information will be formatted and presented to end user in a table on a page. Can this be done in SQL or should I reformat it in PHP?
There is no "general" solution to your problem (called cross tabulation) that can be achieved with a single query. There are four possible solutions:
Hardcode all possible ExitStatus'es in your query and keep it updated as you see the need for more and more of them. For example:
SELECT
Day(Time) AS TimePeriod,
SUM(IF(ExitStatus = 'NoAgentID', 1, 0)) AS NoAgentID,
SUM(IF(ExitStatus = 'Success', 1, 0)) AS Success
-- #TODO: Add others here when/if needed
FROM table
WHERE ...
GROUP BY TimePeriod
Do a first query to get all possible ExitStatus'es and then create your final query from your high-level programming language based on those results.
Use a special module for cross tabulation on your high-level programming language. For Perl, you have the SQLCrossTab module but I couldn't find one for PHP
Add another layer to your application by using OLAP (multi-dimensional views of your data) like Pentaho and then querying that layer instead of your original data
You can read a lot more about these solutions and an overall discussion of the subject
This is one way; you can use SUM() to count the number of items a particular condition is true. At the end you just group by the time as per normal.
SELECT DAY(time) AS TimePeriod,
SUM('NoAgentID' = exitStatus) AS NoAgentID,
SUM('Success' = exitStatus) AS Success, ...
FROM `table`
GROUP BY TimePeriod
Output:
4 1 3
5 1 5
The columns here are not dynamic though, which means you have to add conditions as you go along.
SELECT week(time) AS week,
SUM(ExitStatus = 'NoAgentID') AS 'COUNT(NoAgentID)',
SUM(ExitStatus = 'Success') AS 'COUNT(Success)'
FROM `table`
GROUP BY week
I'm making some guesses about how ExitStatus column works. Also, there are many ways of interpretting "week", such as week of year, of month, or quarter, ... You will need to put the appropriate function there.